diff options
Diffstat (limited to 'drivers/media/i2c')
181 files changed, 145129 insertions, 0 deletions
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig new file mode 100644 index 000000000..878f66ef2 --- /dev/null +++ b/drivers/media/i2c/Kconfig @@ -0,0 +1,1338 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Multimedia Video device configuration +# + +if VIDEO_V4L2 + +comment "IR I2C driver auto-selected by 'Autoselect ancillary drivers'" + depends on MEDIA_SUBDRV_AUTOSELECT && I2C && RC_CORE + +config VIDEO_IR_I2C + tristate "I2C module for IR" if !MEDIA_SUBDRV_AUTOSELECT || EXPERT + depends on I2C && RC_CORE + default y + help + Most boards have an IR chip directly connected via GPIO. However, + some video boards have the IR connected via I2C bus. + + If your board doesn't have an I2C IR chip, you may disable this + option. + + In doubt, say Y. + +# +# V4L2 I2C drivers that aren't related with Camera support +# + +comment "audio, video and radio I2C drivers auto-selected by 'Autoselect ancillary drivers'" + depends on MEDIA_HIDE_ANCILLARY_SUBDRV +# +# Encoder / Decoder module configuration +# + +menu "Audio decoders, processors and mixers" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_TVAUDIO + tristate "Simple audio decoder chips" + depends on VIDEO_V4L2 && I2C + help + Support for several audio decoder chips found on some bt8xx boards: + Philips: tda9840, tda9873h, tda9874h/a, tda9850, tda985x, tea6300, + tea6320, tea6420, tda8425, ta8874z. + Microchip: pic16c54 based design on ProVideo PV951 board. + + To compile this driver as a module, choose M here: the + module will be called tvaudio. + +config VIDEO_TDA7432 + tristate "Philips TDA7432 audio processor" + depends on VIDEO_V4L2 && I2C + help + Support for tda7432 audio decoder chip found on some bt8xx boards. + + To compile this driver as a module, choose M here: the + module will be called tda7432. + +config VIDEO_TDA9840 + tristate "Philips TDA9840 audio processor" + depends on I2C + help + Support for tda9840 audio decoder chip found on some Zoran boards. + + To compile this driver as a module, choose M here: the + module will be called tda9840. + +config VIDEO_TDA1997X + tristate "NXP TDA1997x HDMI receiver" + depends on VIDEO_V4L2 && I2C + depends on SND_SOC + select HDMI + select SND_PCM + select V4L2_FWNODE + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + V4L2 subdevice driver for the NXP TDA1997x HDMI receivers. + + To compile this driver as a module, choose M here: the + module will be called tda1997x. + +config VIDEO_TEA6415C + tristate "Philips TEA6415C audio processor" + depends on I2C + help + Support for tea6415c audio decoder chip found on some bt8xx boards. + + To compile this driver as a module, choose M here: the + module will be called tea6415c. + +config VIDEO_TEA6420 + tristate "Philips TEA6420 audio processor" + depends on I2C + help + Support for tea6420 audio decoder chip found on some bt8xx boards. + + To compile this driver as a module, choose M here: the + module will be called tea6420. + +config VIDEO_MSP3400 + tristate "Micronas MSP34xx audio decoders" + depends on VIDEO_V4L2 && I2C + help + Support for the Micronas MSP34xx series of audio decoders. + + To compile this driver as a module, choose M here: the + module will be called msp3400. + +config VIDEO_CS3308 + tristate "Cirrus Logic CS3308 audio ADC" + depends on VIDEO_V4L2 && I2C + help + Support for the Cirrus Logic CS3308 High Performance 8-Channel + Analog Volume Control + + To compile this driver as a module, choose M here: the + module will be called cs3308. + +config VIDEO_CS5345 + tristate "Cirrus Logic CS5345 audio ADC" + depends on VIDEO_V4L2 && I2C + help + Support for the Cirrus Logic CS5345 24-bit, 192 kHz + stereo A/D converter. + + To compile this driver as a module, choose M here: the + module will be called cs5345. + +config VIDEO_CS53L32A + tristate "Cirrus Logic CS53L32A audio ADC" + depends on VIDEO_V4L2 && I2C + help + Support for the Cirrus Logic CS53L32A low voltage + stereo A/D converter. + + To compile this driver as a module, choose M here: the + module will be called cs53l32a. + +config VIDEO_TLV320AIC23B + tristate "Texas Instruments TLV320AIC23B audio codec" + depends on VIDEO_V4L2 && I2C + help + Support for the Texas Instruments TLV320AIC23B audio codec. + + To compile this driver as a module, choose M here: the + module will be called tlv320aic23b. + +config VIDEO_UDA1342 + tristate "Philips UDA1342 audio codec" + depends on VIDEO_V4L2 && I2C + help + Support for the Philips UDA1342 audio codec. + + To compile this driver as a module, choose M here: the + module will be called uda1342. + +config VIDEO_WM8775 + tristate "Wolfson Microelectronics WM8775 audio ADC with input mixer" + depends on VIDEO_V4L2 && I2C + help + Support for the Wolfson Microelectronics WM8775 high + performance stereo A/D Converter with a 4 channel input mixer. + + To compile this driver as a module, choose M here: the + module will be called wm8775. + +config VIDEO_WM8739 + tristate "Wolfson Microelectronics WM8739 stereo audio ADC" + depends on VIDEO_V4L2 && I2C + help + Support for the Wolfson Microelectronics WM8739 + stereo A/D Converter. + + To compile this driver as a module, choose M here: the + module will be called wm8739. + +config VIDEO_VP27SMPX + tristate "Panasonic VP27's internal MPX" + depends on VIDEO_V4L2 && I2C + help + Support for the internal MPX of the Panasonic VP27s tuner. + + To compile this driver as a module, choose M here: the + module will be called vp27smpx. + +config VIDEO_SONY_BTF_MPX + tristate "Sony BTF's internal MPX" + depends on VIDEO_V4L2 && I2C + help + Support for the internal MPX of the Sony BTF-PG472Z tuner. + + To compile this driver as a module, choose M here: the + module will be called sony-btf-mpx. +endmenu + +menu "RDS decoders" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_SAA6588 + tristate "SAA6588 Radio Chip RDS decoder support" + depends on VIDEO_V4L2 && I2C + + help + Support for this Radio Data System (RDS) decoder. This allows + seeing radio station identification transmitted using this + standard. + + To compile this driver as a module, choose M here: the + module will be called saa6588. +endmenu + +menu "Video decoders" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_ADV7180 + tristate "Analog Devices ADV7180 decoder" + depends on GPIOLIB && VIDEO_V4L2 && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + Support for the Analog Devices ADV7180 video decoder. + + To compile this driver as a module, choose M here: the + module will be called adv7180. + +config VIDEO_ADV7183 + tristate "Analog Devices ADV7183 decoder" + depends on VIDEO_V4L2 && I2C + help + V4l2 subdevice driver for the Analog Devices + ADV7183 video decoder. + + To compile this driver as a module, choose M here: the + module will be called adv7183. + +config VIDEO_ADV748X + tristate "Analog Devices ADV748x decoder" + depends on VIDEO_V4L2 && I2C + depends on OF + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP_I2C + select V4L2_FWNODE + help + V4L2 subdevice driver for the Analog Devices + ADV7481 and ADV7482 HDMI/Analog video decoders. + + To compile this driver as a module, choose M here: the + module will be called adv748x. + +config VIDEO_ADV7604 + tristate "Analog Devices ADV7604 decoder" + depends on VIDEO_V4L2 && I2C + depends on GPIOLIB || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP_I2C + select HDMI + select V4L2_FWNODE + help + Support for the Analog Devices ADV7604 video decoder. + + This is a Analog Devices Component/Graphics Digitizer + with 4:1 Multiplexed HDMI Receiver. + + To compile this driver as a module, choose M here: the + module will be called adv7604. + +config VIDEO_ADV7604_CEC + bool "Enable Analog Devices ADV7604 CEC support" + depends on VIDEO_ADV7604 + select CEC_CORE + help + When selected the adv7604 will support the optional + HDMI CEC feature. + +config VIDEO_ADV7842 + tristate "Analog Devices ADV7842 decoder" + depends on VIDEO_V4L2 && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select HDMI + help + Support for the Analog Devices ADV7842 video decoder. + + This is a Analog Devices Component/Graphics/SD Digitizer + with 2:1 Multiplexed HDMI Receiver. + + To compile this driver as a module, choose M here: the + module will be called adv7842. + +config VIDEO_ADV7842_CEC + bool "Enable Analog Devices ADV7842 CEC support" + depends on VIDEO_ADV7842 + select CEC_CORE + help + When selected the adv7842 will support the optional + HDMI CEC feature. + +config VIDEO_BT819 + tristate "BT819A VideoStream decoder" + depends on VIDEO_V4L2 && I2C + help + Support for BT819A video decoder. + + To compile this driver as a module, choose M here: the + module will be called bt819. + +config VIDEO_BT856 + tristate "BT856 VideoStream decoder" + depends on VIDEO_V4L2 && I2C + help + Support for BT856 video decoder. + + To compile this driver as a module, choose M here: the + module will be called bt856. + +config VIDEO_BT866 + tristate "BT866 VideoStream decoder" + depends on VIDEO_V4L2 && I2C + help + Support for BT866 video decoder. + + To compile this driver as a module, choose M here: the + module will be called bt866. + +config VIDEO_KS0127 + tristate "KS0127 video decoder" + depends on VIDEO_V4L2 && I2C + help + Support for KS0127 video decoder. + + This chip is used on AverMedia AVS6EYES Zoran-based MJPEG + cards. + + To compile this driver as a module, choose M here: the + module will be called ks0127. + +config VIDEO_ML86V7667 + tristate "OKI ML86V7667 video decoder" + depends on VIDEO_V4L2 && I2C + help + Support for the OKI Semiconductor ML86V7667 video decoder. + + To compile this driver as a module, choose M here: the + module will be called ml86v7667. + +config VIDEO_SAA7110 + tristate "Philips SAA7110 video decoder" + depends on VIDEO_V4L2 && I2C + help + Support for the Philips SAA7110 video decoders. + + To compile this driver as a module, choose M here: the + module will be called saa7110. + +config VIDEO_SAA711X + tristate "Philips SAA7111/3/4/5 video decoders" + depends on VIDEO_V4L2 && I2C + help + Support for the Philips SAA7111/3/4/5 video decoders. + + To compile this driver as a module, choose M here: the + module will be called saa7115. + +config VIDEO_TC358743 + tristate "Toshiba TC358743 decoder" + depends on VIDEO_V4L2 && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select HDMI + select V4L2_FWNODE + help + Support for the Toshiba TC358743 HDMI to MIPI CSI-2 bridge. + + To compile this driver as a module, choose M here: the + module will be called tc358743. + +config VIDEO_TC358743_CEC + bool "Enable Toshiba TC358743 CEC support" + depends on VIDEO_TC358743 + select CEC_CORE + help + When selected the tc358743 will support the optional + HDMI CEC feature. + +config VIDEO_TVP514X + tristate "Texas Instruments TVP514x video decoder" + depends on VIDEO_V4L2 && I2C + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the TI TVP5146/47 + decoder. It is currently working with the TI OMAP3 camera + controller. + + To compile this driver as a module, choose M here: the + module will be called tvp514x. + +config VIDEO_TVP5150 + tristate "Texas Instruments TVP5150 video decoder" + depends on VIDEO_V4L2 && I2C + select V4L2_FWNODE + select REGMAP_I2C + help + Support for the Texas Instruments TVP5150 video decoder. + + To compile this driver as a module, choose M here: the + module will be called tvp5150. + +config VIDEO_TVP7002 + tristate "Texas Instruments TVP7002 video decoder" + depends on VIDEO_V4L2 && I2C + select V4L2_FWNODE + help + Support for the Texas Instruments TVP7002 video decoder. + + To compile this driver as a module, choose M here: the + module will be called tvp7002. + +config VIDEO_TW2804 + tristate "Techwell TW2804 multiple video decoder" + depends on VIDEO_V4L2 && I2C + help + Support for the Techwell tw2804 multiple video decoder. + + To compile this driver as a module, choose M here: the + module will be called tw2804. + +config VIDEO_TW9903 + tristate "Techwell TW9903 video decoder" + depends on VIDEO_V4L2 && I2C + help + Support for the Techwell tw9903 multi-standard video decoder + with high quality down scaler. + + To compile this driver as a module, choose M here: the + module will be called tw9903. + +config VIDEO_TW9906 + tristate "Techwell TW9906 video decoder" + depends on VIDEO_V4L2 && I2C + help + Support for the Techwell tw9906 enhanced multi-standard comb filter + video decoder with YCbCr input support. + + To compile this driver as a module, choose M here: the + module will be called tw9906. + +config VIDEO_TW9910 + tristate "Techwell TW9910 video decoder" + depends on VIDEO_V4L2 && I2C + help + Support for Techwell TW9910 NTSC/PAL/SECAM video decoder. + + To compile this driver as a module, choose M here: the + module will be called tw9910. + +config VIDEO_VPX3220 + tristate "vpx3220a, vpx3216b & vpx3214c video decoders" + depends on VIDEO_V4L2 && I2C + help + Support for VPX322x video decoders. + + To compile this driver as a module, choose M here: the + module will be called vpx3220. + +config VIDEO_MAX9286 + tristate "Maxim MAX9286 GMSL deserializer support" + depends on I2C && I2C_MUX + depends on OF_GPIO + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + help + This driver supports the Maxim MAX9286 GMSL deserializer. + + To compile this driver as a module, choose M here: the + module will be called max9286. + +comment "Video and audio decoders" + +config VIDEO_SAA717X + tristate "Philips SAA7171/3/4 audio/video decoders" + depends on VIDEO_V4L2 && I2C + help + Support for the Philips SAA7171/3/4 audio/video decoders. + + To compile this driver as a module, choose M here: the + module will be called saa717x. + +source "drivers/media/i2c/cx25840/Kconfig" + +endmenu + +menu "Video encoders" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_SAA7127 + tristate "Philips SAA7127/9 digital video encoders" + depends on VIDEO_V4L2 && I2C + help + Support for the Philips SAA7127/9 digital video encoders. + + To compile this driver as a module, choose M here: the + module will be called saa7127. + +config VIDEO_SAA7185 + tristate "Philips SAA7185 video encoder" + depends on VIDEO_V4L2 && I2C + help + Support for the Philips SAA7185 video encoder. + + To compile this driver as a module, choose M here: the + module will be called saa7185. + +config VIDEO_ADV7170 + tristate "Analog Devices ADV7170 video encoder" + depends on VIDEO_V4L2 && I2C + help + Support for the Analog Devices ADV7170 video encoder driver + + To compile this driver as a module, choose M here: the + module will be called adv7170. + +config VIDEO_ADV7175 + tristate "Analog Devices ADV7175 video encoder" + depends on VIDEO_V4L2 && I2C + help + Support for the Analog Devices ADV7175 video encoder driver + + To compile this driver as a module, choose M here: the + module will be called adv7175. + +config VIDEO_ADV7343 + tristate "ADV7343 video encoder" + depends on I2C + help + Support for Analog Devices I2C bus based ADV7343 encoder. + + To compile this driver as a module, choose M here: the + module will be called adv7343. + +config VIDEO_ADV7393 + tristate "ADV7393 video encoder" + depends on I2C + help + Support for Analog Devices I2C bus based ADV7393 encoder. + + To compile this driver as a module, choose M here: the + module will be called adv7393. + +config VIDEO_ADV7511 + tristate "Analog Devices ADV7511 encoder" + depends on VIDEO_V4L2 && I2C + depends on DRM_I2C_ADV7511=n || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select HDMI + help + Support for the Analog Devices ADV7511 video encoder. + + This is a Analog Devices HDMI transmitter. + + To compile this driver as a module, choose M here: the + module will be called adv7511. + +config VIDEO_ADV7511_CEC + bool "Enable Analog Devices ADV7511 CEC support" + depends on VIDEO_ADV7511 + select CEC_CORE + help + When selected the adv7511 will support the optional + HDMI CEC feature. + +config VIDEO_AD9389B + tristate "Analog Devices AD9389B encoder" + depends on VIDEO_V4L2 && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + + help + Support for the Analog Devices AD9389B video encoder. + + This is a Analog Devices HDMI transmitter. + + To compile this driver as a module, choose M here: the + module will be called ad9389b. + +config VIDEO_AK881X + tristate "AK8813/AK8814 video encoders" + depends on I2C + help + Video output driver for AKM AK8813 and AK8814 TV encoders + +config VIDEO_THS8200 + tristate "Texas Instruments THS8200 video encoder" + depends on VIDEO_V4L2 && I2C + help + Support for the Texas Instruments THS8200 video encoder. + + To compile this driver as a module, choose M here: the + module will be called ths8200. +endmenu + +menu "Video improvement chips" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_UPD64031A + tristate "NEC Electronics uPD64031A Ghost Reduction" + depends on VIDEO_V4L2 && I2C + help + Support for the NEC Electronics uPD64031A Ghost Reduction + video chip. It is most often found in NTSC TV cards made for + Japan and is used to reduce the 'ghosting' effect that can + be present in analog TV broadcasts. + + To compile this driver as a module, choose M here: the + module will be called upd64031a. + +config VIDEO_UPD64083 + tristate "NEC Electronics uPD64083 3-Dimensional Y/C separation" + depends on VIDEO_V4L2 && I2C + help + Support for the NEC Electronics uPD64083 3-Dimensional Y/C + separation video chip. It is used to improve the quality of + the colors of a composite signal. + + To compile this driver as a module, choose M here: the + module will be called upd64083. +endmenu + +menu "Audio/Video compression chips" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_SAA6752HS + tristate "Philips SAA6752HS MPEG-2 Audio/Video Encoder" + depends on VIDEO_V4L2 && I2C + select CRC32 + help + Support for the Philips SAA6752HS MPEG-2 video and MPEG-audio/AC-3 + audio encoder with multiplexer. + + To compile this driver as a module, choose M here: the + module will be called saa6752hs. + +endmenu + +menu "SDR tuner chips" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config SDR_MAX2175 + tristate "Maxim 2175 RF to Bits tuner" + depends on VIDEO_V4L2 && MEDIA_SDR_SUPPORT && I2C + select REGMAP_I2C + help + Support for Maxim 2175 tuner. It is an advanced analog/digital + radio receiver with RF-to-Bits front-end designed for SDR solutions. + + To compile this driver as a module, choose M here; the + module will be called max2175. + + +endmenu + +menu "Miscellaneous helper chips" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_THS7303 + tristate "THS7303/53 Video Amplifier" + depends on VIDEO_V4L2 && I2C + help + Support for TI THS7303/53 video amplifier + + To compile this driver as a module, choose M here: the + module will be called ths7303. + +config VIDEO_M52790 + tristate "Mitsubishi M52790 A/V switch" + depends on VIDEO_V4L2 && I2C + help + Support for the Mitsubishi M52790 A/V switch. + + To compile this driver as a module, choose M here: the + module will be called m52790. + +config VIDEO_I2C + tristate "I2C transport video support" + depends on VIDEO_V4L2 && I2C + select VIDEOBUF2_VMALLOC + imply HWMON + help + Enable the I2C transport video support which supports the + following: + * Panasonic AMG88xx Grid-Eye Sensors + * Melexis MLX90640 Thermal Cameras + + To compile this driver as a module, choose M here: the + module will be called video-i2c + +config VIDEO_ST_MIPID02 + tristate "STMicroelectronics MIPID02 CSI-2 to PARALLEL bridge" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + Support for STMicroelectronics MIPID02 CSI-2 to PARALLEL bridge. + It is used to allow usage of CSI-2 sensor with PARALLEL port + controller. + + To compile this driver as a module, choose M here: the + module will be called st-mipid02. +endmenu + +# +# V4L2 I2C drivers that are related with Camera support +# + +menu "Camera sensor devices" + visible if MEDIA_CAMERA_SUPPORT + +config VIDEO_APTINA_PLL + tristate + +config VIDEO_SMIAPP_PLL + tristate + +config VIDEO_HI556 + tristate "Hynix Hi-556 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Hynix + Hi-556 camera. + + To compile this driver as a module, choose M here: the + module will be called hi556. + +config VIDEO_IMX214 + tristate "Sony IMX214 sensor support" + depends on GPIOLIB && I2C && VIDEO_V4L2 + select V4L2_FWNODE + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX214 camera. + + To compile this driver as a module, choose M here: the + module will be called imx214. + +config VIDEO_IMX219 + tristate "Sony IMX219 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Sony + IMX219 camera. + + To compile this driver as a module, choose M here: the + module will be called imx219. + +config VIDEO_IMX258 + tristate "Sony IMX258 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor driver for the Sony + IMX258 camera. + + To compile this driver as a module, choose M here: the + module will be called imx258. + +config VIDEO_IMX274 + tristate "Sony IMX274 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP_I2C + help + This is a V4L2 sensor driver for the Sony IMX274 + CMOS image sensor. + +config VIDEO_IMX290 + tristate "Sony IMX290 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP_I2C + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Sony + IMX290 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called imx290. + +config VIDEO_IMX319 + tristate "Sony IMX319 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor driver for the Sony + IMX319 camera. + + To compile this driver as a module, choose M here: the + module will be called imx319. + +config VIDEO_IMX355 + tristate "Sony IMX355 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor driver for the Sony + IMX355 camera. + + To compile this driver as a module, choose M here: the + module will be called imx355. + +config VIDEO_OV2640 + tristate "OmniVision OV2640 sensor support" + depends on VIDEO_V4L2 && I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2640 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2640. + +config VIDEO_OV2659 + tristate "OmniVision OV2659 sensor support" + depends on VIDEO_V4L2 && I2C && GPIOLIB + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2659 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2659. + +config VIDEO_OV2680 + tristate "OmniVision OV2680 sensor support" + depends on VIDEO_V4L2 && I2C + select MEDIA_CONTROLLER + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2680 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2680. + +config VIDEO_OV2685 + tristate "OmniVision OV2685 sensor support" + depends on VIDEO_V4L2 && I2C + select MEDIA_CONTROLLER + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2685 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2685. + +config VIDEO_OV2740 + tristate "OmniVision OV2740 sensor support" + depends on VIDEO_V4L2 && I2C + depends on ACPI || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2740 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2740. + +config VIDEO_OV5640 + tristate "OmniVision OV5640 sensor support" + depends on OF + depends on GPIOLIB && VIDEO_V4L2 && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Omnivision + OV5640 camera sensor with a MIPI CSI-2 interface. + +config VIDEO_OV5645 + tristate "OmniVision OV5645 sensor support" + depends on OF + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5645 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5645. + +config VIDEO_OV5647 + tristate "OmniVision OV5647 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5647 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5647. + +config VIDEO_OV6650 + tristate "OmniVision OV6650 sensor support" + depends on I2C && VIDEO_V4L2 + help + This is a Video4Linux2 sensor driver for the OmniVision + OV6650 camera. + + To compile this driver as a module, choose M here: the + module will be called ov6650. + +config VIDEO_OV5670 + tristate "OmniVision OV5670 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5670 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5670. + +config VIDEO_OV5675 + tristate "OmniVision OV5675 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5675 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5675. + +config VIDEO_OV5695 + tristate "OmniVision OV5695 sensor support" + depends on I2C && VIDEO_V4L2 + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5695 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5695. + +config VIDEO_OV7251 + tristate "OmniVision OV7251 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV7251 camera. + + To compile this driver as a module, choose M here: the + module will be called ov7251. + +config VIDEO_OV772X + tristate "OmniVision OV772x sensor support" + depends on I2C && VIDEO_V4L2 + select REGMAP_SCCB + help + This is a Video4Linux2 sensor driver for the OmniVision + OV772x camera. + + To compile this driver as a module, choose M here: the + module will be called ov772x. + +config VIDEO_OV7640 + tristate "OmniVision OV7640 sensor support" + depends on I2C && VIDEO_V4L2 + help + This is a Video4Linux2 sensor driver for the OmniVision + OV7640 camera. + + To compile this driver as a module, choose M here: the + module will be called ov7640. + +config VIDEO_OV7670 + tristate "OmniVision OV7670 sensor support" + depends on I2C && VIDEO_V4L2 + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV7670 VGA camera. It currently only works with the M88ALP01 + controller. + +config VIDEO_OV7740 + tristate "OmniVision OV7740 sensor support" + depends on I2C && VIDEO_V4L2 + select REGMAP_SCCB + help + This is a Video4Linux2 sensor driver for the OmniVision + OV7740 VGA camera sensor. + +config VIDEO_OV8856 + tristate "OmniVision OV8856 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV8856 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called ov8856. + +config VIDEO_OV9640 + tristate "OmniVision OV9640 sensor support" + depends on I2C && VIDEO_V4L2 + help + This is a Video4Linux2 sensor driver for the OmniVision + OV9640 camera sensor. + +config VIDEO_OV9650 + tristate "OmniVision OV9650/OV9652 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP_SCCB + help + This is a V4L2 sensor driver for the Omnivision + OV9650 and OV9652 camera sensors. + +config VIDEO_OV13858 + tristate "OmniVision OV13858 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV13858 camera. + +config VIDEO_VS6624 + tristate "ST VS6624 sensor support" + depends on VIDEO_V4L2 && I2C + help + This is a Video4Linux2 sensor driver for the ST VS6624 + camera. + + To compile this driver as a module, choose M here: the + module will be called vs6624. + +config VIDEO_MT9M001 + tristate "mt9m001 support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This driver supports MT9M001 cameras from Micron, monochrome + and colour models. + +config VIDEO_MT9M032 + tristate "MT9M032 camera sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEO_APTINA_PLL + help + This driver supports MT9M032 camera sensors from Aptina, monochrome + models only. + +config VIDEO_MT9M111 + tristate "mt9m111, mt9m112 and mt9m131 support" + depends on I2C && VIDEO_V4L2 + select V4L2_FWNODE + help + This driver supports MT9M111, MT9M112 and MT9M131 cameras from + Micron/Aptina + +config VIDEO_MT9P031 + tristate "Aptina MT9P031 support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEO_APTINA_PLL + help + This is a Video4Linux2 sensor driver for the Aptina + (Micron) mt9p031 5 Mpixel camera. + +config VIDEO_MT9T001 + tristate "Aptina MT9T001 support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor driver for the Aptina + (Micron) mt0t001 3 Mpixel camera. + +config VIDEO_MT9T112 + tristate "Aptina MT9T111/MT9T112 support" + depends on I2C && VIDEO_V4L2 + help + This is a Video4Linux2 sensor driver for the Aptina + (Micron) MT9T111 and MT9T112 3 Mpixel camera. + + To compile this driver as a module, choose M here: the + module will be called mt9t112. + +config VIDEO_MT9V011 + tristate "Micron mt9v011 sensor support" + depends on I2C && VIDEO_V4L2 + help + This is a Video4Linux2 sensor driver for the Micron + mt0v011 1.3 Mpixel camera. It currently only works with the + em28xx driver. + +config VIDEO_MT9V032 + tristate "Micron MT9V032 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP_I2C + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Micron + MT9V032 752x480 CMOS sensor. + +config VIDEO_MT9V111 + tristate "Aptina MT9V111 sensor support" + depends on I2C && VIDEO_V4L2 + help + This is a Video4Linux2 sensor driver for the Aptina/Micron + MT9V111 sensor. + + To compile this driver as a module, choose M here: the + module will be called mt9v111. + +config VIDEO_SR030PC30 + tristate "Siliconfile SR030PC30 sensor support" + depends on I2C && VIDEO_V4L2 + help + This driver supports SR030PC30 VGA camera from Siliconfile + +config VIDEO_NOON010PC30 + tristate "Siliconfile NOON010PC30 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This driver supports NOON010PC30 CIF camera from Siliconfile + +source "drivers/media/i2c/m5mols/Kconfig" + +config VIDEO_RDACM20 + tristate "IMI RDACM20 camera support" + depends on I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + help + This driver supports the IMI RDACM20 GMSL camera, used in + ADAS systems. + + This camera should be used in conjunction with a GMSL + deserialiser such as the MAX9286. + +config VIDEO_RJ54N1 + tristate "Sharp RJ54N1CB0C sensor support" + depends on I2C && VIDEO_V4L2 + help + This is a V4L2 sensor driver for Sharp RJ54N1CB0C CMOS image + sensor. + + To compile this driver as a module, choose M here: the + module will be called rj54n1. + +config VIDEO_S5K6AA + tristate "Samsung S5K6AAFX sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a V4L2 sensor driver for Samsung S5K6AA(FX) 1.3M + camera sensor with an embedded SoC image signal processor. + +config VIDEO_S5K6A3 + tristate "Samsung S5K6A3 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a V4L2 sensor driver for Samsung S5K6A3 raw + camera sensor. + +config VIDEO_S5K4ECGX + tristate "Samsung S5K4ECGX sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select CRC32 + help + This is a V4L2 sensor driver for Samsung S5K4ECGX 5M + camera sensor with an embedded SoC image signal processor. + +config VIDEO_S5K5BAF + tristate "Samsung S5K5BAF sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a V4L2 sensor driver for Samsung S5K5BAF 2M + camera sensor with an embedded SoC image signal processor. + +source "drivers/media/i2c/smiapp/Kconfig" +source "drivers/media/i2c/et8ek8/Kconfig" + +config VIDEO_S5C73M3 + tristate "Samsung S5C73M3 sensor support" + depends on I2C && SPI && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a V4L2 sensor driver for Samsung S5C73M3 + 8 Mpixel camera. + +endmenu + +menu "Lens drivers" + visible if MEDIA_CAMERA_SUPPORT + +config VIDEO_AD5820 + tristate "AD5820 lens voice coil support" + depends on GPIOLIB && I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + help + This is a driver for the AD5820 camera lens voice coil. + It is used for example in Nokia N900 (RX-51). + +config VIDEO_AK7375 + tristate "AK7375 lens voice coil support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a driver for the AK7375 camera lens voice coil. + AK7375 is a 12 bit DAC with 120mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + +config VIDEO_DW9714 + tristate "DW9714 lens voice coil support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a driver for the DW9714 camera lens voice coil. + DW9714 is a 10 bit DAC with 120mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + +config VIDEO_DW9768 + tristate "DW9768 lens voice coil support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a driver for the DW9768 camera lens voice coil. + DW9768 is a 10 bit DAC with 100mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + +config VIDEO_DW9807_VCM + tristate "DW9807 lens voice coil support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a driver for the DW9807 camera lens voice coil. + DW9807 is a 10 bit DAC with 100mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + +endmenu + +menu "Flash devices" + visible if MEDIA_CAMERA_SUPPORT + +config VIDEO_ADP1653 + tristate "ADP1653 flash support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + help + This is a driver for the ADP1653 flash controller. It is used for + example in Nokia N900. + +config VIDEO_LM3560 + tristate "LM3560 dual flash driver support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select REGMAP_I2C + help + This is a driver for the lm3560 dual flash controllers. It controls + flash, torch LEDs. + +config VIDEO_LM3646 + tristate "LM3646 dual flash driver support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select REGMAP_I2C + help + This is a driver for the lm3646 dual flash controllers. It controls + flash, torch LEDs. +endmenu + +endif # VIDEO_V4L2 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile new file mode 100644 index 000000000..f0a774739 --- /dev/null +++ b/drivers/media/i2c/Makefile @@ -0,0 +1,127 @@ +# SPDX-License-Identifier: GPL-2.0 +msp3400-objs := msp3400-driver.o msp3400-kthreads.o +obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o + +obj-$(CONFIG_VIDEO_SMIAPP) += smiapp/ +obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ +obj-$(CONFIG_VIDEO_CX25840) += cx25840/ +obj-$(CONFIG_VIDEO_M5MOLS) += m5mols/ + +obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o +obj-$(CONFIG_VIDEO_TVAUDIO) += tvaudio.o +obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o +obj-$(CONFIG_VIDEO_SAA6588) += saa6588.o +obj-$(CONFIG_VIDEO_TDA9840) += tda9840.o +obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o +obj-$(CONFIG_VIDEO_TEA6415C) += tea6415c.o +obj-$(CONFIG_VIDEO_TEA6420) += tea6420.o +obj-$(CONFIG_VIDEO_SAA7110) += saa7110.o +obj-$(CONFIG_VIDEO_SAA711X) += saa7115.o +obj-$(CONFIG_VIDEO_SAA717X) += saa717x.o +obj-$(CONFIG_VIDEO_SAA7127) += saa7127.o +obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o +obj-$(CONFIG_VIDEO_SAA6752HS) += saa6752hs.o +obj-$(CONFIG_VIDEO_AD5820) += ad5820.o +obj-$(CONFIG_VIDEO_AK7375) += ak7375.o +obj-$(CONFIG_VIDEO_DW9714) += dw9714.o +obj-$(CONFIG_VIDEO_DW9768) += dw9768.o +obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o +obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o +obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o +obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o +obj-$(CONFIG_VIDEO_ADV7183) += adv7183.o +obj-$(CONFIG_VIDEO_ADV7343) += adv7343.o +obj-$(CONFIG_VIDEO_ADV7393) += adv7393.o +obj-$(CONFIG_VIDEO_ADV748X) += adv748x/ +obj-$(CONFIG_VIDEO_ADV7604) += adv7604.o +obj-$(CONFIG_VIDEO_ADV7842) += adv7842.o +obj-$(CONFIG_VIDEO_AD9389B) += ad9389b.o +obj-$(CONFIG_VIDEO_ADV7511) += adv7511-v4l2.o +obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o +obj-$(CONFIG_VIDEO_VS6624) += vs6624.o +obj-$(CONFIG_VIDEO_BT819) += bt819.o +obj-$(CONFIG_VIDEO_BT856) += bt856.o +obj-$(CONFIG_VIDEO_BT866) += bt866.o +obj-$(CONFIG_VIDEO_KS0127) += ks0127.o +obj-$(CONFIG_VIDEO_THS7303) += ths7303.o +obj-$(CONFIG_VIDEO_THS8200) += ths8200.o +obj-$(CONFIG_VIDEO_TVP5150) += tvp5150.o +obj-$(CONFIG_VIDEO_TVP514X) += tvp514x.o +obj-$(CONFIG_VIDEO_TVP7002) += tvp7002.o +obj-$(CONFIG_VIDEO_TW2804) += tw2804.o +obj-$(CONFIG_VIDEO_TW9903) += tw9903.o +obj-$(CONFIG_VIDEO_TW9906) += tw9906.o +obj-$(CONFIG_VIDEO_TW9910) += tw9910.o +obj-$(CONFIG_VIDEO_CS3308) += cs3308.o +obj-$(CONFIG_VIDEO_CS5345) += cs5345.o +obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o +obj-$(CONFIG_VIDEO_M52790) += m52790.o +obj-$(CONFIG_VIDEO_TLV320AIC23B) += tlv320aic23b.o +obj-$(CONFIG_VIDEO_UDA1342) += uda1342.o +obj-$(CONFIG_VIDEO_WM8775) += wm8775.o +obj-$(CONFIG_VIDEO_WM8739) += wm8739.o +obj-$(CONFIG_VIDEO_VP27SMPX) += vp27smpx.o +obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o +obj-$(CONFIG_VIDEO_UPD64031A) += upd64031a.o +obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o +obj-$(CONFIG_VIDEO_OV2640) += ov2640.o +obj-$(CONFIG_VIDEO_OV2680) += ov2680.o +obj-$(CONFIG_VIDEO_OV2685) += ov2685.o +obj-$(CONFIG_VIDEO_OV2740) += ov2740.o +obj-$(CONFIG_VIDEO_OV5640) += ov5640.o +obj-$(CONFIG_VIDEO_OV5645) += ov5645.o +obj-$(CONFIG_VIDEO_OV5647) += ov5647.o +obj-$(CONFIG_VIDEO_OV5670) += ov5670.o +obj-$(CONFIG_VIDEO_OV5675) += ov5675.o +obj-$(CONFIG_VIDEO_OV5695) += ov5695.o +obj-$(CONFIG_VIDEO_OV6650) += ov6650.o +obj-$(CONFIG_VIDEO_OV7251) += ov7251.o +obj-$(CONFIG_VIDEO_OV7640) += ov7640.o +obj-$(CONFIG_VIDEO_OV7670) += ov7670.o +obj-$(CONFIG_VIDEO_OV772X) += ov772x.o +obj-$(CONFIG_VIDEO_OV7740) += ov7740.o +obj-$(CONFIG_VIDEO_OV8856) += ov8856.o +obj-$(CONFIG_VIDEO_OV9640) += ov9640.o +obj-$(CONFIG_VIDEO_OV9650) += ov9650.o +obj-$(CONFIG_VIDEO_OV13858) += ov13858.o +obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o +obj-$(CONFIG_VIDEO_MT9M032) += mt9m032.o +obj-$(CONFIG_VIDEO_MT9M111) += mt9m111.o +obj-$(CONFIG_VIDEO_MT9P031) += mt9p031.o +obj-$(CONFIG_VIDEO_MT9T001) += mt9t001.o +obj-$(CONFIG_VIDEO_MT9T112) += mt9t112.o +obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o +obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o +obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o +obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o +obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o +obj-$(CONFIG_VIDEO_RJ54N1) += rj54n1cb0c.o +obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o +obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o +obj-$(CONFIG_VIDEO_S5K4ECGX) += s5k4ecgx.o +obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o +obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/ +obj-$(CONFIG_VIDEO_ADP1653) += adp1653.o +obj-$(CONFIG_VIDEO_LM3560) += lm3560.o +obj-$(CONFIG_VIDEO_LM3646) += lm3646.o +obj-$(CONFIG_VIDEO_SMIAPP_PLL) += smiapp-pll.o +obj-$(CONFIG_VIDEO_AK881X) += ak881x.o +obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o +obj-$(CONFIG_VIDEO_I2C) += video-i2c.o +obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o +obj-$(CONFIG_VIDEO_OV2659) += ov2659.o +obj-$(CONFIG_VIDEO_TC358743) += tc358743.o +obj-$(CONFIG_VIDEO_HI556) += hi556.o +obj-$(CONFIG_VIDEO_IMX214) += imx214.o +obj-$(CONFIG_VIDEO_IMX219) += imx219.o +obj-$(CONFIG_VIDEO_IMX258) += imx258.o +obj-$(CONFIG_VIDEO_IMX274) += imx274.o +obj-$(CONFIG_VIDEO_IMX290) += imx290.o +obj-$(CONFIG_VIDEO_IMX319) += imx319.o +obj-$(CONFIG_VIDEO_IMX355) += imx355.o +obj-$(CONFIG_VIDEO_MAX9286) += max9286.o +rdacm20-camera_module-objs := rdacm20.o max9271.o +obj-$(CONFIG_VIDEO_RDACM20) += rdacm20-camera_module.o +obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o + +obj-$(CONFIG_SDR_MAX2175) += max2175.o diff --git a/drivers/media/i2c/ad5820.c b/drivers/media/i2c/ad5820.c new file mode 100644 index 000000000..d2c69ee27 --- /dev/null +++ b/drivers/media/i2c/ad5820.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/ad5820.c + * + * AD5820 DAC driver for camera voice coil focus. + * + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2007 Texas Instruments + * Copyright (C) 2016 Pavel Machek <pavel@ucw.cz> + * + * Contact: Tuukka Toivonen <tuukkat76@gmail.com> + * Sakari Ailus <sakari.ailus@iki.fi> + * + * Based on af_d88.c by Texas Instruments. + */ + +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +/* Register definitions */ +#define AD5820_POWER_DOWN (1 << 15) +#define AD5820_DAC_SHIFT 4 +#define AD5820_RAMP_MODE_LINEAR (0 << 3) +#define AD5820_RAMP_MODE_64_16 (1 << 3) + +#define CODE_TO_RAMP_US(s) ((s) == 0 ? 0 : (1 << ((s) - 1)) * 50) +#define RAMP_US_TO_CODE(c) fls(((c) + ((c)>>1)) / 50) + +#define to_ad5820_device(sd) container_of(sd, struct ad5820_device, subdev) + +struct ad5820_device { + struct v4l2_subdev subdev; + struct ad5820_platform_data *platform_data; + struct regulator *vana; + + struct v4l2_ctrl_handler ctrls; + u32 focus_absolute; + u32 focus_ramp_time; + u32 focus_ramp_mode; + + struct gpio_desc *enable_gpio; + + struct mutex power_lock; + int power_count; + + bool standby; +}; + +static int ad5820_write(struct ad5820_device *coil, u16 data) +{ + struct i2c_client *client = v4l2_get_subdevdata(&coil->subdev); + struct i2c_msg msg; + __be16 be_data; + int r; + + if (!client->adapter) + return -ENODEV; + + be_data = cpu_to_be16(data); + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = (u8 *)&be_data; + + r = i2c_transfer(client->adapter, &msg, 1); + if (r < 0) { + dev_err(&client->dev, "write failed, error %d\n", r); + return r; + } + + return 0; +} + +/* + * Calculate status word and write it to the device based on current + * values of V4L2 controls. It is assumed that the stored V4L2 control + * values are properly limited and rounded. + */ +static int ad5820_update_hw(struct ad5820_device *coil) +{ + u16 status; + + status = RAMP_US_TO_CODE(coil->focus_ramp_time); + status |= coil->focus_ramp_mode + ? AD5820_RAMP_MODE_64_16 : AD5820_RAMP_MODE_LINEAR; + status |= coil->focus_absolute << AD5820_DAC_SHIFT; + + if (coil->standby) + status |= AD5820_POWER_DOWN; + + return ad5820_write(coil, status); +} + +/* + * Power handling + */ +static int ad5820_power_off(struct ad5820_device *coil, bool standby) +{ + int ret = 0, ret2; + + /* + * Go to standby first as real power off my be denied by the hardware + * (single power line control for both coil and sensor). + */ + if (standby) { + coil->standby = true; + ret = ad5820_update_hw(coil); + } + + gpiod_set_value_cansleep(coil->enable_gpio, 0); + + ret2 = regulator_disable(coil->vana); + if (ret) + return ret; + return ret2; +} + +static int ad5820_power_on(struct ad5820_device *coil, bool restore) +{ + int ret; + + ret = regulator_enable(coil->vana); + if (ret < 0) + return ret; + + gpiod_set_value_cansleep(coil->enable_gpio, 1); + + if (restore) { + /* Restore the hardware settings. */ + coil->standby = false; + ret = ad5820_update_hw(coil); + if (ret) + goto fail; + } + return 0; + +fail: + gpiod_set_value_cansleep(coil->enable_gpio, 0); + coil->standby = true; + regulator_disable(coil->vana); + + return ret; +} + +/* + * V4L2 controls + */ +static int ad5820_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ad5820_device *coil = + container_of(ctrl->handler, struct ad5820_device, ctrls); + + switch (ctrl->id) { + case V4L2_CID_FOCUS_ABSOLUTE: + coil->focus_absolute = ctrl->val; + return ad5820_update_hw(coil); + } + + return 0; +} + +static const struct v4l2_ctrl_ops ad5820_ctrl_ops = { + .s_ctrl = ad5820_set_ctrl, +}; + + +static int ad5820_init_controls(struct ad5820_device *coil) +{ + v4l2_ctrl_handler_init(&coil->ctrls, 1); + + /* + * V4L2_CID_FOCUS_ABSOLUTE + * + * Minimum current is 0 mA, maximum is 100 mA. Thus, 1 code is + * equivalent to 100/1023 = 0.0978 mA. Nevertheless, we do not use [mA] + * for focus position, because it is meaningless for user. Meaningful + * would be to use focus distance or even its inverse, but since the + * driver doesn't have sufficiently knowledge to do the conversion, we + * will just use abstract codes here. In any case, smaller value = focus + * position farther from camera. The default zero value means focus at + * infinity, and also least current consumption. + */ + v4l2_ctrl_new_std(&coil->ctrls, &ad5820_ctrl_ops, + V4L2_CID_FOCUS_ABSOLUTE, 0, 1023, 1, 0); + + if (coil->ctrls.error) + return coil->ctrls.error; + + coil->focus_absolute = 0; + coil->focus_ramp_time = 0; + coil->focus_ramp_mode = 0; + + coil->subdev.ctrl_handler = &coil->ctrls; + + return 0; +} + +/* + * V4L2 subdev operations + */ +static int ad5820_registered(struct v4l2_subdev *subdev) +{ + struct ad5820_device *coil = to_ad5820_device(subdev); + + return ad5820_init_controls(coil); +} + +static int +ad5820_set_power(struct v4l2_subdev *subdev, int on) +{ + struct ad5820_device *coil = to_ad5820_device(subdev); + int ret = 0; + + mutex_lock(&coil->power_lock); + + /* + * If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (coil->power_count == !on) { + ret = on ? ad5820_power_on(coil, true) : + ad5820_power_off(coil, true); + if (ret < 0) + goto done; + } + + /* Update the power count. */ + coil->power_count += on ? 1 : -1; + WARN_ON(coil->power_count < 0); + +done: + mutex_unlock(&coil->power_lock); + return ret; +} + +static int ad5820_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return ad5820_set_power(sd, 1); +} + +static int ad5820_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return ad5820_set_power(sd, 0); +} + +static const struct v4l2_subdev_core_ops ad5820_core_ops = { + .s_power = ad5820_set_power, +}; + +static const struct v4l2_subdev_ops ad5820_ops = { + .core = &ad5820_core_ops, +}; + +static const struct v4l2_subdev_internal_ops ad5820_internal_ops = { + .registered = ad5820_registered, + .open = ad5820_open, + .close = ad5820_close, +}; + +/* + * I2C driver + */ +static int __maybe_unused ad5820_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ad5820_device *coil = to_ad5820_device(subdev); + + if (!coil->power_count) + return 0; + + return ad5820_power_off(coil, false); +} + +static int __maybe_unused ad5820_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ad5820_device *coil = to_ad5820_device(subdev); + + if (!coil->power_count) + return 0; + + return ad5820_power_on(coil, true); +} + +static int ad5820_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ad5820_device *coil; + int ret; + + coil = devm_kzalloc(&client->dev, sizeof(*coil), GFP_KERNEL); + if (!coil) + return -ENOMEM; + + coil->vana = devm_regulator_get(&client->dev, "VANA"); + if (IS_ERR(coil->vana)) { + ret = PTR_ERR(coil->vana); + if (ret != -EPROBE_DEFER) + dev_err(&client->dev, "could not get regulator for vana\n"); + return ret; + } + + coil->enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(coil->enable_gpio)) { + ret = PTR_ERR(coil->enable_gpio); + if (ret != -EPROBE_DEFER) + dev_err(&client->dev, "could not get enable gpio\n"); + return ret; + } + + mutex_init(&coil->power_lock); + + v4l2_i2c_subdev_init(&coil->subdev, client, &ad5820_ops); + coil->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + coil->subdev.internal_ops = &ad5820_internal_ops; + coil->subdev.entity.function = MEDIA_ENT_F_LENS; + strscpy(coil->subdev.name, "ad5820 focus", sizeof(coil->subdev.name)); + + ret = media_entity_pads_init(&coil->subdev.entity, 0, NULL); + if (ret < 0) + goto clean_mutex; + + ret = v4l2_async_register_subdev(&coil->subdev); + if (ret < 0) + goto clean_entity; + + return ret; + +clean_entity: + media_entity_cleanup(&coil->subdev.entity); +clean_mutex: + mutex_destroy(&coil->power_lock); + return ret; +} + +static int ad5820_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ad5820_device *coil = to_ad5820_device(subdev); + + v4l2_async_unregister_subdev(&coil->subdev); + v4l2_ctrl_handler_free(&coil->ctrls); + media_entity_cleanup(&coil->subdev.entity); + mutex_destroy(&coil->power_lock); + return 0; +} + +static const struct i2c_device_id ad5820_id_table[] = { + { "ad5820", 0 }, + { "ad5821", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5820_id_table); + +static const struct of_device_id ad5820_of_table[] = { + { .compatible = "adi,ad5820" }, + { .compatible = "adi,ad5821" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5820_of_table); + +static SIMPLE_DEV_PM_OPS(ad5820_pm, ad5820_suspend, ad5820_resume); + +static struct i2c_driver ad5820_i2c_driver = { + .driver = { + .name = "ad5820", + .pm = &ad5820_pm, + .of_match_table = ad5820_of_table, + }, + .probe = ad5820_probe, + .remove = ad5820_remove, + .id_table = ad5820_id_table, +}; + +module_i2c_driver(ad5820_i2c_driver); + +MODULE_AUTHOR("Tuukka Toivonen"); +MODULE_DESCRIPTION("AD5820 camera lens driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ad9389b.c b/drivers/media/i2c/ad9389b.c new file mode 100644 index 000000000..8679a44e6 --- /dev/null +++ b/drivers/media/i2c/ad9389b.c @@ -0,0 +1,1216 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD9389B/AD9889B video encoder driver + * + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + */ + +/* + * References (c = chapter, p = page): + * REF_01 - Analog Devices, Programming Guide, AD9889B/AD9389B, + * HDMI Transitter, Rev. A, October 2010 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <linux/workqueue.h> +#include <linux/v4l2-dv-timings.h> +#include <media/v4l2-device.h> +#include <media/v4l2-common.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-ctrls.h> +#include <media/i2c/ad9389b.h> + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices AD9389B/AD9889B video encoder driver"); +MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>"); +MODULE_AUTHOR("Martin Bugge <marbugge@cisco.com>"); +MODULE_LICENSE("GPL"); + +#define MASK_AD9389B_EDID_RDY_INT 0x04 +#define MASK_AD9389B_MSEN_INT 0x40 +#define MASK_AD9389B_HPD_INT 0x80 + +#define MASK_AD9389B_HPD_DETECT 0x40 +#define MASK_AD9389B_MSEN_DETECT 0x20 +#define MASK_AD9389B_EDID_RDY 0x10 + +#define EDID_MAX_RETRIES (8) +#define EDID_DELAY 250 +#define EDID_MAX_SEGM 8 + +/* +********************************************************************** +* +* Arrays with configuration parameters for the AD9389B +* +********************************************************************** +*/ + +struct ad9389b_state_edid { + /* total number of blocks */ + u32 blocks; + /* Number of segments read */ + u32 segments; + u8 data[EDID_MAX_SEGM * 256]; + /* Number of EDID read retries left */ + unsigned read_retries; +}; + +struct ad9389b_state { + struct ad9389b_platform_data pdata; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + int chip_revision; + /* Is the ad9389b powered on? */ + bool power_on; + /* Did we receive hotplug and rx-sense signals? */ + bool have_monitor; + /* timings from s_dv_timings */ + struct v4l2_dv_timings dv_timings; + /* controls */ + struct v4l2_ctrl *hdmi_mode_ctrl; + struct v4l2_ctrl *hotplug_ctrl; + struct v4l2_ctrl *rx_sense_ctrl; + struct v4l2_ctrl *have_edid0_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; + struct i2c_client *edid_i2c_client; + struct ad9389b_state_edid edid; + /* Running counter of the number of detected EDIDs (for debugging) */ + unsigned edid_detect_counter; + struct delayed_work edid_handler; /* work entry */ +}; + +static void ad9389b_check_monitor_present_status(struct v4l2_subdev *sd); +static bool ad9389b_check_edid_status(struct v4l2_subdev *sd); +static void ad9389b_setup(struct v4l2_subdev *sd); +static int ad9389b_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq); +static int ad9389b_s_clock_freq(struct v4l2_subdev *sd, u32 freq); + +static inline struct ad9389b_state *get_ad9389b_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ad9389b_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ad9389b_state, hdl)->sd; +} + +/* ------------------------ I2C ----------------------------------------------- */ + +static int ad9389b_rd(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int ad9389b_wr(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "%s: failed reg 0x%x, val 0x%x\n", __func__, reg, val); + return ret; +} + +/* To set specific bits in the register, a clear-mask is given (to be AND-ed), + and then the value-mask (to be OR-ed). */ +static inline void ad9389b_wr_and_or(struct v4l2_subdev *sd, u8 reg, + u8 clr_mask, u8 val_mask) +{ + ad9389b_wr(sd, reg, (ad9389b_rd(sd, reg) & clr_mask) | val_mask); +} + +static void ad9389b_edid_rd(struct v4l2_subdev *sd, u16 len, u8 *buf) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + int i; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + for (i = 0; i < len; i++) + buf[i] = i2c_smbus_read_byte_data(state->edid_i2c_client, i); +} + +static inline bool ad9389b_have_hotplug(struct v4l2_subdev *sd) +{ + return ad9389b_rd(sd, 0x42) & MASK_AD9389B_HPD_DETECT; +} + +static inline bool ad9389b_have_rx_sense(struct v4l2_subdev *sd) +{ + return ad9389b_rd(sd, 0x42) & MASK_AD9389B_MSEN_DETECT; +} + +static void ad9389b_csc_conversion_mode(struct v4l2_subdev *sd, u8 mode) +{ + ad9389b_wr_and_or(sd, 0x17, 0xe7, (mode & 0x3)<<3); + ad9389b_wr_and_or(sd, 0x18, 0x9f, (mode & 0x3)<<5); +} + +static void ad9389b_csc_coeff(struct v4l2_subdev *sd, + u16 A1, u16 A2, u16 A3, u16 A4, + u16 B1, u16 B2, u16 B3, u16 B4, + u16 C1, u16 C2, u16 C3, u16 C4) +{ + /* A */ + ad9389b_wr_and_or(sd, 0x18, 0xe0, A1>>8); + ad9389b_wr(sd, 0x19, A1); + ad9389b_wr_and_or(sd, 0x1A, 0xe0, A2>>8); + ad9389b_wr(sd, 0x1B, A2); + ad9389b_wr_and_or(sd, 0x1c, 0xe0, A3>>8); + ad9389b_wr(sd, 0x1d, A3); + ad9389b_wr_and_or(sd, 0x1e, 0xe0, A4>>8); + ad9389b_wr(sd, 0x1f, A4); + + /* B */ + ad9389b_wr_and_or(sd, 0x20, 0xe0, B1>>8); + ad9389b_wr(sd, 0x21, B1); + ad9389b_wr_and_or(sd, 0x22, 0xe0, B2>>8); + ad9389b_wr(sd, 0x23, B2); + ad9389b_wr_and_or(sd, 0x24, 0xe0, B3>>8); + ad9389b_wr(sd, 0x25, B3); + ad9389b_wr_and_or(sd, 0x26, 0xe0, B4>>8); + ad9389b_wr(sd, 0x27, B4); + + /* C */ + ad9389b_wr_and_or(sd, 0x28, 0xe0, C1>>8); + ad9389b_wr(sd, 0x29, C1); + ad9389b_wr_and_or(sd, 0x2A, 0xe0, C2>>8); + ad9389b_wr(sd, 0x2B, C2); + ad9389b_wr_and_or(sd, 0x2C, 0xe0, C3>>8); + ad9389b_wr(sd, 0x2D, C3); + ad9389b_wr_and_or(sd, 0x2E, 0xe0, C4>>8); + ad9389b_wr(sd, 0x2F, C4); +} + +static void ad9389b_csc_rgb_full2limit(struct v4l2_subdev *sd, bool enable) +{ + if (enable) { + u8 csc_mode = 0; + + ad9389b_csc_conversion_mode(sd, csc_mode); + ad9389b_csc_coeff(sd, + 4096-564, 0, 0, 256, + 0, 4096-564, 0, 256, + 0, 0, 4096-564, 256); + /* enable CSC */ + ad9389b_wr_and_or(sd, 0x3b, 0xfe, 0x1); + /* AVI infoframe: Limited range RGB (16-235) */ + ad9389b_wr_and_or(sd, 0xcd, 0xf9, 0x02); + } else { + /* disable CSC */ + ad9389b_wr_and_or(sd, 0x3b, 0xfe, 0x0); + /* AVI infoframe: Full range RGB (0-255) */ + ad9389b_wr_and_or(sd, 0xcd, 0xf9, 0x04); + } +} + +static void ad9389b_set_IT_content_AVI_InfoFrame(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + if (state->dv_timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) { + /* CE format, not IT */ + ad9389b_wr_and_or(sd, 0xcd, 0xbf, 0x00); + } else { + /* IT format */ + ad9389b_wr_and_or(sd, 0xcd, 0xbf, 0x40); + } +} + +static int ad9389b_set_rgb_quantization_mode(struct v4l2_subdev *sd, struct v4l2_ctrl *ctrl) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + switch (ctrl->val) { + case V4L2_DV_RGB_RANGE_AUTO: + /* automatic */ + if (state->dv_timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) { + /* CE format, RGB limited range (16-235) */ + ad9389b_csc_rgb_full2limit(sd, true); + } else { + /* not CE format, RGB full range (0-255) */ + ad9389b_csc_rgb_full2limit(sd, false); + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + /* RGB limited range (16-235) */ + ad9389b_csc_rgb_full2limit(sd, true); + break; + case V4L2_DV_RGB_RANGE_FULL: + /* RGB full range (0-255) */ + ad9389b_csc_rgb_full2limit(sd, false); + break; + default: + return -EINVAL; + } + return 0; +} + +static void ad9389b_set_manual_pll_gear(struct v4l2_subdev *sd, u32 pixelclock) +{ + u8 gear; + + /* Workaround for TMDS PLL problem + * The TMDS PLL in AD9389b change gear when the chip is heated above a + * certain temperature. The output is disabled when the PLL change gear + * so the monitor has to lock on the signal again. A workaround for + * this is to use the manual PLL gears. This is a solution from Analog + * Devices that is not documented in the datasheets. + * 0x98 [7] = enable manual gearing. 0x98 [6:4] = gear + * + * The pixel frequency ranges are based on readout of the gear the + * automatic gearing selects for different pixel clocks + * (read from 0x9e [3:1]). + */ + + if (pixelclock > 140000000) + gear = 0xc0; /* 4th gear */ + else if (pixelclock > 117000000) + gear = 0xb0; /* 3rd gear */ + else if (pixelclock > 87000000) + gear = 0xa0; /* 2nd gear */ + else if (pixelclock > 60000000) + gear = 0x90; /* 1st gear */ + else + gear = 0x80; /* 0th gear */ + + ad9389b_wr_and_or(sd, 0x98, 0x0f, gear); +} + +/* ------------------------------ CTRL OPS ------------------------------ */ + +static int ad9389b_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct ad9389b_state *state = get_ad9389b_state(sd); + + v4l2_dbg(1, debug, sd, + "%s: ctrl id: %d, ctrl->val %d\n", __func__, ctrl->id, ctrl->val); + + if (state->hdmi_mode_ctrl == ctrl) { + /* Set HDMI or DVI-D */ + ad9389b_wr_and_or(sd, 0xaf, 0xfd, + ctrl->val == V4L2_DV_TX_MODE_HDMI ? 0x02 : 0x00); + return 0; + } + if (state->rgb_quantization_range_ctrl == ctrl) + return ad9389b_set_rgb_quantization_mode(sd, ctrl); + return -EINVAL; +} + +static const struct v4l2_ctrl_ops ad9389b_ctrl_ops = { + .s_ctrl = ad9389b_s_ctrl, +}; + +/* ---------------------------- CORE OPS ------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ad9389b_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->val = ad9389b_rd(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int ad9389b_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + ad9389b_wr(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static int ad9389b_log_status(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + struct ad9389b_state_edid *edid = &state->edid; + + static const char * const states[] = { + "in reset", + "reading EDID", + "idle", + "initializing HDCP", + "HDCP enabled", + "initializing HDCP repeater", + "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" + }; + static const char * const errors[] = { + "no error", + "bad receiver BKSV", + "Ri mismatch", + "Pj mismatch", + "i2c error", + "timed out", + "max repeater cascade exceeded", + "hash check failed", + "too many devices", + "9", "A", "B", "C", "D", "E", "F" + }; + + u8 manual_gear; + + v4l2_info(sd, "chip revision %d\n", state->chip_revision); + v4l2_info(sd, "power %s\n", state->power_on ? "on" : "off"); + v4l2_info(sd, "%s hotplug, %s Rx Sense, %s EDID (%d block(s))\n", + (ad9389b_rd(sd, 0x42) & MASK_AD9389B_HPD_DETECT) ? + "detected" : "no", + (ad9389b_rd(sd, 0x42) & MASK_AD9389B_MSEN_DETECT) ? + "detected" : "no", + edid->segments ? "found" : "no", edid->blocks); + v4l2_info(sd, "%s output %s\n", + (ad9389b_rd(sd, 0xaf) & 0x02) ? + "HDMI" : "DVI-D", + (ad9389b_rd(sd, 0xa1) & 0x3c) ? + "disabled" : "enabled"); + v4l2_info(sd, "ad9389b: %s\n", (ad9389b_rd(sd, 0xb8) & 0x40) ? + "encrypted" : "no encryption"); + v4l2_info(sd, "state: %s, error: %s, detect count: %u, msk/irq: %02x/%02x\n", + states[ad9389b_rd(sd, 0xc8) & 0xf], + errors[ad9389b_rd(sd, 0xc8) >> 4], + state->edid_detect_counter, + ad9389b_rd(sd, 0x94), ad9389b_rd(sd, 0x96)); + manual_gear = ad9389b_rd(sd, 0x98) & 0x80; + v4l2_info(sd, "ad9389b: RGB quantization: %s range\n", + ad9389b_rd(sd, 0x3b) & 0x01 ? "limited" : "full"); + v4l2_info(sd, "ad9389b: %s gear %d\n", + manual_gear ? "manual" : "automatic", + manual_gear ? ((ad9389b_rd(sd, 0x98) & 0x70) >> 4) : + ((ad9389b_rd(sd, 0x9e) & 0x0e) >> 1)); + if (ad9389b_rd(sd, 0xaf) & 0x02) { + /* HDMI only */ + u8 manual_cts = ad9389b_rd(sd, 0x0a) & 0x80; + u32 N = (ad9389b_rd(sd, 0x01) & 0xf) << 16 | + ad9389b_rd(sd, 0x02) << 8 | + ad9389b_rd(sd, 0x03); + u8 vic_detect = ad9389b_rd(sd, 0x3e) >> 2; + u8 vic_sent = ad9389b_rd(sd, 0x3d) & 0x3f; + u32 CTS; + + if (manual_cts) + CTS = (ad9389b_rd(sd, 0x07) & 0xf) << 16 | + ad9389b_rd(sd, 0x08) << 8 | + ad9389b_rd(sd, 0x09); + else + CTS = (ad9389b_rd(sd, 0x04) & 0xf) << 16 | + ad9389b_rd(sd, 0x05) << 8 | + ad9389b_rd(sd, 0x06); + N = (ad9389b_rd(sd, 0x01) & 0xf) << 16 | + ad9389b_rd(sd, 0x02) << 8 | + ad9389b_rd(sd, 0x03); + + v4l2_info(sd, "ad9389b: CTS %s mode: N %d, CTS %d\n", + manual_cts ? "manual" : "automatic", N, CTS); + + v4l2_info(sd, "ad9389b: VIC: detected %d, sent %d\n", + vic_detect, vic_sent); + } + if (state->dv_timings.type == V4L2_DV_BT_656_1120) + v4l2_print_dv_timings(sd->name, "timings: ", + &state->dv_timings, false); + else + v4l2_info(sd, "no timings set\n"); + return 0; +} + +/* Power up/down ad9389b */ +static int ad9389b_s_power(struct v4l2_subdev *sd, int on) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + struct ad9389b_platform_data *pdata = &state->pdata; + const int retries = 20; + int i; + + v4l2_dbg(1, debug, sd, "%s: power %s\n", __func__, on ? "on" : "off"); + + state->power_on = on; + + if (!on) { + /* Power down */ + ad9389b_wr_and_or(sd, 0x41, 0xbf, 0x40); + return true; + } + + /* Power up */ + /* The ad9389b does not always come up immediately. + Retry multiple times. */ + for (i = 0; i < retries; i++) { + ad9389b_wr_and_or(sd, 0x41, 0xbf, 0x0); + if ((ad9389b_rd(sd, 0x41) & 0x40) == 0) + break; + ad9389b_wr_and_or(sd, 0x41, 0xbf, 0x40); + msleep(10); + } + if (i == retries) { + v4l2_dbg(1, debug, sd, "failed to powerup the ad9389b\n"); + ad9389b_s_power(sd, 0); + return false; + } + if (i > 1) + v4l2_dbg(1, debug, sd, + "needed %d retries to powerup the ad9389b\n", i); + + /* Select chip: AD9389B */ + ad9389b_wr_and_or(sd, 0xba, 0xef, 0x10); + + /* Reserved registers that must be set according to REF_01 p. 11*/ + ad9389b_wr_and_or(sd, 0x98, 0xf0, 0x07); + ad9389b_wr(sd, 0x9c, 0x38); + ad9389b_wr_and_or(sd, 0x9d, 0xfc, 0x01); + + /* Differential output drive strength */ + if (pdata->diff_data_drive_strength > 0) + ad9389b_wr(sd, 0xa2, pdata->diff_data_drive_strength); + else + ad9389b_wr(sd, 0xa2, 0x87); + + if (pdata->diff_clk_drive_strength > 0) + ad9389b_wr(sd, 0xa3, pdata->diff_clk_drive_strength); + else + ad9389b_wr(sd, 0xa3, 0x87); + + ad9389b_wr(sd, 0x0a, 0x01); + ad9389b_wr(sd, 0xbb, 0xff); + + /* Set number of attempts to read the EDID */ + ad9389b_wr(sd, 0xc9, 0xf); + return true; +} + +/* Enable interrupts */ +static void ad9389b_set_isr(struct v4l2_subdev *sd, bool enable) +{ + u8 irqs = MASK_AD9389B_HPD_INT | MASK_AD9389B_MSEN_INT; + u8 irqs_rd; + int retries = 100; + + /* The datasheet says that the EDID ready interrupt should be + disabled if there is no hotplug. */ + if (!enable) + irqs = 0; + else if (ad9389b_have_hotplug(sd)) + irqs |= MASK_AD9389B_EDID_RDY_INT; + + /* + * This i2c write can fail (approx. 1 in 1000 writes). But it + * is essential that this register is correct, so retry it + * multiple times. + * + * Note that the i2c write does not report an error, but the readback + * clearly shows the wrong value. + */ + do { + ad9389b_wr(sd, 0x94, irqs); + irqs_rd = ad9389b_rd(sd, 0x94); + } while (retries-- && irqs_rd != irqs); + + if (irqs_rd != irqs) + v4l2_err(sd, "Could not set interrupts: hw failure?\n"); +} + +/* Interrupt handler */ +static int ad9389b_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + u8 irq_status; + + /* disable interrupts to prevent a race condition */ + ad9389b_set_isr(sd, false); + irq_status = ad9389b_rd(sd, 0x96); + /* clear detected interrupts */ + ad9389b_wr(sd, 0x96, irq_status); + /* enable interrupts */ + ad9389b_set_isr(sd, true); + + v4l2_dbg(1, debug, sd, "%s: irq_status 0x%x\n", __func__, irq_status); + + if (irq_status & (MASK_AD9389B_HPD_INT)) + ad9389b_check_monitor_present_status(sd); + if (irq_status & MASK_AD9389B_EDID_RDY_INT) + ad9389b_check_edid_status(sd); + + *handled = true; + return 0; +} + +static const struct v4l2_subdev_core_ops ad9389b_core_ops = { + .log_status = ad9389b_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ad9389b_g_register, + .s_register = ad9389b_s_register, +#endif + .s_power = ad9389b_s_power, + .interrupt_service_routine = ad9389b_isr, +}; + +/* ------------------------------ VIDEO OPS ------------------------------ */ + +/* Enable/disable ad9389b output */ +static int ad9389b_s_stream(struct v4l2_subdev *sd, int enable) +{ + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); + + ad9389b_wr_and_or(sd, 0xa1, ~0x3c, (enable ? 0 : 0x3c)); + if (enable) { + ad9389b_check_monitor_present_status(sd); + } else { + ad9389b_s_power(sd, 0); + } + return 0; +} + +static const struct v4l2_dv_timings_cap ad9389b_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1200, 25000000, 170000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +static int ad9389b_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + /* quick sanity check */ + if (!v4l2_valid_dv_timings(timings, &ad9389b_timings_cap, NULL, NULL)) + return -EINVAL; + + /* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is one of the CEA or DMT timings. */ + v4l2_find_dv_timings_cap(timings, &ad9389b_timings_cap, 0, NULL, NULL); + + timings->bt.flags &= ~V4L2_DV_FL_REDUCED_FPS; + + /* save timings */ + state->dv_timings = *timings; + + /* update quantization range based on new dv_timings */ + ad9389b_set_rgb_quantization_mode(sd, state->rgb_quantization_range_ctrl); + + /* update PLL gear based on new dv_timings */ + if (state->pdata.tmds_pll_gear == AD9389B_TMDS_PLL_GEAR_SEMI_AUTOMATIC) + ad9389b_set_manual_pll_gear(sd, (u32)timings->bt.pixelclock); + + /* update AVI infoframe */ + ad9389b_set_IT_content_AVI_InfoFrame(sd); + + return 0; +} + +static int ad9389b_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (!timings) + return -EINVAL; + + *timings = state->dv_timings; + + return 0; +} + +static int ad9389b_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->pad != 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, &ad9389b_timings_cap, + NULL, NULL); +} + +static int ad9389b_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + if (cap->pad != 0) + return -EINVAL; + + *cap = ad9389b_timings_cap; + return 0; +} + +static const struct v4l2_subdev_video_ops ad9389b_video_ops = { + .s_stream = ad9389b_s_stream, + .s_dv_timings = ad9389b_s_dv_timings, + .g_dv_timings = ad9389b_g_dv_timings, +}; + +/* ------------------------------ PAD OPS ------------------------------ */ + +static int ad9389b_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + if (edid->pad != 0) + return -EINVAL; + if (edid->blocks == 0 || edid->blocks > 256) + return -EINVAL; + if (!state->edid.segments) { + v4l2_dbg(1, debug, sd, "EDID segment 0 not found\n"); + return -ENODATA; + } + if (edid->start_block >= state->edid.segments * 2) + return -E2BIG; + if (edid->blocks + edid->start_block >= state->edid.segments * 2) + edid->blocks = state->edid.segments * 2 - edid->start_block; + memcpy(edid->edid, &state->edid.data[edid->start_block * 128], + 128 * edid->blocks); + return 0; +} + +static const struct v4l2_subdev_pad_ops ad9389b_pad_ops = { + .get_edid = ad9389b_get_edid, + .enum_dv_timings = ad9389b_enum_dv_timings, + .dv_timings_cap = ad9389b_dv_timings_cap, +}; + +/* ------------------------------ AUDIO OPS ------------------------------ */ + +static int ad9389b_s_audio_stream(struct v4l2_subdev *sd, int enable) +{ + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); + + if (enable) + ad9389b_wr_and_or(sd, 0x45, 0x3f, 0x80); + else + ad9389b_wr_and_or(sd, 0x45, 0x3f, 0x40); + + return 0; +} + +static int ad9389b_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + u32 N; + + switch (freq) { + case 32000: N = 4096; break; + case 44100: N = 6272; break; + case 48000: N = 6144; break; + case 88200: N = 12544; break; + case 96000: N = 12288; break; + case 176400: N = 25088; break; + case 192000: N = 24576; break; + default: + return -EINVAL; + } + + /* Set N (used with CTS to regenerate the audio clock) */ + ad9389b_wr(sd, 0x01, (N >> 16) & 0xf); + ad9389b_wr(sd, 0x02, (N >> 8) & 0xff); + ad9389b_wr(sd, 0x03, N & 0xff); + + return 0; +} + +static int ad9389b_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + u32 i2s_sf; + + switch (freq) { + case 32000: i2s_sf = 0x30; break; + case 44100: i2s_sf = 0x00; break; + case 48000: i2s_sf = 0x20; break; + case 88200: i2s_sf = 0x80; break; + case 96000: i2s_sf = 0xa0; break; + case 176400: i2s_sf = 0xc0; break; + case 192000: i2s_sf = 0xe0; break; + default: + return -EINVAL; + } + + /* Set sampling frequency for I2S audio to 48 kHz */ + ad9389b_wr_and_or(sd, 0x15, 0xf, i2s_sf); + + return 0; +} + +static int ad9389b_s_routing(struct v4l2_subdev *sd, u32 input, u32 output, u32 config) +{ + /* TODO based on input/output/config */ + /* TODO See datasheet "Programmers guide" p. 39-40 */ + + /* Only 2 channels in use for application */ + ad9389b_wr_and_or(sd, 0x50, 0x1f, 0x20); + /* Speaker mapping */ + ad9389b_wr(sd, 0x51, 0x00); + + /* TODO Where should this be placed? */ + /* 16 bit audio word length */ + ad9389b_wr_and_or(sd, 0x14, 0xf0, 0x02); + + return 0; +} + +static const struct v4l2_subdev_audio_ops ad9389b_audio_ops = { + .s_stream = ad9389b_s_audio_stream, + .s_clock_freq = ad9389b_s_clock_freq, + .s_i2s_clock_freq = ad9389b_s_i2s_clock_freq, + .s_routing = ad9389b_s_routing, +}; + +/* --------------------- SUBDEV OPS --------------------------------------- */ + +static const struct v4l2_subdev_ops ad9389b_ops = { + .core = &ad9389b_core_ops, + .video = &ad9389b_video_ops, + .audio = &ad9389b_audio_ops, + .pad = &ad9389b_pad_ops, +}; + +/* ----------------------------------------------------------------------- */ +static void ad9389b_dbg_dump_edid(int lvl, int debug, struct v4l2_subdev *sd, + int segment, u8 *buf) +{ + int i, j; + + if (debug < lvl) + return; + + v4l2_dbg(lvl, debug, sd, "edid segment %d\n", segment); + for (i = 0; i < 256; i += 16) { + u8 b[128]; + u8 *bp = b; + + if (i == 128) + v4l2_dbg(lvl, debug, sd, "\n"); + for (j = i; j < i + 16; j++) { + sprintf(bp, "0x%02x, ", buf[j]); + bp += 6; + } + bp[0] = '\0'; + v4l2_dbg(lvl, debug, sd, "%s\n", b); + } +} + +static void ad9389b_edid_handler(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct ad9389b_state *state = + container_of(dwork, struct ad9389b_state, edid_handler); + struct v4l2_subdev *sd = &state->sd; + struct ad9389b_edid_detect ed; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (ad9389b_check_edid_status(sd)) { + /* Return if we received the EDID. */ + return; + } + + if (ad9389b_have_hotplug(sd)) { + /* We must retry reading the EDID several times, it is possible + * that initially the EDID couldn't be read due to i2c errors + * (DVI connectors are particularly prone to this problem). */ + if (state->edid.read_retries) { + state->edid.read_retries--; + v4l2_dbg(1, debug, sd, "%s: edid read failed\n", __func__); + ad9389b_s_power(sd, false); + ad9389b_s_power(sd, true); + schedule_delayed_work(&state->edid_handler, EDID_DELAY); + return; + } + } + + /* We failed to read the EDID, so send an event for this. */ + ed.present = false; + ed.segment = ad9389b_rd(sd, 0xc4); + v4l2_subdev_notify(sd, AD9389B_EDID_DETECT, (void *)&ed); + v4l2_dbg(1, debug, sd, "%s: no edid found\n", __func__); +} + +static void ad9389b_audio_setup(struct v4l2_subdev *sd) +{ + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + ad9389b_s_i2s_clock_freq(sd, 48000); + ad9389b_s_clock_freq(sd, 48000); + ad9389b_s_routing(sd, 0, 0, 0); +} + +/* Initial setup of AD9389b */ + +/* Configure hdmi transmitter. */ +static void ad9389b_setup(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* Input format: RGB 4:4:4 */ + ad9389b_wr_and_or(sd, 0x15, 0xf1, 0x0); + /* Output format: RGB 4:4:4 */ + ad9389b_wr_and_or(sd, 0x16, 0x3f, 0x0); + /* 1st order interpolation 4:2:2 -> 4:4:4 up conversion, + Aspect ratio: 16:9 */ + ad9389b_wr_and_or(sd, 0x17, 0xf9, 0x06); + /* Output format: RGB 4:4:4, Active Format Information is valid. */ + ad9389b_wr_and_or(sd, 0x45, 0xc7, 0x08); + /* Underscanned */ + ad9389b_wr_and_or(sd, 0x46, 0x3f, 0x80); + /* Setup video format */ + ad9389b_wr(sd, 0x3c, 0x0); + /* Active format aspect ratio: same as picure. */ + ad9389b_wr(sd, 0x47, 0x80); + /* No encryption */ + ad9389b_wr_and_or(sd, 0xaf, 0xef, 0x0); + /* Positive clk edge capture for input video clock */ + ad9389b_wr_and_or(sd, 0xba, 0x1f, 0x60); + + ad9389b_audio_setup(sd); + + v4l2_ctrl_handler_setup(&state->hdl); + + ad9389b_set_IT_content_AVI_InfoFrame(sd); +} + +static void ad9389b_notify_monitor_detect(struct v4l2_subdev *sd) +{ + struct ad9389b_monitor_detect mdt; + struct ad9389b_state *state = get_ad9389b_state(sd); + + mdt.present = state->have_monitor; + v4l2_subdev_notify(sd, AD9389B_MONITOR_DETECT, (void *)&mdt); +} + +static void ad9389b_update_monitor_present_status(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + /* read hotplug and rx-sense state */ + u8 status = ad9389b_rd(sd, 0x42); + + v4l2_dbg(1, debug, sd, "%s: status: 0x%x%s%s\n", + __func__, + status, + status & MASK_AD9389B_HPD_DETECT ? ", hotplug" : "", + status & MASK_AD9389B_MSEN_DETECT ? ", rx-sense" : ""); + + if (status & MASK_AD9389B_HPD_DETECT) { + v4l2_dbg(1, debug, sd, "%s: hotplug detected\n", __func__); + state->have_monitor = true; + if (!ad9389b_s_power(sd, true)) { + v4l2_dbg(1, debug, sd, + "%s: monitor detected, powerup failed\n", __func__); + return; + } + ad9389b_setup(sd); + ad9389b_notify_monitor_detect(sd); + state->edid.read_retries = EDID_MAX_RETRIES; + schedule_delayed_work(&state->edid_handler, EDID_DELAY); + } else if (!(status & MASK_AD9389B_HPD_DETECT)) { + v4l2_dbg(1, debug, sd, "%s: hotplug not detected\n", __func__); + state->have_monitor = false; + ad9389b_notify_monitor_detect(sd); + ad9389b_s_power(sd, false); + memset(&state->edid, 0, sizeof(struct ad9389b_state_edid)); + } + + /* update read only ctrls */ + v4l2_ctrl_s_ctrl(state->hotplug_ctrl, ad9389b_have_hotplug(sd) ? 0x1 : 0x0); + v4l2_ctrl_s_ctrl(state->rx_sense_ctrl, ad9389b_have_rx_sense(sd) ? 0x1 : 0x0); + v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, state->edid.segments ? 0x1 : 0x0); + + /* update with setting from ctrls */ + ad9389b_s_ctrl(state->rgb_quantization_range_ctrl); + ad9389b_s_ctrl(state->hdmi_mode_ctrl); +} + +static void ad9389b_check_monitor_present_status(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + int retry = 0; + + ad9389b_update_monitor_present_status(sd); + + /* + * Rapid toggling of the hotplug may leave the chip powered off, + * even if we think it is on. In that case reset and power up again. + */ + while (state->power_on && (ad9389b_rd(sd, 0x41) & 0x40)) { + if (++retry > 5) { + v4l2_err(sd, "retried %d times, give up\n", retry); + return; + } + v4l2_dbg(1, debug, sd, "%s: reset and re-check status (%d)\n", __func__, retry); + ad9389b_notify_monitor_detect(sd); + cancel_delayed_work_sync(&state->edid_handler); + memset(&state->edid, 0, sizeof(struct ad9389b_state_edid)); + ad9389b_s_power(sd, false); + ad9389b_update_monitor_present_status(sd); + } +} + +static bool edid_block_verify_crc(u8 *edid_block) +{ + u8 sum = 0; + int i; + + for (i = 0; i < 128; i++) + sum += edid_block[i]; + return sum == 0; +} + +static bool edid_verify_crc(struct v4l2_subdev *sd, u32 segment) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + u32 blocks = state->edid.blocks; + u8 *data = state->edid.data; + + if (edid_block_verify_crc(&data[segment * 256])) { + if ((segment + 1) * 2 <= blocks) + return edid_block_verify_crc(&data[segment * 256 + 128]); + return true; + } + return false; +} + +static bool edid_verify_header(struct v4l2_subdev *sd, u32 segment) +{ + static const u8 hdmi_header[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 + }; + struct ad9389b_state *state = get_ad9389b_state(sd); + u8 *data = state->edid.data; + int i; + + if (segment) + return true; + + for (i = 0; i < ARRAY_SIZE(hdmi_header); i++) + if (data[i] != hdmi_header[i]) + return false; + + return true; +} + +static bool ad9389b_check_edid_status(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + struct ad9389b_edid_detect ed; + int segment; + u8 edidRdy = ad9389b_rd(sd, 0xc5); + + v4l2_dbg(1, debug, sd, "%s: edid ready (retries: %d)\n", + __func__, EDID_MAX_RETRIES - state->edid.read_retries); + + if (!(edidRdy & MASK_AD9389B_EDID_RDY)) + return false; + + segment = ad9389b_rd(sd, 0xc4); + if (segment >= EDID_MAX_SEGM) { + v4l2_err(sd, "edid segment number too big\n"); + return false; + } + v4l2_dbg(1, debug, sd, "%s: got segment %d\n", __func__, segment); + ad9389b_edid_rd(sd, 256, &state->edid.data[segment * 256]); + ad9389b_dbg_dump_edid(2, debug, sd, segment, + &state->edid.data[segment * 256]); + if (segment == 0) { + state->edid.blocks = state->edid.data[0x7e] + 1; + v4l2_dbg(1, debug, sd, "%s: %d blocks in total\n", + __func__, state->edid.blocks); + } + if (!edid_verify_crc(sd, segment) || + !edid_verify_header(sd, segment)) { + /* edid crc error, force reread of edid segment */ + v4l2_err(sd, "%s: edid crc or header error\n", __func__); + ad9389b_s_power(sd, false); + ad9389b_s_power(sd, true); + return false; + } + /* one more segment read ok */ + state->edid.segments = segment + 1; + if (((state->edid.data[0x7e] >> 1) + 1) > state->edid.segments) { + /* Request next EDID segment */ + v4l2_dbg(1, debug, sd, "%s: request segment %d\n", + __func__, state->edid.segments); + ad9389b_wr(sd, 0xc9, 0xf); + ad9389b_wr(sd, 0xc4, state->edid.segments); + state->edid.read_retries = EDID_MAX_RETRIES; + schedule_delayed_work(&state->edid_handler, EDID_DELAY); + return false; + } + + /* report when we have all segments but report only for segment 0 */ + ed.present = true; + ed.segment = 0; + v4l2_subdev_notify(sd, AD9389B_EDID_DETECT, (void *)&ed); + state->edid_detect_counter++; + v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, state->edid.segments ? 0x1 : 0x0); + return ed.present; +} + +/* ----------------------------------------------------------------------- */ + +static void ad9389b_init_setup(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + struct ad9389b_state_edid *edid = &state->edid; + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* clear all interrupts */ + ad9389b_wr(sd, 0x96, 0xff); + + memset(edid, 0, sizeof(struct ad9389b_state_edid)); + state->have_monitor = false; + ad9389b_set_isr(sd, false); +} + +static int ad9389b_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + const struct v4l2_dv_timings dv1080p60 = V4L2_DV_BT_CEA_1920X1080P60; + struct ad9389b_state *state; + struct ad9389b_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; + struct v4l2_subdev *sd; + int err = -EIO; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_dbg(1, debug, client, "detecting ad9389b client on address 0x%x\n", + client->addr << 1); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + /* Platform data */ + if (pdata == NULL) { + v4l_err(client, "No platform data!\n"); + return -ENODEV; + } + memcpy(&state->pdata, pdata, sizeof(state->pdata)); + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &ad9389b_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 5); + + state->hdmi_mode_ctrl = v4l2_ctrl_new_std_menu(hdl, &ad9389b_ctrl_ops, + V4L2_CID_DV_TX_MODE, V4L2_DV_TX_MODE_HDMI, + 0, V4L2_DV_TX_MODE_DVI_D); + state->hotplug_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_HOTPLUG, 0, 1, 0, 0); + state->rx_sense_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_RXSENSE, 0, 1, 0, 0); + state->have_edid0_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_EDID_PRESENT, 0, 1, 0, 0); + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &ad9389b_ctrl_ops, + V4L2_CID_DV_TX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + + goto err_hdl; + } + state->pad.flags = MEDIA_PAD_FL_SINK; + sd->entity.function = MEDIA_ENT_F_DV_ENCODER; + err = media_entity_pads_init(&sd->entity, 1, &state->pad); + if (err) + goto err_hdl; + + state->chip_revision = ad9389b_rd(sd, 0x0); + if (state->chip_revision != 2) { + v4l2_err(sd, "chip_revision %d != 2\n", state->chip_revision); + err = -EIO; + goto err_entity; + } + v4l2_dbg(1, debug, sd, "reg 0x41 0x%x, chip version (reg 0x00) 0x%x\n", + ad9389b_rd(sd, 0x41), state->chip_revision); + + state->edid_i2c_client = i2c_new_dummy_device(client->adapter, (0x7e >> 1)); + if (IS_ERR(state->edid_i2c_client)) { + v4l2_err(sd, "failed to register edid i2c client\n"); + err = PTR_ERR(state->edid_i2c_client); + goto err_entity; + } + + INIT_DELAYED_WORK(&state->edid_handler, ad9389b_edid_handler); + state->dv_timings = dv1080p60; + + ad9389b_init_setup(sd); + ad9389b_set_isr(sd, true); + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + return 0; + +err_entity: + media_entity_cleanup(&sd->entity); +err_hdl: + v4l2_ctrl_handler_free(&state->hdl); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static int ad9389b_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ad9389b_state *state = get_ad9389b_state(sd); + + state->chip_revision = -1; + + v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + ad9389b_s_stream(sd, false); + ad9389b_s_audio_stream(sd, false); + ad9389b_init_setup(sd); + cancel_delayed_work_sync(&state->edid_handler); + i2c_unregister_device(state->edid_i2c_client); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id ad9389b_id[] = { + { "ad9389b", 0 }, + { "ad9889b", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad9389b_id); + +static struct i2c_driver ad9389b_driver = { + .driver = { + .name = "ad9389b", + }, + .probe = ad9389b_probe, + .remove = ad9389b_remove, + .id_table = ad9389b_id, +}; + +module_i2c_driver(ad9389b_driver); diff --git a/drivers/media/i2c/adp1653.c b/drivers/media/i2c/adp1653.c new file mode 100644 index 000000000..694125a59 --- /dev/null +++ b/drivers/media/i2c/adp1653.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/adp1653.c + * + * Copyright (C) 2008--2011 Nokia Corporation + * + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + * + * Contributors: + * Sakari Ailus <sakari.ailus@iki.fi> + * Tuukka Toivonen <tuukkat76@gmail.com> + * Pavel Machek <pavel@ucw.cz> + * + * TODO: + * - fault interrupt handling + * - hardware strobe + * - power doesn't need to be ON if all lights are off + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/gpio/consumer.h> +#include <media/i2c/adp1653.h> +#include <media/v4l2-device.h> + +#define TIMEOUT_MAX 820000 +#define TIMEOUT_STEP 54600 +#define TIMEOUT_MIN (TIMEOUT_MAX - ADP1653_REG_CONFIG_TMR_SET_MAX \ + * TIMEOUT_STEP) +#define TIMEOUT_US_TO_CODE(t) ((TIMEOUT_MAX + (TIMEOUT_STEP / 2) - (t)) \ + / TIMEOUT_STEP) +#define TIMEOUT_CODE_TO_US(c) (TIMEOUT_MAX - (c) * TIMEOUT_STEP) + +/* Write values into ADP1653 registers. */ +static int adp1653_update_hw(struct adp1653_flash *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + u8 out_sel; + u8 config = 0; + int rval; + + out_sel = ADP1653_INDICATOR_INTENSITY_uA_TO_REG( + flash->indicator_intensity->val) + << ADP1653_REG_OUT_SEL_ILED_SHIFT; + + switch (flash->led_mode->val) { + case V4L2_FLASH_LED_MODE_NONE: + break; + case V4L2_FLASH_LED_MODE_FLASH: + /* Flash mode, light on with strobe, duration from timer */ + config = ADP1653_REG_CONFIG_TMR_CFG; + config |= TIMEOUT_US_TO_CODE(flash->flash_timeout->val) + << ADP1653_REG_CONFIG_TMR_SET_SHIFT; + break; + case V4L2_FLASH_LED_MODE_TORCH: + /* Torch mode, light immediately on, duration indefinite */ + out_sel |= ADP1653_FLASH_INTENSITY_mA_TO_REG( + flash->torch_intensity->val) + << ADP1653_REG_OUT_SEL_HPLED_SHIFT; + break; + } + + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, out_sel); + if (rval < 0) + return rval; + + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_CONFIG, config); + if (rval < 0) + return rval; + + return 0; +} + +static int adp1653_get_fault(struct adp1653_flash *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int fault; + int rval; + + fault = i2c_smbus_read_byte_data(client, ADP1653_REG_FAULT); + if (fault < 0) + return fault; + + flash->fault |= fault; + + if (!flash->fault) + return 0; + + /* Clear faults. */ + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, 0); + if (rval < 0) + return rval; + + flash->led_mode->val = V4L2_FLASH_LED_MODE_NONE; + + rval = adp1653_update_hw(flash); + if (rval) + return rval; + + return flash->fault; +} + +static int adp1653_strobe(struct adp1653_flash *flash, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + u8 out_sel = ADP1653_INDICATOR_INTENSITY_uA_TO_REG( + flash->indicator_intensity->val) + << ADP1653_REG_OUT_SEL_ILED_SHIFT; + int rval; + + if (flash->led_mode->val != V4L2_FLASH_LED_MODE_FLASH) + return -EBUSY; + + if (!enable) + return i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, + out_sel); + + out_sel |= ADP1653_FLASH_INTENSITY_mA_TO_REG( + flash->flash_intensity->val) + << ADP1653_REG_OUT_SEL_HPLED_SHIFT; + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, out_sel); + if (rval) + return rval; + + /* Software strobe using i2c */ + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_SW_STROBE, + ADP1653_REG_SW_STROBE_SW_STROBE); + if (rval) + return rval; + return i2c_smbus_write_byte_data(client, ADP1653_REG_SW_STROBE, 0); +} + +/* -------------------------------------------------------------------------- + * V4L2 controls + */ + +static int adp1653_get_ctrl(struct v4l2_ctrl *ctrl) +{ + struct adp1653_flash *flash = + container_of(ctrl->handler, struct adp1653_flash, ctrls); + int rval; + + rval = adp1653_get_fault(flash); + if (rval) + return rval; + + ctrl->cur.val = 0; + + if (flash->fault & ADP1653_REG_FAULT_FLT_SCP) + ctrl->cur.val |= V4L2_FLASH_FAULT_SHORT_CIRCUIT; + if (flash->fault & ADP1653_REG_FAULT_FLT_OT) + ctrl->cur.val |= V4L2_FLASH_FAULT_OVER_TEMPERATURE; + if (flash->fault & ADP1653_REG_FAULT_FLT_TMR) + ctrl->cur.val |= V4L2_FLASH_FAULT_TIMEOUT; + if (flash->fault & ADP1653_REG_FAULT_FLT_OV) + ctrl->cur.val |= V4L2_FLASH_FAULT_OVER_VOLTAGE; + + flash->fault = 0; + + return 0; +} + +static int adp1653_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct adp1653_flash *flash = + container_of(ctrl->handler, struct adp1653_flash, ctrls); + int rval; + + rval = adp1653_get_fault(flash); + if (rval) + return rval; + if ((rval & (ADP1653_REG_FAULT_FLT_SCP | + ADP1653_REG_FAULT_FLT_OT | + ADP1653_REG_FAULT_FLT_OV)) && + (ctrl->id == V4L2_CID_FLASH_STROBE || + ctrl->id == V4L2_CID_FLASH_TORCH_INTENSITY || + ctrl->id == V4L2_CID_FLASH_LED_MODE)) + return -EBUSY; + + switch (ctrl->id) { + case V4L2_CID_FLASH_STROBE: + return adp1653_strobe(flash, 1); + case V4L2_CID_FLASH_STROBE_STOP: + return adp1653_strobe(flash, 0); + } + + return adp1653_update_hw(flash); +} + +static const struct v4l2_ctrl_ops adp1653_ctrl_ops = { + .g_volatile_ctrl = adp1653_get_ctrl, + .s_ctrl = adp1653_set_ctrl, +}; + +static int adp1653_init_controls(struct adp1653_flash *flash) +{ + struct v4l2_ctrl *fault; + + v4l2_ctrl_handler_init(&flash->ctrls, 9); + + flash->led_mode = + v4l2_ctrl_new_std_menu(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_LED_MODE, + V4L2_FLASH_LED_MODE_TORCH, ~0x7, 0); + v4l2_ctrl_new_std_menu(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_STROBE_SOURCE, + V4L2_FLASH_STROBE_SOURCE_SOFTWARE, ~0x1, 0); + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_STROBE, 0, 0, 0, 0); + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_STROBE_STOP, 0, 0, 0, 0); + flash->flash_timeout = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_TIMEOUT, TIMEOUT_MIN, + flash->platform_data->max_flash_timeout, + TIMEOUT_STEP, + flash->platform_data->max_flash_timeout); + flash->flash_intensity = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_INTENSITY, + ADP1653_FLASH_INTENSITY_MIN, + flash->platform_data->max_flash_intensity, + 1, flash->platform_data->max_flash_intensity); + flash->torch_intensity = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_TORCH_INTENSITY, + ADP1653_TORCH_INTENSITY_MIN, + flash->platform_data->max_torch_intensity, + ADP1653_FLASH_INTENSITY_STEP, + flash->platform_data->max_torch_intensity); + flash->indicator_intensity = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_INDICATOR_INTENSITY, + ADP1653_INDICATOR_INTENSITY_MIN, + flash->platform_data->max_indicator_intensity, + ADP1653_INDICATOR_INTENSITY_STEP, + ADP1653_INDICATOR_INTENSITY_MIN); + fault = v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_FAULT, 0, + V4L2_FLASH_FAULT_OVER_VOLTAGE + | V4L2_FLASH_FAULT_OVER_TEMPERATURE + | V4L2_FLASH_FAULT_SHORT_CIRCUIT, 0, 0); + + if (flash->ctrls.error) + return flash->ctrls.error; + + fault->flags |= V4L2_CTRL_FLAG_VOLATILE; + + flash->subdev.ctrl_handler = &flash->ctrls; + return 0; +} + +/* -------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static int +adp1653_init_device(struct adp1653_flash *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int rval; + + /* Clear FAULT register by writing zero to OUT_SEL */ + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, 0); + if (rval < 0) { + dev_err(&client->dev, "failed writing fault register\n"); + return -EIO; + } + + mutex_lock(flash->ctrls.lock); + /* Reset faults before reading new ones. */ + flash->fault = 0; + rval = adp1653_get_fault(flash); + mutex_unlock(flash->ctrls.lock); + if (rval > 0) { + dev_err(&client->dev, "faults detected: 0x%1.1x\n", rval); + return -EIO; + } + + mutex_lock(flash->ctrls.lock); + rval = adp1653_update_hw(flash); + mutex_unlock(flash->ctrls.lock); + if (rval) { + dev_err(&client->dev, + "adp1653_update_hw failed at %s\n", __func__); + return -EIO; + } + + return 0; +} + +static int +__adp1653_set_power(struct adp1653_flash *flash, int on) +{ + int ret; + + if (flash->platform_data->power) { + ret = flash->platform_data->power(&flash->subdev, on); + if (ret < 0) + return ret; + } else { + gpiod_set_value(flash->platform_data->enable_gpio, on); + if (on) + /* Some delay is apparently required. */ + udelay(20); + } + + if (!on) + return 0; + + ret = adp1653_init_device(flash); + if (ret >= 0) + return ret; + + if (flash->platform_data->power) + flash->platform_data->power(&flash->subdev, 0); + else + gpiod_set_value(flash->platform_data->enable_gpio, 0); + + return ret; +} + +static int +adp1653_set_power(struct v4l2_subdev *subdev, int on) +{ + struct adp1653_flash *flash = to_adp1653_flash(subdev); + int ret = 0; + + mutex_lock(&flash->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (flash->power_count == !on) { + ret = __adp1653_set_power(flash, !!on); + if (ret < 0) + goto done; + } + + /* Update the power count. */ + flash->power_count += on ? 1 : -1; + WARN_ON(flash->power_count < 0); + +done: + mutex_unlock(&flash->power_lock); + return ret; +} + +static int adp1653_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return adp1653_set_power(sd, 1); +} + +static int adp1653_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return adp1653_set_power(sd, 0); +} + +static const struct v4l2_subdev_core_ops adp1653_core_ops = { + .s_power = adp1653_set_power, +}; + +static const struct v4l2_subdev_ops adp1653_ops = { + .core = &adp1653_core_ops, +}; + +static const struct v4l2_subdev_internal_ops adp1653_internal_ops = { + .open = adp1653_open, + .close = adp1653_close, +}; + +/* -------------------------------------------------------------------------- + * I2C driver + */ +#ifdef CONFIG_PM + +static int adp1653_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct adp1653_flash *flash = to_adp1653_flash(subdev); + + if (!flash->power_count) + return 0; + + return __adp1653_set_power(flash, 0); +} + +static int adp1653_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct adp1653_flash *flash = to_adp1653_flash(subdev); + + if (!flash->power_count) + return 0; + + return __adp1653_set_power(flash, 1); +} + +#else + +#define adp1653_suspend NULL +#define adp1653_resume NULL + +#endif /* CONFIG_PM */ + +static int adp1653_of_init(struct i2c_client *client, + struct adp1653_flash *flash, + struct device_node *node) +{ + struct adp1653_platform_data *pd; + struct device_node *child; + + pd = devm_kzalloc(&client->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + flash->platform_data = pd; + + child = of_get_child_by_name(node, "flash"); + if (!child) + return -EINVAL; + + if (of_property_read_u32(child, "flash-timeout-us", + &pd->max_flash_timeout)) + goto err; + + if (of_property_read_u32(child, "flash-max-microamp", + &pd->max_flash_intensity)) + goto err; + + pd->max_flash_intensity /= 1000; + + if (of_property_read_u32(child, "led-max-microamp", + &pd->max_torch_intensity)) + goto err; + + pd->max_torch_intensity /= 1000; + of_node_put(child); + + child = of_get_child_by_name(node, "indicator"); + if (!child) + return -EINVAL; + + if (of_property_read_u32(child, "led-max-microamp", + &pd->max_indicator_intensity)) + goto err; + + of_node_put(child); + + pd->enable_gpio = devm_gpiod_get(&client->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(pd->enable_gpio)) { + dev_err(&client->dev, "Error getting GPIO\n"); + return PTR_ERR(pd->enable_gpio); + } + + return 0; +err: + dev_err(&client->dev, "Required property not found\n"); + of_node_put(child); + return -EINVAL; +} + + +static int adp1653_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct adp1653_flash *flash; + int ret; + + flash = devm_kzalloc(&client->dev, sizeof(*flash), GFP_KERNEL); + if (flash == NULL) + return -ENOMEM; + + if (client->dev.of_node) { + ret = adp1653_of_init(client, flash, client->dev.of_node); + if (ret) + return ret; + } else { + if (!client->dev.platform_data) { + dev_err(&client->dev, + "Neither DT not platform data provided\n"); + return -EINVAL; + } + flash->platform_data = client->dev.platform_data; + } + + mutex_init(&flash->power_lock); + + v4l2_i2c_subdev_init(&flash->subdev, client, &adp1653_ops); + flash->subdev.internal_ops = &adp1653_internal_ops; + flash->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + ret = adp1653_init_controls(flash); + if (ret) + goto free_and_quit; + + ret = media_entity_pads_init(&flash->subdev.entity, 0, NULL); + if (ret < 0) + goto free_and_quit; + + flash->subdev.entity.function = MEDIA_ENT_F_FLASH; + + return 0; + +free_and_quit: + dev_err(&client->dev, "adp1653: failed to register device\n"); + v4l2_ctrl_handler_free(&flash->ctrls); + return ret; +} + +static int adp1653_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct adp1653_flash *flash = to_adp1653_flash(subdev); + + v4l2_device_unregister_subdev(&flash->subdev); + v4l2_ctrl_handler_free(&flash->ctrls); + media_entity_cleanup(&flash->subdev.entity); + + return 0; +} + +static const struct i2c_device_id adp1653_id_table[] = { + { ADP1653_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adp1653_id_table); + +static const struct dev_pm_ops adp1653_pm_ops = { + .suspend = adp1653_suspend, + .resume = adp1653_resume, +}; + +static struct i2c_driver adp1653_i2c_driver = { + .driver = { + .name = ADP1653_NAME, + .pm = &adp1653_pm_ops, + }, + .probe = adp1653_probe, + .remove = adp1653_remove, + .id_table = adp1653_id_table, +}; + +module_i2c_driver(adp1653_i2c_driver); + +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@nokia.com>"); +MODULE_DESCRIPTION("Analog Devices ADP1653 LED flash driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/adv7170.c b/drivers/media/i2c/adv7170.c new file mode 100644 index 000000000..e4e8fda51 --- /dev/null +++ b/drivers/media/i2c/adv7170.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * adv7170 - adv7170, adv7171 video encoder driver version 0.0.1 + * + * Copyright (C) 2002 Maxim Yevtyushkin <max@linuxmedialabs.com> + * + * Based on adv7176 driver by: + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> + +MODULE_DESCRIPTION("Analog Devices ADV7170 video encoder driver"); +MODULE_AUTHOR("Maxim Yevtyushkin"); +MODULE_LICENSE("GPL"); + + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +/* ----------------------------------------------------------------------- */ + +struct adv7170 { + struct v4l2_subdev sd; + unsigned char reg[128]; + + v4l2_std_id norm; + int input; +}; + +static inline struct adv7170 *to_adv7170(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7170, sd); +} + +static char *inputs[] = { "pass_through", "play_back" }; + +static u32 adv7170_codes[] = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_UYVY8_1X16, +}; + +/* ----------------------------------------------------------------------- */ + +static inline int adv7170_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7170 *encoder = to_adv7170(sd); + + encoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int adv7170_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int adv7170_write_block(struct v4l2_subdev *sd, + const u8 *data, unsigned int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7170 *encoder = to_adv7170(sd); + int ret = -1; + u8 reg; + + /* the adv7170 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = + encoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && block_len < 32); + ret = i2c_master_send(client, block_data, block_len); + if (ret < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + ret = adv7170_write(sd, reg, *data++); + if (ret < 0) + break; + len -= 2; + } + } + return ret; +} + +/* ----------------------------------------------------------------------- */ + +#define TR0MODE 0x4c +#define TR0RST 0x80 + +#define TR1CAPT 0x00 +#define TR1PLAY 0x00 + +static const unsigned char init_NTSC[] = { + 0x00, 0x10, /* MR0 */ + 0x01, 0x20, /* MR1 */ + 0x02, 0x0e, /* MR2 RTC control: bits 2 and 1 */ + 0x03, 0x80, /* MR3 */ + 0x04, 0x30, /* MR4 */ + 0x05, 0x00, /* Reserved */ + 0x06, 0x00, /* Reserved */ + 0x07, TR0MODE, /* TM0 */ + 0x08, TR1CAPT, /* TM1 */ + 0x09, 0x16, /* Fsc0 */ + 0x0a, 0x7c, /* Fsc1 */ + 0x0b, 0xf0, /* Fsc2 */ + 0x0c, 0x21, /* Fsc3 */ + 0x0d, 0x00, /* Subcarrier Phase */ + 0x0e, 0x00, /* Closed Capt. Ext 0 */ + 0x0f, 0x00, /* Closed Capt. Ext 1 */ + 0x10, 0x00, /* Closed Capt. 0 */ + 0x11, 0x00, /* Closed Capt. 1 */ + 0x12, 0x00, /* Pedestal Ctl 0 */ + 0x13, 0x00, /* Pedestal Ctl 1 */ + 0x14, 0x00, /* Pedestal Ctl 2 */ + 0x15, 0x00, /* Pedestal Ctl 3 */ + 0x16, 0x00, /* CGMS_WSS_0 */ + 0x17, 0x00, /* CGMS_WSS_1 */ + 0x18, 0x00, /* CGMS_WSS_2 */ + 0x19, 0x00, /* Teletext Ctl */ +}; + +static const unsigned char init_PAL[] = { + 0x00, 0x71, /* MR0 */ + 0x01, 0x20, /* MR1 */ + 0x02, 0x0e, /* MR2 RTC control: bits 2 and 1 */ + 0x03, 0x80, /* MR3 */ + 0x04, 0x30, /* MR4 */ + 0x05, 0x00, /* Reserved */ + 0x06, 0x00, /* Reserved */ + 0x07, TR0MODE, /* TM0 */ + 0x08, TR1CAPT, /* TM1 */ + 0x09, 0xcb, /* Fsc0 */ + 0x0a, 0x8a, /* Fsc1 */ + 0x0b, 0x09, /* Fsc2 */ + 0x0c, 0x2a, /* Fsc3 */ + 0x0d, 0x00, /* Subcarrier Phase */ + 0x0e, 0x00, /* Closed Capt. Ext 0 */ + 0x0f, 0x00, /* Closed Capt. Ext 1 */ + 0x10, 0x00, /* Closed Capt. 0 */ + 0x11, 0x00, /* Closed Capt. 1 */ + 0x12, 0x00, /* Pedestal Ctl 0 */ + 0x13, 0x00, /* Pedestal Ctl 1 */ + 0x14, 0x00, /* Pedestal Ctl 2 */ + 0x15, 0x00, /* Pedestal Ctl 3 */ + 0x16, 0x00, /* CGMS_WSS_0 */ + 0x17, 0x00, /* CGMS_WSS_1 */ + 0x18, 0x00, /* CGMS_WSS_2 */ + 0x19, 0x00, /* Teletext Ctl */ +}; + + +static int adv7170_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7170 *encoder = to_adv7170(sd); + + v4l2_dbg(1, debug, sd, "set norm %llx\n", (unsigned long long)std); + + if (std & V4L2_STD_NTSC) { + adv7170_write_block(sd, init_NTSC, sizeof(init_NTSC)); + if (encoder->input == 0) + adv7170_write(sd, 0x02, 0x0e); /* Enable genlock */ + adv7170_write(sd, 0x07, TR0MODE | TR0RST); + adv7170_write(sd, 0x07, TR0MODE); + } else if (std & V4L2_STD_PAL) { + adv7170_write_block(sd, init_PAL, sizeof(init_PAL)); + if (encoder->input == 0) + adv7170_write(sd, 0x02, 0x0e); /* Enable genlock */ + adv7170_write(sd, 0x07, TR0MODE | TR0RST); + adv7170_write(sd, 0x07, TR0MODE); + } else { + v4l2_dbg(1, debug, sd, "illegal norm: %llx\n", + (unsigned long long)std); + return -EINVAL; + } + v4l2_dbg(1, debug, sd, "switched to %llx\n", (unsigned long long)std); + encoder->norm = std; + return 0; +} + +static int adv7170_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7170 *encoder = to_adv7170(sd); + + /* RJ: input = 0: input is from decoder + input = 1: input is from ZR36060 + input = 2: color bar */ + + v4l2_dbg(1, debug, sd, "set input from %s\n", + input == 0 ? "decoder" : "ZR36060"); + + switch (input) { + case 0: + adv7170_write(sd, 0x01, 0x20); + adv7170_write(sd, 0x08, TR1CAPT); /* TR1 */ + adv7170_write(sd, 0x02, 0x0e); /* Enable genlock */ + adv7170_write(sd, 0x07, TR0MODE | TR0RST); + adv7170_write(sd, 0x07, TR0MODE); + /* udelay(10); */ + break; + + case 1: + adv7170_write(sd, 0x01, 0x00); + adv7170_write(sd, 0x08, TR1PLAY); /* TR1 */ + adv7170_write(sd, 0x02, 0x08); + adv7170_write(sd, 0x07, TR0MODE | TR0RST); + adv7170_write(sd, 0x07, TR0MODE); + /* udelay(10); */ + break; + + default: + v4l2_dbg(1, debug, sd, "illegal input: %d\n", input); + return -EINVAL; + } + v4l2_dbg(1, debug, sd, "switched to %s\n", inputs[input]); + encoder->input = input; + return 0; +} + +static int adv7170_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(adv7170_codes)) + return -EINVAL; + + code->code = adv7170_codes[code->index]; + return 0; +} + +static int adv7170_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + u8 val = adv7170_read(sd, 0x7); + + if (format->pad) + return -EINVAL; + + if ((val & 0x40) == (1 << 6)) + mf->code = MEDIA_BUS_FMT_UYVY8_1X16; + else + mf->code = MEDIA_BUS_FMT_UYVY8_2X8; + + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->width = 0; + mf->height = 0; + mf->field = V4L2_FIELD_ANY; + + return 0; +} + +static int adv7170_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + u8 val = adv7170_read(sd, 0x7); + + if (format->pad) + return -EINVAL; + + switch (mf->code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + val &= ~0x40; + break; + + case MEDIA_BUS_FMT_UYVY8_1X16: + val |= 0x40; + break; + + default: + v4l2_dbg(1, debug, sd, + "illegal v4l2_mbus_framefmt code: %d\n", mf->code); + return -EINVAL; + } + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return adv7170_write(sd, 0x7, val); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_video_ops adv7170_video_ops = { + .s_std_output = adv7170_s_std_output, + .s_routing = adv7170_s_routing, +}; + +static const struct v4l2_subdev_pad_ops adv7170_pad_ops = { + .enum_mbus_code = adv7170_enum_mbus_code, + .get_fmt = adv7170_get_fmt, + .set_fmt = adv7170_set_fmt, +}; + +static const struct v4l2_subdev_ops adv7170_ops = { + .video = &adv7170_video_ops, + .pad = &adv7170_pad_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int adv7170_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adv7170 *encoder; + struct v4l2_subdev *sd; + int i; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + encoder = devm_kzalloc(&client->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + sd = &encoder->sd; + v4l2_i2c_subdev_init(sd, client, &adv7170_ops); + encoder->norm = V4L2_STD_NTSC; + encoder->input = 0; + + i = adv7170_write_block(sd, init_NTSC, sizeof(init_NTSC)); + if (i >= 0) { + i = adv7170_write(sd, 0x07, TR0MODE | TR0RST); + i = adv7170_write(sd, 0x07, TR0MODE); + i = adv7170_read(sd, 0x12); + v4l2_dbg(1, debug, sd, "revision %d\n", i & 1); + } + if (i < 0) + v4l2_dbg(1, debug, sd, "init error 0x%x\n", i); + return 0; +} + +static int adv7170_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id adv7170_id[] = { + { "adv7170", 0 }, + { "adv7171", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7170_id); + +static struct i2c_driver adv7170_driver = { + .driver = { + .name = "adv7170", + }, + .probe = adv7170_probe, + .remove = adv7170_remove, + .id_table = adv7170_id, +}; + +module_i2c_driver(adv7170_driver); diff --git a/drivers/media/i2c/adv7175.c b/drivers/media/i2c/adv7175.c new file mode 100644 index 000000000..0cdd8e033 --- /dev/null +++ b/drivers/media/i2c/adv7175.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * adv7175 - adv7175a video encoder driver version 0.0.3 + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> + +MODULE_DESCRIPTION("Analog Devices ADV7175 video encoder driver"); +MODULE_AUTHOR("Dave Perks"); +MODULE_LICENSE("GPL"); + +#define I2C_ADV7175 0xd4 +#define I2C_ADV7176 0x54 + + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +/* ----------------------------------------------------------------------- */ + +struct adv7175 { + struct v4l2_subdev sd; + v4l2_std_id norm; + int input; +}; + +static inline struct adv7175 *to_adv7175(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7175, sd); +} + +static char *inputs[] = { "pass_through", "play_back", "color_bar" }; + +static u32 adv7175_codes[] = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_UYVY8_1X16, +}; + +/* ----------------------------------------------------------------------- */ + +static inline int adv7175_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int adv7175_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int adv7175_write_block(struct v4l2_subdev *sd, + const u8 *data, unsigned int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = -1; + u8 reg; + + /* the adv7175 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = data[1]; + reg++; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && block_len < 32); + ret = i2c_master_send(client, block_data, block_len); + if (ret < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + ret = adv7175_write(sd, reg, *data++); + if (ret < 0) + break; + len -= 2; + } + } + + return ret; +} + +static void set_subcarrier_freq(struct v4l2_subdev *sd, int pass_through) +{ + /* for some reason pass_through NTSC needs + * a different sub-carrier freq to remain stable. */ + if (pass_through) + adv7175_write(sd, 0x02, 0x00); + else + adv7175_write(sd, 0x02, 0x55); + + adv7175_write(sd, 0x03, 0x55); + adv7175_write(sd, 0x04, 0x55); + adv7175_write(sd, 0x05, 0x25); +} + +/* ----------------------------------------------------------------------- */ +/* Output filter: S-Video Composite */ + +#define MR050 0x11 /* 0x09 */ +#define MR060 0x14 /* 0x0c */ + +/* ----------------------------------------------------------------------- */ + +#define TR0MODE 0x46 +#define TR0RST 0x80 + +#define TR1CAPT 0x80 +#define TR1PLAY 0x00 + +static const unsigned char init_common[] = { + + 0x00, MR050, /* MR0, PAL enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x0c, /* subc. freq. */ + 0x03, 0x8c, /* subc. freq. */ + 0x04, 0x79, /* subc. freq. */ + 0x05, 0x26, /* subc. freq. */ + 0x06, 0x40, /* subc. phase */ + + 0x07, TR0MODE, /* TR0, 16bit */ + 0x08, 0x21, /* */ + 0x09, 0x00, /* */ + 0x0a, 0x00, /* */ + 0x0b, 0x00, /* */ + 0x0c, TR1CAPT, /* TR1 */ + 0x0d, 0x4f, /* MR2 */ + 0x0e, 0x00, /* */ + 0x0f, 0x00, /* */ + 0x10, 0x00, /* */ + 0x11, 0x00, /* */ +}; + +static const unsigned char init_pal[] = { + 0x00, MR050, /* MR0, PAL enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x0c, /* subc. freq. */ + 0x03, 0x8c, /* subc. freq. */ + 0x04, 0x79, /* subc. freq. */ + 0x05, 0x26, /* subc. freq. */ + 0x06, 0x40, /* subc. phase */ +}; + +static const unsigned char init_ntsc[] = { + 0x00, MR060, /* MR0, NTSC enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x55, /* subc. freq. */ + 0x03, 0x55, /* subc. freq. */ + 0x04, 0x55, /* subc. freq. */ + 0x05, 0x25, /* subc. freq. */ + 0x06, 0x1a, /* subc. phase */ +}; + +static int adv7175_init(struct v4l2_subdev *sd, u32 val) +{ + /* This is just for testing!!! */ + adv7175_write_block(sd, init_common, sizeof(init_common)); + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + return 0; +} + +static int adv7175_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7175 *encoder = to_adv7175(sd); + + if (std & V4L2_STD_NTSC) { + adv7175_write_block(sd, init_ntsc, sizeof(init_ntsc)); + if (encoder->input == 0) + adv7175_write(sd, 0x0d, 0x4f); /* Enable genlock */ + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + } else if (std & V4L2_STD_PAL) { + adv7175_write_block(sd, init_pal, sizeof(init_pal)); + if (encoder->input == 0) + adv7175_write(sd, 0x0d, 0x4f); /* Enable genlock */ + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + } else if (std & V4L2_STD_SECAM) { + /* This is an attempt to convert + * SECAM->PAL (typically it does not work + * due to genlock: when decoder is in SECAM + * and encoder in in PAL the subcarrier can + * not be synchronized with horizontal + * quency) */ + adv7175_write_block(sd, init_pal, sizeof(init_pal)); + if (encoder->input == 0) + adv7175_write(sd, 0x0d, 0x49); /* Disable genlock */ + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + } else { + v4l2_dbg(1, debug, sd, "illegal norm: %llx\n", + (unsigned long long)std); + return -EINVAL; + } + v4l2_dbg(1, debug, sd, "switched to %llx\n", (unsigned long long)std); + encoder->norm = std; + return 0; +} + +static int adv7175_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7175 *encoder = to_adv7175(sd); + + /* RJ: input = 0: input is from decoder + input = 1: input is from ZR36060 + input = 2: color bar */ + + switch (input) { + case 0: + adv7175_write(sd, 0x01, 0x00); + + if (encoder->norm & V4L2_STD_NTSC) + set_subcarrier_freq(sd, 1); + + adv7175_write(sd, 0x0c, TR1CAPT); /* TR1 */ + if (encoder->norm & V4L2_STD_SECAM) + adv7175_write(sd, 0x0d, 0x49); /* Disable genlock */ + else + adv7175_write(sd, 0x0d, 0x4f); /* Enable genlock */ + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + /*udelay(10);*/ + break; + + case 1: + adv7175_write(sd, 0x01, 0x00); + + if (encoder->norm & V4L2_STD_NTSC) + set_subcarrier_freq(sd, 0); + + adv7175_write(sd, 0x0c, TR1PLAY); /* TR1 */ + adv7175_write(sd, 0x0d, 0x49); + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + /* udelay(10); */ + break; + + case 2: + adv7175_write(sd, 0x01, 0x80); + + if (encoder->norm & V4L2_STD_NTSC) + set_subcarrier_freq(sd, 0); + + adv7175_write(sd, 0x0d, 0x49); + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + /* udelay(10); */ + break; + + default: + v4l2_dbg(1, debug, sd, "illegal input: %d\n", input); + return -EINVAL; + } + v4l2_dbg(1, debug, sd, "switched to %s\n", inputs[input]); + encoder->input = input; + return 0; +} + +static int adv7175_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(adv7175_codes)) + return -EINVAL; + + code->code = adv7175_codes[code->index]; + return 0; +} + +static int adv7175_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + u8 val = adv7175_read(sd, 0x7); + + if (format->pad) + return -EINVAL; + + if ((val & 0x40) == (1 << 6)) + mf->code = MEDIA_BUS_FMT_UYVY8_1X16; + else + mf->code = MEDIA_BUS_FMT_UYVY8_2X8; + + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->width = 0; + mf->height = 0; + mf->field = V4L2_FIELD_ANY; + + return 0; +} + +static int adv7175_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + u8 val = adv7175_read(sd, 0x7); + int ret = 0; + + if (format->pad) + return -EINVAL; + + switch (mf->code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + val &= ~0x40; + break; + + case MEDIA_BUS_FMT_UYVY8_1X16: + val |= 0x40; + break; + + default: + v4l2_dbg(1, debug, sd, + "illegal v4l2_mbus_framefmt code: %d\n", mf->code); + return -EINVAL; + } + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + ret = adv7175_write(sd, 0x7, val); + + return ret; +} + +static int adv7175_s_power(struct v4l2_subdev *sd, int on) +{ + if (on) + adv7175_write(sd, 0x01, 0x00); + else + adv7175_write(sd, 0x01, 0x78); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops adv7175_core_ops = { + .init = adv7175_init, + .s_power = adv7175_s_power, +}; + +static const struct v4l2_subdev_video_ops adv7175_video_ops = { + .s_std_output = adv7175_s_std_output, + .s_routing = adv7175_s_routing, +}; + +static const struct v4l2_subdev_pad_ops adv7175_pad_ops = { + .enum_mbus_code = adv7175_enum_mbus_code, + .get_fmt = adv7175_get_fmt, + .set_fmt = adv7175_set_fmt, +}; + +static const struct v4l2_subdev_ops adv7175_ops = { + .core = &adv7175_core_ops, + .video = &adv7175_video_ops, + .pad = &adv7175_pad_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int adv7175_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i; + struct adv7175 *encoder; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + encoder = devm_kzalloc(&client->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + sd = &encoder->sd; + v4l2_i2c_subdev_init(sd, client, &adv7175_ops); + encoder->norm = V4L2_STD_NTSC; + encoder->input = 0; + + i = adv7175_write_block(sd, init_common, sizeof(init_common)); + if (i >= 0) { + i = adv7175_write(sd, 0x07, TR0MODE | TR0RST); + i = adv7175_write(sd, 0x07, TR0MODE); + i = adv7175_read(sd, 0x12); + v4l2_dbg(1, debug, sd, "revision %d\n", i & 1); + } + if (i < 0) + v4l2_dbg(1, debug, sd, "init error 0x%x\n", i); + return 0; +} + +static int adv7175_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id adv7175_id[] = { + { "adv7175", 0 }, + { "adv7176", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7175_id); + +static struct i2c_driver adv7175_driver = { + .driver = { + .name = "adv7175", + }, + .probe = adv7175_probe, + .remove = adv7175_remove, + .id_table = adv7175_id, +}; + +module_i2c_driver(adv7175_driver); diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c new file mode 100644 index 000000000..4498d14d3 --- /dev/null +++ b/drivers/media/i2c/adv7180.c @@ -0,0 +1,1523 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * adv7180.c Analog Devices ADV7180 video decoder driver + * Copyright (c) 2009 Intel Corporation + * Copyright (C) 2013 Cogent Embedded, Inc. + * Copyright (C) 2013 Renesas Solutions Corp. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/gpio/consumer.h> +#include <linux/videodev2.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <linux/mutex.h> +#include <linux/delay.h> + +#define ADV7180_STD_AD_PAL_BG_NTSC_J_SECAM 0x0 +#define ADV7180_STD_AD_PAL_BG_NTSC_J_SECAM_PED 0x1 +#define ADV7180_STD_AD_PAL_N_NTSC_J_SECAM 0x2 +#define ADV7180_STD_AD_PAL_N_NTSC_M_SECAM 0x3 +#define ADV7180_STD_NTSC_J 0x4 +#define ADV7180_STD_NTSC_M 0x5 +#define ADV7180_STD_PAL60 0x6 +#define ADV7180_STD_NTSC_443 0x7 +#define ADV7180_STD_PAL_BG 0x8 +#define ADV7180_STD_PAL_N 0x9 +#define ADV7180_STD_PAL_M 0xa +#define ADV7180_STD_PAL_M_PED 0xb +#define ADV7180_STD_PAL_COMB_N 0xc +#define ADV7180_STD_PAL_COMB_N_PED 0xd +#define ADV7180_STD_PAL_SECAM 0xe +#define ADV7180_STD_PAL_SECAM_PED 0xf + +#define ADV7180_REG_INPUT_CONTROL 0x0000 +#define ADV7180_INPUT_CONTROL_INSEL_MASK 0x0f + +#define ADV7182_REG_INPUT_VIDSEL 0x0002 + +#define ADV7180_REG_OUTPUT_CONTROL 0x0003 +#define ADV7180_REG_EXTENDED_OUTPUT_CONTROL 0x0004 +#define ADV7180_EXTENDED_OUTPUT_CONTROL_NTSCDIS 0xC5 + +#define ADV7180_REG_AUTODETECT_ENABLE 0x0007 +#define ADV7180_AUTODETECT_DEFAULT 0x7f +/* Contrast */ +#define ADV7180_REG_CON 0x0008 /*Unsigned */ +#define ADV7180_CON_MIN 0 +#define ADV7180_CON_DEF 128 +#define ADV7180_CON_MAX 255 +/* Brightness*/ +#define ADV7180_REG_BRI 0x000a /*Signed */ +#define ADV7180_BRI_MIN -128 +#define ADV7180_BRI_DEF 0 +#define ADV7180_BRI_MAX 127 +/* Hue */ +#define ADV7180_REG_HUE 0x000b /*Signed, inverted */ +#define ADV7180_HUE_MIN -127 +#define ADV7180_HUE_DEF 0 +#define ADV7180_HUE_MAX 128 + +#define ADV7180_REG_CTRL 0x000e +#define ADV7180_CTRL_IRQ_SPACE 0x20 + +#define ADV7180_REG_PWR_MAN 0x0f +#define ADV7180_PWR_MAN_ON 0x04 +#define ADV7180_PWR_MAN_OFF 0x24 +#define ADV7180_PWR_MAN_RES 0x80 + +#define ADV7180_REG_STATUS1 0x0010 +#define ADV7180_STATUS1_IN_LOCK 0x01 +#define ADV7180_STATUS1_AUTOD_MASK 0x70 +#define ADV7180_STATUS1_AUTOD_NTSM_M_J 0x00 +#define ADV7180_STATUS1_AUTOD_NTSC_4_43 0x10 +#define ADV7180_STATUS1_AUTOD_PAL_M 0x20 +#define ADV7180_STATUS1_AUTOD_PAL_60 0x30 +#define ADV7180_STATUS1_AUTOD_PAL_B_G 0x40 +#define ADV7180_STATUS1_AUTOD_SECAM 0x50 +#define ADV7180_STATUS1_AUTOD_PAL_COMB 0x60 +#define ADV7180_STATUS1_AUTOD_SECAM_525 0x70 + +#define ADV7180_REG_IDENT 0x0011 +#define ADV7180_ID_7180 0x18 + +#define ADV7180_REG_STATUS3 0x0013 +#define ADV7180_REG_ANALOG_CLAMP_CTL 0x0014 +#define ADV7180_REG_SHAP_FILTER_CTL_1 0x0017 +#define ADV7180_REG_CTRL_2 0x001d +#define ADV7180_REG_VSYNC_FIELD_CTL_1 0x0031 +#define ADV7180_REG_MANUAL_WIN_CTL_1 0x003d +#define ADV7180_REG_MANUAL_WIN_CTL_2 0x003e +#define ADV7180_REG_MANUAL_WIN_CTL_3 0x003f +#define ADV7180_REG_LOCK_CNT 0x0051 +#define ADV7180_REG_CVBS_TRIM 0x0052 +#define ADV7180_REG_CLAMP_ADJ 0x005a +#define ADV7180_REG_RES_CIR 0x005f +#define ADV7180_REG_DIFF_MODE 0x0060 + +#define ADV7180_REG_ICONF1 0x2040 +#define ADV7180_ICONF1_ACTIVE_LOW 0x01 +#define ADV7180_ICONF1_PSYNC_ONLY 0x10 +#define ADV7180_ICONF1_ACTIVE_TO_CLR 0xC0 +/* Saturation */ +#define ADV7180_REG_SD_SAT_CB 0x00e3 /*Unsigned */ +#define ADV7180_REG_SD_SAT_CR 0x00e4 /*Unsigned */ +#define ADV7180_SAT_MIN 0 +#define ADV7180_SAT_DEF 128 +#define ADV7180_SAT_MAX 255 + +#define ADV7180_IRQ1_LOCK 0x01 +#define ADV7180_IRQ1_UNLOCK 0x02 +#define ADV7180_REG_ISR1 0x2042 +#define ADV7180_REG_ICR1 0x2043 +#define ADV7180_REG_IMR1 0x2044 +#define ADV7180_REG_IMR2 0x2048 +#define ADV7180_IRQ3_AD_CHANGE 0x08 +#define ADV7180_REG_ISR3 0x204A +#define ADV7180_REG_ICR3 0x204B +#define ADV7180_REG_IMR3 0x204C +#define ADV7180_REG_IMR4 0x2050 + +#define ADV7180_REG_NTSC_V_BIT_END 0x00E6 +#define ADV7180_NTSC_V_BIT_END_MANUAL_NVEND 0x4F + +#define ADV7180_REG_VPP_SLAVE_ADDR 0xFD +#define ADV7180_REG_CSI_SLAVE_ADDR 0xFE + +#define ADV7180_REG_ACE_CTRL1 0x4080 +#define ADV7180_REG_ACE_CTRL5 0x4084 +#define ADV7180_REG_FLCONTROL 0x40e0 +#define ADV7180_FLCONTROL_FL_ENABLE 0x1 + +#define ADV7180_REG_RST_CLAMP 0x809c +#define ADV7180_REG_AGC_ADJ1 0x80b6 +#define ADV7180_REG_AGC_ADJ2 0x80c0 + +#define ADV7180_CSI_REG_PWRDN 0x00 +#define ADV7180_CSI_PWRDN 0x80 + +#define ADV7180_INPUT_CVBS_AIN1 0x00 +#define ADV7180_INPUT_CVBS_AIN2 0x01 +#define ADV7180_INPUT_CVBS_AIN3 0x02 +#define ADV7180_INPUT_CVBS_AIN4 0x03 +#define ADV7180_INPUT_CVBS_AIN5 0x04 +#define ADV7180_INPUT_CVBS_AIN6 0x05 +#define ADV7180_INPUT_SVIDEO_AIN1_AIN2 0x06 +#define ADV7180_INPUT_SVIDEO_AIN3_AIN4 0x07 +#define ADV7180_INPUT_SVIDEO_AIN5_AIN6 0x08 +#define ADV7180_INPUT_YPRPB_AIN1_AIN2_AIN3 0x09 +#define ADV7180_INPUT_YPRPB_AIN4_AIN5_AIN6 0x0a + +#define ADV7182_INPUT_CVBS_AIN1 0x00 +#define ADV7182_INPUT_CVBS_AIN2 0x01 +#define ADV7182_INPUT_CVBS_AIN3 0x02 +#define ADV7182_INPUT_CVBS_AIN4 0x03 +#define ADV7182_INPUT_CVBS_AIN5 0x04 +#define ADV7182_INPUT_CVBS_AIN6 0x05 +#define ADV7182_INPUT_CVBS_AIN7 0x06 +#define ADV7182_INPUT_CVBS_AIN8 0x07 +#define ADV7182_INPUT_SVIDEO_AIN1_AIN2 0x08 +#define ADV7182_INPUT_SVIDEO_AIN3_AIN4 0x09 +#define ADV7182_INPUT_SVIDEO_AIN5_AIN6 0x0a +#define ADV7182_INPUT_SVIDEO_AIN7_AIN8 0x0b +#define ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3 0x0c +#define ADV7182_INPUT_YPRPB_AIN4_AIN5_AIN6 0x0d +#define ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2 0x0e +#define ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4 0x0f +#define ADV7182_INPUT_DIFF_CVBS_AIN5_AIN6 0x10 +#define ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8 0x11 + +#define ADV7180_DEFAULT_CSI_I2C_ADDR 0x44 +#define ADV7180_DEFAULT_VPP_I2C_ADDR 0x42 + +#define V4L2_CID_ADV_FAST_SWITCH (V4L2_CID_USER_ADV7180_BASE + 0x00) + +/* Initial number of frames to skip to avoid possible garbage */ +#define ADV7180_NUM_OF_SKIP_FRAMES 2 + +struct adv7180_state; + +#define ADV7180_FLAG_RESET_POWERED BIT(0) +#define ADV7180_FLAG_V2 BIT(1) +#define ADV7180_FLAG_MIPI_CSI2 BIT(2) +#define ADV7180_FLAG_I2P BIT(3) + +struct adv7180_chip_info { + unsigned int flags; + unsigned int valid_input_mask; + int (*set_std)(struct adv7180_state *st, unsigned int std); + int (*select_input)(struct adv7180_state *st, unsigned int input); + int (*init)(struct adv7180_state *state); +}; + +struct adv7180_state { + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_subdev sd; + struct media_pad pad; + struct mutex mutex; /* mutual excl. when accessing chip */ + int irq; + struct gpio_desc *pwdn_gpio; + v4l2_std_id curr_norm; + bool powered; + bool streaming; + u8 input; + + struct i2c_client *client; + unsigned int register_page; + struct i2c_client *csi_client; + struct i2c_client *vpp_client; + const struct adv7180_chip_info *chip_info; + enum v4l2_field field; +}; +#define to_adv7180_sd(_ctrl) (&container_of(_ctrl->handler, \ + struct adv7180_state, \ + ctrl_hdl)->sd) + +static int adv7180_select_page(struct adv7180_state *state, unsigned int page) +{ + if (state->register_page != page) { + i2c_smbus_write_byte_data(state->client, ADV7180_REG_CTRL, + page); + state->register_page = page; + } + + return 0; +} + +static int adv7180_write(struct adv7180_state *state, unsigned int reg, + unsigned int value) +{ + lockdep_assert_held(&state->mutex); + adv7180_select_page(state, reg >> 8); + return i2c_smbus_write_byte_data(state->client, reg & 0xff, value); +} + +static int adv7180_read(struct adv7180_state *state, unsigned int reg) +{ + lockdep_assert_held(&state->mutex); + adv7180_select_page(state, reg >> 8); + return i2c_smbus_read_byte_data(state->client, reg & 0xff); +} + +static int adv7180_csi_write(struct adv7180_state *state, unsigned int reg, + unsigned int value) +{ + return i2c_smbus_write_byte_data(state->csi_client, reg, value); +} + +static int adv7180_set_video_standard(struct adv7180_state *state, + unsigned int std) +{ + return state->chip_info->set_std(state, std); +} + +static int adv7180_vpp_write(struct adv7180_state *state, unsigned int reg, + unsigned int value) +{ + return i2c_smbus_write_byte_data(state->vpp_client, reg, value); +} + +static v4l2_std_id adv7180_std_to_v4l2(u8 status1) +{ + /* in case V4L2_IN_ST_NO_SIGNAL */ + if (!(status1 & ADV7180_STATUS1_IN_LOCK)) + return V4L2_STD_UNKNOWN; + + switch (status1 & ADV7180_STATUS1_AUTOD_MASK) { + case ADV7180_STATUS1_AUTOD_NTSM_M_J: + return V4L2_STD_NTSC; + case ADV7180_STATUS1_AUTOD_NTSC_4_43: + return V4L2_STD_NTSC_443; + case ADV7180_STATUS1_AUTOD_PAL_M: + return V4L2_STD_PAL_M; + case ADV7180_STATUS1_AUTOD_PAL_60: + return V4L2_STD_PAL_60; + case ADV7180_STATUS1_AUTOD_PAL_B_G: + return V4L2_STD_PAL; + case ADV7180_STATUS1_AUTOD_SECAM: + return V4L2_STD_SECAM; + case ADV7180_STATUS1_AUTOD_PAL_COMB: + return V4L2_STD_PAL_Nc | V4L2_STD_PAL_N; + case ADV7180_STATUS1_AUTOD_SECAM_525: + return V4L2_STD_SECAM; + default: + return V4L2_STD_UNKNOWN; + } +} + +static int v4l2_std_to_adv7180(v4l2_std_id std) +{ + if (std == V4L2_STD_PAL_60) + return ADV7180_STD_PAL60; + if (std == V4L2_STD_NTSC_443) + return ADV7180_STD_NTSC_443; + if (std == V4L2_STD_PAL_N) + return ADV7180_STD_PAL_N; + if (std == V4L2_STD_PAL_M) + return ADV7180_STD_PAL_M; + if (std == V4L2_STD_PAL_Nc) + return ADV7180_STD_PAL_COMB_N; + + if (std & V4L2_STD_PAL) + return ADV7180_STD_PAL_BG; + if (std & V4L2_STD_NTSC) + return ADV7180_STD_NTSC_M; + if (std & V4L2_STD_SECAM) + return ADV7180_STD_PAL_SECAM; + + return -EINVAL; +} + +static u32 adv7180_status_to_v4l2(u8 status1) +{ + if (!(status1 & ADV7180_STATUS1_IN_LOCK)) + return V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +static int __adv7180_status(struct adv7180_state *state, u32 *status, + v4l2_std_id *std) +{ + int status1 = adv7180_read(state, ADV7180_REG_STATUS1); + + if (status1 < 0) + return status1; + + if (status) + *status = adv7180_status_to_v4l2(status1); + if (std) + *std = adv7180_std_to_v4l2(status1); + + return 0; +} + +static inline struct adv7180_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7180_state, sd); +} + +static int adv7180_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7180_state *state = to_state(sd); + int err = mutex_lock_interruptible(&state->mutex); + if (err) + return err; + + if (state->streaming) { + err = -EBUSY; + goto unlock; + } + + err = adv7180_set_video_standard(state, + ADV7180_STD_AD_PAL_BG_NTSC_J_SECAM); + if (err) + goto unlock; + + msleep(100); + __adv7180_status(state, NULL, std); + + err = v4l2_std_to_adv7180(state->curr_norm); + if (err < 0) + goto unlock; + + err = adv7180_set_video_standard(state, err); + +unlock: + mutex_unlock(&state->mutex); + return err; +} + +static int adv7180_s_routing(struct v4l2_subdev *sd, u32 input, + u32 output, u32 config) +{ + struct adv7180_state *state = to_state(sd); + int ret = mutex_lock_interruptible(&state->mutex); + + if (ret) + return ret; + + if (input > 31 || !(BIT(input) & state->chip_info->valid_input_mask)) { + ret = -EINVAL; + goto out; + } + + ret = state->chip_info->select_input(state, input); + + if (ret == 0) + state->input = input; +out: + mutex_unlock(&state->mutex); + return ret; +} + +static int adv7180_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct adv7180_state *state = to_state(sd); + int ret = mutex_lock_interruptible(&state->mutex); + if (ret) + return ret; + + ret = __adv7180_status(state, status, NULL); + mutex_unlock(&state->mutex); + return ret; +} + +static int adv7180_program_std(struct adv7180_state *state) +{ + int ret; + + ret = v4l2_std_to_adv7180(state->curr_norm); + if (ret < 0) + return ret; + + ret = adv7180_set_video_standard(state, ret); + if (ret < 0) + return ret; + return 0; +} + +static int adv7180_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7180_state *state = to_state(sd); + int ret = mutex_lock_interruptible(&state->mutex); + + if (ret) + return ret; + + /* Make sure we can support this std */ + ret = v4l2_std_to_adv7180(std); + if (ret < 0) + goto out; + + state->curr_norm = std; + + ret = adv7180_program_std(state); +out: + mutex_unlock(&state->mutex); + return ret; +} + +static int adv7180_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + struct adv7180_state *state = to_state(sd); + + *norm = state->curr_norm; + + return 0; +} + +static int adv7180_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct adv7180_state *state = to_state(sd); + + if (state->curr_norm & V4L2_STD_525_60) { + fi->interval.numerator = 1001; + fi->interval.denominator = 30000; + } else { + fi->interval.numerator = 1; + fi->interval.denominator = 25; + } + + return 0; +} + +static void adv7180_set_power_pin(struct adv7180_state *state, bool on) +{ + if (!state->pwdn_gpio) + return; + + if (on) { + gpiod_set_value_cansleep(state->pwdn_gpio, 0); + usleep_range(5000, 10000); + } else { + gpiod_set_value_cansleep(state->pwdn_gpio, 1); + } +} + +static int adv7180_set_power(struct adv7180_state *state, bool on) +{ + u8 val; + int ret; + + if (on) + val = ADV7180_PWR_MAN_ON; + else + val = ADV7180_PWR_MAN_OFF; + + ret = adv7180_write(state, ADV7180_REG_PWR_MAN, val); + if (ret) + return ret; + + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + if (on) { + adv7180_csi_write(state, 0xDE, 0x02); + adv7180_csi_write(state, 0xD2, 0xF7); + adv7180_csi_write(state, 0xD8, 0x65); + adv7180_csi_write(state, 0xE0, 0x09); + adv7180_csi_write(state, 0x2C, 0x00); + if (state->field == V4L2_FIELD_NONE) + adv7180_csi_write(state, 0x1D, 0x80); + adv7180_csi_write(state, 0x00, 0x00); + } else { + adv7180_csi_write(state, 0x00, 0x80); + } + } + + return 0; +} + +static int adv7180_s_power(struct v4l2_subdev *sd, int on) +{ + struct adv7180_state *state = to_state(sd); + int ret; + + ret = mutex_lock_interruptible(&state->mutex); + if (ret) + return ret; + + ret = adv7180_set_power(state, on); + if (ret == 0) + state->powered = on; + + mutex_unlock(&state->mutex); + return ret; +} + +static int adv7180_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_adv7180_sd(ctrl); + struct adv7180_state *state = to_state(sd); + int ret = mutex_lock_interruptible(&state->mutex); + int val; + + if (ret) + return ret; + val = ctrl->val; + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ret = adv7180_write(state, ADV7180_REG_BRI, val); + break; + case V4L2_CID_HUE: + /*Hue is inverted according to HSL chart */ + ret = adv7180_write(state, ADV7180_REG_HUE, -val); + break; + case V4L2_CID_CONTRAST: + ret = adv7180_write(state, ADV7180_REG_CON, val); + break; + case V4L2_CID_SATURATION: + /* + *This could be V4L2_CID_BLUE_BALANCE/V4L2_CID_RED_BALANCE + *Let's not confuse the user, everybody understands saturation + */ + ret = adv7180_write(state, ADV7180_REG_SD_SAT_CB, val); + if (ret < 0) + break; + ret = adv7180_write(state, ADV7180_REG_SD_SAT_CR, val); + break; + case V4L2_CID_ADV_FAST_SWITCH: + if (ctrl->val) { + /* ADI required write */ + adv7180_write(state, 0x80d9, 0x44); + adv7180_write(state, ADV7180_REG_FLCONTROL, + ADV7180_FLCONTROL_FL_ENABLE); + } else { + /* ADI required write */ + adv7180_write(state, 0x80d9, 0xc4); + adv7180_write(state, ADV7180_REG_FLCONTROL, 0x00); + } + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&state->mutex); + return ret; +} + +static const struct v4l2_ctrl_ops adv7180_ctrl_ops = { + .s_ctrl = adv7180_s_ctrl, +}; + +static const struct v4l2_ctrl_config adv7180_ctrl_fast_switch = { + .ops = &adv7180_ctrl_ops, + .id = V4L2_CID_ADV_FAST_SWITCH, + .name = "Fast Switching", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, +}; + +static int adv7180_init_controls(struct adv7180_state *state) +{ + v4l2_ctrl_handler_init(&state->ctrl_hdl, 4); + + v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7180_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV7180_BRI_MIN, + ADV7180_BRI_MAX, 1, ADV7180_BRI_DEF); + v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7180_ctrl_ops, + V4L2_CID_CONTRAST, ADV7180_CON_MIN, + ADV7180_CON_MAX, 1, ADV7180_CON_DEF); + v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7180_ctrl_ops, + V4L2_CID_SATURATION, ADV7180_SAT_MIN, + ADV7180_SAT_MAX, 1, ADV7180_SAT_DEF); + v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7180_ctrl_ops, + V4L2_CID_HUE, ADV7180_HUE_MIN, + ADV7180_HUE_MAX, 1, ADV7180_HUE_DEF); + v4l2_ctrl_new_custom(&state->ctrl_hdl, &adv7180_ctrl_fast_switch, NULL); + + state->sd.ctrl_handler = &state->ctrl_hdl; + if (state->ctrl_hdl.error) { + int err = state->ctrl_hdl.error; + + v4l2_ctrl_handler_free(&state->ctrl_hdl); + return err; + } + v4l2_ctrl_handler_setup(&state->ctrl_hdl); + + return 0; +} +static void adv7180_exit_controls(struct adv7180_state *state) +{ + v4l2_ctrl_handler_free(&state->ctrl_hdl); +} + +static int adv7180_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_2X8; + + return 0; +} + +static int adv7180_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct adv7180_state *state = to_state(sd); + + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt->width = 720; + fmt->height = state->curr_norm & V4L2_STD_525_60 ? 480 : 576; + + if (state->field == V4L2_FIELD_ALTERNATE) + fmt->height /= 2; + + return 0; +} + +static int adv7180_set_field_mode(struct adv7180_state *state) +{ + if (!(state->chip_info->flags & ADV7180_FLAG_I2P)) + return 0; + + if (state->field == V4L2_FIELD_NONE) { + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + adv7180_csi_write(state, 0x01, 0x20); + adv7180_csi_write(state, 0x02, 0x28); + adv7180_csi_write(state, 0x03, 0x38); + adv7180_csi_write(state, 0x04, 0x30); + adv7180_csi_write(state, 0x05, 0x30); + adv7180_csi_write(state, 0x06, 0x80); + adv7180_csi_write(state, 0x07, 0x70); + adv7180_csi_write(state, 0x08, 0x50); + } + adv7180_vpp_write(state, 0xa3, 0x00); + adv7180_vpp_write(state, 0x5b, 0x00); + adv7180_vpp_write(state, 0x55, 0x80); + } else { + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + adv7180_csi_write(state, 0x01, 0x18); + adv7180_csi_write(state, 0x02, 0x18); + adv7180_csi_write(state, 0x03, 0x30); + adv7180_csi_write(state, 0x04, 0x20); + adv7180_csi_write(state, 0x05, 0x28); + adv7180_csi_write(state, 0x06, 0x40); + adv7180_csi_write(state, 0x07, 0x58); + adv7180_csi_write(state, 0x08, 0x30); + } + adv7180_vpp_write(state, 0xa3, 0x70); + adv7180_vpp_write(state, 0x5b, 0x80); + adv7180_vpp_write(state, 0x55, 0x00); + } + + return 0; +} + +static int adv7180_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct adv7180_state *state = to_state(sd); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + format->format = *v4l2_subdev_get_try_format(sd, cfg, 0); + } else { + adv7180_mbus_fmt(sd, &format->format); + format->format.field = state->field; + } + + return 0; +} + +static int adv7180_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct adv7180_state *state = to_state(sd); + struct v4l2_mbus_framefmt *framefmt; + int ret; + + switch (format->format.field) { + case V4L2_FIELD_NONE: + if (state->chip_info->flags & ADV7180_FLAG_I2P) + break; + fallthrough; + default: + format->format.field = V4L2_FIELD_ALTERNATE; + break; + } + + ret = adv7180_mbus_fmt(sd, &format->format); + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + if (state->field != format->format.field) { + state->field = format->format.field; + adv7180_set_power(state, false); + adv7180_set_field_mode(state); + adv7180_set_power(state, true); + } + } else { + framefmt = v4l2_subdev_get_try_format(sd, cfg, 0); + *framefmt = format->format; + } + + return ret; +} + +static int adv7180_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct v4l2_subdev_format fmt = { + .which = cfg ? V4L2_SUBDEV_FORMAT_TRY + : V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + return adv7180_set_pad_format(sd, cfg, &fmt); +} + +static int adv7180_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct adv7180_state *state = to_state(sd); + + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + cfg->type = V4L2_MBUS_CSI2_DPHY; + cfg->flags = V4L2_MBUS_CSI2_1_LANE | + V4L2_MBUS_CSI2_CHANNEL_0 | + V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; + } else { + /* + * The ADV7180 sensor supports BT.601/656 output modes. + * The BT.656 is default and not yet configurable by s/w. + */ + cfg->flags = V4L2_MBUS_MASTER | V4L2_MBUS_PCLK_SAMPLE_RISING | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_BT656; + } + + return 0; +} + +static int adv7180_get_skip_frames(struct v4l2_subdev *sd, u32 *frames) +{ + *frames = ADV7180_NUM_OF_SKIP_FRAMES; + + return 0; +} + +static int adv7180_g_pixelaspect(struct v4l2_subdev *sd, struct v4l2_fract *aspect) +{ + struct adv7180_state *state = to_state(sd); + + if (state->curr_norm & V4L2_STD_525_60) { + aspect->numerator = 11; + aspect->denominator = 10; + } else { + aspect->numerator = 54; + aspect->denominator = 59; + } + + return 0; +} + +static int adv7180_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + *norm = V4L2_STD_ALL; + return 0; +} + +static int adv7180_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv7180_state *state = to_state(sd); + int ret; + + /* It's always safe to stop streaming, no need to take the lock */ + if (!enable) { + state->streaming = enable; + return 0; + } + + /* Must wait until querystd released the lock */ + ret = mutex_lock_interruptible(&state->mutex); + if (ret) + return ret; + state->streaming = enable; + mutex_unlock(&state->mutex); + return 0; +} + +static int adv7180_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_subdev_video_ops adv7180_video_ops = { + .s_std = adv7180_s_std, + .g_std = adv7180_g_std, + .g_frame_interval = adv7180_g_frame_interval, + .querystd = adv7180_querystd, + .g_input_status = adv7180_g_input_status, + .s_routing = adv7180_s_routing, + .g_pixelaspect = adv7180_g_pixelaspect, + .g_tvnorms = adv7180_g_tvnorms, + .s_stream = adv7180_s_stream, +}; + +static const struct v4l2_subdev_core_ops adv7180_core_ops = { + .s_power = adv7180_s_power, + .subscribe_event = adv7180_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_pad_ops adv7180_pad_ops = { + .init_cfg = adv7180_init_cfg, + .enum_mbus_code = adv7180_enum_mbus_code, + .set_fmt = adv7180_set_pad_format, + .get_fmt = adv7180_get_pad_format, + .get_mbus_config = adv7180_get_mbus_config, +}; + +static const struct v4l2_subdev_sensor_ops adv7180_sensor_ops = { + .g_skip_frames = adv7180_get_skip_frames, +}; + +static const struct v4l2_subdev_ops adv7180_ops = { + .core = &adv7180_core_ops, + .video = &adv7180_video_ops, + .pad = &adv7180_pad_ops, + .sensor = &adv7180_sensor_ops, +}; + +static irqreturn_t adv7180_irq(int irq, void *devid) +{ + struct adv7180_state *state = devid; + u8 isr3; + + mutex_lock(&state->mutex); + isr3 = adv7180_read(state, ADV7180_REG_ISR3); + /* clear */ + adv7180_write(state, ADV7180_REG_ICR3, isr3); + + if (isr3 & ADV7180_IRQ3_AD_CHANGE) { + static const struct v4l2_event src_ch = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + v4l2_subdev_notify_event(&state->sd, &src_ch); + } + mutex_unlock(&state->mutex); + + return IRQ_HANDLED; +} + +static int adv7180_init(struct adv7180_state *state) +{ + int ret; + + /* ITU-R BT.656-4 compatible */ + ret = adv7180_write(state, ADV7180_REG_EXTENDED_OUTPUT_CONTROL, + ADV7180_EXTENDED_OUTPUT_CONTROL_NTSCDIS); + if (ret < 0) + return ret; + + /* Manually set V bit end position in NTSC mode */ + return adv7180_write(state, ADV7180_REG_NTSC_V_BIT_END, + ADV7180_NTSC_V_BIT_END_MANUAL_NVEND); +} + +static int adv7180_set_std(struct adv7180_state *state, unsigned int std) +{ + return adv7180_write(state, ADV7180_REG_INPUT_CONTROL, + (std << 4) | state->input); +} + +static int adv7180_select_input(struct adv7180_state *state, unsigned int input) +{ + int ret; + + ret = adv7180_read(state, ADV7180_REG_INPUT_CONTROL); + if (ret < 0) + return ret; + + ret &= ~ADV7180_INPUT_CONTROL_INSEL_MASK; + ret |= input; + return adv7180_write(state, ADV7180_REG_INPUT_CONTROL, ret); +} + +static int adv7182_init(struct adv7180_state *state) +{ + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) + adv7180_write(state, ADV7180_REG_CSI_SLAVE_ADDR, + ADV7180_DEFAULT_CSI_I2C_ADDR << 1); + + if (state->chip_info->flags & ADV7180_FLAG_I2P) + adv7180_write(state, ADV7180_REG_VPP_SLAVE_ADDR, + ADV7180_DEFAULT_VPP_I2C_ADDR << 1); + + if (state->chip_info->flags & ADV7180_FLAG_V2) { + /* ADI recommended writes for improved video quality */ + adv7180_write(state, 0x0080, 0x51); + adv7180_write(state, 0x0081, 0x51); + adv7180_write(state, 0x0082, 0x68); + } + + /* ADI required writes */ + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + adv7180_write(state, ADV7180_REG_OUTPUT_CONTROL, 0x4e); + adv7180_write(state, ADV7180_REG_EXTENDED_OUTPUT_CONTROL, 0x57); + adv7180_write(state, ADV7180_REG_CTRL_2, 0xc0); + } else { + if (state->chip_info->flags & ADV7180_FLAG_V2) + adv7180_write(state, + ADV7180_REG_EXTENDED_OUTPUT_CONTROL, + 0x17); + else + adv7180_write(state, + ADV7180_REG_EXTENDED_OUTPUT_CONTROL, + 0x07); + adv7180_write(state, ADV7180_REG_OUTPUT_CONTROL, 0x0c); + adv7180_write(state, ADV7180_REG_CTRL_2, 0x40); + } + + adv7180_write(state, 0x0013, 0x00); + + return 0; +} + +static int adv7182_set_std(struct adv7180_state *state, unsigned int std) +{ + return adv7180_write(state, ADV7182_REG_INPUT_VIDSEL, std << 4); +} + +enum adv7182_input_type { + ADV7182_INPUT_TYPE_CVBS, + ADV7182_INPUT_TYPE_DIFF_CVBS, + ADV7182_INPUT_TYPE_SVIDEO, + ADV7182_INPUT_TYPE_YPBPR, +}; + +static enum adv7182_input_type adv7182_get_input_type(unsigned int input) +{ + switch (input) { + case ADV7182_INPUT_CVBS_AIN1: + case ADV7182_INPUT_CVBS_AIN2: + case ADV7182_INPUT_CVBS_AIN3: + case ADV7182_INPUT_CVBS_AIN4: + case ADV7182_INPUT_CVBS_AIN5: + case ADV7182_INPUT_CVBS_AIN6: + case ADV7182_INPUT_CVBS_AIN7: + case ADV7182_INPUT_CVBS_AIN8: + return ADV7182_INPUT_TYPE_CVBS; + case ADV7182_INPUT_SVIDEO_AIN1_AIN2: + case ADV7182_INPUT_SVIDEO_AIN3_AIN4: + case ADV7182_INPUT_SVIDEO_AIN5_AIN6: + case ADV7182_INPUT_SVIDEO_AIN7_AIN8: + return ADV7182_INPUT_TYPE_SVIDEO; + case ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3: + case ADV7182_INPUT_YPRPB_AIN4_AIN5_AIN6: + return ADV7182_INPUT_TYPE_YPBPR; + case ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2: + case ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4: + case ADV7182_INPUT_DIFF_CVBS_AIN5_AIN6: + case ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8: + return ADV7182_INPUT_TYPE_DIFF_CVBS; + default: /* Will never happen */ + return 0; + } +} + +/* ADI recommended writes to registers 0x52, 0x53, 0x54 */ +static unsigned int adv7182_lbias_settings[][3] = { + [ADV7182_INPUT_TYPE_CVBS] = { 0xCB, 0x4E, 0x80 }, + [ADV7182_INPUT_TYPE_DIFF_CVBS] = { 0xC0, 0x4E, 0x80 }, + [ADV7182_INPUT_TYPE_SVIDEO] = { 0x0B, 0xCE, 0x80 }, + [ADV7182_INPUT_TYPE_YPBPR] = { 0x0B, 0x4E, 0xC0 }, +}; + +static unsigned int adv7280_lbias_settings[][3] = { + [ADV7182_INPUT_TYPE_CVBS] = { 0xCD, 0x4E, 0x80 }, + [ADV7182_INPUT_TYPE_DIFF_CVBS] = { 0xC0, 0x4E, 0x80 }, + [ADV7182_INPUT_TYPE_SVIDEO] = { 0x0B, 0xCE, 0x80 }, + [ADV7182_INPUT_TYPE_YPBPR] = { 0x0B, 0x4E, 0xC0 }, +}; + +static int adv7182_select_input(struct adv7180_state *state, unsigned int input) +{ + enum adv7182_input_type input_type; + unsigned int *lbias; + unsigned int i; + int ret; + + ret = adv7180_write(state, ADV7180_REG_INPUT_CONTROL, input); + if (ret) + return ret; + + /* Reset clamp circuitry - ADI recommended writes */ + adv7180_write(state, ADV7180_REG_RST_CLAMP, 0x00); + adv7180_write(state, ADV7180_REG_RST_CLAMP, 0xff); + + input_type = adv7182_get_input_type(input); + + switch (input_type) { + case ADV7182_INPUT_TYPE_CVBS: + case ADV7182_INPUT_TYPE_DIFF_CVBS: + /* ADI recommends to use the SH1 filter */ + adv7180_write(state, ADV7180_REG_SHAP_FILTER_CTL_1, 0x41); + break; + default: + adv7180_write(state, ADV7180_REG_SHAP_FILTER_CTL_1, 0x01); + break; + } + + if (state->chip_info->flags & ADV7180_FLAG_V2) + lbias = adv7280_lbias_settings[input_type]; + else + lbias = adv7182_lbias_settings[input_type]; + + for (i = 0; i < ARRAY_SIZE(adv7182_lbias_settings[0]); i++) + adv7180_write(state, ADV7180_REG_CVBS_TRIM + i, lbias[i]); + + if (input_type == ADV7182_INPUT_TYPE_DIFF_CVBS) { + /* ADI required writes to make differential CVBS work */ + adv7180_write(state, ADV7180_REG_RES_CIR, 0xa8); + adv7180_write(state, ADV7180_REG_CLAMP_ADJ, 0x90); + adv7180_write(state, ADV7180_REG_DIFF_MODE, 0xb0); + adv7180_write(state, ADV7180_REG_AGC_ADJ1, 0x08); + adv7180_write(state, ADV7180_REG_AGC_ADJ2, 0xa0); + } else { + adv7180_write(state, ADV7180_REG_RES_CIR, 0xf0); + adv7180_write(state, ADV7180_REG_CLAMP_ADJ, 0xd0); + adv7180_write(state, ADV7180_REG_DIFF_MODE, 0x10); + adv7180_write(state, ADV7180_REG_AGC_ADJ1, 0x9c); + adv7180_write(state, ADV7180_REG_AGC_ADJ2, 0x00); + } + + return 0; +} + +static const struct adv7180_chip_info adv7180_info = { + .flags = ADV7180_FLAG_RESET_POWERED, + /* We cannot discriminate between LQFP and 40-pin LFCSP, so accept + * all inputs and let the card driver take care of validation + */ + .valid_input_mask = BIT(ADV7180_INPUT_CVBS_AIN1) | + BIT(ADV7180_INPUT_CVBS_AIN2) | + BIT(ADV7180_INPUT_CVBS_AIN3) | + BIT(ADV7180_INPUT_CVBS_AIN4) | + BIT(ADV7180_INPUT_CVBS_AIN5) | + BIT(ADV7180_INPUT_CVBS_AIN6) | + BIT(ADV7180_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7180_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7180_INPUT_SVIDEO_AIN5_AIN6) | + BIT(ADV7180_INPUT_YPRPB_AIN1_AIN2_AIN3) | + BIT(ADV7180_INPUT_YPRPB_AIN4_AIN5_AIN6), + .init = adv7180_init, + .set_std = adv7180_set_std, + .select_input = adv7180_select_input, +}; + +static const struct adv7180_chip_info adv7182_info = { + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7280_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_I2P, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7280_m_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_MIPI_CSI2 | ADV7180_FLAG_I2P, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_CVBS_AIN5) | + BIT(ADV7182_INPUT_CVBS_AIN6) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN5_AIN6) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3) | + BIT(ADV7182_INPUT_YPRPB_AIN4_AIN5_AIN6), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7281_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_MIPI_CSI2, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7281_m_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_MIPI_CSI2, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7281_ma_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_MIPI_CSI2, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_CVBS_AIN5) | + BIT(ADV7182_INPUT_CVBS_AIN6) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN5_AIN6) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3) | + BIT(ADV7182_INPUT_YPRPB_AIN4_AIN5_AIN6) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN5_AIN6) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7282_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_I2P, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7282_m_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_MIPI_CSI2 | ADV7180_FLAG_I2P, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static int init_device(struct adv7180_state *state) +{ + int ret; + + mutex_lock(&state->mutex); + + adv7180_set_power_pin(state, true); + + adv7180_write(state, ADV7180_REG_PWR_MAN, ADV7180_PWR_MAN_RES); + usleep_range(5000, 10000); + + ret = state->chip_info->init(state); + if (ret) + goto out_unlock; + + ret = adv7180_program_std(state); + if (ret) + goto out_unlock; + + adv7180_set_field_mode(state); + + /* register for interrupts */ + if (state->irq > 0) { + /* config the Interrupt pin to be active low */ + ret = adv7180_write(state, ADV7180_REG_ICONF1, + ADV7180_ICONF1_ACTIVE_LOW | + ADV7180_ICONF1_PSYNC_ONLY); + if (ret < 0) + goto out_unlock; + + ret = adv7180_write(state, ADV7180_REG_IMR1, 0); + if (ret < 0) + goto out_unlock; + + ret = adv7180_write(state, ADV7180_REG_IMR2, 0); + if (ret < 0) + goto out_unlock; + + /* enable AD change interrupts interrupts */ + ret = adv7180_write(state, ADV7180_REG_IMR3, + ADV7180_IRQ3_AD_CHANGE); + if (ret < 0) + goto out_unlock; + + ret = adv7180_write(state, ADV7180_REG_IMR4, 0); + if (ret < 0) + goto out_unlock; + } + +out_unlock: + mutex_unlock(&state->mutex); + + return ret; +} + +static int adv7180_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adv7180_state *state; + struct v4l2_subdev *sd; + int ret; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + state->client = client; + state->field = V4L2_FIELD_ALTERNATE; + state->chip_info = (struct adv7180_chip_info *)id->driver_data; + + state->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(state->pwdn_gpio)) { + ret = PTR_ERR(state->pwdn_gpio); + v4l_err(client, "request for power pin failed: %d\n", ret); + return ret; + } + + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + state->csi_client = i2c_new_dummy_device(client->adapter, + ADV7180_DEFAULT_CSI_I2C_ADDR); + if (IS_ERR(state->csi_client)) + return PTR_ERR(state->csi_client); + } + + if (state->chip_info->flags & ADV7180_FLAG_I2P) { + state->vpp_client = i2c_new_dummy_device(client->adapter, + ADV7180_DEFAULT_VPP_I2C_ADDR); + if (IS_ERR(state->vpp_client)) { + ret = PTR_ERR(state->vpp_client); + goto err_unregister_csi_client; + } + } + + state->irq = client->irq; + mutex_init(&state->mutex); + state->curr_norm = V4L2_STD_NTSC; + if (state->chip_info->flags & ADV7180_FLAG_RESET_POWERED) + state->powered = true; + else + state->powered = false; + state->input = 0; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &adv7180_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + + ret = adv7180_init_controls(state); + if (ret) + goto err_unregister_vpp_client; + + state->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_ATV_DECODER; + ret = media_entity_pads_init(&sd->entity, 1, &state->pad); + if (ret) + goto err_free_ctrl; + + ret = init_device(state); + if (ret) + goto err_media_entity_cleanup; + + if (state->irq) { + ret = request_threaded_irq(client->irq, NULL, adv7180_irq, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, + KBUILD_MODNAME, state); + if (ret) + goto err_media_entity_cleanup; + } + + ret = v4l2_async_register_subdev(sd); + if (ret) + goto err_free_irq; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr, client->adapter->name); + + return 0; + +err_free_irq: + if (state->irq > 0) + free_irq(client->irq, state); +err_media_entity_cleanup: + media_entity_cleanup(&sd->entity); +err_free_ctrl: + adv7180_exit_controls(state); +err_unregister_vpp_client: + i2c_unregister_device(state->vpp_client); +err_unregister_csi_client: + i2c_unregister_device(state->csi_client); + mutex_destroy(&state->mutex); + return ret; +} + +static int adv7180_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7180_state *state = to_state(sd); + + v4l2_async_unregister_subdev(sd); + + if (state->irq > 0) + free_irq(client->irq, state); + + media_entity_cleanup(&sd->entity); + adv7180_exit_controls(state); + + i2c_unregister_device(state->vpp_client); + i2c_unregister_device(state->csi_client); + + adv7180_set_power_pin(state, false); + + mutex_destroy(&state->mutex); + + return 0; +} + +static const struct i2c_device_id adv7180_id[] = { + { "adv7180", (kernel_ulong_t)&adv7180_info }, + { "adv7180cp", (kernel_ulong_t)&adv7180_info }, + { "adv7180st", (kernel_ulong_t)&adv7180_info }, + { "adv7182", (kernel_ulong_t)&adv7182_info }, + { "adv7280", (kernel_ulong_t)&adv7280_info }, + { "adv7280-m", (kernel_ulong_t)&adv7280_m_info }, + { "adv7281", (kernel_ulong_t)&adv7281_info }, + { "adv7281-m", (kernel_ulong_t)&adv7281_m_info }, + { "adv7281-ma", (kernel_ulong_t)&adv7281_ma_info }, + { "adv7282", (kernel_ulong_t)&adv7282_info }, + { "adv7282-m", (kernel_ulong_t)&adv7282_m_info }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, adv7180_id); + +#ifdef CONFIG_PM_SLEEP +static int adv7180_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7180_state *state = to_state(sd); + + return adv7180_set_power(state, false); +} + +static int adv7180_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7180_state *state = to_state(sd); + int ret; + + ret = init_device(state); + if (ret < 0) + return ret; + + ret = adv7180_set_power(state, state->powered); + if (ret) + return ret; + + return 0; +} + +static SIMPLE_DEV_PM_OPS(adv7180_pm_ops, adv7180_suspend, adv7180_resume); +#define ADV7180_PM_OPS (&adv7180_pm_ops) + +#else +#define ADV7180_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static const struct of_device_id adv7180_of_id[] = { + { .compatible = "adi,adv7180", }, + { .compatible = "adi,adv7180cp", }, + { .compatible = "adi,adv7180st", }, + { .compatible = "adi,adv7182", }, + { .compatible = "adi,adv7280", }, + { .compatible = "adi,adv7280-m", }, + { .compatible = "adi,adv7281", }, + { .compatible = "adi,adv7281-m", }, + { .compatible = "adi,adv7281-ma", }, + { .compatible = "adi,adv7282", }, + { .compatible = "adi,adv7282-m", }, + { }, +}; + +MODULE_DEVICE_TABLE(of, adv7180_of_id); +#endif + +static struct i2c_driver adv7180_driver = { + .driver = { + .name = KBUILD_MODNAME, + .pm = ADV7180_PM_OPS, + .of_match_table = of_match_ptr(adv7180_of_id), + }, + .probe = adv7180_probe, + .remove = adv7180_remove, + .id_table = adv7180_id, +}; + +module_i2c_driver(adv7180_driver); + +MODULE_DESCRIPTION("Analog Devices ADV7180 video decoder driver"); +MODULE_AUTHOR("Mocean Laboratories"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/adv7183.c b/drivers/media/i2c/adv7183.c new file mode 100644 index 000000000..8bcd632c0 --- /dev/null +++ b/drivers/media/i2c/adv7183.c @@ -0,0 +1,646 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adv7183.c Analog Devices ADV7183 video decoder driver + * + * Copyright (c) 2011 Analog Devices Inc. + */ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/videodev2.h> + +#include <media/i2c/adv7183.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#include "adv7183_regs.h" + +struct adv7183 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + + v4l2_std_id std; /* Current set standard */ + u32 input; + u32 output; + unsigned reset_pin; + unsigned oe_pin; + struct v4l2_mbus_framefmt fmt; +}; + +/* EXAMPLES USING 27 MHz CLOCK + * Mode 1 CVBS Input (Composite Video on AIN5) + * All standards are supported through autodetect, 8-bit, 4:2:2, ITU-R BT.656 output on P15 to P8. + */ +static const unsigned char adv7183_init_regs[] = { + ADV7183_IN_CTRL, 0x04, /* CVBS input on AIN5 */ + ADV7183_DIGI_CLAMP_CTRL_1, 0x00, /* Slow down digital clamps */ + ADV7183_SHAP_FILT_CTRL, 0x41, /* Set CSFM to SH1 */ + ADV7183_ADC_CTRL, 0x16, /* Power down ADC 1 and ADC 2 */ + ADV7183_CTI_DNR_CTRL_4, 0x04, /* Set DNR threshold to 4 for flat response */ + /* ADI recommended programming sequence */ + ADV7183_ADI_CTRL, 0x80, + ADV7183_CTI_DNR_CTRL_4, 0x20, + 0x52, 0x18, + 0x58, 0xED, + 0x77, 0xC5, + 0x7C, 0x93, + 0x7D, 0x00, + 0xD0, 0x48, + 0xD5, 0xA0, + 0xD7, 0xEA, + ADV7183_SD_SATURATION_CR, 0x3E, + ADV7183_PAL_V_END, 0x3E, + ADV7183_PAL_F_TOGGLE, 0x0F, + ADV7183_ADI_CTRL, 0x00, +}; + +static inline struct adv7183 *to_adv7183(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7183, sd); +} +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7183, hdl)->sd; +} + +static inline int adv7183_read(struct v4l2_subdev *sd, unsigned char reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static inline int adv7183_write(struct v4l2_subdev *sd, unsigned char reg, + unsigned char value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int adv7183_writeregs(struct v4l2_subdev *sd, + const unsigned char *regs, unsigned int num) +{ + unsigned char reg, data; + unsigned int cnt = 0; + + if (num & 0x1) { + v4l2_err(sd, "invalid regs array\n"); + return -1; + } + + while (cnt < num) { + reg = *regs++; + data = *regs++; + cnt += 2; + + adv7183_write(sd, reg, data); + } + return 0; +} + +static int adv7183_log_status(struct v4l2_subdev *sd) +{ + struct adv7183 *decoder = to_adv7183(sd); + + v4l2_info(sd, "adv7183: Input control = 0x%02x\n", + adv7183_read(sd, ADV7183_IN_CTRL)); + v4l2_info(sd, "adv7183: Video selection = 0x%02x\n", + adv7183_read(sd, ADV7183_VD_SEL)); + v4l2_info(sd, "adv7183: Output control = 0x%02x\n", + adv7183_read(sd, ADV7183_OUT_CTRL)); + v4l2_info(sd, "adv7183: Extended output control = 0x%02x\n", + adv7183_read(sd, ADV7183_EXT_OUT_CTRL)); + v4l2_info(sd, "adv7183: Autodetect enable = 0x%02x\n", + adv7183_read(sd, ADV7183_AUTO_DET_EN)); + v4l2_info(sd, "adv7183: Contrast = 0x%02x\n", + adv7183_read(sd, ADV7183_CONTRAST)); + v4l2_info(sd, "adv7183: Brightness = 0x%02x\n", + adv7183_read(sd, ADV7183_BRIGHTNESS)); + v4l2_info(sd, "adv7183: Hue = 0x%02x\n", + adv7183_read(sd, ADV7183_HUE)); + v4l2_info(sd, "adv7183: Default value Y = 0x%02x\n", + adv7183_read(sd, ADV7183_DEF_Y)); + v4l2_info(sd, "adv7183: Default value C = 0x%02x\n", + adv7183_read(sd, ADV7183_DEF_C)); + v4l2_info(sd, "adv7183: ADI control = 0x%02x\n", + adv7183_read(sd, ADV7183_ADI_CTRL)); + v4l2_info(sd, "adv7183: Power Management = 0x%02x\n", + adv7183_read(sd, ADV7183_POW_MANAGE)); + v4l2_info(sd, "adv7183: Status 1 2 and 3 = 0x%02x 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_STATUS_1), + adv7183_read(sd, ADV7183_STATUS_2), + adv7183_read(sd, ADV7183_STATUS_3)); + v4l2_info(sd, "adv7183: Ident = 0x%02x\n", + adv7183_read(sd, ADV7183_IDENT)); + v4l2_info(sd, "adv7183: Analog clamp control = 0x%02x\n", + adv7183_read(sd, ADV7183_ANAL_CLAMP_CTRL)); + v4l2_info(sd, "adv7183: Digital clamp control 1 = 0x%02x\n", + adv7183_read(sd, ADV7183_DIGI_CLAMP_CTRL_1)); + v4l2_info(sd, "adv7183: Shaping filter control 1 and 2 = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_SHAP_FILT_CTRL), + adv7183_read(sd, ADV7183_SHAP_FILT_CTRL_2)); + v4l2_info(sd, "adv7183: Comb filter control = 0x%02x\n", + adv7183_read(sd, ADV7183_COMB_FILT_CTRL)); + v4l2_info(sd, "adv7183: ADI control 2 = 0x%02x\n", + adv7183_read(sd, ADV7183_ADI_CTRL_2)); + v4l2_info(sd, "adv7183: Pixel delay control = 0x%02x\n", + adv7183_read(sd, ADV7183_PIX_DELAY_CTRL)); + v4l2_info(sd, "adv7183: Misc gain control = 0x%02x\n", + adv7183_read(sd, ADV7183_MISC_GAIN_CTRL)); + v4l2_info(sd, "adv7183: AGC mode control = 0x%02x\n", + adv7183_read(sd, ADV7183_AGC_MODE_CTRL)); + v4l2_info(sd, "adv7183: Chroma gain control 1 and 2 = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_CHRO_GAIN_CTRL_1), + adv7183_read(sd, ADV7183_CHRO_GAIN_CTRL_2)); + v4l2_info(sd, "adv7183: Luma gain control 1 and 2 = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_LUMA_GAIN_CTRL_1), + adv7183_read(sd, ADV7183_LUMA_GAIN_CTRL_2)); + v4l2_info(sd, "adv7183: Vsync field control 1 2 and 3 = 0x%02x 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_VS_FIELD_CTRL_1), + adv7183_read(sd, ADV7183_VS_FIELD_CTRL_2), + adv7183_read(sd, ADV7183_VS_FIELD_CTRL_3)); + v4l2_info(sd, "adv7183: Hsync position control 1 2 and 3 = 0x%02x 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_HS_POS_CTRL_1), + adv7183_read(sd, ADV7183_HS_POS_CTRL_2), + adv7183_read(sd, ADV7183_HS_POS_CTRL_3)); + v4l2_info(sd, "adv7183: Polarity = 0x%02x\n", + adv7183_read(sd, ADV7183_POLARITY)); + v4l2_info(sd, "adv7183: ADC control = 0x%02x\n", + adv7183_read(sd, ADV7183_ADC_CTRL)); + v4l2_info(sd, "adv7183: SD offset Cb and Cr = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_SD_OFFSET_CB), + adv7183_read(sd, ADV7183_SD_OFFSET_CR)); + v4l2_info(sd, "adv7183: SD saturation Cb and Cr = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_SD_SATURATION_CB), + adv7183_read(sd, ADV7183_SD_SATURATION_CR)); + v4l2_info(sd, "adv7183: Drive strength = 0x%02x\n", + adv7183_read(sd, ADV7183_DRIVE_STR)); + v4l2_ctrl_handler_log_status(&decoder->hdl, sd->name); + return 0; +} + +static int adv7183_g_std(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7183 *decoder = to_adv7183(sd); + + *std = decoder->std; + return 0; +} + +static int adv7183_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7183 *decoder = to_adv7183(sd); + int reg; + + reg = adv7183_read(sd, ADV7183_IN_CTRL) & 0xF; + if (std == V4L2_STD_PAL_60) + reg |= 0x60; + else if (std == V4L2_STD_NTSC_443) + reg |= 0x70; + else if (std == V4L2_STD_PAL_N) + reg |= 0x90; + else if (std == V4L2_STD_PAL_M) + reg |= 0xA0; + else if (std == V4L2_STD_PAL_Nc) + reg |= 0xC0; + else if (std & V4L2_STD_PAL) + reg |= 0x80; + else if (std & V4L2_STD_NTSC) + reg |= 0x50; + else if (std & V4L2_STD_SECAM) + reg |= 0xE0; + else + return -EINVAL; + adv7183_write(sd, ADV7183_IN_CTRL, reg); + + decoder->std = std; + + return 0; +} + +static int adv7183_reset(struct v4l2_subdev *sd, u32 val) +{ + int reg; + + reg = adv7183_read(sd, ADV7183_POW_MANAGE) | 0x80; + adv7183_write(sd, ADV7183_POW_MANAGE, reg); + /* wait 5ms before any further i2c writes are performed */ + usleep_range(5000, 10000); + return 0; +} + +static int adv7183_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7183 *decoder = to_adv7183(sd); + int reg; + + if ((input > ADV7183_COMPONENT1) || (output > ADV7183_16BIT_OUT)) + return -EINVAL; + + if (input != decoder->input) { + decoder->input = input; + reg = adv7183_read(sd, ADV7183_IN_CTRL) & 0xF0; + switch (input) { + case ADV7183_COMPOSITE1: + reg |= 0x1; + break; + case ADV7183_COMPOSITE2: + reg |= 0x2; + break; + case ADV7183_COMPOSITE3: + reg |= 0x3; + break; + case ADV7183_COMPOSITE4: + reg |= 0x4; + break; + case ADV7183_COMPOSITE5: + reg |= 0x5; + break; + case ADV7183_COMPOSITE6: + reg |= 0xB; + break; + case ADV7183_COMPOSITE7: + reg |= 0xC; + break; + case ADV7183_COMPOSITE8: + reg |= 0xD; + break; + case ADV7183_COMPOSITE9: + reg |= 0xE; + break; + case ADV7183_COMPOSITE10: + reg |= 0xF; + break; + case ADV7183_SVIDEO0: + reg |= 0x6; + break; + case ADV7183_SVIDEO1: + reg |= 0x7; + break; + case ADV7183_SVIDEO2: + reg |= 0x8; + break; + case ADV7183_COMPONENT0: + reg |= 0x9; + break; + case ADV7183_COMPONENT1: + reg |= 0xA; + break; + default: + break; + } + adv7183_write(sd, ADV7183_IN_CTRL, reg); + } + + if (output != decoder->output) { + decoder->output = output; + reg = adv7183_read(sd, ADV7183_OUT_CTRL) & 0xC0; + switch (output) { + case ADV7183_16BIT_OUT: + reg |= 0x9; + break; + default: + reg |= 0xC; + break; + } + adv7183_write(sd, ADV7183_OUT_CTRL, reg); + } + + return 0; +} + +static int adv7183_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + int val = ctrl->val; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (val < 0) + val = 127 - val; + adv7183_write(sd, ADV7183_BRIGHTNESS, val); + break; + case V4L2_CID_CONTRAST: + adv7183_write(sd, ADV7183_CONTRAST, val); + break; + case V4L2_CID_SATURATION: + adv7183_write(sd, ADV7183_SD_SATURATION_CB, val >> 8); + adv7183_write(sd, ADV7183_SD_SATURATION_CR, (val & 0xFF)); + break; + case V4L2_CID_HUE: + adv7183_write(sd, ADV7183_SD_OFFSET_CB, val >> 8); + adv7183_write(sd, ADV7183_SD_OFFSET_CR, (val & 0xFF)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int adv7183_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7183 *decoder = to_adv7183(sd); + int reg; + + /* enable autodetection block */ + reg = adv7183_read(sd, ADV7183_IN_CTRL) & 0xF; + adv7183_write(sd, ADV7183_IN_CTRL, reg); + + /* wait autodetection switch */ + mdelay(10); + + /* get autodetection result */ + reg = adv7183_read(sd, ADV7183_STATUS_1); + switch ((reg >> 0x4) & 0x7) { + case 0: + *std &= V4L2_STD_NTSC; + break; + case 1: + *std &= V4L2_STD_NTSC_443; + break; + case 2: + *std &= V4L2_STD_PAL_M; + break; + case 3: + *std &= V4L2_STD_PAL_60; + break; + case 4: + *std &= V4L2_STD_PAL; + break; + case 5: + *std &= V4L2_STD_SECAM; + break; + case 6: + *std &= V4L2_STD_PAL_Nc; + break; + case 7: + *std &= V4L2_STD_SECAM; + break; + default: + *std = V4L2_STD_UNKNOWN; + break; + } + + /* after std detection, write back user set std */ + adv7183_s_std(sd, decoder->std); + return 0; +} + +static int adv7183_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + int reg; + + *status = V4L2_IN_ST_NO_SIGNAL; + reg = adv7183_read(sd, ADV7183_STATUS_1); + if (reg < 0) + return reg; + if (reg & 0x1) + *status = 0; + return 0; +} + +static int adv7183_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_2X8; + return 0; +} + +static int adv7183_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct adv7183 *decoder = to_adv7183(sd); + struct v4l2_mbus_framefmt *fmt = &format->format; + + if (format->pad) + return -EINVAL; + + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + if (decoder->std & V4L2_STD_525_60) { + fmt->field = V4L2_FIELD_SEQ_TB; + fmt->width = 720; + fmt->height = 480; + } else { + fmt->field = V4L2_FIELD_SEQ_BT; + fmt->width = 720; + fmt->height = 576; + } + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + decoder->fmt = *fmt; + else + cfg->try_fmt = *fmt; + return 0; +} + +static int adv7183_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct adv7183 *decoder = to_adv7183(sd); + + if (format->pad) + return -EINVAL; + + format->format = decoder->fmt; + return 0; +} + +static int adv7183_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv7183 *decoder = to_adv7183(sd); + + if (enable) + gpio_set_value(decoder->oe_pin, 0); + else + gpio_set_value(decoder->oe_pin, 1); + udelay(1); + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int adv7183_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->val = adv7183_read(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int adv7183_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + adv7183_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static const struct v4l2_ctrl_ops adv7183_ctrl_ops = { + .s_ctrl = adv7183_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7183_core_ops = { + .log_status = adv7183_log_status, + .reset = adv7183_reset, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7183_g_register, + .s_register = adv7183_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops adv7183_video_ops = { + .g_std = adv7183_g_std, + .s_std = adv7183_s_std, + .s_routing = adv7183_s_routing, + .querystd = adv7183_querystd, + .g_input_status = adv7183_g_input_status, + .s_stream = adv7183_s_stream, +}; + +static const struct v4l2_subdev_pad_ops adv7183_pad_ops = { + .enum_mbus_code = adv7183_enum_mbus_code, + .get_fmt = adv7183_get_fmt, + .set_fmt = adv7183_set_fmt, +}; + +static const struct v4l2_subdev_ops adv7183_ops = { + .core = &adv7183_core_ops, + .video = &adv7183_video_ops, + .pad = &adv7183_pad_ops, +}; + +static int adv7183_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adv7183 *decoder; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + int ret; + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + const unsigned *pin_array; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + pin_array = client->dev.platform_data; + if (pin_array == NULL) + return -EINVAL; + + decoder = devm_kzalloc(&client->dev, sizeof(*decoder), GFP_KERNEL); + if (decoder == NULL) + return -ENOMEM; + + decoder->reset_pin = pin_array[0]; + decoder->oe_pin = pin_array[1]; + + if (devm_gpio_request_one(&client->dev, decoder->reset_pin, + GPIOF_OUT_INIT_LOW, "ADV7183 Reset")) { + v4l_err(client, "failed to request GPIO %d\n", decoder->reset_pin); + return -EBUSY; + } + + if (devm_gpio_request_one(&client->dev, decoder->oe_pin, + GPIOF_OUT_INIT_HIGH, + "ADV7183 Output Enable")) { + v4l_err(client, "failed to request GPIO %d\n", decoder->oe_pin); + return -EBUSY; + } + + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &adv7183_ops); + + hdl = &decoder->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_CONTRAST, 0, 0xFF, 1, 0x80); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_SATURATION, 0, 0xFFFF, 1, 0x8080); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_HUE, 0, 0xFFFF, 1, 0x8080); + /* hook the control handler into the driver */ + sd->ctrl_handler = hdl; + if (hdl->error) { + ret = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return ret; + } + + /* v4l2 doesn't support an autodetect standard, pick PAL as default */ + decoder->std = V4L2_STD_PAL; + decoder->input = ADV7183_COMPOSITE4; + decoder->output = ADV7183_8BIT_OUT; + + /* reset chip */ + /* reset pulse width at least 5ms */ + mdelay(10); + gpio_set_value(decoder->reset_pin, 1); + /* wait 5ms before any further i2c writes are performed */ + mdelay(5); + + adv7183_writeregs(sd, adv7183_init_regs, ARRAY_SIZE(adv7183_init_regs)); + adv7183_s_std(sd, decoder->std); + fmt.format.width = 720; + fmt.format.height = 576; + adv7183_set_fmt(sd, NULL, &fmt); + + /* initialize the hardware to the default control values */ + ret = v4l2_ctrl_handler_setup(hdl); + if (ret) { + v4l2_ctrl_handler_free(hdl); + return ret; + } + + return 0; +} + +static int adv7183_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return 0; +} + +static const struct i2c_device_id adv7183_id[] = { + {"adv7183", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, adv7183_id); + +static struct i2c_driver adv7183_driver = { + .driver = { + .name = "adv7183", + }, + .probe = adv7183_probe, + .remove = adv7183_remove, + .id_table = adv7183_id, +}; + +module_i2c_driver(adv7183_driver); + +MODULE_DESCRIPTION("Analog Devices ADV7183 video decoder driver"); +MODULE_AUTHOR("Scott Jiang <Scott.Jiang.Linux@gmail.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/adv7183_regs.h b/drivers/media/i2c/adv7183_regs.h new file mode 100644 index 000000000..d241efe73 --- /dev/null +++ b/drivers/media/i2c/adv7183_regs.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * adv7183 - Analog Devices ADV7183 video decoder registers + * + * Copyright (c) 2011 Analog Devices Inc. + */ + +#ifndef _ADV7183_REGS_H_ +#define _ADV7183_REGS_H_ + +#define ADV7183_IN_CTRL 0x00 /* Input control */ +#define ADV7183_VD_SEL 0x01 /* Video selection */ +#define ADV7183_OUT_CTRL 0x03 /* Output control */ +#define ADV7183_EXT_OUT_CTRL 0x04 /* Extended output control */ +#define ADV7183_AUTO_DET_EN 0x07 /* Autodetect enable */ +#define ADV7183_CONTRAST 0x08 /* Contrast */ +#define ADV7183_BRIGHTNESS 0x0A /* Brightness */ +#define ADV7183_HUE 0x0B /* Hue */ +#define ADV7183_DEF_Y 0x0C /* Default value Y */ +#define ADV7183_DEF_C 0x0D /* Default value C */ +#define ADV7183_ADI_CTRL 0x0E /* ADI control */ +#define ADV7183_POW_MANAGE 0x0F /* Power Management */ +#define ADV7183_STATUS_1 0x10 /* Status 1 */ +#define ADV7183_IDENT 0x11 /* Ident */ +#define ADV7183_STATUS_2 0x12 /* Status 2 */ +#define ADV7183_STATUS_3 0x13 /* Status 3 */ +#define ADV7183_ANAL_CLAMP_CTRL 0x14 /* Analog clamp control */ +#define ADV7183_DIGI_CLAMP_CTRL_1 0x15 /* Digital clamp control 1 */ +#define ADV7183_SHAP_FILT_CTRL 0x17 /* Shaping filter control */ +#define ADV7183_SHAP_FILT_CTRL_2 0x18 /* Shaping filter control 2 */ +#define ADV7183_COMB_FILT_CTRL 0x19 /* Comb filter control */ +#define ADV7183_ADI_CTRL_2 0x1D /* ADI control 2 */ +#define ADV7183_PIX_DELAY_CTRL 0x27 /* Pixel delay control */ +#define ADV7183_MISC_GAIN_CTRL 0x2B /* Misc gain control */ +#define ADV7183_AGC_MODE_CTRL 0x2C /* AGC mode control */ +#define ADV7183_CHRO_GAIN_CTRL_1 0x2D /* Chroma gain control 1 */ +#define ADV7183_CHRO_GAIN_CTRL_2 0x2E /* Chroma gain control 2 */ +#define ADV7183_LUMA_GAIN_CTRL_1 0x2F /* Luma gain control 1 */ +#define ADV7183_LUMA_GAIN_CTRL_2 0x30 /* Luma gain control 2 */ +#define ADV7183_VS_FIELD_CTRL_1 0x31 /* Vsync field control 1 */ +#define ADV7183_VS_FIELD_CTRL_2 0x32 /* Vsync field control 2 */ +#define ADV7183_VS_FIELD_CTRL_3 0x33 /* Vsync field control 3 */ +#define ADV7183_HS_POS_CTRL_1 0x34 /* Hsync position control 1 */ +#define ADV7183_HS_POS_CTRL_2 0x35 /* Hsync position control 2 */ +#define ADV7183_HS_POS_CTRL_3 0x36 /* Hsync position control 3 */ +#define ADV7183_POLARITY 0x37 /* Polarity */ +#define ADV7183_NTSC_COMB_CTRL 0x38 /* NTSC comb control */ +#define ADV7183_PAL_COMB_CTRL 0x39 /* PAL comb control */ +#define ADV7183_ADC_CTRL 0x3A /* ADC control */ +#define ADV7183_MAN_WIN_CTRL 0x3D /* Manual window control */ +#define ADV7183_RESAMPLE_CTRL 0x41 /* Resample control */ +#define ADV7183_GEMSTAR_CTRL_1 0x48 /* Gemstar ctrl 1 */ +#define ADV7183_GEMSTAR_CTRL_2 0x49 /* Gemstar ctrl 2 */ +#define ADV7183_GEMSTAR_CTRL_3 0x4A /* Gemstar ctrl 3 */ +#define ADV7183_GEMSTAR_CTRL_4 0x4B /* Gemstar ctrl 4 */ +#define ADV7183_GEMSTAR_CTRL_5 0x4C /* Gemstar ctrl 5 */ +#define ADV7183_CTI_DNR_CTRL_1 0x4D /* CTI DNR ctrl 1 */ +#define ADV7183_CTI_DNR_CTRL_2 0x4E /* CTI DNR ctrl 2 */ +#define ADV7183_CTI_DNR_CTRL_4 0x50 /* CTI DNR ctrl 4 */ +#define ADV7183_LOCK_CNT 0x51 /* Lock count */ +#define ADV7183_FREE_LINE_LEN 0x8F /* Free-Run line length 1 */ +#define ADV7183_VBI_INFO 0x90 /* VBI info */ +#define ADV7183_WSS_1 0x91 /* WSS 1 */ +#define ADV7183_WSS_2 0x92 /* WSS 2 */ +#define ADV7183_EDTV_1 0x93 /* EDTV 1 */ +#define ADV7183_EDTV_2 0x94 /* EDTV 2 */ +#define ADV7183_EDTV_3 0x95 /* EDTV 3 */ +#define ADV7183_CGMS_1 0x96 /* CGMS 1 */ +#define ADV7183_CGMS_2 0x97 /* CGMS 2 */ +#define ADV7183_CGMS_3 0x98 /* CGMS 3 */ +#define ADV7183_CCAP_1 0x99 /* CCAP 1 */ +#define ADV7183_CCAP_2 0x9A /* CCAP 2 */ +#define ADV7183_LETTERBOX_1 0x9B /* Letterbox 1 */ +#define ADV7183_LETTERBOX_2 0x9C /* Letterbox 2 */ +#define ADV7183_LETTERBOX_3 0x9D /* Letterbox 3 */ +#define ADV7183_CRC_EN 0xB2 /* CRC enable */ +#define ADV7183_ADC_SWITCH_1 0xC3 /* ADC switch 1 */ +#define ADV7183_ADC_SWITCH_2 0xC4 /* ADC switch 2 */ +#define ADV7183_LETTERBOX_CTRL_1 0xDC /* Letterbox control 1 */ +#define ADV7183_LETTERBOX_CTRL_2 0xDD /* Letterbox control 2 */ +#define ADV7183_SD_OFFSET_CB 0xE1 /* SD offset Cb */ +#define ADV7183_SD_OFFSET_CR 0xE2 /* SD offset Cr */ +#define ADV7183_SD_SATURATION_CB 0xE3 /* SD saturation Cb */ +#define ADV7183_SD_SATURATION_CR 0xE4 /* SD saturation Cr */ +#define ADV7183_NTSC_V_BEGIN 0xE5 /* NTSC V bit begin */ +#define ADV7183_NTSC_V_END 0xE6 /* NTSC V bit end */ +#define ADV7183_NTSC_F_TOGGLE 0xE7 /* NTSC F bit toggle */ +#define ADV7183_PAL_V_BEGIN 0xE8 /* PAL V bit begin */ +#define ADV7183_PAL_V_END 0xE9 /* PAL V bit end */ +#define ADV7183_PAL_F_TOGGLE 0xEA /* PAL F bit toggle */ +#define ADV7183_DRIVE_STR 0xF4 /* Drive strength */ +#define ADV7183_IF_COMP_CTRL 0xF8 /* IF comp control */ +#define ADV7183_VS_MODE_CTRL 0xF9 /* VS mode control */ + +#endif diff --git a/drivers/media/i2c/adv7343.c b/drivers/media/i2c/adv7343.c new file mode 100644 index 000000000..63e94dfcb --- /dev/null +++ b/drivers/media/i2c/adv7343.c @@ -0,0 +1,531 @@ +/* + * adv7343 - ADV7343 Video Encoder Driver + * + * The encoder hardware does not support SECAM. + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/videodev2.h> +#include <linux/uaccess.h> +#include <linux/of.h> +#include <linux/of_graph.h> + +#include <media/i2c/adv7343.h> +#include <media/v4l2-async.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +#include "adv7343_regs.h" + +MODULE_DESCRIPTION("ADV7343 video encoder driver"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +struct adv7343_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + const struct adv7343_platform_data *pdata; + u8 reg00; + u8 reg01; + u8 reg02; + u8 reg35; + u8 reg80; + u8 reg82; + u32 output; + v4l2_std_id std; +}; + +static inline struct adv7343_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7343_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7343_state, hdl)->sd; +} + +static inline int adv7343_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static const u8 adv7343_init_reg_val[] = { + ADV7343_SOFT_RESET, ADV7343_SOFT_RESET_DEFAULT, + ADV7343_POWER_MODE_REG, ADV7343_POWER_MODE_REG_DEFAULT, + + ADV7343_HD_MODE_REG1, ADV7343_HD_MODE_REG1_DEFAULT, + ADV7343_HD_MODE_REG2, ADV7343_HD_MODE_REG2_DEFAULT, + ADV7343_HD_MODE_REG3, ADV7343_HD_MODE_REG3_DEFAULT, + ADV7343_HD_MODE_REG4, ADV7343_HD_MODE_REG4_DEFAULT, + ADV7343_HD_MODE_REG5, ADV7343_HD_MODE_REG5_DEFAULT, + ADV7343_HD_MODE_REG6, ADV7343_HD_MODE_REG6_DEFAULT, + ADV7343_HD_MODE_REG7, ADV7343_HD_MODE_REG7_DEFAULT, + + ADV7343_SD_MODE_REG1, ADV7343_SD_MODE_REG1_DEFAULT, + ADV7343_SD_MODE_REG2, ADV7343_SD_MODE_REG2_DEFAULT, + ADV7343_SD_MODE_REG3, ADV7343_SD_MODE_REG3_DEFAULT, + ADV7343_SD_MODE_REG4, ADV7343_SD_MODE_REG4_DEFAULT, + ADV7343_SD_MODE_REG5, ADV7343_SD_MODE_REG5_DEFAULT, + ADV7343_SD_MODE_REG6, ADV7343_SD_MODE_REG6_DEFAULT, + ADV7343_SD_MODE_REG7, ADV7343_SD_MODE_REG7_DEFAULT, + ADV7343_SD_MODE_REG8, ADV7343_SD_MODE_REG8_DEFAULT, + + ADV7343_SD_HUE_REG, ADV7343_SD_HUE_REG_DEFAULT, + ADV7343_SD_CGMS_WSS0, ADV7343_SD_CGMS_WSS0_DEFAULT, + ADV7343_SD_BRIGHTNESS_WSS, ADV7343_SD_BRIGHTNESS_WSS_DEFAULT, +}; + +/* + * 2^32 + * FSC(reg) = FSC (HZ) * -------- + * 27000000 + */ +static const struct adv7343_std_info stdinfo[] = { + { + /* FSC(Hz) = 3,579,545.45 Hz */ + SD_STD_NTSC, 569408542, V4L2_STD_NTSC, + }, { + /* FSC(Hz) = 3,575,611.00 Hz */ + SD_STD_PAL_M, 568782678, V4L2_STD_PAL_M, + }, { + /* FSC(Hz) = 3,582,056.00 */ + SD_STD_PAL_N, 569807903, V4L2_STD_PAL_Nc, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_N, 705268427, V4L2_STD_PAL_N, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_BDGHI, 705268427, V4L2_STD_PAL, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_NTSC, 705268427, V4L2_STD_NTSC_443, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_M, 705268427, V4L2_STD_PAL_60, + }, +}; + +static int adv7343_setstd(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7343_state *state = to_state(sd); + struct adv7343_std_info *std_info; + int num_std; + char *fsc_ptr; + u8 reg, val; + int err = 0; + int i = 0; + + std_info = (struct adv7343_std_info *)stdinfo; + num_std = ARRAY_SIZE(stdinfo); + + for (i = 0; i < num_std; i++) { + if (std_info[i].stdid & std) + break; + } + + if (i == num_std) { + v4l2_dbg(1, debug, sd, + "Invalid std or std is not supported: %llx\n", + (unsigned long long)std); + return -EINVAL; + } + + /* Set the standard */ + val = state->reg80 & (~(SD_STD_MASK)); + val |= std_info[i].standard_val3; + err = adv7343_write(sd, ADV7343_SD_MODE_REG1, val); + if (err < 0) + goto setstd_exit; + + state->reg80 = val; + + /* Configure the input mode register */ + val = state->reg01 & (~((u8) INPUT_MODE_MASK)); + val |= SD_INPUT_MODE; + err = adv7343_write(sd, ADV7343_MODE_SELECT_REG, val); + if (err < 0) + goto setstd_exit; + + state->reg01 = val; + + /* Program the sub carrier frequency registers */ + fsc_ptr = (unsigned char *)&std_info[i].fsc_val; + reg = ADV7343_FSC_REG0; + for (i = 0; i < 4; i++, reg++, fsc_ptr++) { + err = adv7343_write(sd, reg, *fsc_ptr); + if (err < 0) + goto setstd_exit; + } + + val = state->reg80; + + /* Filter settings */ + if (std & (V4L2_STD_NTSC | V4L2_STD_NTSC_443)) + val &= 0x03; + else if (std & ~V4L2_STD_SECAM) + val |= 0x04; + + err = adv7343_write(sd, ADV7343_SD_MODE_REG1, val); + if (err < 0) + goto setstd_exit; + + state->reg80 = val; + +setstd_exit: + if (err != 0) + v4l2_err(sd, "Error setting std, write failed\n"); + + return err; +} + +static int adv7343_setoutput(struct v4l2_subdev *sd, u32 output_type) +{ + struct adv7343_state *state = to_state(sd); + unsigned char val; + int err = 0; + + if (output_type > ADV7343_SVIDEO_ID) { + v4l2_dbg(1, debug, sd, + "Invalid output type or output type not supported:%d\n", + output_type); + return -EINVAL; + } + + /* Enable Appropriate DAC */ + val = state->reg00 & 0x03; + + /* configure default configuration */ + if (!state->pdata) + if (output_type == ADV7343_COMPOSITE_ID) + val |= ADV7343_COMPOSITE_POWER_VALUE; + else if (output_type == ADV7343_COMPONENT_ID) + val |= ADV7343_COMPONENT_POWER_VALUE; + else + val |= ADV7343_SVIDEO_POWER_VALUE; + else + val = state->pdata->mode_config.sleep_mode << 0 | + state->pdata->mode_config.pll_control << 1 | + state->pdata->mode_config.dac[2] << 2 | + state->pdata->mode_config.dac[1] << 3 | + state->pdata->mode_config.dac[0] << 4 | + state->pdata->mode_config.dac[5] << 5 | + state->pdata->mode_config.dac[4] << 6 | + state->pdata->mode_config.dac[3] << 7; + + err = adv7343_write(sd, ADV7343_POWER_MODE_REG, val); + if (err < 0) + goto setoutput_exit; + + state->reg00 = val; + + /* Enable YUV output */ + val = state->reg02 | YUV_OUTPUT_SELECT; + err = adv7343_write(sd, ADV7343_MODE_REG0, val); + if (err < 0) + goto setoutput_exit; + + state->reg02 = val; + + /* configure SD DAC Output 2 and SD DAC Output 1 bit to zero */ + val = state->reg82 & (SD_DAC_1_DI & SD_DAC_2_DI); + + if (state->pdata && state->pdata->sd_config.sd_dac_out[0]) + val = val | (state->pdata->sd_config.sd_dac_out[0] << 1); + else if (state->pdata && !state->pdata->sd_config.sd_dac_out[0]) + val = val & ~(state->pdata->sd_config.sd_dac_out[0] << 1); + + if (state->pdata && state->pdata->sd_config.sd_dac_out[1]) + val = val | (state->pdata->sd_config.sd_dac_out[1] << 2); + else if (state->pdata && !state->pdata->sd_config.sd_dac_out[1]) + val = val & ~(state->pdata->sd_config.sd_dac_out[1] << 2); + + err = adv7343_write(sd, ADV7343_SD_MODE_REG2, val); + if (err < 0) + goto setoutput_exit; + + state->reg82 = val; + + /* configure ED/HD Color DAC Swap and ED/HD RGB Input Enable bit to + * zero */ + val = state->reg35 & (HD_RGB_INPUT_DI & HD_DAC_SWAP_DI); + err = adv7343_write(sd, ADV7343_HD_MODE_REG6, val); + if (err < 0) + goto setoutput_exit; + + state->reg35 = val; + +setoutput_exit: + if (err != 0) + v4l2_err(sd, "Error setting output, write failed\n"); + + return err; +} + +static int adv7343_log_status(struct v4l2_subdev *sd) +{ + struct adv7343_state *state = to_state(sd); + + v4l2_info(sd, "Standard: %llx\n", (unsigned long long)state->std); + v4l2_info(sd, "Output: %s\n", (state->output == 0) ? "Composite" : + ((state->output == 1) ? "Component" : "S-Video")); + return 0; +} + +static int adv7343_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + return adv7343_write(sd, ADV7343_SD_BRIGHTNESS_WSS, + ctrl->val); + + case V4L2_CID_HUE: + return adv7343_write(sd, ADV7343_SD_HUE_REG, ctrl->val); + + case V4L2_CID_GAIN: + return adv7343_write(sd, ADV7343_DAC2_OUTPUT_LEVEL, ctrl->val); + } + return -EINVAL; +} + +static const struct v4l2_ctrl_ops adv7343_ctrl_ops = { + .s_ctrl = adv7343_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7343_core_ops = { + .log_status = adv7343_log_status, +}; + +static int adv7343_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7343_state *state = to_state(sd); + int err = 0; + + if (state->std == std) + return 0; + + err = adv7343_setstd(sd, std); + if (!err) + state->std = std; + + return err; +} + +static int adv7343_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7343_state *state = to_state(sd); + int err = 0; + + if (state->output == output) + return 0; + + err = adv7343_setoutput(sd, output); + if (!err) + state->output = output; + + return err; +} + +static const struct v4l2_subdev_video_ops adv7343_video_ops = { + .s_std_output = adv7343_s_std_output, + .s_routing = adv7343_s_routing, +}; + +static const struct v4l2_subdev_ops adv7343_ops = { + .core = &adv7343_core_ops, + .video = &adv7343_video_ops, +}; + +static int adv7343_initialize(struct v4l2_subdev *sd) +{ + struct adv7343_state *state = to_state(sd); + int err = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(adv7343_init_reg_val); i += 2) { + + err = adv7343_write(sd, adv7343_init_reg_val[i], + adv7343_init_reg_val[i+1]); + if (err) { + v4l2_err(sd, "Error initializing\n"); + return err; + } + } + + /* Configure for default video standard */ + err = adv7343_setoutput(sd, state->output); + if (err < 0) { + v4l2_err(sd, "Error setting output during init\n"); + return -EINVAL; + } + + err = adv7343_setstd(sd, state->std); + if (err < 0) { + v4l2_err(sd, "Error setting std during init\n"); + return -EINVAL; + } + + return err; +} + +static struct adv7343_platform_data * +adv7343_get_pdata(struct i2c_client *client) +{ + struct adv7343_platform_data *pdata; + struct device_node *np; + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return client->dev.platform_data; + + np = of_graph_get_next_endpoint(client->dev.of_node, NULL); + if (!np) + return NULL; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto done; + + pdata->mode_config.sleep_mode = + of_property_read_bool(np, "adi,power-mode-sleep-mode"); + + pdata->mode_config.pll_control = + of_property_read_bool(np, "adi,power-mode-pll-ctrl"); + + of_property_read_u32_array(np, "adi,dac-enable", + pdata->mode_config.dac, 6); + + of_property_read_u32_array(np, "adi,sd-dac-enable", + pdata->sd_config.sd_dac_out, 2); + +done: + of_node_put(np); + return pdata; +} + +static int adv7343_probe(struct i2c_client *client) +{ + struct adv7343_state *state; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(struct adv7343_state), + GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + /* Copy board specific information here */ + state->pdata = adv7343_get_pdata(client); + + state->reg00 = 0x80; + state->reg01 = 0x00; + state->reg02 = 0x20; + state->reg35 = 0x00; + state->reg80 = ADV7343_SD_MODE_REG1_DEFAULT; + state->reg82 = ADV7343_SD_MODE_REG2_DEFAULT; + + state->output = ADV7343_COMPOSITE_ID; + state->std = V4L2_STD_NTSC; + + v4l2_i2c_subdev_init(&state->sd, client, &adv7343_ops); + + v4l2_ctrl_handler_init(&state->hdl, 2); + v4l2_ctrl_new_std(&state->hdl, &adv7343_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV7343_BRIGHTNESS_MIN, + ADV7343_BRIGHTNESS_MAX, 1, + ADV7343_BRIGHTNESS_DEF); + v4l2_ctrl_new_std(&state->hdl, &adv7343_ctrl_ops, + V4L2_CID_HUE, ADV7343_HUE_MIN, + ADV7343_HUE_MAX, 1, + ADV7343_HUE_DEF); + v4l2_ctrl_new_std(&state->hdl, &adv7343_ctrl_ops, + V4L2_CID_GAIN, ADV7343_GAIN_MIN, + ADV7343_GAIN_MAX, 1, + ADV7343_GAIN_DEF); + state->sd.ctrl_handler = &state->hdl; + if (state->hdl.error) { + err = state->hdl.error; + goto done; + } + v4l2_ctrl_handler_setup(&state->hdl); + + err = adv7343_initialize(&state->sd); + if (err) + goto done; + + err = v4l2_async_register_subdev(&state->sd); + +done: + if (err < 0) + v4l2_ctrl_handler_free(&state->hdl); + + return err; +} + +static int adv7343_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7343_state *state = to_state(sd); + + v4l2_async_unregister_subdev(&state->sd); + v4l2_ctrl_handler_free(&state->hdl); + + return 0; +} + +static const struct i2c_device_id adv7343_id[] = { + {"adv7343", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, adv7343_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id adv7343_of_match[] = { + {.compatible = "adi,adv7343", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, adv7343_of_match); +#endif + +static struct i2c_driver adv7343_driver = { + .driver = { + .of_match_table = of_match_ptr(adv7343_of_match), + .name = "adv7343", + }, + .probe_new = adv7343_probe, + .remove = adv7343_remove, + .id_table = adv7343_id, +}; + +module_i2c_driver(adv7343_driver); diff --git a/drivers/media/i2c/adv7343_regs.h b/drivers/media/i2c/adv7343_regs.h new file mode 100644 index 000000000..2f04ce4b9 --- /dev/null +++ b/drivers/media/i2c/adv7343_regs.h @@ -0,0 +1,181 @@ +/* + * ADV7343 encoder related structure and register definitions + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ADV7343_REGS_H +#define ADV7343_REGS_H + +struct adv7343_std_info { + u32 standard_val3; + u32 fsc_val; + v4l2_std_id stdid; +}; + +/* Register offset macros */ +#define ADV7343_POWER_MODE_REG (0x00) +#define ADV7343_MODE_SELECT_REG (0x01) +#define ADV7343_MODE_REG0 (0x02) + +#define ADV7343_DAC2_OUTPUT_LEVEL (0x0b) + +#define ADV7343_SOFT_RESET (0x17) + +#define ADV7343_HD_MODE_REG1 (0x30) +#define ADV7343_HD_MODE_REG2 (0x31) +#define ADV7343_HD_MODE_REG3 (0x32) +#define ADV7343_HD_MODE_REG4 (0x33) +#define ADV7343_HD_MODE_REG5 (0x34) +#define ADV7343_HD_MODE_REG6 (0x35) + +#define ADV7343_HD_MODE_REG7 (0x39) + +#define ADV7343_SD_MODE_REG1 (0x80) +#define ADV7343_SD_MODE_REG2 (0x82) +#define ADV7343_SD_MODE_REG3 (0x83) +#define ADV7343_SD_MODE_REG4 (0x84) +#define ADV7343_SD_MODE_REG5 (0x86) +#define ADV7343_SD_MODE_REG6 (0x87) +#define ADV7343_SD_MODE_REG7 (0x88) +#define ADV7343_SD_MODE_REG8 (0x89) + +#define ADV7343_FSC_REG0 (0x8C) +#define ADV7343_FSC_REG1 (0x8D) +#define ADV7343_FSC_REG2 (0x8E) +#define ADV7343_FSC_REG3 (0x8F) + +#define ADV7343_SD_CGMS_WSS0 (0x99) + +#define ADV7343_SD_HUE_REG (0xA0) +#define ADV7343_SD_BRIGHTNESS_WSS (0xA1) + +/* Default values for the registers */ +#define ADV7343_POWER_MODE_REG_DEFAULT (0x10) +#define ADV7343_HD_MODE_REG1_DEFAULT (0x3C) /* Changed Default + 720p EAVSAV code*/ +#define ADV7343_HD_MODE_REG2_DEFAULT (0x01) /* Changed Pixel data + valid */ +#define ADV7343_HD_MODE_REG3_DEFAULT (0x00) /* Color delay 0 clks */ +#define ADV7343_HD_MODE_REG4_DEFAULT (0xE8) /* Changed */ +#define ADV7343_HD_MODE_REG5_DEFAULT (0x08) +#define ADV7343_HD_MODE_REG6_DEFAULT (0x00) +#define ADV7343_HD_MODE_REG7_DEFAULT (0x00) +#define ADV7343_SD_MODE_REG8_DEFAULT (0x00) +#define ADV7343_SOFT_RESET_DEFAULT (0x02) +#define ADV7343_COMPOSITE_POWER_VALUE (0x80) +#define ADV7343_COMPONENT_POWER_VALUE (0x1C) +#define ADV7343_SVIDEO_POWER_VALUE (0x60) +#define ADV7343_SD_HUE_REG_DEFAULT (127) +#define ADV7343_SD_BRIGHTNESS_WSS_DEFAULT (0x03) + +#define ADV7343_SD_CGMS_WSS0_DEFAULT (0x10) + +#define ADV7343_SD_MODE_REG1_DEFAULT (0x00) +#define ADV7343_SD_MODE_REG2_DEFAULT (0xC9) +#define ADV7343_SD_MODE_REG3_DEFAULT (0x10) +#define ADV7343_SD_MODE_REG4_DEFAULT (0x01) +#define ADV7343_SD_MODE_REG5_DEFAULT (0x02) +#define ADV7343_SD_MODE_REG6_DEFAULT (0x0C) +#define ADV7343_SD_MODE_REG7_DEFAULT (0x04) +#define ADV7343_SD_MODE_REG8_DEFAULT (0x00) + +/* Bit masks for Mode Select Register */ +#define INPUT_MODE_MASK (0x70) +#define SD_INPUT_MODE (0x00) +#define HD_720P_INPUT_MODE (0x10) +#define HD_1080I_INPUT_MODE (0x10) + +/* Bit masks for Mode Register 0 */ +#define TEST_PATTERN_BLACK_BAR_EN (0x04) +#define YUV_OUTPUT_SELECT (0x20) +#define RGB_OUTPUT_SELECT (0xDF) + +/* Bit masks for DAC output levels */ +#define DAC_OUTPUT_LEVEL_MASK (0xFF) + +/* Bit masks for soft reset register */ +#define SOFT_RESET (0x02) + +/* Bit masks for HD Mode Register 1 */ +#define OUTPUT_STD_MASK (0x03) +#define OUTPUT_STD_SHIFT (0) +#define OUTPUT_STD_EIA0_2 (0x00) +#define OUTPUT_STD_EIA0_1 (0x01) +#define OUTPUT_STD_FULL (0x02) +#define EMBEDDED_SYNC (0x04) +#define EXTERNAL_SYNC (0xFB) +#define STD_MODE_SHIFT (3) +#define STD_MODE_MASK (0x1F) +#define STD_MODE_720P (0x05) +#define STD_MODE_720P_25 (0x08) +#define STD_MODE_720P_30 (0x07) +#define STD_MODE_720P_50 (0x06) +#define STD_MODE_1080I (0x0D) +#define STD_MODE_1080I_25fps (0x0E) +#define STD_MODE_1080P_24 (0x12) +#define STD_MODE_1080P_25 (0x10) +#define STD_MODE_1080P_30 (0x0F) +#define STD_MODE_525P (0x00) +#define STD_MODE_625P (0x03) + +/* Bit masks for SD Mode Register 1 */ +#define SD_STD_MASK (0x03) +#define SD_STD_NTSC (0x00) +#define SD_STD_PAL_BDGHI (0x01) +#define SD_STD_PAL_M (0x02) +#define SD_STD_PAL_N (0x03) +#define SD_LUMA_FLTR_MASK (0x7) +#define SD_LUMA_FLTR_SHIFT (0x2) +#define SD_CHROMA_FLTR_MASK (0x7) +#define SD_CHROMA_FLTR_SHIFT (0x5) + +/* Bit masks for SD Mode Register 2 */ +#define SD_PBPR_SSAF_EN (0x01) +#define SD_PBPR_SSAF_DI (0xFE) +#define SD_DAC_1_DI (0xFD) +#define SD_DAC_2_DI (0xFB) +#define SD_PEDESTAL_EN (0x08) +#define SD_PEDESTAL_DI (0xF7) +#define SD_SQUARE_PIXEL_EN (0x10) +#define SD_SQUARE_PIXEL_DI (0xEF) +#define SD_PIXEL_DATA_VALID (0x40) +#define SD_ACTIVE_EDGE_EN (0x80) +#define SD_ACTIVE_EDGE_DI (0x7F) + +/* Bit masks for HD Mode Register 6 */ +#define HD_RGB_INPUT_EN (0x02) +#define HD_RGB_INPUT_DI (0xFD) +#define HD_PBPR_SYNC_EN (0x04) +#define HD_PBPR_SYNC_DI (0xFB) +#define HD_DAC_SWAP_EN (0x08) +#define HD_DAC_SWAP_DI (0xF7) +#define HD_GAMMA_CURVE_A (0xEF) +#define HD_GAMMA_CURVE_B (0x10) +#define HD_GAMMA_EN (0x20) +#define HD_GAMMA_DI (0xDF) +#define HD_ADPT_FLTR_MODEB (0x40) +#define HD_ADPT_FLTR_MODEA (0xBF) +#define HD_ADPT_FLTR_EN (0x80) +#define HD_ADPT_FLTR_DI (0x7F) + +#define ADV7343_BRIGHTNESS_MAX (127) +#define ADV7343_BRIGHTNESS_MIN (0) +#define ADV7343_BRIGHTNESS_DEF (3) +#define ADV7343_HUE_MAX (255) +#define ADV7343_HUE_MIN (0) +#define ADV7343_HUE_DEF (127) +#define ADV7343_GAIN_MAX (64) +#define ADV7343_GAIN_MIN (-64) +#define ADV7343_GAIN_DEF (0) + +#endif diff --git a/drivers/media/i2c/adv7393.c b/drivers/media/i2c/adv7393.c new file mode 100644 index 000000000..b6234c823 --- /dev/null +++ b/drivers/media/i2c/adv7393.c @@ -0,0 +1,465 @@ +/* + * adv7393 - ADV7393 Video Encoder Driver + * + * The encoder hardware does not support SECAM. + * + * Copyright (C) 2010-2012 ADVANSEE - http://www.advansee.com/ + * Benoît Thébaudeau <benoit.thebaudeau@advansee.com> + * + * Based on ADV7343 driver, + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/videodev2.h> +#include <linux/uaccess.h> + +#include <media/i2c/adv7393.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +#include "adv7393_regs.h" + +MODULE_DESCRIPTION("ADV7393 video encoder driver"); +MODULE_LICENSE("GPL"); + +static bool debug; +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +struct adv7393_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + u8 reg00; + u8 reg01; + u8 reg02; + u8 reg35; + u8 reg80; + u8 reg82; + u32 output; + v4l2_std_id std; +}; + +static inline struct adv7393_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7393_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7393_state, hdl)->sd; +} + +static inline int adv7393_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static const u8 adv7393_init_reg_val[] = { + ADV7393_SOFT_RESET, ADV7393_SOFT_RESET_DEFAULT, + ADV7393_POWER_MODE_REG, ADV7393_POWER_MODE_REG_DEFAULT, + + ADV7393_HD_MODE_REG1, ADV7393_HD_MODE_REG1_DEFAULT, + ADV7393_HD_MODE_REG2, ADV7393_HD_MODE_REG2_DEFAULT, + ADV7393_HD_MODE_REG3, ADV7393_HD_MODE_REG3_DEFAULT, + ADV7393_HD_MODE_REG4, ADV7393_HD_MODE_REG4_DEFAULT, + ADV7393_HD_MODE_REG5, ADV7393_HD_MODE_REG5_DEFAULT, + ADV7393_HD_MODE_REG6, ADV7393_HD_MODE_REG6_DEFAULT, + ADV7393_HD_MODE_REG7, ADV7393_HD_MODE_REG7_DEFAULT, + + ADV7393_SD_MODE_REG1, ADV7393_SD_MODE_REG1_DEFAULT, + ADV7393_SD_MODE_REG2, ADV7393_SD_MODE_REG2_DEFAULT, + ADV7393_SD_MODE_REG3, ADV7393_SD_MODE_REG3_DEFAULT, + ADV7393_SD_MODE_REG4, ADV7393_SD_MODE_REG4_DEFAULT, + ADV7393_SD_MODE_REG5, ADV7393_SD_MODE_REG5_DEFAULT, + ADV7393_SD_MODE_REG6, ADV7393_SD_MODE_REG6_DEFAULT, + ADV7393_SD_MODE_REG7, ADV7393_SD_MODE_REG7_DEFAULT, + ADV7393_SD_MODE_REG8, ADV7393_SD_MODE_REG8_DEFAULT, + + ADV7393_SD_TIMING_REG0, ADV7393_SD_TIMING_REG0_DEFAULT, + + ADV7393_SD_HUE_ADJUST, ADV7393_SD_HUE_ADJUST_DEFAULT, + ADV7393_SD_CGMS_WSS0, ADV7393_SD_CGMS_WSS0_DEFAULT, + ADV7393_SD_BRIGHTNESS_WSS, ADV7393_SD_BRIGHTNESS_WSS_DEFAULT, +}; + +/* + * 2^32 + * FSC(reg) = FSC (HZ) * -------- + * 27000000 + */ +static const struct adv7393_std_info stdinfo[] = { + { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_NTSC, 705268427, V4L2_STD_NTSC_443, + }, { + /* FSC(Hz) = 3,579,545.45 Hz */ + SD_STD_NTSC, 569408542, V4L2_STD_NTSC, + }, { + /* FSC(Hz) = 3,575,611.00 Hz */ + SD_STD_PAL_M, 568782678, V4L2_STD_PAL_M, + }, { + /* FSC(Hz) = 3,582,056.00 Hz */ + SD_STD_PAL_N, 569807903, V4L2_STD_PAL_Nc, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_N, 705268427, V4L2_STD_PAL_N, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_M, 705268427, V4L2_STD_PAL_60, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_BDGHI, 705268427, V4L2_STD_PAL, + }, +}; + +static int adv7393_setstd(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7393_state *state = to_state(sd); + const struct adv7393_std_info *std_info; + int num_std; + u8 reg; + u32 val; + int err = 0; + int i; + + num_std = ARRAY_SIZE(stdinfo); + + for (i = 0; i < num_std; i++) { + if (stdinfo[i].stdid & std) + break; + } + + if (i == num_std) { + v4l2_dbg(1, debug, sd, + "Invalid std or std is not supported: %llx\n", + (unsigned long long)std); + return -EINVAL; + } + + std_info = &stdinfo[i]; + + /* Set the standard */ + val = state->reg80 & ~SD_STD_MASK; + val |= std_info->standard_val3; + err = adv7393_write(sd, ADV7393_SD_MODE_REG1, val); + if (err < 0) + goto setstd_exit; + + state->reg80 = val; + + /* Configure the input mode register */ + val = state->reg01 & ~INPUT_MODE_MASK; + val |= SD_INPUT_MODE; + err = adv7393_write(sd, ADV7393_MODE_SELECT_REG, val); + if (err < 0) + goto setstd_exit; + + state->reg01 = val; + + /* Program the sub carrier frequency registers */ + val = std_info->fsc_val; + for (reg = ADV7393_FSC_REG0; reg <= ADV7393_FSC_REG3; reg++) { + err = adv7393_write(sd, reg, val); + if (err < 0) + goto setstd_exit; + val >>= 8; + } + + val = state->reg82; + + /* Pedestal settings */ + if (std & (V4L2_STD_NTSC | V4L2_STD_NTSC_443)) + val |= SD_PEDESTAL_EN; + else + val &= SD_PEDESTAL_DI; + + err = adv7393_write(sd, ADV7393_SD_MODE_REG2, val); + if (err < 0) + goto setstd_exit; + + state->reg82 = val; + +setstd_exit: + if (err != 0) + v4l2_err(sd, "Error setting std, write failed\n"); + + return err; +} + +static int adv7393_setoutput(struct v4l2_subdev *sd, u32 output_type) +{ + struct adv7393_state *state = to_state(sd); + u8 val; + int err = 0; + + if (output_type > ADV7393_SVIDEO_ID) { + v4l2_dbg(1, debug, sd, + "Invalid output type or output type not supported:%d\n", + output_type); + return -EINVAL; + } + + /* Enable Appropriate DAC */ + val = state->reg00 & 0x03; + + if (output_type == ADV7393_COMPOSITE_ID) + val |= ADV7393_COMPOSITE_POWER_VALUE; + else if (output_type == ADV7393_COMPONENT_ID) + val |= ADV7393_COMPONENT_POWER_VALUE; + else + val |= ADV7393_SVIDEO_POWER_VALUE; + + err = adv7393_write(sd, ADV7393_POWER_MODE_REG, val); + if (err < 0) + goto setoutput_exit; + + state->reg00 = val; + + /* Enable YUV output */ + val = state->reg02 | YUV_OUTPUT_SELECT; + err = adv7393_write(sd, ADV7393_MODE_REG0, val); + if (err < 0) + goto setoutput_exit; + + state->reg02 = val; + + /* configure SD DAC Output 1 bit */ + val = state->reg82; + if (output_type == ADV7393_COMPONENT_ID) + val &= SD_DAC_OUT1_DI; + else + val |= SD_DAC_OUT1_EN; + err = adv7393_write(sd, ADV7393_SD_MODE_REG2, val); + if (err < 0) + goto setoutput_exit; + + state->reg82 = val; + + /* configure ED/HD Color DAC Swap bit to zero */ + val = state->reg35 & HD_DAC_SWAP_DI; + err = adv7393_write(sd, ADV7393_HD_MODE_REG6, val); + if (err < 0) + goto setoutput_exit; + + state->reg35 = val; + +setoutput_exit: + if (err != 0) + v4l2_err(sd, "Error setting output, write failed\n"); + + return err; +} + +static int adv7393_log_status(struct v4l2_subdev *sd) +{ + struct adv7393_state *state = to_state(sd); + + v4l2_info(sd, "Standard: %llx\n", (unsigned long long)state->std); + v4l2_info(sd, "Output: %s\n", (state->output == 0) ? "Composite" : + ((state->output == 1) ? "Component" : "S-Video")); + return 0; +} + +static int adv7393_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + return adv7393_write(sd, ADV7393_SD_BRIGHTNESS_WSS, + ctrl->val & SD_BRIGHTNESS_VALUE_MASK); + + case V4L2_CID_HUE: + return adv7393_write(sd, ADV7393_SD_HUE_ADJUST, + ctrl->val - ADV7393_HUE_MIN); + + case V4L2_CID_GAIN: + return adv7393_write(sd, ADV7393_DAC123_OUTPUT_LEVEL, + ctrl->val); + } + return -EINVAL; +} + +static const struct v4l2_ctrl_ops adv7393_ctrl_ops = { + .s_ctrl = adv7393_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7393_core_ops = { + .log_status = adv7393_log_status, +}; + +static int adv7393_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7393_state *state = to_state(sd); + int err = 0; + + if (state->std == std) + return 0; + + err = adv7393_setstd(sd, std); + if (!err) + state->std = std; + + return err; +} + +static int adv7393_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7393_state *state = to_state(sd); + int err = 0; + + if (state->output == output) + return 0; + + err = adv7393_setoutput(sd, output); + if (!err) + state->output = output; + + return err; +} + +static const struct v4l2_subdev_video_ops adv7393_video_ops = { + .s_std_output = adv7393_s_std_output, + .s_routing = adv7393_s_routing, +}; + +static const struct v4l2_subdev_ops adv7393_ops = { + .core = &adv7393_core_ops, + .video = &adv7393_video_ops, +}; + +static int adv7393_initialize(struct v4l2_subdev *sd) +{ + struct adv7393_state *state = to_state(sd); + int err = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(adv7393_init_reg_val); i += 2) { + + err = adv7393_write(sd, adv7393_init_reg_val[i], + adv7393_init_reg_val[i+1]); + if (err) { + v4l2_err(sd, "Error initializing\n"); + return err; + } + } + + /* Configure for default video standard */ + err = adv7393_setoutput(sd, state->output); + if (err < 0) { + v4l2_err(sd, "Error setting output during init\n"); + return -EINVAL; + } + + err = adv7393_setstd(sd, state->std); + if (err < 0) { + v4l2_err(sd, "Error setting std during init\n"); + return -EINVAL; + } + + return err; +} + +static int adv7393_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adv7393_state *state; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + state->reg00 = ADV7393_POWER_MODE_REG_DEFAULT; + state->reg01 = 0x00; + state->reg02 = 0x20; + state->reg35 = ADV7393_HD_MODE_REG6_DEFAULT; + state->reg80 = ADV7393_SD_MODE_REG1_DEFAULT; + state->reg82 = ADV7393_SD_MODE_REG2_DEFAULT; + + state->output = ADV7393_COMPOSITE_ID; + state->std = V4L2_STD_NTSC; + + v4l2_i2c_subdev_init(&state->sd, client, &adv7393_ops); + + v4l2_ctrl_handler_init(&state->hdl, 3); + v4l2_ctrl_new_std(&state->hdl, &adv7393_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV7393_BRIGHTNESS_MIN, + ADV7393_BRIGHTNESS_MAX, 1, + ADV7393_BRIGHTNESS_DEF); + v4l2_ctrl_new_std(&state->hdl, &adv7393_ctrl_ops, + V4L2_CID_HUE, ADV7393_HUE_MIN, + ADV7393_HUE_MAX, 1, + ADV7393_HUE_DEF); + v4l2_ctrl_new_std(&state->hdl, &adv7393_ctrl_ops, + V4L2_CID_GAIN, ADV7393_GAIN_MIN, + ADV7393_GAIN_MAX, 1, + ADV7393_GAIN_DEF); + state->sd.ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + v4l2_ctrl_handler_setup(&state->hdl); + + err = adv7393_initialize(&state->sd); + if (err) + v4l2_ctrl_handler_free(&state->hdl); + return err; +} + +static int adv7393_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7393_state *state = to_state(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); + + return 0; +} + +static const struct i2c_device_id adv7393_id[] = { + {"adv7393", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, adv7393_id); + +static struct i2c_driver adv7393_driver = { + .driver = { + .name = "adv7393", + }, + .probe = adv7393_probe, + .remove = adv7393_remove, + .id_table = adv7393_id, +}; +module_i2c_driver(adv7393_driver); diff --git a/drivers/media/i2c/adv7393_regs.h b/drivers/media/i2c/adv7393_regs.h new file mode 100644 index 000000000..78968330f --- /dev/null +++ b/drivers/media/i2c/adv7393_regs.h @@ -0,0 +1,188 @@ +/* + * ADV7393 encoder related structure and register definitions + * + * Copyright (C) 2010-2012 ADVANSEE - http://www.advansee.com/ + * Benoît Thébaudeau <benoit.thebaudeau@advansee.com> + * + * Based on ADV7343 driver, + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ADV7393_REGS_H +#define ADV7393_REGS_H + +struct adv7393_std_info { + u32 standard_val3; + u32 fsc_val; + v4l2_std_id stdid; +}; + +/* Register offset macros */ +#define ADV7393_POWER_MODE_REG (0x00) +#define ADV7393_MODE_SELECT_REG (0x01) +#define ADV7393_MODE_REG0 (0x02) + +#define ADV7393_DAC123_OUTPUT_LEVEL (0x0B) + +#define ADV7393_SOFT_RESET (0x17) + +#define ADV7393_HD_MODE_REG1 (0x30) +#define ADV7393_HD_MODE_REG2 (0x31) +#define ADV7393_HD_MODE_REG3 (0x32) +#define ADV7393_HD_MODE_REG4 (0x33) +#define ADV7393_HD_MODE_REG5 (0x34) +#define ADV7393_HD_MODE_REG6 (0x35) + +#define ADV7393_HD_MODE_REG7 (0x39) + +#define ADV7393_SD_MODE_REG1 (0x80) +#define ADV7393_SD_MODE_REG2 (0x82) +#define ADV7393_SD_MODE_REG3 (0x83) +#define ADV7393_SD_MODE_REG4 (0x84) +#define ADV7393_SD_MODE_REG5 (0x86) +#define ADV7393_SD_MODE_REG6 (0x87) +#define ADV7393_SD_MODE_REG7 (0x88) +#define ADV7393_SD_MODE_REG8 (0x89) + +#define ADV7393_SD_TIMING_REG0 (0x8A) + +#define ADV7393_FSC_REG0 (0x8C) +#define ADV7393_FSC_REG1 (0x8D) +#define ADV7393_FSC_REG2 (0x8E) +#define ADV7393_FSC_REG3 (0x8F) + +#define ADV7393_SD_CGMS_WSS0 (0x99) + +#define ADV7393_SD_HUE_ADJUST (0xA0) +#define ADV7393_SD_BRIGHTNESS_WSS (0xA1) + +/* Default values for the registers */ +#define ADV7393_POWER_MODE_REG_DEFAULT (0x10) +#define ADV7393_HD_MODE_REG1_DEFAULT (0x3C) /* Changed Default + 720p EAV/SAV code*/ +#define ADV7393_HD_MODE_REG2_DEFAULT (0x01) /* Changed Pixel data + valid */ +#define ADV7393_HD_MODE_REG3_DEFAULT (0x00) /* Color delay 0 clks */ +#define ADV7393_HD_MODE_REG4_DEFAULT (0xEC) /* Changed */ +#define ADV7393_HD_MODE_REG5_DEFAULT (0x08) +#define ADV7393_HD_MODE_REG6_DEFAULT (0x00) +#define ADV7393_HD_MODE_REG7_DEFAULT (0x00) +#define ADV7393_SOFT_RESET_DEFAULT (0x02) +#define ADV7393_COMPOSITE_POWER_VALUE (0x10) +#define ADV7393_COMPONENT_POWER_VALUE (0x1C) +#define ADV7393_SVIDEO_POWER_VALUE (0x0C) +#define ADV7393_SD_HUE_ADJUST_DEFAULT (0x80) +#define ADV7393_SD_BRIGHTNESS_WSS_DEFAULT (0x00) + +#define ADV7393_SD_CGMS_WSS0_DEFAULT (0x10) + +#define ADV7393_SD_MODE_REG1_DEFAULT (0x10) +#define ADV7393_SD_MODE_REG2_DEFAULT (0xC9) +#define ADV7393_SD_MODE_REG3_DEFAULT (0x00) +#define ADV7393_SD_MODE_REG4_DEFAULT (0x00) +#define ADV7393_SD_MODE_REG5_DEFAULT (0x02) +#define ADV7393_SD_MODE_REG6_DEFAULT (0x8C) +#define ADV7393_SD_MODE_REG7_DEFAULT (0x14) +#define ADV7393_SD_MODE_REG8_DEFAULT (0x00) + +#define ADV7393_SD_TIMING_REG0_DEFAULT (0x0C) + +/* Bit masks for Mode Select Register */ +#define INPUT_MODE_MASK (0x70) +#define SD_INPUT_MODE (0x00) +#define HD_720P_INPUT_MODE (0x10) +#define HD_1080I_INPUT_MODE (0x10) + +/* Bit masks for Mode Register 0 */ +#define TEST_PATTERN_BLACK_BAR_EN (0x04) +#define YUV_OUTPUT_SELECT (0x20) +#define RGB_OUTPUT_SELECT (0xDF) + +/* Bit masks for SD brightness/WSS */ +#define SD_BRIGHTNESS_VALUE_MASK (0x7F) +#define SD_BLANK_WSS_DATA_MASK (0x80) + +/* Bit masks for soft reset register */ +#define SOFT_RESET (0x02) + +/* Bit masks for HD Mode Register 1 */ +#define OUTPUT_STD_MASK (0x03) +#define OUTPUT_STD_SHIFT (0) +#define OUTPUT_STD_EIA0_2 (0x00) +#define OUTPUT_STD_EIA0_1 (0x01) +#define OUTPUT_STD_FULL (0x02) +#define EMBEDDED_SYNC (0x04) +#define EXTERNAL_SYNC (0xFB) +#define STD_MODE_MASK (0x1F) +#define STD_MODE_SHIFT (3) +#define STD_MODE_720P (0x05) +#define STD_MODE_720P_25 (0x08) +#define STD_MODE_720P_30 (0x07) +#define STD_MODE_720P_50 (0x06) +#define STD_MODE_1080I (0x0D) +#define STD_MODE_1080I_25 (0x0E) +#define STD_MODE_1080P_24 (0x11) +#define STD_MODE_1080P_25 (0x10) +#define STD_MODE_1080P_30 (0x0F) +#define STD_MODE_525P (0x00) +#define STD_MODE_625P (0x03) + +/* Bit masks for SD Mode Register 1 */ +#define SD_STD_MASK (0x03) +#define SD_STD_NTSC (0x00) +#define SD_STD_PAL_BDGHI (0x01) +#define SD_STD_PAL_M (0x02) +#define SD_STD_PAL_N (0x03) +#define SD_LUMA_FLTR_MASK (0x07) +#define SD_LUMA_FLTR_SHIFT (2) +#define SD_CHROMA_FLTR_MASK (0x07) +#define SD_CHROMA_FLTR_SHIFT (5) + +/* Bit masks for SD Mode Register 2 */ +#define SD_PRPB_SSAF_EN (0x01) +#define SD_PRPB_SSAF_DI (0xFE) +#define SD_DAC_OUT1_EN (0x02) +#define SD_DAC_OUT1_DI (0xFD) +#define SD_PEDESTAL_EN (0x08) +#define SD_PEDESTAL_DI (0xF7) +#define SD_SQUARE_PIXEL_EN (0x10) +#define SD_SQUARE_PIXEL_DI (0xEF) +#define SD_PIXEL_DATA_VALID (0x40) +#define SD_ACTIVE_EDGE_EN (0x80) +#define SD_ACTIVE_EDGE_DI (0x7F) + +/* Bit masks for HD Mode Register 6 */ +#define HD_PRPB_SYNC_EN (0x04) +#define HD_PRPB_SYNC_DI (0xFB) +#define HD_DAC_SWAP_EN (0x08) +#define HD_DAC_SWAP_DI (0xF7) +#define HD_GAMMA_CURVE_A (0xEF) +#define HD_GAMMA_CURVE_B (0x10) +#define HD_GAMMA_EN (0x20) +#define HD_GAMMA_DI (0xDF) +#define HD_ADPT_FLTR_MODEA (0xBF) +#define HD_ADPT_FLTR_MODEB (0x40) +#define HD_ADPT_FLTR_EN (0x80) +#define HD_ADPT_FLTR_DI (0x7F) + +#define ADV7393_BRIGHTNESS_MAX (63) +#define ADV7393_BRIGHTNESS_MIN (-64) +#define ADV7393_BRIGHTNESS_DEF (0) +#define ADV7393_HUE_MAX (127) +#define ADV7393_HUE_MIN (-128) +#define ADV7393_HUE_DEF (0) +#define ADV7393_GAIN_MAX (64) +#define ADV7393_GAIN_MIN (-64) +#define ADV7393_GAIN_DEF (0) + +#endif diff --git a/drivers/media/i2c/adv748x/Makefile b/drivers/media/i2c/adv748x/Makefile new file mode 100644 index 000000000..93844f14c --- /dev/null +++ b/drivers/media/i2c/adv748x/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +adv748x-objs := \ + adv748x-afe.o \ + adv748x-core.o \ + adv748x-csi2.o \ + adv748x-hdmi.o + +obj-$(CONFIG_VIDEO_ADV748X) += adv748x.o diff --git a/drivers/media/i2c/adv748x/adv748x-afe.c b/drivers/media/i2c/adv748x/adv748x-afe.c new file mode 100644 index 000000000..dbbb1e4d6 --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x-afe.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices ADV748X 8 channel analog front end (AFE) receiver + * with standard definition processor (SDP) + * + * Copyright (C) 2017 Renesas Electronics Corp. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/v4l2-dv-timings.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-ioctl.h> + +#include "adv748x.h" + +/* ----------------------------------------------------------------------------- + * SDP + */ + +#define ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM 0x0 +#define ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM_PED 0x1 +#define ADV748X_AFE_STD_AD_PAL_N_NTSC_J_SECAM 0x2 +#define ADV748X_AFE_STD_AD_PAL_N_NTSC_M_SECAM 0x3 +#define ADV748X_AFE_STD_NTSC_J 0x4 +#define ADV748X_AFE_STD_NTSC_M 0x5 +#define ADV748X_AFE_STD_PAL60 0x6 +#define ADV748X_AFE_STD_NTSC_443 0x7 +#define ADV748X_AFE_STD_PAL_BG 0x8 +#define ADV748X_AFE_STD_PAL_N 0x9 +#define ADV748X_AFE_STD_PAL_M 0xa +#define ADV748X_AFE_STD_PAL_M_PED 0xb +#define ADV748X_AFE_STD_PAL_COMB_N 0xc +#define ADV748X_AFE_STD_PAL_COMB_N_PED 0xd +#define ADV748X_AFE_STD_PAL_SECAM 0xe +#define ADV748X_AFE_STD_PAL_SECAM_PED 0xf + +static int adv748x_afe_read_ro_map(struct adv748x_state *state, u8 reg) +{ + int ret; + + /* Select SDP Read-Only Main Map */ + ret = sdp_write(state, ADV748X_SDP_MAP_SEL, + ADV748X_SDP_MAP_SEL_RO_MAIN); + if (ret < 0) + return ret; + + return sdp_read(state, reg); +} + +static int adv748x_afe_status(struct adv748x_afe *afe, u32 *signal, + v4l2_std_id *std) +{ + struct adv748x_state *state = adv748x_afe_to_state(afe); + int info; + + /* Read status from reg 0x10 of SDP RO Map */ + info = adv748x_afe_read_ro_map(state, ADV748X_SDP_RO_10); + if (info < 0) + return info; + + if (signal) + *signal = info & ADV748X_SDP_RO_10_IN_LOCK ? + 0 : V4L2_IN_ST_NO_SIGNAL; + + if (!std) + return 0; + + /* Standard not valid if there is no signal */ + if (!(info & ADV748X_SDP_RO_10_IN_LOCK)) { + *std = V4L2_STD_UNKNOWN; + return 0; + } + + switch (info & 0x70) { + case 0x00: + *std = V4L2_STD_NTSC; + break; + case 0x10: + *std = V4L2_STD_NTSC_443; + break; + case 0x20: + *std = V4L2_STD_PAL_M; + break; + case 0x30: + *std = V4L2_STD_PAL_60; + break; + case 0x40: + *std = V4L2_STD_PAL; + break; + case 0x50: + *std = V4L2_STD_SECAM; + break; + case 0x60: + *std = V4L2_STD_PAL_Nc | V4L2_STD_PAL_N; + break; + case 0x70: + *std = V4L2_STD_SECAM; + break; + default: + *std = V4L2_STD_UNKNOWN; + break; + } + + return 0; +} + +static void adv748x_afe_fill_format(struct adv748x_afe *afe, + struct v4l2_mbus_framefmt *fmt) +{ + memset(fmt, 0, sizeof(*fmt)); + + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt->field = V4L2_FIELD_ALTERNATE; + + fmt->width = 720; + fmt->height = afe->curr_norm & V4L2_STD_525_60 ? 480 : 576; + + /* Field height */ + fmt->height /= 2; +} + +static int adv748x_afe_std(v4l2_std_id std) +{ + if (std == V4L2_STD_PAL_60) + return ADV748X_AFE_STD_PAL60; + if (std == V4L2_STD_NTSC_443) + return ADV748X_AFE_STD_NTSC_443; + if (std == V4L2_STD_PAL_N) + return ADV748X_AFE_STD_PAL_N; + if (std == V4L2_STD_PAL_M) + return ADV748X_AFE_STD_PAL_M; + if (std == V4L2_STD_PAL_Nc) + return ADV748X_AFE_STD_PAL_COMB_N; + if (std & V4L2_STD_NTSC) + return ADV748X_AFE_STD_NTSC_M; + if (std & V4L2_STD_PAL) + return ADV748X_AFE_STD_PAL_BG; + if (std & V4L2_STD_SECAM) + return ADV748X_AFE_STD_PAL_SECAM; + + return -EINVAL; +} + +static void adv748x_afe_set_video_standard(struct adv748x_state *state, + int sdpstd) +{ + sdp_clrset(state, ADV748X_SDP_VID_SEL, ADV748X_SDP_VID_SEL_MASK, + (sdpstd & 0xf) << ADV748X_SDP_VID_SEL_SHIFT); +} + +static int adv748x_afe_s_input(struct adv748x_afe *afe, unsigned int input) +{ + struct adv748x_state *state = adv748x_afe_to_state(afe); + + return sdp_write(state, ADV748X_SDP_INSEL, input); +} + +static int adv748x_afe_g_pixelaspect(struct v4l2_subdev *sd, + struct v4l2_fract *aspect) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + + if (afe->curr_norm & V4L2_STD_525_60) { + aspect->numerator = 11; + aspect->denominator = 10; + } else { + aspect->numerator = 54; + aspect->denominator = 59; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_video_ops + */ + +static int adv748x_afe_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + + *norm = afe->curr_norm; + + return 0; +} + +static int adv748x_afe_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + struct adv748x_state *state = adv748x_afe_to_state(afe); + int afe_std = adv748x_afe_std(std); + + if (afe_std < 0) + return afe_std; + + mutex_lock(&state->mutex); + + adv748x_afe_set_video_standard(state, afe_std); + afe->curr_norm = std; + + mutex_unlock(&state->mutex); + + return 0; +} + +static int adv748x_afe_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + struct adv748x_state *state = adv748x_afe_to_state(afe); + int afe_std; + int ret; + + mutex_lock(&state->mutex); + + if (afe->streaming) { + ret = -EBUSY; + goto unlock; + } + + /* Set auto detect mode */ + adv748x_afe_set_video_standard(state, + ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM); + + msleep(100); + + /* Read detected standard */ + ret = adv748x_afe_status(afe, NULL, std); + + afe_std = adv748x_afe_std(afe->curr_norm); + if (afe_std < 0) + goto unlock; + + /* Restore original state */ + adv748x_afe_set_video_standard(state, afe_std); + +unlock: + mutex_unlock(&state->mutex); + + return ret; +} + +static int adv748x_afe_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + *norm = V4L2_STD_ALL; + + return 0; +} + +static int adv748x_afe_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + struct adv748x_state *state = adv748x_afe_to_state(afe); + int ret; + + mutex_lock(&state->mutex); + + ret = adv748x_afe_status(afe, status, NULL); + + mutex_unlock(&state->mutex); + + return ret; +} + +static int adv748x_afe_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + struct adv748x_state *state = adv748x_afe_to_state(afe); + u32 signal = V4L2_IN_ST_NO_SIGNAL; + int ret; + + mutex_lock(&state->mutex); + + if (enable) { + ret = adv748x_afe_s_input(afe, afe->input); + if (ret) + goto unlock; + } + + ret = adv748x_tx_power(afe->tx, enable); + if (ret) + goto unlock; + + afe->streaming = enable; + + adv748x_afe_status(afe, &signal, NULL); + if (signal != V4L2_IN_ST_NO_SIGNAL) + adv_dbg(state, "Detected SDP signal\n"); + else + adv_dbg(state, "Couldn't detect SDP video signal\n"); + +unlock: + mutex_unlock(&state->mutex); + + return ret; +} + +static const struct v4l2_subdev_video_ops adv748x_afe_video_ops = { + .g_std = adv748x_afe_g_std, + .s_std = adv748x_afe_s_std, + .querystd = adv748x_afe_querystd, + .g_tvnorms = adv748x_afe_g_tvnorms, + .g_input_status = adv748x_afe_g_input_status, + .s_stream = adv748x_afe_s_stream, + .g_pixelaspect = adv748x_afe_g_pixelaspect, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_pad_ops + */ + +static int adv748x_afe_propagate_pixelrate(struct adv748x_afe *afe) +{ + struct v4l2_subdev *tx; + + tx = adv748x_get_remote_sd(&afe->pads[ADV748X_AFE_SOURCE]); + if (!tx) + return -ENOLINK; + + /* + * The ADV748x ADC sampling frequency is twice the externally supplied + * clock whose frequency is required to be 28.63636 MHz. It oversamples + * with a factor of 4 resulting in a pixel rate of 14.3180180 MHz. + */ + return adv748x_csi2_set_pixelrate(tx, 14318180); +} + +static int adv748x_afe_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_2X8; + + return 0; +} + +static int adv748x_afe_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + struct v4l2_mbus_framefmt *mbusformat; + + /* It makes no sense to get the format of the analog sink pads */ + if (sdformat->pad != ADV748X_AFE_SOURCE) + return -EINVAL; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) { + mbusformat = v4l2_subdev_get_try_format(sd, cfg, sdformat->pad); + sdformat->format = *mbusformat; + } else { + adv748x_afe_fill_format(afe, &sdformat->format); + adv748x_afe_propagate_pixelrate(afe); + } + + return 0; +} + +static int adv748x_afe_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct v4l2_mbus_framefmt *mbusformat; + + /* It makes no sense to get the format of the analog sink pads */ + if (sdformat->pad != ADV748X_AFE_SOURCE) + return -EINVAL; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return adv748x_afe_get_format(sd, cfg, sdformat); + + mbusformat = v4l2_subdev_get_try_format(sd, cfg, sdformat->pad); + *mbusformat = sdformat->format; + + return 0; +} + +static const struct v4l2_subdev_pad_ops adv748x_afe_pad_ops = { + .enum_mbus_code = adv748x_afe_enum_mbus_code, + .set_fmt = adv748x_afe_set_format, + .get_fmt = adv748x_afe_get_format, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_ops + */ + +static const struct v4l2_subdev_ops adv748x_afe_ops = { + .video = &adv748x_afe_video_ops, + .pad = &adv748x_afe_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Controls + */ + +static const char * const afe_ctrl_frp_menu[] = { + "Disabled", + "Solid Blue", + "Color Bars", + "Grey Ramp", + "Cb Ramp", + "Cr Ramp", + "Boundary" +}; + +static int adv748x_afe_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct adv748x_afe *afe = adv748x_ctrl_to_afe(ctrl); + struct adv748x_state *state = adv748x_afe_to_state(afe); + bool enable; + int ret; + + ret = sdp_write(state, 0x0e, 0x00); + if (ret < 0) + return ret; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ret = sdp_write(state, ADV748X_SDP_BRI, ctrl->val); + break; + case V4L2_CID_HUE: + /* Hue is inverted according to HSL chart */ + ret = sdp_write(state, ADV748X_SDP_HUE, -ctrl->val); + break; + case V4L2_CID_CONTRAST: + ret = sdp_write(state, ADV748X_SDP_CON, ctrl->val); + break; + case V4L2_CID_SATURATION: + ret = sdp_write(state, ADV748X_SDP_SD_SAT_U, ctrl->val); + if (ret) + break; + ret = sdp_write(state, ADV748X_SDP_SD_SAT_V, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + enable = !!ctrl->val; + + /* Enable/Disable Color bar test patterns */ + ret = sdp_clrset(state, ADV748X_SDP_DEF, ADV748X_SDP_DEF_VAL_EN, + enable); + if (ret) + break; + ret = sdp_clrset(state, ADV748X_SDP_FRP, ADV748X_SDP_FRP_MASK, + enable ? ctrl->val - 1 : 0); + break; + default: + return -EINVAL; + } + + return ret; +} + +static const struct v4l2_ctrl_ops adv748x_afe_ctrl_ops = { + .s_ctrl = adv748x_afe_s_ctrl, +}; + +static int adv748x_afe_init_controls(struct adv748x_afe *afe) +{ + struct adv748x_state *state = adv748x_afe_to_state(afe); + + v4l2_ctrl_handler_init(&afe->ctrl_hdl, 5); + + /* Use our mutex for the controls */ + afe->ctrl_hdl.lock = &state->mutex; + + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV748X_SDP_BRI_MIN, + ADV748X_SDP_BRI_MAX, 1, ADV748X_SDP_BRI_DEF); + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, + V4L2_CID_CONTRAST, ADV748X_SDP_CON_MIN, + ADV748X_SDP_CON_MAX, 1, ADV748X_SDP_CON_DEF); + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, + V4L2_CID_SATURATION, ADV748X_SDP_SAT_MIN, + ADV748X_SDP_SAT_MAX, 1, ADV748X_SDP_SAT_DEF); + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, + V4L2_CID_HUE, ADV748X_SDP_HUE_MIN, + ADV748X_SDP_HUE_MAX, 1, ADV748X_SDP_HUE_DEF); + + v4l2_ctrl_new_std_menu_items(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(afe_ctrl_frp_menu) - 1, + 0, 0, afe_ctrl_frp_menu); + + afe->sd.ctrl_handler = &afe->ctrl_hdl; + if (afe->ctrl_hdl.error) { + v4l2_ctrl_handler_free(&afe->ctrl_hdl); + return afe->ctrl_hdl.error; + } + + return v4l2_ctrl_handler_setup(&afe->ctrl_hdl); +} + +int adv748x_afe_init(struct adv748x_afe *afe) +{ + struct adv748x_state *state = adv748x_afe_to_state(afe); + int ret; + unsigned int i; + + afe->input = 0; + afe->streaming = false; + afe->curr_norm = V4L2_STD_NTSC_M; + + adv748x_subdev_init(&afe->sd, state, &adv748x_afe_ops, + MEDIA_ENT_F_ATV_DECODER, "afe"); + + /* Identify the first connector found as a default input if set */ + for (i = ADV748X_PORT_AIN0; i <= ADV748X_PORT_AIN7; i++) { + /* Inputs and ports are 1-indexed to match the data sheet */ + if (state->endpoints[i]) { + afe->input = i; + break; + } + } + + adv748x_afe_s_input(afe, afe->input); + + adv_dbg(state, "AFE Default input set to %d\n", afe->input); + + /* Entity pads and sinks are 0-indexed to match the pads */ + for (i = ADV748X_AFE_SINK_AIN0; i <= ADV748X_AFE_SINK_AIN7; i++) + afe->pads[i].flags = MEDIA_PAD_FL_SINK; + + afe->pads[ADV748X_AFE_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&afe->sd.entity, ADV748X_AFE_NR_PADS, + afe->pads); + if (ret) + return ret; + + ret = adv748x_afe_init_controls(afe); + if (ret) + goto error; + + return 0; + +error: + media_entity_cleanup(&afe->sd.entity); + + return ret; +} + +void adv748x_afe_cleanup(struct adv748x_afe *afe) +{ + v4l2_device_unregister_subdev(&afe->sd); + media_entity_cleanup(&afe->sd.entity); + v4l2_ctrl_handler_free(&afe->ctrl_hdl); +} diff --git a/drivers/media/i2c/adv748x/adv748x-core.c b/drivers/media/i2c/adv748x/adv748x-core.c new file mode 100644 index 000000000..1fe7f97c6 --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x-core.c @@ -0,0 +1,836 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices ADV748X HDMI receiver with AFE + * + * Copyright (C) 2017 Renesas Electronics Corp. + * + * Authors: + * Koji Matsuoka <koji.matsuoka.xm@renesas.com> + * Niklas Söderlund <niklas.soderlund@ragnatech.se> + * Kieran Bingham <kieran.bingham@ideasonboard.com> + */ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_graph.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/v4l2-dv-timings.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-ioctl.h> + +#include "adv748x.h" + +/* ----------------------------------------------------------------------------- + * Register manipulation + */ + +#define ADV748X_REGMAP_CONF(n) \ +{ \ + .name = n, \ + .reg_bits = 8, \ + .val_bits = 8, \ + .max_register = 0xff, \ + .cache_type = REGCACHE_NONE, \ +} + +static const struct regmap_config adv748x_regmap_cnf[] = { + ADV748X_REGMAP_CONF("io"), + ADV748X_REGMAP_CONF("dpll"), + ADV748X_REGMAP_CONF("cp"), + ADV748X_REGMAP_CONF("hdmi"), + ADV748X_REGMAP_CONF("edid"), + ADV748X_REGMAP_CONF("repeater"), + ADV748X_REGMAP_CONF("infoframe"), + ADV748X_REGMAP_CONF("cbus"), + ADV748X_REGMAP_CONF("cec"), + ADV748X_REGMAP_CONF("sdp"), + ADV748X_REGMAP_CONF("txa"), + ADV748X_REGMAP_CONF("txb"), +}; + +static int adv748x_configure_regmap(struct adv748x_state *state, int region) +{ + int err; + + if (!state->i2c_clients[region]) + return -ENODEV; + + state->regmap[region] = + devm_regmap_init_i2c(state->i2c_clients[region], + &adv748x_regmap_cnf[region]); + + if (IS_ERR(state->regmap[region])) { + err = PTR_ERR(state->regmap[region]); + adv_err(state, + "Error initializing regmap %d with error %d\n", + region, err); + return -EINVAL; + } + + return 0; +} +struct adv748x_register_map { + const char *name; + u8 default_addr; +}; + +static const struct adv748x_register_map adv748x_default_addresses[] = { + [ADV748X_PAGE_IO] = { "main", 0x70 }, + [ADV748X_PAGE_DPLL] = { "dpll", 0x26 }, + [ADV748X_PAGE_CP] = { "cp", 0x22 }, + [ADV748X_PAGE_HDMI] = { "hdmi", 0x34 }, + [ADV748X_PAGE_EDID] = { "edid", 0x36 }, + [ADV748X_PAGE_REPEATER] = { "repeater", 0x32 }, + [ADV748X_PAGE_INFOFRAME] = { "infoframe", 0x31 }, + [ADV748X_PAGE_CBUS] = { "cbus", 0x30 }, + [ADV748X_PAGE_CEC] = { "cec", 0x41 }, + [ADV748X_PAGE_SDP] = { "sdp", 0x79 }, + [ADV748X_PAGE_TXB] = { "txb", 0x48 }, + [ADV748X_PAGE_TXA] = { "txa", 0x4a }, +}; + +static int adv748x_read_check(struct adv748x_state *state, + int client_page, u8 reg) +{ + struct i2c_client *client = state->i2c_clients[client_page]; + int err; + unsigned int val; + + err = regmap_read(state->regmap[client_page], reg, &val); + + if (err) { + adv_err(state, "error reading %02x, %02x\n", + client->addr, reg); + return err; + } + + return val; +} + +int adv748x_read(struct adv748x_state *state, u8 page, u8 reg) +{ + return adv748x_read_check(state, page, reg); +} + +int adv748x_write(struct adv748x_state *state, u8 page, u8 reg, u8 value) +{ + return regmap_write(state->regmap[page], reg, value); +} + +static int adv748x_write_check(struct adv748x_state *state, u8 page, u8 reg, + u8 value, int *error) +{ + if (*error) + return *error; + + *error = adv748x_write(state, page, reg, value); + return *error; +} + +/* adv748x_write_block(): Write raw data with a maximum of I2C_SMBUS_BLOCK_MAX + * size to one or more registers. + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int adv748x_write_block(struct adv748x_state *state, int client_page, + unsigned int init_reg, const void *val, + size_t val_len) +{ + struct regmap *regmap = state->regmap[client_page]; + + if (val_len > I2C_SMBUS_BLOCK_MAX) + val_len = I2C_SMBUS_BLOCK_MAX; + + return regmap_raw_write(regmap, init_reg, val, val_len); +} + +static int adv748x_set_slave_addresses(struct adv748x_state *state) +{ + struct i2c_client *client; + unsigned int i; + u8 io_reg; + + for (i = ADV748X_PAGE_DPLL; i < ADV748X_PAGE_MAX; ++i) { + io_reg = ADV748X_IO_SLAVE_ADDR_BASE + i; + client = state->i2c_clients[i]; + + io_write(state, io_reg, client->addr << 1); + } + + return 0; +} + +static void adv748x_unregister_clients(struct adv748x_state *state) +{ + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(state->i2c_clients); ++i) + i2c_unregister_device(state->i2c_clients[i]); +} + +static int adv748x_initialise_clients(struct adv748x_state *state) +{ + unsigned int i; + int ret; + + for (i = ADV748X_PAGE_DPLL; i < ADV748X_PAGE_MAX; ++i) { + state->i2c_clients[i] = i2c_new_ancillary_device( + state->client, + adv748x_default_addresses[i].name, + adv748x_default_addresses[i].default_addr); + + if (IS_ERR(state->i2c_clients[i])) { + adv_err(state, "failed to create i2c client %u\n", i); + return PTR_ERR(state->i2c_clients[i]); + } + + ret = adv748x_configure_regmap(state, i); + if (ret) + return ret; + } + + return adv748x_set_slave_addresses(state); +} + +/** + * struct adv748x_reg_value - Register write instruction + * @page: Regmap page identifier + * @reg: I2C register + * @value: value to write to @page at @reg + */ +struct adv748x_reg_value { + u8 page; + u8 reg; + u8 value; +}; + +static int adv748x_write_regs(struct adv748x_state *state, + const struct adv748x_reg_value *regs) +{ + int ret; + + for (; regs->page != ADV748X_PAGE_EOR; regs++) { + ret = adv748x_write(state, regs->page, regs->reg, regs->value); + if (ret < 0) { + adv_err(state, "Error regs page: 0x%02x reg: 0x%02x\n", + regs->page, regs->reg); + return ret; + } + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * TXA and TXB + */ + +static int adv748x_power_up_tx(struct adv748x_csi2 *tx) +{ + struct adv748x_state *state = tx->state; + u8 page = is_txa(tx) ? ADV748X_PAGE_TXA : ADV748X_PAGE_TXB; + int ret = 0; + + /* Enable n-lane MIPI */ + adv748x_write_check(state, page, 0x00, 0x80 | tx->active_lanes, &ret); + + /* Set Auto DPHY Timing */ + adv748x_write_check(state, page, 0x00, 0xa0 | tx->active_lanes, &ret); + + /* ADI Required Write */ + if (tx->src == &state->hdmi.sd) { + adv748x_write_check(state, page, 0xdb, 0x10, &ret); + adv748x_write_check(state, page, 0xd6, 0x07, &ret); + } else { + adv748x_write_check(state, page, 0xd2, 0x40, &ret); + } + + adv748x_write_check(state, page, 0xc4, 0x0a, &ret); + adv748x_write_check(state, page, 0x71, 0x33, &ret); + adv748x_write_check(state, page, 0x72, 0x11, &ret); + + /* i2c_dphy_pwdn - 1'b0 */ + adv748x_write_check(state, page, 0xf0, 0x00, &ret); + + /* ADI Required Writes*/ + adv748x_write_check(state, page, 0x31, 0x82, &ret); + adv748x_write_check(state, page, 0x1e, 0x40, &ret); + + /* i2c_mipi_pll_en - 1'b1 */ + adv748x_write_check(state, page, 0xda, 0x01, &ret); + usleep_range(2000, 2500); + + /* Power-up CSI-TX */ + adv748x_write_check(state, page, 0x00, 0x20 | tx->active_lanes, &ret); + usleep_range(1000, 1500); + + /* ADI Required Writes */ + adv748x_write_check(state, page, 0xc1, 0x2b, &ret); + usleep_range(1000, 1500); + adv748x_write_check(state, page, 0x31, 0x80, &ret); + + return ret; +} + +static int adv748x_power_down_tx(struct adv748x_csi2 *tx) +{ + struct adv748x_state *state = tx->state; + u8 page = is_txa(tx) ? ADV748X_PAGE_TXA : ADV748X_PAGE_TXB; + int ret = 0; + + /* ADI Required Writes */ + adv748x_write_check(state, page, 0x31, 0x82, &ret); + adv748x_write_check(state, page, 0x1e, 0x00, &ret); + + /* Enable n-lane MIPI */ + adv748x_write_check(state, page, 0x00, 0x80 | tx->active_lanes, &ret); + + /* i2c_mipi_pll_en - 1'b1 */ + adv748x_write_check(state, page, 0xda, 0x01, &ret); + + /* ADI Required Write */ + adv748x_write_check(state, page, 0xc1, 0x3b, &ret); + + return ret; +} + +int adv748x_tx_power(struct adv748x_csi2 *tx, bool on) +{ + int val; + + if (!is_tx_enabled(tx)) + return 0; + + val = tx_read(tx, ADV748X_CSI_FS_AS_LS); + if (val < 0) + return val; + + /* + * This test against BIT(6) is not documented by the datasheet, but was + * specified in the downstream driver. + * Track with a WARN_ONCE to determine if it is ever set by HW. + */ + WARN_ONCE((on && val & ADV748X_CSI_FS_AS_LS_UNKNOWN), + "Enabling with unknown bit set"); + + return on ? adv748x_power_up_tx(tx) : adv748x_power_down_tx(tx); +} + +/* ----------------------------------------------------------------------------- + * Media Operations + */ +static int adv748x_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *rsd = media_entity_to_v4l2_subdev(remote->entity); + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct adv748x_state *state = v4l2_get_subdevdata(sd); + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + bool enable = flags & MEDIA_LNK_FL_ENABLED; + u8 io10_mask = ADV748X_IO_10_CSI1_EN | + ADV748X_IO_10_CSI4_EN | + ADV748X_IO_10_CSI4_IN_SEL_AFE; + u8 io10 = 0; + + /* Refuse to enable multiple links to the same TX at the same time. */ + if (enable && tx->src) + return -EINVAL; + + /* Set or clear the source (HDMI or AFE) and the current TX. */ + if (rsd == &state->afe.sd) + state->afe.tx = enable ? tx : NULL; + else + state->hdmi.tx = enable ? tx : NULL; + + tx->src = enable ? rsd : NULL; + + if (state->afe.tx) { + /* AFE Requires TXA enabled, even when output to TXB */ + io10 |= ADV748X_IO_10_CSI4_EN; + if (is_txa(tx)) { + /* + * Output from the SD-core (480i and 576i) from the TXA + * interface requires reducing the number of enabled + * data lanes in order to guarantee a valid link + * frequency. + */ + tx->active_lanes = min(tx->num_lanes, 2U); + io10 |= ADV748X_IO_10_CSI4_IN_SEL_AFE; + } else { + /* TXB has a single data lane, no need to adjust. */ + io10 |= ADV748X_IO_10_CSI1_EN; + } + } + + if (state->hdmi.tx) { + /* + * Restore the number of active lanes, in case we have gone + * through an AFE->TXA streaming sessions. + */ + tx->active_lanes = tx->num_lanes; + io10 |= ADV748X_IO_10_CSI4_EN; + } + + return io_clrset(state, ADV748X_IO_10, io10_mask, io10); +} + +static const struct media_entity_operations adv748x_tx_media_ops = { + .link_setup = adv748x_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct media_entity_operations adv748x_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* ----------------------------------------------------------------------------- + * HW setup + */ + +/* Initialize CP Core with RGB888 format. */ +static const struct adv748x_reg_value adv748x_init_hdmi[] = { + /* Disable chip powerdown & Enable HDMI Rx block */ + {ADV748X_PAGE_IO, 0x00, 0x40}, + + {ADV748X_PAGE_REPEATER, 0x40, 0x83}, /* Enable HDCP 1.1 */ + + {ADV748X_PAGE_HDMI, 0x00, 0x08},/* Foreground Channel = A */ + {ADV748X_PAGE_HDMI, 0x98, 0xff},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x99, 0xa3},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x9a, 0x00},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x9b, 0x0a},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x9d, 0x40},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0xcb, 0x09},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x3d, 0x10},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x3e, 0x7b},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x3f, 0x5e},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x4e, 0xfe},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x4f, 0x18},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x57, 0xa3},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x58, 0x04},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x85, 0x10},/* ADI Required Write */ + + {ADV748X_PAGE_HDMI, 0x83, 0x00},/* Enable All Terminations */ + {ADV748X_PAGE_HDMI, 0xa3, 0x01},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0xbe, 0x00},/* ADI Required Write */ + + {ADV748X_PAGE_HDMI, 0x6c, 0x01},/* HPA Manual Enable */ + {ADV748X_PAGE_HDMI, 0xf8, 0x01},/* HPA Asserted */ + {ADV748X_PAGE_HDMI, 0x0f, 0x00},/* Audio Mute Speed Set to Fastest */ + /* (Smallest Step Size) */ + + {ADV748X_PAGE_IO, 0x04, 0x02}, /* RGB Out of CP */ + {ADV748X_PAGE_IO, 0x12, 0xf0}, /* CSC Depends on ip Packets, SDR 444 */ + {ADV748X_PAGE_IO, 0x17, 0x80}, /* Luma & Chroma can reach 254d */ + {ADV748X_PAGE_IO, 0x03, 0x86}, /* CP-Insert_AV_Code */ + + {ADV748X_PAGE_CP, 0x7c, 0x00}, /* ADI Required Write */ + + {ADV748X_PAGE_IO, 0x0c, 0xe0}, /* Enable LLC_DLL & Double LLC Timing */ + {ADV748X_PAGE_IO, 0x0e, 0xdd}, /* LLC/PIX/SPI PINS TRISTATED AUD */ + + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ +}; + +/* Initialize AFE core with YUV8 format. */ +static const struct adv748x_reg_value adv748x_init_afe[] = { + {ADV748X_PAGE_IO, 0x00, 0x30}, /* Disable chip powerdown Rx */ + {ADV748X_PAGE_IO, 0xf2, 0x01}, /* Enable I2C Read Auto-Increment */ + + {ADV748X_PAGE_IO, 0x0e, 0xff}, /* LLC/PIX/AUD/SPI PINS TRISTATED */ + + {ADV748X_PAGE_SDP, 0x0f, 0x00}, /* Exit Power Down Mode */ + {ADV748X_PAGE_SDP, 0x52, 0xcd}, /* ADI Required Write */ + + {ADV748X_PAGE_SDP, 0x0e, 0x80}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0x9c, 0x00}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0x9c, 0xff}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0x0e, 0x00}, /* ADI Required Write */ + + /* ADI recommended writes for improved video quality */ + {ADV748X_PAGE_SDP, 0x80, 0x51}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0x81, 0x51}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0x82, 0x68}, /* ADI Required Write */ + + {ADV748X_PAGE_SDP, 0x03, 0x42}, /* Tri-S Output , PwrDwn 656 pads */ + {ADV748X_PAGE_SDP, 0x04, 0xb5}, /* ITU-R BT.656-4 compatible */ + {ADV748X_PAGE_SDP, 0x13, 0x00}, /* ADI Required Write */ + + {ADV748X_PAGE_SDP, 0x17, 0x41}, /* Select SH1 */ + {ADV748X_PAGE_SDP, 0x31, 0x12}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0xe6, 0x4f}, /* V bit end pos manually in NTSC */ + + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ +}; + +static int adv748x_sw_reset(struct adv748x_state *state) +{ + int ret; + + ret = io_write(state, ADV748X_IO_REG_FF, ADV748X_IO_REG_FF_MAIN_RESET); + if (ret) + return ret; + + usleep_range(5000, 6000); + + /* Disable CEC Wakeup from power-down mode */ + ret = io_clrset(state, ADV748X_IO_REG_01, ADV748X_IO_REG_01_PWRDN_MASK, + ADV748X_IO_REG_01_PWRDNB); + if (ret) + return ret; + + /* Enable I2C Read Auto-Increment for consecutive reads */ + return io_write(state, ADV748X_IO_REG_F2, + ADV748X_IO_REG_F2_READ_AUTO_INC); +} + +static int adv748x_reset(struct adv748x_state *state) +{ + int ret; + u8 regval = 0; + + ret = adv748x_sw_reset(state); + if (ret < 0) + return ret; + + ret = adv748x_set_slave_addresses(state); + if (ret < 0) + return ret; + + /* Initialize CP and AFE cores. */ + ret = adv748x_write_regs(state, adv748x_init_hdmi); + if (ret) + return ret; + + ret = adv748x_write_regs(state, adv748x_init_afe); + if (ret) + return ret; + + /* Reset TXA and TXB */ + adv748x_tx_power(&state->txa, 1); + adv748x_tx_power(&state->txa, 0); + adv748x_tx_power(&state->txb, 1); + adv748x_tx_power(&state->txb, 0); + + /* Disable chip powerdown & Enable HDMI Rx block */ + io_write(state, ADV748X_IO_PD, ADV748X_IO_PD_RX_EN); + + /* Conditionally enable TXa and TXb. */ + if (is_tx_enabled(&state->txa)) + regval |= ADV748X_IO_10_CSI4_EN; + if (is_tx_enabled(&state->txb)) + regval |= ADV748X_IO_10_CSI1_EN; + io_write(state, ADV748X_IO_10, regval); + + /* Use vid_std and v_freq as freerun resolution for CP */ + cp_clrset(state, ADV748X_CP_CLMP_POS, ADV748X_CP_CLMP_POS_DIS_AUTO, + ADV748X_CP_CLMP_POS_DIS_AUTO); + + return 0; +} + +static int adv748x_identify_chip(struct adv748x_state *state) +{ + int msb, lsb; + + lsb = io_read(state, ADV748X_IO_CHIP_REV_ID_1); + msb = io_read(state, ADV748X_IO_CHIP_REV_ID_2); + + if (lsb < 0 || msb < 0) { + adv_err(state, "Failed to read chip revision\n"); + return -EIO; + } + + adv_info(state, "chip found @ 0x%02x revision %02x%02x\n", + state->client->addr << 1, lsb, msb); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * i2c driver + */ + +void adv748x_subdev_init(struct v4l2_subdev *sd, struct adv748x_state *state, + const struct v4l2_subdev_ops *ops, u32 function, + const char *ident) +{ + v4l2_subdev_init(sd, ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + /* the owner is the same as the i2c_client's driver owner */ + sd->owner = state->dev->driver->owner; + sd->dev = state->dev; + + v4l2_set_subdevdata(sd, state); + + /* initialize name */ + snprintf(sd->name, sizeof(sd->name), "%s %d-%04x %s", + state->dev->driver->name, + i2c_adapter_id(state->client->adapter), + state->client->addr, ident); + + sd->entity.function = function; + sd->entity.ops = is_tx(adv748x_sd_to_csi2(sd)) ? + &adv748x_tx_media_ops : &adv748x_media_ops; +} + +static int adv748x_parse_csi2_lanes(struct adv748x_state *state, + unsigned int port, + struct device_node *ep) +{ + struct v4l2_fwnode_endpoint vep; + unsigned int num_lanes; + int ret; + + if (port != ADV748X_PORT_TXA && port != ADV748X_PORT_TXB) + return 0; + + vep.bus_type = V4L2_MBUS_CSI2_DPHY; + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &vep); + if (ret) + return ret; + + num_lanes = vep.bus.mipi_csi2.num_data_lanes; + + if (vep.base.port == ADV748X_PORT_TXA) { + if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4) { + adv_err(state, "TXA: Invalid number (%u) of lanes\n", + num_lanes); + return -EINVAL; + } + + state->txa.num_lanes = num_lanes; + state->txa.active_lanes = num_lanes; + adv_dbg(state, "TXA: using %u lanes\n", state->txa.num_lanes); + } + + if (vep.base.port == ADV748X_PORT_TXB) { + if (num_lanes != 1) { + adv_err(state, "TXB: Invalid number (%u) of lanes\n", + num_lanes); + return -EINVAL; + } + + state->txb.num_lanes = num_lanes; + state->txb.active_lanes = num_lanes; + adv_dbg(state, "TXB: using %u lanes\n", state->txb.num_lanes); + } + + return 0; +} + +static int adv748x_parse_dt(struct adv748x_state *state) +{ + struct device_node *ep_np = NULL; + struct of_endpoint ep; + bool out_found = false; + bool in_found = false; + int ret; + + for_each_endpoint_of_node(state->dev->of_node, ep_np) { + of_graph_parse_endpoint(ep_np, &ep); + adv_info(state, "Endpoint %pOF on port %d", ep.local_node, + ep.port); + + if (ep.port >= ADV748X_PORT_MAX) { + adv_err(state, "Invalid endpoint %pOF on port %d", + ep.local_node, ep.port); + + continue; + } + + if (state->endpoints[ep.port]) { + adv_err(state, + "Multiple port endpoints are not supported"); + continue; + } + + of_node_get(ep_np); + state->endpoints[ep.port] = ep_np; + + /* + * At least one input endpoint and one output endpoint shall + * be defined. + */ + if (ep.port < ADV748X_PORT_TXA) + in_found = true; + else + out_found = true; + + /* Store number of CSI-2 lanes used for TXA and TXB. */ + ret = adv748x_parse_csi2_lanes(state, ep.port, ep_np); + if (ret) + return ret; + } + + return in_found && out_found ? 0 : -ENODEV; +} + +static void adv748x_dt_cleanup(struct adv748x_state *state) +{ + unsigned int i; + + for (i = 0; i < ADV748X_PORT_MAX; i++) + of_node_put(state->endpoints[i]); +} + +static int adv748x_probe(struct i2c_client *client) +{ + struct adv748x_state *state; + int ret; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mutex_init(&state->mutex); + + state->dev = &client->dev; + state->client = client; + state->i2c_clients[ADV748X_PAGE_IO] = client; + i2c_set_clientdata(client, state); + + /* + * We can not use container_of to get back to the state with two TXs; + * Initialize the TXs's fields unconditionally on the endpoint + * presence to access them later. + */ + state->txa.state = state->txb.state = state; + state->txa.page = ADV748X_PAGE_TXA; + state->txb.page = ADV748X_PAGE_TXB; + state->txa.port = ADV748X_PORT_TXA; + state->txb.port = ADV748X_PORT_TXB; + + /* Discover and process ports declared by the Device tree endpoints */ + ret = adv748x_parse_dt(state); + if (ret) { + adv_err(state, "Failed to parse device tree"); + goto err_free_mutex; + } + + /* Configure IO Regmap region */ + ret = adv748x_configure_regmap(state, ADV748X_PAGE_IO); + if (ret) { + adv_err(state, "Error configuring IO regmap region"); + goto err_cleanup_dt; + } + + ret = adv748x_identify_chip(state); + if (ret) { + adv_err(state, "Failed to identify chip"); + goto err_cleanup_dt; + } + + /* Configure remaining pages as I2C clients with regmap access */ + ret = adv748x_initialise_clients(state); + if (ret) { + adv_err(state, "Failed to setup client regmap pages"); + goto err_cleanup_clients; + } + + /* SW reset ADV748X to its default values */ + ret = adv748x_reset(state); + if (ret) { + adv_err(state, "Failed to reset hardware"); + goto err_cleanup_clients; + } + + /* Initialise HDMI */ + ret = adv748x_hdmi_init(&state->hdmi); + if (ret) { + adv_err(state, "Failed to probe HDMI"); + goto err_cleanup_clients; + } + + /* Initialise AFE */ + ret = adv748x_afe_init(&state->afe); + if (ret) { + adv_err(state, "Failed to probe AFE"); + goto err_cleanup_hdmi; + } + + /* Initialise TXA */ + ret = adv748x_csi2_init(state, &state->txa); + if (ret) { + adv_err(state, "Failed to probe TXA"); + goto err_cleanup_afe; + } + + /* Initialise TXB */ + ret = adv748x_csi2_init(state, &state->txb); + if (ret) { + adv_err(state, "Failed to probe TXB"); + goto err_cleanup_txa; + } + + return 0; + +err_cleanup_txa: + adv748x_csi2_cleanup(&state->txa); +err_cleanup_afe: + adv748x_afe_cleanup(&state->afe); +err_cleanup_hdmi: + adv748x_hdmi_cleanup(&state->hdmi); +err_cleanup_clients: + adv748x_unregister_clients(state); +err_cleanup_dt: + adv748x_dt_cleanup(state); +err_free_mutex: + mutex_destroy(&state->mutex); + + return ret; +} + +static int adv748x_remove(struct i2c_client *client) +{ + struct adv748x_state *state = i2c_get_clientdata(client); + + adv748x_afe_cleanup(&state->afe); + adv748x_hdmi_cleanup(&state->hdmi); + + adv748x_csi2_cleanup(&state->txa); + adv748x_csi2_cleanup(&state->txb); + + adv748x_unregister_clients(state); + adv748x_dt_cleanup(state); + mutex_destroy(&state->mutex); + + return 0; +} + +static const struct of_device_id adv748x_of_table[] = { + { .compatible = "adi,adv7481", }, + { .compatible = "adi,adv7482", }, + { } +}; +MODULE_DEVICE_TABLE(of, adv748x_of_table); + +static struct i2c_driver adv748x_driver = { + .driver = { + .name = "adv748x", + .of_match_table = adv748x_of_table, + }, + .probe_new = adv748x_probe, + .remove = adv748x_remove, +}; + +module_i2c_driver(adv748x_driver); + +MODULE_AUTHOR("Kieran Bingham <kieran.bingham@ideasonboard.com>"); +MODULE_DESCRIPTION("ADV748X video decoder"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/adv748x/adv748x-csi2.c b/drivers/media/i2c/adv748x/adv748x-csi2.c new file mode 100644 index 000000000..99bb63d05 --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x-csi2.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices ADV748X CSI-2 Transmitter + * + * Copyright (C) 2017 Renesas Electronics Corp. + */ + +#include <linux/module.h> +#include <linux/mutex.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> + +#include "adv748x.h" + +static int adv748x_csi2_set_virtual_channel(struct adv748x_csi2 *tx, + unsigned int vc) +{ + return tx_write(tx, ADV748X_CSI_VC_REF, vc << ADV748X_CSI_VC_REF_SHIFT); +} + +/** + * adv748x_csi2_register_link : Register and link internal entities + * + * @tx: CSI2 private entity + * @v4l2_dev: Video registration device + * @src: Source subdevice to establish link + * @src_pad: Pad number of source to link to this @tx + * @enable: Link enabled flag + * + * Ensure that the subdevice is registered against the v4l2_device, and link the + * source pad to the sink pad of the CSI2 bus entity. + */ +static int adv748x_csi2_register_link(struct adv748x_csi2 *tx, + struct v4l2_device *v4l2_dev, + struct v4l2_subdev *src, + unsigned int src_pad, + bool enable) +{ + int ret; + + if (!src->v4l2_dev) { + ret = v4l2_device_register_subdev(v4l2_dev, src); + if (ret) + return ret; + } + + ret = media_create_pad_link(&src->entity, src_pad, + &tx->sd.entity, ADV748X_CSI2_SINK, + enable ? MEDIA_LNK_FL_ENABLED : 0); + if (ret) + return ret; + + if (enable) + tx->src = src; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_internal_ops + * + * We use the internal registered operation to be able to ensure that our + * incremental subdevices (not connected in the forward path) can be registered + * against the resulting video path and media device. + */ + +static int adv748x_csi2_registered(struct v4l2_subdev *sd) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + struct adv748x_state *state = tx->state; + int ret; + + adv_dbg(state, "Registered %s (%s)", is_txa(tx) ? "TXA":"TXB", + sd->name); + + /* + * Link TXA to AFE and HDMI, and TXB to AFE only as TXB cannot output + * HDMI. + * + * The HDMI->TXA link is enabled by default, as is the AFE->TXB one. + */ + if (is_afe_enabled(state)) { + ret = adv748x_csi2_register_link(tx, sd->v4l2_dev, + &state->afe.sd, + ADV748X_AFE_SOURCE, + is_txb(tx)); + if (ret) + return ret; + + /* TXB can output AFE signals only. */ + if (is_txb(tx)) + state->afe.tx = tx; + } + + /* Register link to HDMI for TXA only. */ + if (is_txb(tx) || !is_hdmi_enabled(state)) + return 0; + + ret = adv748x_csi2_register_link(tx, sd->v4l2_dev, &state->hdmi.sd, + ADV748X_HDMI_SOURCE, true); + if (ret) + return ret; + + /* The default HDMI output is TXA. */ + state->hdmi.tx = tx; + + return 0; +} + +static const struct v4l2_subdev_internal_ops adv748x_csi2_internal_ops = { + .registered = adv748x_csi2_registered, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_video_ops + */ + +static int adv748x_csi2_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + struct v4l2_subdev *src; + + src = adv748x_get_remote_sd(&tx->pads[ADV748X_CSI2_SINK]); + if (!src) + return -EPIPE; + + return v4l2_subdev_call(src, video, s_stream, enable); +} + +static const struct v4l2_subdev_video_ops adv748x_csi2_video_ops = { + .s_stream = adv748x_csi2_s_stream, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_pad_ops + * + * The CSI2 bus pads are ignorant to the data sizes or formats. + * But we must support setting the pad formats for format propagation. + */ + +static struct v4l2_mbus_framefmt * +adv748x_csi2_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, u32 which) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(sd, cfg, pad); + + return &tx->format; +} + +static int adv748x_csi2_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + struct adv748x_state *state = tx->state; + struct v4l2_mbus_framefmt *mbusformat; + + mbusformat = adv748x_csi2_get_pad_format(sd, cfg, sdformat->pad, + sdformat->which); + if (!mbusformat) + return -EINVAL; + + mutex_lock(&state->mutex); + + sdformat->format = *mbusformat; + + mutex_unlock(&state->mutex); + + return 0; +} + +static int adv748x_csi2_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + struct adv748x_state *state = tx->state; + struct v4l2_mbus_framefmt *mbusformat; + int ret = 0; + + mbusformat = adv748x_csi2_get_pad_format(sd, cfg, sdformat->pad, + sdformat->which); + if (!mbusformat) + return -EINVAL; + + mutex_lock(&state->mutex); + + if (sdformat->pad == ADV748X_CSI2_SOURCE) { + const struct v4l2_mbus_framefmt *sink_fmt; + + sink_fmt = adv748x_csi2_get_pad_format(sd, cfg, + ADV748X_CSI2_SINK, + sdformat->which); + + if (!sink_fmt) { + ret = -EINVAL; + goto unlock; + } + + sdformat->format = *sink_fmt; + } + + *mbusformat = sdformat->format; + +unlock: + mutex_unlock(&state->mutex); + + return ret; +} + +static int adv748x_csi2_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *config) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + + if (pad != ADV748X_CSI2_SOURCE) + return -EINVAL; + + config->type = V4L2_MBUS_CSI2_DPHY; + switch (tx->active_lanes) { + case 1: + config->flags = V4L2_MBUS_CSI2_1_LANE; + break; + + case 2: + config->flags = V4L2_MBUS_CSI2_2_LANE; + break; + + case 3: + config->flags = V4L2_MBUS_CSI2_3_LANE; + break; + + case 4: + config->flags = V4L2_MBUS_CSI2_4_LANE; + break; + } + + return 0; +} + +static const struct v4l2_subdev_pad_ops adv748x_csi2_pad_ops = { + .get_fmt = adv748x_csi2_get_format, + .set_fmt = adv748x_csi2_set_format, + .get_mbus_config = adv748x_csi2_get_mbus_config, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_ops + */ + +static const struct v4l2_subdev_ops adv748x_csi2_ops = { + .video = &adv748x_csi2_video_ops, + .pad = &adv748x_csi2_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Subdev module and controls + */ + +int adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + + if (!tx->pixel_rate) + return -EINVAL; + + return v4l2_ctrl_s_ctrl_int64(tx->pixel_rate, rate); +} + +static int adv748x_csi2_s_ctrl(struct v4l2_ctrl *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_PIXEL_RATE: + return 0; + default: + return -EINVAL; + } +} + +static const struct v4l2_ctrl_ops adv748x_csi2_ctrl_ops = { + .s_ctrl = adv748x_csi2_s_ctrl, +}; + +static int adv748x_csi2_init_controls(struct adv748x_csi2 *tx) +{ + + v4l2_ctrl_handler_init(&tx->ctrl_hdl, 1); + + tx->pixel_rate = v4l2_ctrl_new_std(&tx->ctrl_hdl, + &adv748x_csi2_ctrl_ops, + V4L2_CID_PIXEL_RATE, 1, INT_MAX, + 1, 1); + + tx->sd.ctrl_handler = &tx->ctrl_hdl; + if (tx->ctrl_hdl.error) { + v4l2_ctrl_handler_free(&tx->ctrl_hdl); + return tx->ctrl_hdl.error; + } + + return v4l2_ctrl_handler_setup(&tx->ctrl_hdl); +} + +int adv748x_csi2_init(struct adv748x_state *state, struct adv748x_csi2 *tx) +{ + int ret; + + if (!is_tx_enabled(tx)) + return 0; + + /* Initialise the virtual channel */ + adv748x_csi2_set_virtual_channel(tx, 0); + + adv748x_subdev_init(&tx->sd, state, &adv748x_csi2_ops, + MEDIA_ENT_F_VID_IF_BRIDGE, + is_txa(tx) ? "txa" : "txb"); + + /* Ensure that matching is based upon the endpoint fwnodes */ + tx->sd.fwnode = of_fwnode_handle(state->endpoints[tx->port]); + + /* Register internal ops for incremental subdev registration */ + tx->sd.internal_ops = &adv748x_csi2_internal_ops; + + tx->pads[ADV748X_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; + tx->pads[ADV748X_CSI2_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&tx->sd.entity, ADV748X_CSI2_NR_PADS, + tx->pads); + if (ret) + return ret; + + ret = adv748x_csi2_init_controls(tx); + if (ret) + goto err_free_media; + + ret = v4l2_async_register_subdev(&tx->sd); + if (ret) + goto err_free_ctrl; + + return 0; + +err_free_ctrl: + v4l2_ctrl_handler_free(&tx->ctrl_hdl); +err_free_media: + media_entity_cleanup(&tx->sd.entity); + + return ret; +} + +void adv748x_csi2_cleanup(struct adv748x_csi2 *tx) +{ + if (!is_tx_enabled(tx)) + return; + + v4l2_async_unregister_subdev(&tx->sd); + media_entity_cleanup(&tx->sd.entity); + v4l2_ctrl_handler_free(&tx->ctrl_hdl); +} diff --git a/drivers/media/i2c/adv748x/adv748x-hdmi.c b/drivers/media/i2c/adv748x/adv748x-hdmi.c new file mode 100644 index 000000000..c557f8fdf --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x-hdmi.c @@ -0,0 +1,761 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices ADV748X HDMI receiver and Component Processor (CP) + * + * Copyright (C) 2017 Renesas Electronics Corp. + */ + +#include <linux/module.h> +#include <linux/mutex.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-ioctl.h> + +#include <uapi/linux/v4l2-dv-timings.h> + +#include "adv748x.h" + +/* ----------------------------------------------------------------------------- + * HDMI and CP + */ + +#define ADV748X_HDMI_MIN_WIDTH 640 +#define ADV748X_HDMI_MAX_WIDTH 1920 +#define ADV748X_HDMI_MIN_HEIGHT 480 +#define ADV748X_HDMI_MAX_HEIGHT 1200 + +/* V4L2_DV_BT_CEA_720X480I59_94 - 0.5 MHz */ +#define ADV748X_HDMI_MIN_PIXELCLOCK 13000000 +/* V4L2_DV_BT_DMT_1600X1200P60 */ +#define ADV748X_HDMI_MAX_PIXELCLOCK 162000000 + +static const struct v4l2_dv_timings_cap adv748x_hdmi_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + + V4L2_INIT_BT_TIMINGS(ADV748X_HDMI_MIN_WIDTH, ADV748X_HDMI_MAX_WIDTH, + ADV748X_HDMI_MIN_HEIGHT, ADV748X_HDMI_MAX_HEIGHT, + ADV748X_HDMI_MIN_PIXELCLOCK, + ADV748X_HDMI_MAX_PIXELCLOCK, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT, + V4L2_DV_BT_CAP_PROGRESSIVE) +}; + +struct adv748x_hdmi_video_standards { + struct v4l2_dv_timings timings; + u8 vid_std; + u8 v_freq; +}; + +static const struct adv748x_hdmi_video_standards +adv748x_hdmi_video_standards[] = { + { V4L2_DV_BT_CEA_720X480P59_94, 0x4a, 0x00 }, + { V4L2_DV_BT_CEA_720X576P50, 0x4b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x53, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x53, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P30, 0x53, 0x02 }, + { V4L2_DV_BT_CEA_1280X720P25, 0x53, 0x03 }, + { V4L2_DV_BT_CEA_1280X720P24, 0x53, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x5e, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x5e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x5e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x5e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x5e, 0x04 }, + /* SVGA */ + { V4L2_DV_BT_DMT_800X600P56, 0x80, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x81, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x82, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x83, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x84, 0x00 }, + /* SXGA */ + { V4L2_DV_BT_DMT_1280X1024P60, 0x85, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x86, 0x00 }, + /* VGA */ + { V4L2_DV_BT_DMT_640X480P60, 0x88, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x89, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x8a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x8b, 0x00 }, + /* XGA */ + { V4L2_DV_BT_DMT_1024X768P60, 0x8c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x8d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x8e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x8f, 0x00 }, + /* UXGA */ + { V4L2_DV_BT_DMT_1600X1200P60, 0x96, 0x00 }, +}; + +static void adv748x_hdmi_fill_format(struct adv748x_hdmi *hdmi, + struct v4l2_mbus_framefmt *fmt) +{ + memset(fmt, 0, sizeof(*fmt)); + + fmt->code = MEDIA_BUS_FMT_RGB888_1X24; + fmt->field = hdmi->timings.bt.interlaced ? + V4L2_FIELD_ALTERNATE : V4L2_FIELD_NONE; + + /* TODO: The colorspace depends on the AVI InfoFrame contents */ + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->width = hdmi->timings.bt.width; + fmt->height = hdmi->timings.bt.height; + + if (fmt->field == V4L2_FIELD_ALTERNATE) + fmt->height /= 2; +} + +static void adv748x_fill_optional_dv_timings(struct v4l2_dv_timings *timings) +{ + v4l2_find_dv_timings_cap(timings, &adv748x_hdmi_timings_cap, + 250000, NULL, NULL); +} + +static bool adv748x_hdmi_has_signal(struct adv748x_state *state) +{ + int val; + + /* Check that VERT_FILTER and DE_REGEN is locked */ + val = hdmi_read(state, ADV748X_HDMI_LW1); + return (val & ADV748X_HDMI_LW1_VERT_FILTER) && + (val & ADV748X_HDMI_LW1_DE_REGEN); +} + +static int adv748x_hdmi_read_pixelclock(struct adv748x_state *state) +{ + int a, b; + + a = hdmi_read(state, ADV748X_HDMI_TMDS_1); + b = hdmi_read(state, ADV748X_HDMI_TMDS_2); + if (a < 0 || b < 0) + return -ENODATA; + + /* + * The high 9 bits store TMDS frequency measurement in MHz + * The low 7 bits of TMDS_2 store the 7-bit TMDS fractional frequency + * measurement in 1/128 MHz + */ + return ((a << 1) | (b >> 7)) * 1000000 + (b & 0x7f) * 1000000 / 128; +} + +/* + * adv748x_hdmi_set_de_timings: Adjust horizontal picture offset through DE + * + * HDMI CP uses a Data Enable synchronisation timing reference + * + * Vary the leading and trailing edge position of the DE signal output by the CP + * core. Values are stored as signed-twos-complement in one-pixel-clock units + * + * The start and end are shifted equally by the 10-bit shift value. + */ +static void adv748x_hdmi_set_de_timings(struct adv748x_state *state, int shift) +{ + u8 high, low; + + /* POS_HIGH stores bits 8 and 9 of both the start and end */ + high = ADV748X_CP_DE_POS_HIGH_SET; + high |= (shift & 0x300) >> 8; + low = shift & 0xff; + + /* The sequence of the writes is important and must be followed */ + cp_write(state, ADV748X_CP_DE_POS_HIGH, high); + cp_write(state, ADV748X_CP_DE_POS_END_LOW, low); + + high |= (shift & 0x300) >> 6; + + cp_write(state, ADV748X_CP_DE_POS_HIGH, high); + cp_write(state, ADV748X_CP_DE_POS_START_LOW, low); +} + +static int adv748x_hdmi_set_video_timings(struct adv748x_state *state, + const struct v4l2_dv_timings *timings) +{ + const struct adv748x_hdmi_video_standards *stds = + adv748x_hdmi_video_standards; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(adv748x_hdmi_video_standards); i++) { + if (!v4l2_match_dv_timings(timings, &stds[i].timings, 250000, + false)) + continue; + } + + if (i >= ARRAY_SIZE(adv748x_hdmi_video_standards)) + return -EINVAL; + + /* + * When setting cp_vid_std to either 720p, 1080i, or 1080p, the video + * will get shifted horizontally to the left in active video mode. + * The de_h_start and de_h_end controls are used to centre the picture + * correctly + */ + switch (stds[i].vid_std) { + case 0x53: /* 720p */ + adv748x_hdmi_set_de_timings(state, -40); + break; + case 0x54: /* 1080i */ + case 0x5e: /* 1080p */ + adv748x_hdmi_set_de_timings(state, -44); + break; + default: + adv748x_hdmi_set_de_timings(state, 0); + break; + } + + io_write(state, ADV748X_IO_VID_STD, stds[i].vid_std); + io_clrset(state, ADV748X_IO_DATAPATH, ADV748X_IO_DATAPATH_VFREQ_M, + stds[i].v_freq << ADV748X_IO_DATAPATH_VFREQ_SHIFT); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_video_ops + */ + +static int adv748x_hdmi_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + int ret; + + if (!timings) + return -EINVAL; + + if (v4l2_match_dv_timings(&hdmi->timings, timings, 0, false)) + return 0; + + if (!v4l2_valid_dv_timings(timings, &adv748x_hdmi_timings_cap, + NULL, NULL)) + return -ERANGE; + + adv748x_fill_optional_dv_timings(timings); + + mutex_lock(&state->mutex); + + ret = adv748x_hdmi_set_video_timings(state, timings); + if (ret) + goto error; + + hdmi->timings = *timings; + + cp_clrset(state, ADV748X_CP_VID_ADJ_2, ADV748X_CP_VID_ADJ_2_INTERLACED, + timings->bt.interlaced ? + ADV748X_CP_VID_ADJ_2_INTERLACED : 0); + + mutex_unlock(&state->mutex); + + return 0; + +error: + mutex_unlock(&state->mutex); + return ret; +} + +static int adv748x_hdmi_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + + mutex_lock(&state->mutex); + + *timings = hdmi->timings; + + mutex_unlock(&state->mutex); + + return 0; +} + +static int adv748x_hdmi_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + struct v4l2_bt_timings *bt = &timings->bt; + int pixelclock; + int polarity; + + if (!timings) + return -EINVAL; + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + + if (!adv748x_hdmi_has_signal(state)) + return -ENOLINK; + + pixelclock = adv748x_hdmi_read_pixelclock(state); + if (pixelclock < 0) + return -ENODATA; + + timings->type = V4L2_DV_BT_656_1120; + + bt->pixelclock = pixelclock; + bt->interlaced = hdmi_read(state, ADV748X_HDMI_F1H1) & + ADV748X_HDMI_F1H1_INTERLACED ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + bt->width = hdmi_read16(state, ADV748X_HDMI_LW1, + ADV748X_HDMI_LW1_WIDTH_MASK); + bt->height = hdmi_read16(state, ADV748X_HDMI_F0H1, + ADV748X_HDMI_F0H1_HEIGHT_MASK); + bt->hfrontporch = hdmi_read16(state, ADV748X_HDMI_HFRONT_PORCH, + ADV748X_HDMI_HFRONT_PORCH_MASK); + bt->hsync = hdmi_read16(state, ADV748X_HDMI_HSYNC_WIDTH, + ADV748X_HDMI_HSYNC_WIDTH_MASK); + bt->hbackporch = hdmi_read16(state, ADV748X_HDMI_HBACK_PORCH, + ADV748X_HDMI_HBACK_PORCH_MASK); + bt->vfrontporch = hdmi_read16(state, ADV748X_HDMI_VFRONT_PORCH, + ADV748X_HDMI_VFRONT_PORCH_MASK) / 2; + bt->vsync = hdmi_read16(state, ADV748X_HDMI_VSYNC_WIDTH, + ADV748X_HDMI_VSYNC_WIDTH_MASK) / 2; + bt->vbackporch = hdmi_read16(state, ADV748X_HDMI_VBACK_PORCH, + ADV748X_HDMI_VBACK_PORCH_MASK) / 2; + + polarity = hdmi_read(state, 0x05); + bt->polarities = (polarity & BIT(4) ? V4L2_DV_VSYNC_POS_POL : 0) | + (polarity & BIT(5) ? V4L2_DV_HSYNC_POS_POL : 0); + + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height += hdmi_read16(state, 0x0b, 0x1fff); + bt->il_vfrontporch = hdmi_read16(state, 0x2c, 0x3fff) / 2; + bt->il_vsync = hdmi_read16(state, 0x30, 0x3fff) / 2; + bt->il_vbackporch = hdmi_read16(state, 0x34, 0x3fff) / 2; + } + + adv748x_fill_optional_dv_timings(timings); + + /* + * No interrupt handling is implemented yet. + * There should be an IRQ when a cable is plugged and the new timings + * should be figured out and stored to state. + */ + hdmi->timings = *timings; + + return 0; +} + +static int adv748x_hdmi_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + + mutex_lock(&state->mutex); + + *status = adv748x_hdmi_has_signal(state) ? 0 : V4L2_IN_ST_NO_SIGNAL; + + mutex_unlock(&state->mutex); + + return 0; +} + +static int adv748x_hdmi_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + int ret; + + mutex_lock(&state->mutex); + + ret = adv748x_tx_power(hdmi->tx, enable); + if (ret) + goto done; + + if (adv748x_hdmi_has_signal(state)) + adv_dbg(state, "Detected HDMI signal\n"); + else + adv_dbg(state, "Couldn't detect HDMI video signal\n"); + +done: + mutex_unlock(&state->mutex); + return ret; +} + +static int adv748x_hdmi_g_pixelaspect(struct v4l2_subdev *sd, + struct v4l2_fract *aspect) +{ + aspect->numerator = 1; + aspect->denominator = 1; + + return 0; +} + +static const struct v4l2_subdev_video_ops adv748x_video_ops_hdmi = { + .s_dv_timings = adv748x_hdmi_s_dv_timings, + .g_dv_timings = adv748x_hdmi_g_dv_timings, + .query_dv_timings = adv748x_hdmi_query_dv_timings, + .g_input_status = adv748x_hdmi_g_input_status, + .s_stream = adv748x_hdmi_s_stream, + .g_pixelaspect = adv748x_hdmi_g_pixelaspect, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_pad_ops + */ + +static int adv748x_hdmi_propagate_pixelrate(struct adv748x_hdmi *hdmi) +{ + struct v4l2_subdev *tx; + struct v4l2_dv_timings timings; + + tx = adv748x_get_remote_sd(&hdmi->pads[ADV748X_HDMI_SOURCE]); + if (!tx) + return -ENOLINK; + + adv748x_hdmi_query_dv_timings(&hdmi->sd, &timings); + + return adv748x_csi2_set_pixelrate(tx, timings.bt.pixelclock); +} + +static int adv748x_hdmi_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_RGB888_1X24; + + return 0; +} + +static int adv748x_hdmi_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct v4l2_mbus_framefmt *mbusformat; + + if (sdformat->pad != ADV748X_HDMI_SOURCE) + return -EINVAL; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) { + mbusformat = v4l2_subdev_get_try_format(sd, cfg, sdformat->pad); + sdformat->format = *mbusformat; + } else { + adv748x_hdmi_fill_format(hdmi, &sdformat->format); + adv748x_hdmi_propagate_pixelrate(hdmi); + } + + return 0; +} + +static int adv748x_hdmi_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct v4l2_mbus_framefmt *mbusformat; + + if (sdformat->pad != ADV748X_HDMI_SOURCE) + return -EINVAL; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return adv748x_hdmi_get_format(sd, cfg, sdformat); + + mbusformat = v4l2_subdev_get_try_format(sd, cfg, sdformat->pad); + *mbusformat = sdformat->format; + + return 0; +} + +static int adv748x_hdmi_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (!hdmi->edid.present) + return -ENODATA; + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = hdmi->edid.blocks; + return 0; + } + + if (edid->start_block >= hdmi->edid.blocks) + return -EINVAL; + + if (edid->start_block + edid->blocks > hdmi->edid.blocks) + edid->blocks = hdmi->edid.blocks - edid->start_block; + + memcpy(edid->edid, hdmi->edid.edid + edid->start_block * 128, + edid->blocks * 128); + + return 0; +} + +static inline int adv748x_hdmi_edid_write_block(struct adv748x_hdmi *hdmi, + unsigned int total_len, const u8 *val) +{ + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + int err = 0; + int i = 0; + int len = 0; + + adv_dbg(state, "%s: write EDID block (%d byte)\n", + __func__, total_len); + + while (!err && i < total_len) { + len = (total_len - i) > I2C_SMBUS_BLOCK_MAX ? + I2C_SMBUS_BLOCK_MAX : + (total_len - i); + + err = adv748x_write_block(state, ADV748X_PAGE_EDID, + i, val + i, len); + i += len; + } + + return err; +} + +static int adv748x_hdmi_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + int err; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->start_block != 0) + return -EINVAL; + + if (edid->blocks == 0) { + hdmi->edid.blocks = 0; + hdmi->edid.present = 0; + + /* Fall back to a 16:9 aspect ratio */ + hdmi->aspect_ratio.numerator = 16; + hdmi->aspect_ratio.denominator = 9; + + /* Disable the EDID */ + repeater_write(state, ADV748X_REPEATER_EDID_SZ, + edid->blocks << ADV748X_REPEATER_EDID_SZ_SHIFT); + + repeater_write(state, ADV748X_REPEATER_EDID_CTL, 0); + + return 0; + } + + if (edid->blocks > 4) { + edid->blocks = 4; + return -E2BIG; + } + + memcpy(hdmi->edid.edid, edid->edid, 128 * edid->blocks); + hdmi->edid.blocks = edid->blocks; + hdmi->edid.present = true; + + hdmi->aspect_ratio = v4l2_calc_aspect_ratio(edid->edid[0x15], + edid->edid[0x16]); + + err = adv748x_hdmi_edid_write_block(hdmi, 128 * edid->blocks, + hdmi->edid.edid); + if (err < 0) { + v4l2_err(sd, "error %d writing edid pad %d\n", err, edid->pad); + return err; + } + + repeater_write(state, ADV748X_REPEATER_EDID_SZ, + edid->blocks << ADV748X_REPEATER_EDID_SZ_SHIFT); + + repeater_write(state, ADV748X_REPEATER_EDID_CTL, + ADV748X_REPEATER_EDID_CTL_EN); + + return 0; +} + +static bool adv748x_hdmi_check_dv_timings(const struct v4l2_dv_timings *timings, + void *hdl) +{ + const struct adv748x_hdmi_video_standards *stds = + adv748x_hdmi_video_standards; + unsigned int i; + + for (i = 0; stds[i].timings.bt.width; i++) + if (v4l2_match_dv_timings(timings, &stds[i].timings, 0, false)) + return true; + + return false; +} + +static int adv748x_hdmi_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + return v4l2_enum_dv_timings_cap(timings, &adv748x_hdmi_timings_cap, + adv748x_hdmi_check_dv_timings, NULL); +} + +static int adv748x_hdmi_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + *cap = adv748x_hdmi_timings_cap; + return 0; +} + +static const struct v4l2_subdev_pad_ops adv748x_pad_ops_hdmi = { + .enum_mbus_code = adv748x_hdmi_enum_mbus_code, + .set_fmt = adv748x_hdmi_set_format, + .get_fmt = adv748x_hdmi_get_format, + .get_edid = adv748x_hdmi_get_edid, + .set_edid = adv748x_hdmi_set_edid, + .dv_timings_cap = adv748x_hdmi_dv_timings_cap, + .enum_dv_timings = adv748x_hdmi_enum_dv_timings, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_ops + */ + +static const struct v4l2_subdev_ops adv748x_ops_hdmi = { + .video = &adv748x_video_ops_hdmi, + .pad = &adv748x_pad_ops_hdmi, +}; + +/* ----------------------------------------------------------------------------- + * Controls + */ + +static const char * const hdmi_ctrl_patgen_menu[] = { + "Disabled", + "Solid Color", + "Color Bars", + "Ramp Grey", + "Ramp Blue", + "Ramp Red", + "Checkered" +}; + +static int adv748x_hdmi_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct adv748x_hdmi *hdmi = adv748x_ctrl_to_hdmi(ctrl); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + int ret; + u8 pattern; + + /* Enable video adjustment first */ + ret = cp_clrset(state, ADV748X_CP_VID_ADJ, + ADV748X_CP_VID_ADJ_ENABLE, + ADV748X_CP_VID_ADJ_ENABLE); + if (ret < 0) + return ret; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ret = cp_write(state, ADV748X_CP_BRI, ctrl->val); + break; + case V4L2_CID_HUE: + ret = cp_write(state, ADV748X_CP_HUE, ctrl->val); + break; + case V4L2_CID_CONTRAST: + ret = cp_write(state, ADV748X_CP_CON, ctrl->val); + break; + case V4L2_CID_SATURATION: + ret = cp_write(state, ADV748X_CP_SAT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + pattern = ctrl->val; + + /* Pattern is 0-indexed. Ctrl Menu is 1-indexed */ + if (pattern) { + pattern--; + pattern |= ADV748X_CP_PAT_GEN_EN; + } + + ret = cp_write(state, ADV748X_CP_PAT_GEN, pattern); + + break; + default: + return -EINVAL; + } + + return ret; +} + +static const struct v4l2_ctrl_ops adv748x_hdmi_ctrl_ops = { + .s_ctrl = adv748x_hdmi_s_ctrl, +}; + +static int adv748x_hdmi_init_controls(struct adv748x_hdmi *hdmi) +{ + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + + v4l2_ctrl_handler_init(&hdmi->ctrl_hdl, 5); + + /* Use our mutex for the controls */ + hdmi->ctrl_hdl.lock = &state->mutex; + + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV748X_CP_BRI_MIN, + ADV748X_CP_BRI_MAX, 1, ADV748X_CP_BRI_DEF); + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, + V4L2_CID_CONTRAST, ADV748X_CP_CON_MIN, + ADV748X_CP_CON_MAX, 1, ADV748X_CP_CON_DEF); + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, + V4L2_CID_SATURATION, ADV748X_CP_SAT_MIN, + ADV748X_CP_SAT_MAX, 1, ADV748X_CP_SAT_DEF); + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, + V4L2_CID_HUE, ADV748X_CP_HUE_MIN, + ADV748X_CP_HUE_MAX, 1, ADV748X_CP_HUE_DEF); + + /* + * Todo: V4L2_CID_DV_RX_POWER_PRESENT should also be supported when + * interrupts are handled correctly + */ + + v4l2_ctrl_new_std_menu_items(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(hdmi_ctrl_patgen_menu) - 1, + 0, 0, hdmi_ctrl_patgen_menu); + + hdmi->sd.ctrl_handler = &hdmi->ctrl_hdl; + if (hdmi->ctrl_hdl.error) { + v4l2_ctrl_handler_free(&hdmi->ctrl_hdl); + return hdmi->ctrl_hdl.error; + } + + return v4l2_ctrl_handler_setup(&hdmi->ctrl_hdl); +} + +int adv748x_hdmi_init(struct adv748x_hdmi *hdmi) +{ + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + static const struct v4l2_dv_timings cea1280x720 = + V4L2_DV_BT_CEA_1280X720P30; + int ret; + + hdmi->timings = cea1280x720; + + /* Initialise a default 16:9 aspect ratio */ + hdmi->aspect_ratio.numerator = 16; + hdmi->aspect_ratio.denominator = 9; + + adv748x_subdev_init(&hdmi->sd, state, &adv748x_ops_hdmi, + MEDIA_ENT_F_IO_DTV, "hdmi"); + + hdmi->pads[ADV748X_HDMI_SINK].flags = MEDIA_PAD_FL_SINK; + hdmi->pads[ADV748X_HDMI_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&hdmi->sd.entity, + ADV748X_HDMI_NR_PADS, hdmi->pads); + if (ret) + return ret; + + ret = adv748x_hdmi_init_controls(hdmi); + if (ret) + goto err_free_media; + + return 0; + +err_free_media: + media_entity_cleanup(&hdmi->sd.entity); + + return ret; +} + +void adv748x_hdmi_cleanup(struct adv748x_hdmi *hdmi) +{ + v4l2_device_unregister_subdev(&hdmi->sd); + media_entity_cleanup(&hdmi->sd.entity); + v4l2_ctrl_handler_free(&hdmi->ctrl_hdl); +} diff --git a/drivers/media/i2c/adv748x/adv748x.h b/drivers/media/i2c/adv748x/adv748x.h new file mode 100644 index 000000000..1061f425e --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x.h @@ -0,0 +1,446 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for Analog Devices ADV748X video decoder and HDMI receiver + * + * Copyright (C) 2017 Renesas Electronics Corp. + * + * Authors: + * Koji Matsuoka <koji.matsuoka.xm@renesas.com> + * Niklas Söderlund <niklas.soderlund@ragnatech.se> + * Kieran Bingham <kieran.bingham@ideasonboard.com> + * + * The ADV748x range of receivers have the following configurations: + * + * Analog HDMI MHL 4-Lane 1-Lane + * In In CSI CSI + * ADV7480 X X X + * ADV7481 X X X X X + * ADV7482 X X X X + */ + +#include <linux/i2c.h> + +#ifndef _ADV748X_H_ +#define _ADV748X_H_ + +enum adv748x_page { + ADV748X_PAGE_IO, + ADV748X_PAGE_DPLL, + ADV748X_PAGE_CP, + ADV748X_PAGE_HDMI, + ADV748X_PAGE_EDID, + ADV748X_PAGE_REPEATER, + ADV748X_PAGE_INFOFRAME, + ADV748X_PAGE_CBUS, + ADV748X_PAGE_CEC, + ADV748X_PAGE_SDP, + ADV748X_PAGE_TXB, + ADV748X_PAGE_TXA, + ADV748X_PAGE_MAX, + + /* Fake pages for register sequences */ + ADV748X_PAGE_EOR, /* End Mark */ +}; + +/** + * enum adv748x_ports - Device tree port number definitions + * + * The ADV748X ports define the mapping between subdevices + * and the device tree specification + */ +enum adv748x_ports { + ADV748X_PORT_AIN0 = 0, + ADV748X_PORT_AIN1 = 1, + ADV748X_PORT_AIN2 = 2, + ADV748X_PORT_AIN3 = 3, + ADV748X_PORT_AIN4 = 4, + ADV748X_PORT_AIN5 = 5, + ADV748X_PORT_AIN6 = 6, + ADV748X_PORT_AIN7 = 7, + ADV748X_PORT_HDMI = 8, + ADV748X_PORT_TTL = 9, + ADV748X_PORT_TXA = 10, + ADV748X_PORT_TXB = 11, + ADV748X_PORT_MAX = 12, +}; + +enum adv748x_csi2_pads { + ADV748X_CSI2_SINK, + ADV748X_CSI2_SOURCE, + ADV748X_CSI2_NR_PADS, +}; + +/* CSI2 transmitters can have 2 internal connections, HDMI/AFE */ +#define ADV748X_CSI2_MAX_SUBDEVS 2 + +struct adv748x_csi2 { + struct adv748x_state *state; + struct v4l2_mbus_framefmt format; + unsigned int page; + unsigned int port; + unsigned int num_lanes; + unsigned int active_lanes; + + struct media_pad pads[ADV748X_CSI2_NR_PADS]; + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_ctrl *pixel_rate; + struct v4l2_subdev *src; + struct v4l2_subdev sd; +}; + +#define notifier_to_csi2(n) container_of(n, struct adv748x_csi2, notifier) +#define adv748x_sd_to_csi2(sd) container_of(sd, struct adv748x_csi2, sd) + +#define is_tx_enabled(_tx) ((_tx)->state->endpoints[(_tx)->port] != NULL) +#define is_txa(_tx) ((_tx) == &(_tx)->state->txa) +#define is_txb(_tx) ((_tx) == &(_tx)->state->txb) +#define is_tx(_tx) (is_txa(_tx) || is_txb(_tx)) + +#define is_afe_enabled(_state) \ + ((_state)->endpoints[ADV748X_PORT_AIN0] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN1] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN2] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN3] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN4] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN5] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN6] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN7] != NULL) +#define is_hdmi_enabled(_state) ((_state)->endpoints[ADV748X_PORT_HDMI] != NULL) + +enum adv748x_hdmi_pads { + ADV748X_HDMI_SINK, + ADV748X_HDMI_SOURCE, + ADV748X_HDMI_NR_PADS, +}; + +struct adv748x_hdmi { + struct media_pad pads[ADV748X_HDMI_NR_PADS]; + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_subdev sd; + struct v4l2_mbus_framefmt format; + + struct v4l2_dv_timings timings; + struct v4l2_fract aspect_ratio; + + struct adv748x_csi2 *tx; + + struct { + u8 edid[512]; + u32 present; + unsigned int blocks; + } edid; +}; + +#define adv748x_ctrl_to_hdmi(ctrl) \ + container_of(ctrl->handler, struct adv748x_hdmi, ctrl_hdl) +#define adv748x_sd_to_hdmi(sd) container_of(sd, struct adv748x_hdmi, sd) + +enum adv748x_afe_pads { + ADV748X_AFE_SINK_AIN0, + ADV748X_AFE_SINK_AIN1, + ADV748X_AFE_SINK_AIN2, + ADV748X_AFE_SINK_AIN3, + ADV748X_AFE_SINK_AIN4, + ADV748X_AFE_SINK_AIN5, + ADV748X_AFE_SINK_AIN6, + ADV748X_AFE_SINK_AIN7, + ADV748X_AFE_SOURCE, + ADV748X_AFE_NR_PADS, +}; + +struct adv748x_afe { + struct media_pad pads[ADV748X_AFE_NR_PADS]; + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_subdev sd; + struct v4l2_mbus_framefmt format; + + struct adv748x_csi2 *tx; + + bool streaming; + v4l2_std_id curr_norm; + unsigned int input; +}; + +#define adv748x_ctrl_to_afe(ctrl) \ + container_of(ctrl->handler, struct adv748x_afe, ctrl_hdl) +#define adv748x_sd_to_afe(sd) container_of(sd, struct adv748x_afe, sd) + +/** + * struct adv748x_state - State of ADV748X + * @dev: (OF) device + * @client: I2C client + * @mutex: protect global state + * + * @endpoints: parsed device node endpoints for each port + * + * @i2c_addresses I2C Page addresses + * @i2c_clients I2C clients for the page accesses + * @regmap regmap configuration pages. + * + * @hdmi: state of HDMI receiver context + * @afe: state of AFE receiver context + * @txa: state of TXA transmitter context + * @txb: state of TXB transmitter context + */ +struct adv748x_state { + struct device *dev; + struct i2c_client *client; + struct mutex mutex; + + struct device_node *endpoints[ADV748X_PORT_MAX]; + + struct i2c_client *i2c_clients[ADV748X_PAGE_MAX]; + struct regmap *regmap[ADV748X_PAGE_MAX]; + + struct adv748x_hdmi hdmi; + struct adv748x_afe afe; + struct adv748x_csi2 txa; + struct adv748x_csi2 txb; +}; + +#define adv748x_hdmi_to_state(h) container_of(h, struct adv748x_state, hdmi) +#define adv748x_afe_to_state(a) container_of(a, struct adv748x_state, afe) + +#define adv_err(a, fmt, arg...) dev_err(a->dev, fmt, ##arg) +#define adv_info(a, fmt, arg...) dev_info(a->dev, fmt, ##arg) +#define adv_dbg(a, fmt, arg...) dev_dbg(a->dev, fmt, ##arg) + +/* Register Mappings */ + +/* IO Map */ +#define ADV748X_IO_PD 0x00 /* power down controls */ +#define ADV748X_IO_PD_RX_EN BIT(6) + +#define ADV748X_IO_REG_01 0x01 /* pwrdn{2}b, prog_xtal_freq */ +#define ADV748X_IO_REG_01_PWRDN_MASK (BIT(7) | BIT(6)) +#define ADV748X_IO_REG_01_PWRDN2B BIT(7) /* CEC Wakeup Support */ +#define ADV748X_IO_REG_01_PWRDNB BIT(6) /* CEC Wakeup Support */ + +#define ADV748X_IO_REG_04 0x04 +#define ADV748X_IO_REG_04_FORCE_FR BIT(0) /* Force CP free-run */ + +#define ADV748X_IO_DATAPATH 0x03 /* datapath cntrl */ +#define ADV748X_IO_DATAPATH_VFREQ_M 0x70 +#define ADV748X_IO_DATAPATH_VFREQ_SHIFT 4 + +#define ADV748X_IO_VID_STD 0x05 + +#define ADV748X_IO_10 0x10 /* io_reg_10 */ +#define ADV748X_IO_10_CSI4_EN BIT(7) +#define ADV748X_IO_10_CSI1_EN BIT(6) +#define ADV748X_IO_10_PIX_OUT_EN BIT(5) +#define ADV748X_IO_10_CSI4_IN_SEL_AFE BIT(3) + +#define ADV748X_IO_CHIP_REV_ID_1 0xdf +#define ADV748X_IO_CHIP_REV_ID_2 0xe0 + +#define ADV748X_IO_REG_F2 0xf2 +#define ADV748X_IO_REG_F2_READ_AUTO_INC BIT(0) + +/* For PAGE slave address offsets */ +#define ADV748X_IO_SLAVE_ADDR_BASE 0xf2 + +/* + * The ADV748x_Recommended_Settings_PrA_2014-08-20.pdf details both 0x80 and + * 0xff as examples for performing a software reset. + */ +#define ADV748X_IO_REG_FF 0xff +#define ADV748X_IO_REG_FF_MAIN_RESET 0xff + +/* HDMI RX Map */ +#define ADV748X_HDMI_LW1 0x07 /* line width_1 */ +#define ADV748X_HDMI_LW1_VERT_FILTER BIT(7) +#define ADV748X_HDMI_LW1_DE_REGEN BIT(5) +#define ADV748X_HDMI_LW1_WIDTH_MASK 0x1fff + +#define ADV748X_HDMI_F0H1 0x09 /* field0 height_1 */ +#define ADV748X_HDMI_F0H1_HEIGHT_MASK 0x1fff + +#define ADV748X_HDMI_F1H1 0x0b /* field1 height_1 */ +#define ADV748X_HDMI_F1H1_INTERLACED BIT(5) + +#define ADV748X_HDMI_HFRONT_PORCH 0x20 /* hsync_front_porch_1 */ +#define ADV748X_HDMI_HFRONT_PORCH_MASK 0x1fff + +#define ADV748X_HDMI_HSYNC_WIDTH 0x22 /* hsync_pulse_width_1 */ +#define ADV748X_HDMI_HSYNC_WIDTH_MASK 0x1fff + +#define ADV748X_HDMI_HBACK_PORCH 0x24 /* hsync_back_porch_1 */ +#define ADV748X_HDMI_HBACK_PORCH_MASK 0x1fff + +#define ADV748X_HDMI_VFRONT_PORCH 0x2a /* field0_vs_front_porch_1 */ +#define ADV748X_HDMI_VFRONT_PORCH_MASK 0x3fff + +#define ADV748X_HDMI_VSYNC_WIDTH 0x2e /* field0_vs_pulse_width_1 */ +#define ADV748X_HDMI_VSYNC_WIDTH_MASK 0x3fff + +#define ADV748X_HDMI_VBACK_PORCH 0x32 /* field0_vs_back_porch_1 */ +#define ADV748X_HDMI_VBACK_PORCH_MASK 0x3fff + +#define ADV748X_HDMI_TMDS_1 0x51 /* hdmi_reg_51 */ +#define ADV748X_HDMI_TMDS_2 0x52 /* hdmi_reg_52 */ + +/* HDMI RX Repeater Map */ +#define ADV748X_REPEATER_EDID_SZ 0x70 /* primary_edid_size */ +#define ADV748X_REPEATER_EDID_SZ_SHIFT 4 + +#define ADV748X_REPEATER_EDID_CTL 0x74 /* hdcp edid controls */ +#define ADV748X_REPEATER_EDID_CTL_EN BIT(0) /* man_edid_a_enable */ + +/* SDP Main Map */ +#define ADV748X_SDP_INSEL 0x00 /* user_map_rw_reg_00 */ + +#define ADV748X_SDP_VID_SEL 0x02 /* user_map_rw_reg_02 */ +#define ADV748X_SDP_VID_SEL_MASK 0xf0 +#define ADV748X_SDP_VID_SEL_SHIFT 4 + +/* Contrast - Unsigned*/ +#define ADV748X_SDP_CON 0x08 /* user_map_rw_reg_08 */ +#define ADV748X_SDP_CON_MIN 0 +#define ADV748X_SDP_CON_DEF 128 +#define ADV748X_SDP_CON_MAX 255 + +/* Brightness - Signed */ +#define ADV748X_SDP_BRI 0x0a /* user_map_rw_reg_0a */ +#define ADV748X_SDP_BRI_MIN -128 +#define ADV748X_SDP_BRI_DEF 0 +#define ADV748X_SDP_BRI_MAX 127 + +/* Hue - Signed, inverted*/ +#define ADV748X_SDP_HUE 0x0b /* user_map_rw_reg_0b */ +#define ADV748X_SDP_HUE_MIN -127 +#define ADV748X_SDP_HUE_DEF 0 +#define ADV748X_SDP_HUE_MAX 128 + +/* Test Patterns / Default Values */ +#define ADV748X_SDP_DEF 0x0c /* user_map_rw_reg_0c */ +#define ADV748X_SDP_DEF_VAL_EN BIT(0) /* Force free run mode */ +#define ADV748X_SDP_DEF_VAL_AUTO_EN BIT(1) /* Free run when no signal */ + +#define ADV748X_SDP_MAP_SEL 0x0e /* user_map_rw_reg_0e */ +#define ADV748X_SDP_MAP_SEL_RO_MAIN 1 + +/* Free run pattern select */ +#define ADV748X_SDP_FRP 0x14 +#define ADV748X_SDP_FRP_MASK GENMASK(3, 1) + +/* Saturation */ +#define ADV748X_SDP_SD_SAT_U 0xe3 /* user_map_rw_reg_e3 */ +#define ADV748X_SDP_SD_SAT_V 0xe4 /* user_map_rw_reg_e4 */ +#define ADV748X_SDP_SAT_MIN 0 +#define ADV748X_SDP_SAT_DEF 128 +#define ADV748X_SDP_SAT_MAX 255 + +/* SDP RO Main Map */ +#define ADV748X_SDP_RO_10 0x10 +#define ADV748X_SDP_RO_10_IN_LOCK BIT(0) + +/* CP Map */ +#define ADV748X_CP_PAT_GEN 0x37 /* int_pat_gen_1 */ +#define ADV748X_CP_PAT_GEN_EN BIT(7) + +/* Contrast Control - Unsigned */ +#define ADV748X_CP_CON 0x3a /* contrast_cntrl */ +#define ADV748X_CP_CON_MIN 0 /* Minimum contrast */ +#define ADV748X_CP_CON_DEF 128 /* Default */ +#define ADV748X_CP_CON_MAX 255 /* Maximum contrast */ + +/* Saturation Control - Unsigned */ +#define ADV748X_CP_SAT 0x3b /* saturation_cntrl */ +#define ADV748X_CP_SAT_MIN 0 /* Minimum saturation */ +#define ADV748X_CP_SAT_DEF 128 /* Default */ +#define ADV748X_CP_SAT_MAX 255 /* Maximum saturation */ + +/* Brightness Control - Signed */ +#define ADV748X_CP_BRI 0x3c /* brightness_cntrl */ +#define ADV748X_CP_BRI_MIN -128 /* Luma is -512d */ +#define ADV748X_CP_BRI_DEF 0 /* Luma is 0 */ +#define ADV748X_CP_BRI_MAX 127 /* Luma is 508d */ + +/* Hue Control */ +#define ADV748X_CP_HUE 0x3d /* hue_cntrl */ +#define ADV748X_CP_HUE_MIN 0 /* -90 degree */ +#define ADV748X_CP_HUE_DEF 0 /* -90 degree */ +#define ADV748X_CP_HUE_MAX 255 /* +90 degree */ + +#define ADV748X_CP_VID_ADJ 0x3e /* vid_adj_0 */ +#define ADV748X_CP_VID_ADJ_ENABLE BIT(7) /* Enable colour controls */ + +#define ADV748X_CP_DE_POS_HIGH 0x8b /* de_pos_adj_6 */ +#define ADV748X_CP_DE_POS_HIGH_SET BIT(6) +#define ADV748X_CP_DE_POS_END_LOW 0x8c /* de_pos_adj_7 */ +#define ADV748X_CP_DE_POS_START_LOW 0x8d /* de_pos_adj_8 */ + +#define ADV748X_CP_VID_ADJ_2 0x91 +#define ADV748X_CP_VID_ADJ_2_INTERLACED BIT(6) +#define ADV748X_CP_VID_ADJ_2_INTERLACED_3D BIT(4) + +#define ADV748X_CP_CLMP_POS 0xc9 /* clmp_pos_cntrl_4 */ +#define ADV748X_CP_CLMP_POS_DIS_AUTO BIT(0) /* dis_auto_param_buff */ + +/* CSI : TXA/TXB Maps */ +#define ADV748X_CSI_VC_REF 0x0d /* csi_tx_top_reg_0d */ +#define ADV748X_CSI_VC_REF_SHIFT 6 + +#define ADV748X_CSI_FS_AS_LS 0x1e /* csi_tx_top_reg_1e */ +#define ADV748X_CSI_FS_AS_LS_UNKNOWN BIT(6) /* Undocumented bit */ + +/* Register handling */ + +int adv748x_read(struct adv748x_state *state, u8 addr, u8 reg); +int adv748x_write(struct adv748x_state *state, u8 page, u8 reg, u8 value); +int adv748x_write_block(struct adv748x_state *state, int client_page, + unsigned int init_reg, const void *val, + size_t val_len); + +#define io_read(s, r) adv748x_read(s, ADV748X_PAGE_IO, r) +#define io_write(s, r, v) adv748x_write(s, ADV748X_PAGE_IO, r, v) +#define io_clrset(s, r, m, v) io_write(s, r, (io_read(s, r) & ~(m)) | (v)) + +#define hdmi_read(s, r) adv748x_read(s, ADV748X_PAGE_HDMI, r) +#define hdmi_read16(s, r, m) (((hdmi_read(s, r) << 8) | hdmi_read(s, (r)+1)) & (m)) +#define hdmi_write(s, r, v) adv748x_write(s, ADV748X_PAGE_HDMI, r, v) + +#define repeater_read(s, r) adv748x_read(s, ADV748X_PAGE_REPEATER, r) +#define repeater_write(s, r, v) adv748x_write(s, ADV748X_PAGE_REPEATER, r, v) + +#define sdp_read(s, r) adv748x_read(s, ADV748X_PAGE_SDP, r) +#define sdp_write(s, r, v) adv748x_write(s, ADV748X_PAGE_SDP, r, v) +#define sdp_clrset(s, r, m, v) sdp_write(s, r, (sdp_read(s, r) & ~(m)) | (v)) + +#define cp_read(s, r) adv748x_read(s, ADV748X_PAGE_CP, r) +#define cp_write(s, r, v) adv748x_write(s, ADV748X_PAGE_CP, r, v) +#define cp_clrset(s, r, m, v) cp_write(s, r, (cp_read(s, r) & ~(m)) | (v)) + +#define tx_read(t, r) adv748x_read(t->state, t->page, r) +#define tx_write(t, r, v) adv748x_write(t->state, t->page, r, v) + +static inline struct v4l2_subdev *adv748x_get_remote_sd(struct media_pad *pad) +{ + pad = media_entity_remote_pad(pad); + if (!pad) + return NULL; + + return media_entity_to_v4l2_subdev(pad->entity); +} + +void adv748x_subdev_init(struct v4l2_subdev *sd, struct adv748x_state *state, + const struct v4l2_subdev_ops *ops, u32 function, + const char *ident); + +int adv748x_register_subdevs(struct adv748x_state *state, + struct v4l2_device *v4l2_dev); + +int adv748x_tx_power(struct adv748x_csi2 *tx, bool on); + +int adv748x_afe_init(struct adv748x_afe *afe); +void adv748x_afe_cleanup(struct adv748x_afe *afe); + +int adv748x_csi2_init(struct adv748x_state *state, struct adv748x_csi2 *tx); +void adv748x_csi2_cleanup(struct adv748x_csi2 *tx); +int adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate); + +int adv748x_hdmi_init(struct adv748x_hdmi *hdmi); +void adv748x_hdmi_cleanup(struct adv748x_hdmi *hdmi); + +#endif /* _ADV748X_H_ */ diff --git a/drivers/media/i2c/adv7511-v4l2.c b/drivers/media/i2c/adv7511-v4l2.c new file mode 100644 index 000000000..9f5713b76 --- /dev/null +++ b/drivers/media/i2c/adv7511-v4l2.c @@ -0,0 +1,1995 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADV7511 HDMI Transmitter Device Driver + * + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + */ + +/* + * This file is named adv7511-v4l2.c so it doesn't conflict with the Analog + * Device ADV7511 (config fragment CONFIG_DRM_I2C_ADV7511). + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <linux/gpio.h> +#include <linux/workqueue.h> +#include <linux/hdmi.h> +#include <linux/v4l2-dv-timings.h> +#include <media/v4l2-device.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dv-timings.h> +#include <media/i2c/adv7511.h> +#include <media/cec.h> + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices ADV7511 HDMI Transmitter Device Driver"); +MODULE_AUTHOR("Hans Verkuil"); +MODULE_LICENSE("GPL v2"); + +#define MASK_ADV7511_EDID_RDY_INT 0x04 +#define MASK_ADV7511_MSEN_INT 0x40 +#define MASK_ADV7511_HPD_INT 0x80 + +#define MASK_ADV7511_HPD_DETECT 0x40 +#define MASK_ADV7511_MSEN_DETECT 0x20 +#define MASK_ADV7511_EDID_RDY 0x10 + +#define EDID_MAX_RETRIES (8) +#define EDID_DELAY 250 +#define EDID_MAX_SEGM 8 + +#define ADV7511_MAX_WIDTH 1920 +#define ADV7511_MAX_HEIGHT 1200 +#define ADV7511_MIN_PIXELCLOCK 20000000 +#define ADV7511_MAX_PIXELCLOCK 225000000 + +#define ADV7511_MAX_ADDRS (3) + +/* +********************************************************************** +* +* Arrays with configuration parameters for the ADV7511 +* +********************************************************************** +*/ + +struct i2c_reg_value { + unsigned char reg; + unsigned char value; +}; + +struct adv7511_state_edid { + /* total number of blocks */ + u32 blocks; + /* Number of segments read */ + u32 segments; + u8 data[EDID_MAX_SEGM * 256]; + /* Number of EDID read retries left */ + unsigned read_retries; + bool complete; +}; + +struct adv7511_state { + struct adv7511_platform_data pdata; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + int chip_revision; + u8 i2c_edid_addr; + u8 i2c_pktmem_addr; + u8 i2c_cec_addr; + + struct i2c_client *i2c_cec; + struct cec_adapter *cec_adap; + u8 cec_addr[ADV7511_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + + /* Is the adv7511 powered on? */ + bool power_on; + /* Did we receive hotplug and rx-sense signals? */ + bool have_monitor; + bool enabled_irq; + /* timings from s_dv_timings */ + struct v4l2_dv_timings dv_timings; + u32 fmt_code; + u32 colorspace; + u32 ycbcr_enc; + u32 quantization; + u32 xfer_func; + u32 content_type; + /* controls */ + struct v4l2_ctrl *hdmi_mode_ctrl; + struct v4l2_ctrl *hotplug_ctrl; + struct v4l2_ctrl *rx_sense_ctrl; + struct v4l2_ctrl *have_edid0_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; + struct v4l2_ctrl *content_type_ctrl; + struct i2c_client *i2c_edid; + struct i2c_client *i2c_pktmem; + struct adv7511_state_edid edid; + /* Running counter of the number of detected EDIDs (for debugging) */ + unsigned edid_detect_counter; + struct workqueue_struct *work_queue; + struct delayed_work edid_handler; /* work entry */ +}; + +static void adv7511_check_monitor_present_status(struct v4l2_subdev *sd); +static bool adv7511_check_edid_status(struct v4l2_subdev *sd); +static void adv7511_setup(struct v4l2_subdev *sd); +static int adv7511_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq); +static int adv7511_s_clock_freq(struct v4l2_subdev *sd, u32 freq); + + +static const struct v4l2_dv_timings_cap adv7511_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, ADV7511_MAX_WIDTH, 350, ADV7511_MAX_HEIGHT, + ADV7511_MIN_PIXELCLOCK, ADV7511_MAX_PIXELCLOCK, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +static inline struct adv7511_state *get_adv7511_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7511_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7511_state, hdl)->sd; +} + +/* ------------------------ I2C ----------------------------------------------- */ + +static s32 adv_smbus_read_byte_data_check(struct i2c_client *client, + u8 command, bool check) +{ + union i2c_smbus_data data; + + if (!i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_BYTE_DATA, &data)) + return data.byte; + if (check) + v4l_err(client, "error reading %02x, %02x\n", + client->addr, command); + return -1; +} + +static s32 adv_smbus_read_byte_data(struct i2c_client *client, u8 command) +{ + int i; + for (i = 0; i < 3; i++) { + int ret = adv_smbus_read_byte_data_check(client, command, true); + if (ret >= 0) { + if (i) + v4l_err(client, "read ok after %d retries\n", i); + return ret; + } + } + v4l_err(client, "read failed\n"); + return -1; +} + +static int adv7511_rd(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_read_byte_data(client, reg); +} + +static int adv7511_wr(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "%s: i2c write error\n", __func__); + return ret; +} + +/* To set specific bits in the register, a clear-mask is given (to be AND-ed), + and then the value-mask (to be OR-ed). */ +static inline void adv7511_wr_and_or(struct v4l2_subdev *sd, u8 reg, u8 clr_mask, u8 val_mask) +{ + adv7511_wr(sd, reg, (adv7511_rd(sd, reg) & clr_mask) | val_mask); +} + +static int adv_smbus_read_i2c_block_data(struct i2c_client *client, + u8 command, unsigned length, u8 *values) +{ + union i2c_smbus_data data; + int ret; + + if (length > I2C_SMBUS_BLOCK_MAX) + length = I2C_SMBUS_BLOCK_MAX; + data.block[0] = length; + + ret = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_I2C_BLOCK_DATA, &data); + memcpy(values, data.block + 1, length); + return ret; +} + +static void adv7511_edid_rd(struct v4l2_subdev *sd, uint16_t len, uint8_t *buf) +{ + struct adv7511_state *state = get_adv7511_state(sd); + int i; + int err = 0; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + for (i = 0; !err && i < len; i += I2C_SMBUS_BLOCK_MAX) + err = adv_smbus_read_i2c_block_data(state->i2c_edid, i, + I2C_SMBUS_BLOCK_MAX, buf + i); + if (err) + v4l2_err(sd, "%s: i2c read error\n", __func__); +} + +static inline int adv7511_cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + return i2c_smbus_read_byte_data(state->i2c_cec, reg); +} + +static int adv7511_cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7511_state *state = get_adv7511_state(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(state->i2c_cec, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "%s: I2C Write Problem\n", __func__); + return ret; +} + +static inline int adv7511_cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return adv7511_cec_write(sd, reg, (adv7511_cec_read(sd, reg) & mask) | val); +} + +static int adv7511_pktmem_rd(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + return adv_smbus_read_byte_data(state->i2c_pktmem, reg); +} + +static int adv7511_pktmem_wr(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7511_state *state = get_adv7511_state(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(state->i2c_pktmem, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "%s: i2c write error\n", __func__); + return ret; +} + +/* To set specific bits in the register, a clear-mask is given (to be AND-ed), + and then the value-mask (to be OR-ed). */ +static inline void adv7511_pktmem_wr_and_or(struct v4l2_subdev *sd, u8 reg, u8 clr_mask, u8 val_mask) +{ + adv7511_pktmem_wr(sd, reg, (adv7511_pktmem_rd(sd, reg) & clr_mask) | val_mask); +} + +static inline bool adv7511_have_hotplug(struct v4l2_subdev *sd) +{ + return adv7511_rd(sd, 0x42) & MASK_ADV7511_HPD_DETECT; +} + +static inline bool adv7511_have_rx_sense(struct v4l2_subdev *sd) +{ + return adv7511_rd(sd, 0x42) & MASK_ADV7511_MSEN_DETECT; +} + +static void adv7511_csc_conversion_mode(struct v4l2_subdev *sd, u8 mode) +{ + adv7511_wr_and_or(sd, 0x18, 0x9f, (mode & 0x3)<<5); +} + +static void adv7511_csc_coeff(struct v4l2_subdev *sd, + u16 A1, u16 A2, u16 A3, u16 A4, + u16 B1, u16 B2, u16 B3, u16 B4, + u16 C1, u16 C2, u16 C3, u16 C4) +{ + /* A */ + adv7511_wr_and_or(sd, 0x18, 0xe0, A1>>8); + adv7511_wr(sd, 0x19, A1); + adv7511_wr_and_or(sd, 0x1A, 0xe0, A2>>8); + adv7511_wr(sd, 0x1B, A2); + adv7511_wr_and_or(sd, 0x1c, 0xe0, A3>>8); + adv7511_wr(sd, 0x1d, A3); + adv7511_wr_and_or(sd, 0x1e, 0xe0, A4>>8); + adv7511_wr(sd, 0x1f, A4); + + /* B */ + adv7511_wr_and_or(sd, 0x20, 0xe0, B1>>8); + adv7511_wr(sd, 0x21, B1); + adv7511_wr_and_or(sd, 0x22, 0xe0, B2>>8); + adv7511_wr(sd, 0x23, B2); + adv7511_wr_and_or(sd, 0x24, 0xe0, B3>>8); + adv7511_wr(sd, 0x25, B3); + adv7511_wr_and_or(sd, 0x26, 0xe0, B4>>8); + adv7511_wr(sd, 0x27, B4); + + /* C */ + adv7511_wr_and_or(sd, 0x28, 0xe0, C1>>8); + adv7511_wr(sd, 0x29, C1); + adv7511_wr_and_or(sd, 0x2A, 0xe0, C2>>8); + adv7511_wr(sd, 0x2B, C2); + adv7511_wr_and_or(sd, 0x2C, 0xe0, C3>>8); + adv7511_wr(sd, 0x2D, C3); + adv7511_wr_and_or(sd, 0x2E, 0xe0, C4>>8); + adv7511_wr(sd, 0x2F, C4); +} + +static void adv7511_csc_rgb_full2limit(struct v4l2_subdev *sd, bool enable) +{ + if (enable) { + u8 csc_mode = 0; + adv7511_csc_conversion_mode(sd, csc_mode); + adv7511_csc_coeff(sd, + 4096-564, 0, 0, 256, + 0, 4096-564, 0, 256, + 0, 0, 4096-564, 256); + /* enable CSC */ + adv7511_wr_and_or(sd, 0x18, 0x7f, 0x80); + /* AVI infoframe: Limited range RGB (16-235) */ + adv7511_wr_and_or(sd, 0x57, 0xf3, 0x04); + } else { + /* disable CSC */ + adv7511_wr_and_or(sd, 0x18, 0x7f, 0x0); + /* AVI infoframe: Full range RGB (0-255) */ + adv7511_wr_and_or(sd, 0x57, 0xf3, 0x08); + } +} + +static void adv7511_set_rgb_quantization_mode(struct v4l2_subdev *sd, struct v4l2_ctrl *ctrl) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + /* Only makes sense for RGB formats */ + if (state->fmt_code != MEDIA_BUS_FMT_RGB888_1X24) { + /* so just keep quantization */ + adv7511_csc_rgb_full2limit(sd, false); + return; + } + + switch (ctrl->val) { + case V4L2_DV_RGB_RANGE_AUTO: + /* automatic */ + if (state->dv_timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) { + /* CE format, RGB limited range (16-235) */ + adv7511_csc_rgb_full2limit(sd, true); + } else { + /* not CE format, RGB full range (0-255) */ + adv7511_csc_rgb_full2limit(sd, false); + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + /* RGB limited range (16-235) */ + adv7511_csc_rgb_full2limit(sd, true); + break; + case V4L2_DV_RGB_RANGE_FULL: + /* RGB full range (0-255) */ + adv7511_csc_rgb_full2limit(sd, false); + break; + } +} + +/* ------------------------------ CTRL OPS ------------------------------ */ + +static int adv7511_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_dbg(1, debug, sd, "%s: ctrl id: %d, ctrl->val %d\n", __func__, ctrl->id, ctrl->val); + + if (state->hdmi_mode_ctrl == ctrl) { + /* Set HDMI or DVI-D */ + adv7511_wr_and_or(sd, 0xaf, 0xfd, ctrl->val == V4L2_DV_TX_MODE_HDMI ? 0x02 : 0x00); + return 0; + } + if (state->rgb_quantization_range_ctrl == ctrl) { + adv7511_set_rgb_quantization_mode(sd, ctrl); + return 0; + } + if (state->content_type_ctrl == ctrl) { + u8 itc, cn; + + state->content_type = ctrl->val; + itc = state->content_type != V4L2_DV_IT_CONTENT_TYPE_NO_ITC; + cn = itc ? state->content_type : V4L2_DV_IT_CONTENT_TYPE_GRAPHICS; + adv7511_wr_and_or(sd, 0x57, 0x7f, itc << 7); + adv7511_wr_and_or(sd, 0x59, 0xcf, cn << 4); + return 0; + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops adv7511_ctrl_ops = { + .s_ctrl = adv7511_s_ctrl, +}; + +/* ---------------------------- CORE OPS ------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static void adv7511_inv_register(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_info(sd, "0x000-0x0ff: Main Map\n"); + if (state->i2c_cec) + v4l2_info(sd, "0x100-0x1ff: CEC Map\n"); +} + +static int adv7511_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + reg->size = 1; + switch (reg->reg >> 8) { + case 0: + reg->val = adv7511_rd(sd, reg->reg & 0xff); + break; + case 1: + if (state->i2c_cec) { + reg->val = adv7511_cec_read(sd, reg->reg & 0xff); + break; + } + fallthrough; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7511_inv_register(sd); + break; + } + return 0; +} + +static int adv7511_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + switch (reg->reg >> 8) { + case 0: + adv7511_wr(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 1: + if (state->i2c_cec) { + adv7511_cec_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + } + fallthrough; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7511_inv_register(sd); + break; + } + return 0; +} +#endif + +struct adv7511_cfg_read_infoframe { + const char *desc; + u8 present_reg; + u8 present_mask; + u8 header[3]; + u16 payload_addr; +}; + +static u8 hdmi_infoframe_checksum(u8 *ptr, size_t size) +{ + u8 csum = 0; + size_t i; + + /* compute checksum */ + for (i = 0; i < size; i++) + csum += ptr[i]; + + return 256 - csum; +} + +static void log_infoframe(struct v4l2_subdev *sd, const struct adv7511_cfg_read_infoframe *cri) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct device *dev = &client->dev; + union hdmi_infoframe frame; + u8 buffer[32]; + u8 len; + int i; + + if (!(adv7511_rd(sd, cri->present_reg) & cri->present_mask)) { + v4l2_info(sd, "%s infoframe not transmitted\n", cri->desc); + return; + } + + memcpy(buffer, cri->header, sizeof(cri->header)); + + len = buffer[2]; + + if (len + 4 > sizeof(buffer)) { + v4l2_err(sd, "%s: invalid %s infoframe length %d\n", __func__, cri->desc, len); + return; + } + + if (cri->payload_addr >= 0x100) { + for (i = 0; i < len; i++) + buffer[i + 4] = adv7511_pktmem_rd(sd, cri->payload_addr + i - 0x100); + } else { + for (i = 0; i < len; i++) + buffer[i + 4] = adv7511_rd(sd, cri->payload_addr + i); + } + buffer[3] = 0; + buffer[3] = hdmi_infoframe_checksum(buffer, len + 4); + + if (hdmi_infoframe_unpack(&frame, buffer, len + 4) < 0) { + v4l2_err(sd, "%s: unpack of %s infoframe failed\n", __func__, cri->desc); + return; + } + + hdmi_infoframe_log(KERN_INFO, dev, &frame); +} + +static void adv7511_log_infoframes(struct v4l2_subdev *sd) +{ + static const struct adv7511_cfg_read_infoframe cri[] = { + { "AVI", 0x44, 0x10, { 0x82, 2, 13 }, 0x55 }, + { "Audio", 0x44, 0x08, { 0x84, 1, 10 }, 0x73 }, + { "SDP", 0x40, 0x40, { 0x83, 1, 25 }, 0x103 }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(cri); i++) + log_infoframe(sd, &cri[i]); +} + +static int adv7511_log_status(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct adv7511_state_edid *edid = &state->edid; + int i; + + static const char * const states[] = { + "in reset", + "reading EDID", + "idle", + "initializing HDCP", + "HDCP enabled", + "initializing HDCP repeater", + "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" + }; + static const char * const errors[] = { + "no error", + "bad receiver BKSV", + "Ri mismatch", + "Pj mismatch", + "i2c error", + "timed out", + "max repeater cascade exceeded", + "hash check failed", + "too many devices", + "9", "A", "B", "C", "D", "E", "F" + }; + + v4l2_info(sd, "power %s\n", state->power_on ? "on" : "off"); + v4l2_info(sd, "%s hotplug, %s Rx Sense, %s EDID (%d block(s))\n", + (adv7511_rd(sd, 0x42) & MASK_ADV7511_HPD_DETECT) ? "detected" : "no", + (adv7511_rd(sd, 0x42) & MASK_ADV7511_MSEN_DETECT) ? "detected" : "no", + edid->segments ? "found" : "no", + edid->blocks); + v4l2_info(sd, "%s output %s\n", + (adv7511_rd(sd, 0xaf) & 0x02) ? + "HDMI" : "DVI-D", + (adv7511_rd(sd, 0xa1) & 0x3c) ? + "disabled" : "enabled"); + v4l2_info(sd, "state: %s, error: %s, detect count: %u, msk/irq: %02x/%02x\n", + states[adv7511_rd(sd, 0xc8) & 0xf], + errors[adv7511_rd(sd, 0xc8) >> 4], state->edid_detect_counter, + adv7511_rd(sd, 0x94), adv7511_rd(sd, 0x96)); + v4l2_info(sd, "RGB quantization: %s range\n", adv7511_rd(sd, 0x18) & 0x80 ? "limited" : "full"); + if (adv7511_rd(sd, 0xaf) & 0x02) { + /* HDMI only */ + u8 manual_cts = adv7511_rd(sd, 0x0a) & 0x80; + u32 N = (adv7511_rd(sd, 0x01) & 0xf) << 16 | + adv7511_rd(sd, 0x02) << 8 | + adv7511_rd(sd, 0x03); + u8 vic_detect = adv7511_rd(sd, 0x3e) >> 2; + u8 vic_sent = adv7511_rd(sd, 0x3d) & 0x3f; + u32 CTS; + + if (manual_cts) + CTS = (adv7511_rd(sd, 0x07) & 0xf) << 16 | + adv7511_rd(sd, 0x08) << 8 | + adv7511_rd(sd, 0x09); + else + CTS = (adv7511_rd(sd, 0x04) & 0xf) << 16 | + adv7511_rd(sd, 0x05) << 8 | + adv7511_rd(sd, 0x06); + v4l2_info(sd, "CTS %s mode: N %d, CTS %d\n", + manual_cts ? "manual" : "automatic", N, CTS); + v4l2_info(sd, "VIC: detected %d, sent %d\n", + vic_detect, vic_sent); + adv7511_log_infoframes(sd); + } + if (state->dv_timings.type == V4L2_DV_BT_656_1120) + v4l2_print_dv_timings(sd->name, "timings: ", + &state->dv_timings, false); + else + v4l2_info(sd, "no timings set\n"); + v4l2_info(sd, "i2c edid addr: 0x%x\n", state->i2c_edid_addr); + + if (state->i2c_cec == NULL) + return 0; + + v4l2_info(sd, "i2c cec addr: 0x%x\n", state->i2c_cec_addr); + + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? + "enabled" : "disabled"); + if (state->cec_enabled_adap) { + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + } + v4l2_info(sd, "i2c pktmem addr: 0x%x\n", state->i2c_pktmem_addr); + return 0; +} + +/* Power up/down adv7511 */ +static int adv7511_s_power(struct v4l2_subdev *sd, int on) +{ + struct adv7511_state *state = get_adv7511_state(sd); + const int retries = 20; + int i; + + v4l2_dbg(1, debug, sd, "%s: power %s\n", __func__, on ? "on" : "off"); + + state->power_on = on; + + if (!on) { + /* Power down */ + adv7511_wr_and_or(sd, 0x41, 0xbf, 0x40); + return true; + } + + /* Power up */ + /* The adv7511 does not always come up immediately. + Retry multiple times. */ + for (i = 0; i < retries; i++) { + adv7511_wr_and_or(sd, 0x41, 0xbf, 0x0); + if ((adv7511_rd(sd, 0x41) & 0x40) == 0) + break; + adv7511_wr_and_or(sd, 0x41, 0xbf, 0x40); + msleep(10); + } + if (i == retries) { + v4l2_dbg(1, debug, sd, "%s: failed to powerup the adv7511!\n", __func__); + adv7511_s_power(sd, 0); + return false; + } + if (i > 1) + v4l2_dbg(1, debug, sd, "%s: needed %d retries to powerup the adv7511\n", __func__, i); + + /* Reserved registers that must be set */ + adv7511_wr(sd, 0x98, 0x03); + adv7511_wr_and_or(sd, 0x9a, 0xfe, 0x70); + adv7511_wr(sd, 0x9c, 0x30); + adv7511_wr_and_or(sd, 0x9d, 0xfc, 0x01); + adv7511_wr(sd, 0xa2, 0xa4); + adv7511_wr(sd, 0xa3, 0xa4); + adv7511_wr(sd, 0xe0, 0xd0); + adv7511_wr(sd, 0xf9, 0x00); + + adv7511_wr(sd, 0x43, state->i2c_edid_addr); + adv7511_wr(sd, 0x45, state->i2c_pktmem_addr); + + /* Set number of attempts to read the EDID */ + adv7511_wr(sd, 0xc9, 0xf); + return true; +} + +#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC) +static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv7511_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + + if (state->i2c_cec == NULL) + return -EIO; + + if (!state->cec_enabled_adap && enable) { + /* power up cec section */ + adv7511_cec_write_and_or(sd, 0x4e, 0xfc, 0x01); + /* legacy mode and clear all rx buffers */ + adv7511_cec_write(sd, 0x4a, 0x00); + adv7511_cec_write(sd, 0x4a, 0x07); + adv7511_cec_write_and_or(sd, 0x11, 0xfe, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready 1 */ + if (state->enabled_irq) + adv7511_wr_and_or(sd, 0x95, 0xc0, 0x39); + } else if (state->cec_enabled_adap && !enable) { + if (state->enabled_irq) + adv7511_wr_and_or(sd, 0x95, 0xc0, 0x00); + /* disable address mask 1-3 */ + adv7511_cec_write_and_or(sd, 0x4b, 0x8f, 0x00); + /* power down cec section */ + adv7511_cec_write_and_or(sd, 0x4e, 0xfc, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + return 0; +} + +static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv7511_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + unsigned int i, free_idx = ADV7511_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + adv7511_cec_write_and_or(sd, 0x4b, 0x8f, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7511_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV7511_MAX_ADDRS) { + i = free_idx; + if (i == ADV7511_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xef, 0x10); + /* set address for mask 0 */ + adv7511_cec_write_and_or(sd, 0x4c, 0xf0, addr); + break; + case 1: + /* enable address mask 1 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xdf, 0x20); + /* set address for mask 1 */ + adv7511_cec_write_and_or(sd, 0x4c, 0x0f, addr << 4); + break; + case 2: + /* enable address mask 2 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xbf, 0x40); + /* set address for mask 1 */ + adv7511_cec_write_and_or(sd, 0x4d, 0xf0, addr); + break; + } + return 0; +} + +static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv7511_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + u8 len = msg->len; + unsigned int i; + + v4l2_dbg(1, debug, sd, "%s: len %d\n", __func__, len); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + adv7511_cec_write_and_or(sd, 0x12, ~0x70, max(1, attempts - 1) << 4); + + /* clear cec tx irq status */ + adv7511_wr(sd, 0x97, 0x38); + + /* write data */ + for (i = 0; i < len; i++) + adv7511_cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + adv7511_cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + adv7511_cec_write(sd, 0x11, 0x01); + return 0; +} + +static void adv_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + if ((adv7511_cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x10) { + v4l2_dbg(1, debug, sd, + "%s: tx raw: arbitration lost\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + return; + } + if (tx_raw_status & 0x08) { + u8 status; + u8 nack_cnt; + u8 low_drive_cnt; + + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + nack_cnt = adv7511_cec_read(sd, 0x14) & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = adv7511_cec_read(sd, 0x14) >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + cec_transmit_done(state->cec_adap, status, + 0, nack_cnt, low_drive_cnt, 0); + return; + } + if (tx_raw_status & 0x20) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + return; + } +} + +static const struct cec_adap_ops adv7511_cec_adap_ops = { + .adap_enable = adv7511_cec_adap_enable, + .adap_log_addr = adv7511_cec_adap_log_addr, + .adap_transmit = adv7511_cec_adap_transmit, +}; +#endif + +/* Enable interrupts */ +static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable) +{ + struct adv7511_state *state = get_adv7511_state(sd); + u8 irqs = MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT; + u8 irqs_rd; + int retries = 100; + + v4l2_dbg(2, debug, sd, "%s: %s\n", __func__, enable ? "enable" : "disable"); + + if (state->enabled_irq == enable) + return; + state->enabled_irq = enable; + + /* The datasheet says that the EDID ready interrupt should be + disabled if there is no hotplug. */ + if (!enable) + irqs = 0; + else if (adv7511_have_hotplug(sd)) + irqs |= MASK_ADV7511_EDID_RDY_INT; + + /* + * This i2c write can fail (approx. 1 in 1000 writes). But it + * is essential that this register is correct, so retry it + * multiple times. + * + * Note that the i2c write does not report an error, but the readback + * clearly shows the wrong value. + */ + do { + adv7511_wr(sd, 0x94, irqs); + irqs_rd = adv7511_rd(sd, 0x94); + } while (retries-- && irqs_rd != irqs); + + if (irqs_rd != irqs) + v4l2_err(sd, "Could not set interrupts: hw failure?\n"); + + adv7511_wr_and_or(sd, 0x95, 0xc0, + (state->cec_enabled_adap && enable) ? 0x39 : 0x00); +} + +/* Interrupt handler */ +static int adv7511_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + u8 irq_status; + u8 cec_irq; + + /* disable interrupts to prevent a race condition */ + adv7511_set_isr(sd, false); + irq_status = adv7511_rd(sd, 0x96); + cec_irq = adv7511_rd(sd, 0x97); + /* clear detected interrupts */ + adv7511_wr(sd, 0x96, irq_status); + adv7511_wr(sd, 0x97, cec_irq); + + v4l2_dbg(1, debug, sd, "%s: irq 0x%x, cec-irq 0x%x\n", __func__, + irq_status, cec_irq); + + if (irq_status & (MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT)) + adv7511_check_monitor_present_status(sd); + if (irq_status & MASK_ADV7511_EDID_RDY_INT) + adv7511_check_edid_status(sd); + +#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC) + if (cec_irq & 0x38) + adv_cec_tx_raw_status(sd, cec_irq); + + if (cec_irq & 1) { + struct adv7511_state *state = get_adv7511_state(sd); + struct cec_msg msg; + + msg.len = adv7511_cec_read(sd, 0x25) & 0x1f; + + v4l2_dbg(1, debug, sd, "%s: cec msg len %d\n", __func__, + msg.len); + + if (msg.len > 16) + msg.len = 16; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = adv7511_cec_read(sd, i + 0x15); + + adv7511_cec_write(sd, 0x4a, 0); /* toggle to re-enable rx 1 */ + adv7511_cec_write(sd, 0x4a, 1); + cec_received_msg(state->cec_adap, &msg); + } + } +#endif + + /* enable interrupts */ + adv7511_set_isr(sd, true); + + if (handled) + *handled = true; + return 0; +} + +static const struct v4l2_subdev_core_ops adv7511_core_ops = { + .log_status = adv7511_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7511_g_register, + .s_register = adv7511_s_register, +#endif + .s_power = adv7511_s_power, + .interrupt_service_routine = adv7511_isr, +}; + +/* ------------------------------ VIDEO OPS ------------------------------ */ + +/* Enable/disable adv7511 output */ +static int adv7511_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); + adv7511_wr_and_or(sd, 0xa1, ~0x3c, (enable ? 0 : 0x3c)); + if (enable) { + adv7511_check_monitor_present_status(sd); + } else { + adv7511_s_power(sd, 0); + state->have_monitor = false; + } + return 0; +} + +static int adv7511_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct v4l2_bt_timings *bt = &timings->bt; + u32 fps; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + /* quick sanity check */ + if (!v4l2_valid_dv_timings(timings, &adv7511_timings_cap, NULL, NULL)) + return -EINVAL; + + /* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is one of the CEA or DMT timings. */ + v4l2_find_dv_timings_cap(timings, &adv7511_timings_cap, 0, NULL, NULL); + + /* save timings */ + state->dv_timings = *timings; + + /* set h/vsync polarities */ + adv7511_wr_and_or(sd, 0x17, 0x9f, + ((bt->polarities & V4L2_DV_VSYNC_POS_POL) ? 0 : 0x40) | + ((bt->polarities & V4L2_DV_HSYNC_POS_POL) ? 0 : 0x20)); + + fps = (u32)bt->pixelclock / (V4L2_DV_BT_FRAME_WIDTH(bt) * V4L2_DV_BT_FRAME_HEIGHT(bt)); + switch (fps) { + case 24: + adv7511_wr_and_or(sd, 0xfb, 0xf9, 1 << 1); + break; + case 25: + adv7511_wr_and_or(sd, 0xfb, 0xf9, 2 << 1); + break; + case 30: + adv7511_wr_and_or(sd, 0xfb, 0xf9, 3 << 1); + break; + default: + adv7511_wr_and_or(sd, 0xfb, 0xf9, 0); + break; + } + + /* update quantization range based on new dv_timings */ + adv7511_set_rgb_quantization_mode(sd, state->rgb_quantization_range_ctrl); + + return 0; +} + +static int adv7511_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (!timings) + return -EINVAL; + + *timings = state->dv_timings; + + return 0; +} + +static int adv7511_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->pad != 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, &adv7511_timings_cap, NULL, NULL); +} + +static int adv7511_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + if (cap->pad != 0) + return -EINVAL; + + *cap = adv7511_timings_cap; + return 0; +} + +static const struct v4l2_subdev_video_ops adv7511_video_ops = { + .s_stream = adv7511_s_stream, + .s_dv_timings = adv7511_s_dv_timings, + .g_dv_timings = adv7511_g_dv_timings, +}; + +/* ------------------------------ AUDIO OPS ------------------------------ */ +static int adv7511_s_audio_stream(struct v4l2_subdev *sd, int enable) +{ + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); + + if (enable) + adv7511_wr_and_or(sd, 0x4b, 0x3f, 0x80); + else + adv7511_wr_and_or(sd, 0x4b, 0x3f, 0x40); + + return 0; +} + +static int adv7511_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + u32 N; + + switch (freq) { + case 32000: N = 4096; break; + case 44100: N = 6272; break; + case 48000: N = 6144; break; + case 88200: N = 12544; break; + case 96000: N = 12288; break; + case 176400: N = 25088; break; + case 192000: N = 24576; break; + default: + return -EINVAL; + } + + /* Set N (used with CTS to regenerate the audio clock) */ + adv7511_wr(sd, 0x01, (N >> 16) & 0xf); + adv7511_wr(sd, 0x02, (N >> 8) & 0xff); + adv7511_wr(sd, 0x03, N & 0xff); + + return 0; +} + +static int adv7511_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + u32 i2s_sf; + + switch (freq) { + case 32000: i2s_sf = 0x30; break; + case 44100: i2s_sf = 0x00; break; + case 48000: i2s_sf = 0x20; break; + case 88200: i2s_sf = 0x80; break; + case 96000: i2s_sf = 0xa0; break; + case 176400: i2s_sf = 0xc0; break; + case 192000: i2s_sf = 0xe0; break; + default: + return -EINVAL; + } + + /* Set sampling frequency for I2S audio to 48 kHz */ + adv7511_wr_and_or(sd, 0x15, 0xf, i2s_sf); + + return 0; +} + +static int adv7511_s_routing(struct v4l2_subdev *sd, u32 input, u32 output, u32 config) +{ + /* Only 2 channels in use for application */ + adv7511_wr_and_or(sd, 0x73, 0xf8, 0x1); + /* Speaker mapping */ + adv7511_wr(sd, 0x76, 0x00); + + /* 16 bit audio word length */ + adv7511_wr_and_or(sd, 0x14, 0xf0, 0x02); + + return 0; +} + +static const struct v4l2_subdev_audio_ops adv7511_audio_ops = { + .s_stream = adv7511_s_audio_stream, + .s_clock_freq = adv7511_s_clock_freq, + .s_i2s_clock_freq = adv7511_s_i2s_clock_freq, + .s_routing = adv7511_s_routing, +}; + +/* ---------------------------- PAD OPS ------------------------------------- */ + +static int adv7511_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->pad != 0) + return -EINVAL; + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = state->edid.segments * 2; + return 0; + } + + if (state->edid.segments == 0) + return -ENODATA; + + if (edid->start_block >= state->edid.segments * 2) + return -EINVAL; + + if (edid->start_block + edid->blocks > state->edid.segments * 2) + edid->blocks = state->edid.segments * 2 - edid->start_block; + + memcpy(edid->edid, &state->edid.data[edid->start_block * 128], + 128 * edid->blocks); + + return 0; +} + +static int adv7511_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad != 0) + return -EINVAL; + + switch (code->index) { + case 0: + code->code = MEDIA_BUS_FMT_RGB888_1X24; + break; + case 1: + code->code = MEDIA_BUS_FMT_YUYV8_1X16; + break; + case 2: + code->code = MEDIA_BUS_FMT_UYVY8_1X16; + break; + default: + return -EINVAL; + } + return 0; +} + +static void adv7511_fill_format(struct adv7511_state *state, + struct v4l2_mbus_framefmt *format) +{ + format->width = state->dv_timings.bt.width; + format->height = state->dv_timings.bt.height; + format->field = V4L2_FIELD_NONE; +} + +static int adv7511_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + if (format->pad != 0) + return -EINVAL; + + memset(&format->format, 0, sizeof(format->format)); + adv7511_fill_format(state, &format->format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + format->format.code = fmt->code; + format->format.colorspace = fmt->colorspace; + format->format.ycbcr_enc = fmt->ycbcr_enc; + format->format.quantization = fmt->quantization; + format->format.xfer_func = fmt->xfer_func; + } else { + format->format.code = state->fmt_code; + format->format.colorspace = state->colorspace; + format->format.ycbcr_enc = state->ycbcr_enc; + format->format.quantization = state->quantization; + format->format.xfer_func = state->xfer_func; + } + + return 0; +} + +static int adv7511_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct adv7511_state *state = get_adv7511_state(sd); + /* + * Bitfield namings come the CEA-861-F standard, table 8 "Auxiliary + * Video Information (AVI) InfoFrame Format" + * + * c = Colorimetry + * ec = Extended Colorimetry + * y = RGB or YCbCr + * q = RGB Quantization Range + * yq = YCC Quantization Range + */ + u8 c = HDMI_COLORIMETRY_NONE; + u8 ec = HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + u8 y = HDMI_COLORSPACE_RGB; + u8 q = HDMI_QUANTIZATION_RANGE_DEFAULT; + u8 yq = HDMI_YCC_QUANTIZATION_RANGE_LIMITED; + u8 itc = state->content_type != V4L2_DV_IT_CONTENT_TYPE_NO_ITC; + u8 cn = itc ? state->content_type : V4L2_DV_IT_CONTENT_TYPE_GRAPHICS; + + if (format->pad != 0) + return -EINVAL; + switch (format->format.code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_RGB888_1X24: + break; + default: + return -EINVAL; + } + + adv7511_fill_format(state, &format->format); + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + fmt->code = format->format.code; + fmt->colorspace = format->format.colorspace; + fmt->ycbcr_enc = format->format.ycbcr_enc; + fmt->quantization = format->format.quantization; + fmt->xfer_func = format->format.xfer_func; + return 0; + } + + switch (format->format.code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + adv7511_wr_and_or(sd, 0x15, 0xf0, 0x01); + adv7511_wr_and_or(sd, 0x16, 0x03, 0xb8); + y = HDMI_COLORSPACE_YUV422; + break; + case MEDIA_BUS_FMT_YUYV8_1X16: + adv7511_wr_and_or(sd, 0x15, 0xf0, 0x01); + adv7511_wr_and_or(sd, 0x16, 0x03, 0xbc); + y = HDMI_COLORSPACE_YUV422; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + default: + adv7511_wr_and_or(sd, 0x15, 0xf0, 0x00); + adv7511_wr_and_or(sd, 0x16, 0x03, 0x00); + break; + } + state->fmt_code = format->format.code; + state->colorspace = format->format.colorspace; + state->ycbcr_enc = format->format.ycbcr_enc; + state->quantization = format->format.quantization; + state->xfer_func = format->format.xfer_func; + + switch (format->format.colorspace) { + case V4L2_COLORSPACE_OPRGB: + c = HDMI_COLORIMETRY_EXTENDED; + ec = y ? HDMI_EXTENDED_COLORIMETRY_OPYCC_601 : + HDMI_EXTENDED_COLORIMETRY_OPRGB; + break; + case V4L2_COLORSPACE_SMPTE170M: + c = y ? HDMI_COLORIMETRY_ITU_601 : HDMI_COLORIMETRY_NONE; + if (y && format->format.ycbcr_enc == V4L2_YCBCR_ENC_XV601) { + c = HDMI_COLORIMETRY_EXTENDED; + ec = HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + } + break; + case V4L2_COLORSPACE_REC709: + c = y ? HDMI_COLORIMETRY_ITU_709 : HDMI_COLORIMETRY_NONE; + if (y && format->format.ycbcr_enc == V4L2_YCBCR_ENC_XV709) { + c = HDMI_COLORIMETRY_EXTENDED; + ec = HDMI_EXTENDED_COLORIMETRY_XV_YCC_709; + } + break; + case V4L2_COLORSPACE_SRGB: + c = y ? HDMI_COLORIMETRY_EXTENDED : HDMI_COLORIMETRY_NONE; + ec = y ? HDMI_EXTENDED_COLORIMETRY_S_YCC_601 : + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + break; + case V4L2_COLORSPACE_BT2020: + c = HDMI_COLORIMETRY_EXTENDED; + if (y && format->format.ycbcr_enc == V4L2_YCBCR_ENC_BT2020_CONST_LUM) + ec = 5; /* Not yet available in hdmi.h */ + else + ec = 6; /* Not yet available in hdmi.h */ + break; + default: + break; + } + + /* + * CEA-861-F says that for RGB formats the YCC range must match the + * RGB range, although sources should ignore the YCC range. + * + * The RGB quantization range shouldn't be non-zero if the EDID doesn't + * have the Q bit set in the Video Capabilities Data Block, however this + * isn't checked at the moment. The assumption is that the application + * knows the EDID and can detect this. + * + * The same is true for the YCC quantization range: non-standard YCC + * quantization ranges should only be sent if the EDID has the YQ bit + * set in the Video Capabilities Data Block. + */ + switch (format->format.quantization) { + case V4L2_QUANTIZATION_FULL_RANGE: + q = y ? HDMI_QUANTIZATION_RANGE_DEFAULT : + HDMI_QUANTIZATION_RANGE_FULL; + yq = q ? q - 1 : HDMI_YCC_QUANTIZATION_RANGE_FULL; + break; + case V4L2_QUANTIZATION_LIM_RANGE: + q = y ? HDMI_QUANTIZATION_RANGE_DEFAULT : + HDMI_QUANTIZATION_RANGE_LIMITED; + yq = q ? q - 1 : HDMI_YCC_QUANTIZATION_RANGE_LIMITED; + break; + } + + adv7511_wr_and_or(sd, 0x4a, 0xbf, 0); + adv7511_wr_and_or(sd, 0x55, 0x9f, y << 5); + adv7511_wr_and_or(sd, 0x56, 0x3f, c << 6); + adv7511_wr_and_or(sd, 0x57, 0x83, (ec << 4) | (q << 2) | (itc << 7)); + adv7511_wr_and_or(sd, 0x59, 0x0f, (yq << 6) | (cn << 4)); + adv7511_wr_and_or(sd, 0x4a, 0xff, 1); + adv7511_set_rgb_quantization_mode(sd, state->rgb_quantization_range_ctrl); + + return 0; +} + +static const struct v4l2_subdev_pad_ops adv7511_pad_ops = { + .get_edid = adv7511_get_edid, + .enum_mbus_code = adv7511_enum_mbus_code, + .get_fmt = adv7511_get_fmt, + .set_fmt = adv7511_set_fmt, + .enum_dv_timings = adv7511_enum_dv_timings, + .dv_timings_cap = adv7511_dv_timings_cap, +}; + +/* --------------------- SUBDEV OPS --------------------------------------- */ + +static const struct v4l2_subdev_ops adv7511_ops = { + .core = &adv7511_core_ops, + .pad = &adv7511_pad_ops, + .video = &adv7511_video_ops, + .audio = &adv7511_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ +static void adv7511_dbg_dump_edid(int lvl, int debug, struct v4l2_subdev *sd, int segment, u8 *buf) +{ + if (debug >= lvl) { + int i, j; + v4l2_dbg(lvl, debug, sd, "edid segment %d\n", segment); + for (i = 0; i < 256; i += 16) { + u8 b[128]; + u8 *bp = b; + if (i == 128) + v4l2_dbg(lvl, debug, sd, "\n"); + for (j = i; j < i + 16; j++) { + sprintf(bp, "0x%02x, ", buf[j]); + bp += 6; + } + bp[0] = '\0'; + v4l2_dbg(lvl, debug, sd, "%s\n", b); + } + } +} + +static void adv7511_notify_no_edid(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct adv7511_edid_detect ed; + + /* We failed to read the EDID, so send an event for this. */ + ed.present = false; + ed.segment = adv7511_rd(sd, 0xc4); + ed.phys_addr = CEC_PHYS_ADDR_INVALID; + cec_s_phys_addr(state->cec_adap, ed.phys_addr, false); + v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed); + v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, 0x0); +} + +static void adv7511_edid_handler(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adv7511_state *state = container_of(dwork, struct adv7511_state, edid_handler); + struct v4l2_subdev *sd = &state->sd; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (adv7511_check_edid_status(sd)) { + /* Return if we received the EDID. */ + return; + } + + if (adv7511_have_hotplug(sd)) { + /* We must retry reading the EDID several times, it is possible + * that initially the EDID couldn't be read due to i2c errors + * (DVI connectors are particularly prone to this problem). */ + if (state->edid.read_retries) { + state->edid.read_retries--; + v4l2_dbg(1, debug, sd, "%s: edid read failed\n", __func__); + state->have_monitor = false; + adv7511_s_power(sd, false); + adv7511_s_power(sd, true); + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + return; + } + } + + /* We failed to read the EDID, so send an event for this. */ + adv7511_notify_no_edid(sd); + v4l2_dbg(1, debug, sd, "%s: no edid found\n", __func__); +} + +static void adv7511_audio_setup(struct v4l2_subdev *sd) +{ + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + adv7511_s_i2s_clock_freq(sd, 48000); + adv7511_s_clock_freq(sd, 48000); + adv7511_s_routing(sd, 0, 0, 0); +} + +/* Configure hdmi transmitter. */ +static void adv7511_setup(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* Input format: RGB 4:4:4 */ + adv7511_wr_and_or(sd, 0x15, 0xf0, 0x0); + /* Output format: RGB 4:4:4 */ + adv7511_wr_and_or(sd, 0x16, 0x7f, 0x0); + /* 1st order interpolation 4:2:2 -> 4:4:4 up conversion, Aspect ratio: 16:9 */ + adv7511_wr_and_or(sd, 0x17, 0xf9, 0x06); + /* Disable pixel repetition */ + adv7511_wr_and_or(sd, 0x3b, 0x9f, 0x0); + /* Disable CSC */ + adv7511_wr_and_or(sd, 0x18, 0x7f, 0x0); + /* Output format: RGB 4:4:4, Active Format Information is valid, + * underscanned */ + adv7511_wr_and_or(sd, 0x55, 0x9c, 0x12); + /* AVI Info frame packet enable, Audio Info frame disable */ + adv7511_wr_and_or(sd, 0x44, 0xe7, 0x10); + /* Colorimetry, Active format aspect ratio: same as picure. */ + adv7511_wr(sd, 0x56, 0xa8); + /* No encryption */ + adv7511_wr_and_or(sd, 0xaf, 0xed, 0x0); + + /* Positive clk edge capture for input video clock */ + adv7511_wr_and_or(sd, 0xba, 0x1f, 0x60); + + adv7511_audio_setup(sd); + + v4l2_ctrl_handler_setup(&state->hdl); +} + +static void adv7511_notify_monitor_detect(struct v4l2_subdev *sd) +{ + struct adv7511_monitor_detect mdt; + struct adv7511_state *state = get_adv7511_state(sd); + + mdt.present = state->have_monitor; + v4l2_subdev_notify(sd, ADV7511_MONITOR_DETECT, (void *)&mdt); +} + +static void adv7511_check_monitor_present_status(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + /* read hotplug and rx-sense state */ + u8 status = adv7511_rd(sd, 0x42); + + v4l2_dbg(1, debug, sd, "%s: status: 0x%x%s%s\n", + __func__, + status, + status & MASK_ADV7511_HPD_DETECT ? ", hotplug" : "", + status & MASK_ADV7511_MSEN_DETECT ? ", rx-sense" : ""); + + /* update read only ctrls */ + v4l2_ctrl_s_ctrl(state->hotplug_ctrl, adv7511_have_hotplug(sd) ? 0x1 : 0x0); + v4l2_ctrl_s_ctrl(state->rx_sense_ctrl, adv7511_have_rx_sense(sd) ? 0x1 : 0x0); + + if ((status & MASK_ADV7511_HPD_DETECT) && ((status & MASK_ADV7511_MSEN_DETECT) || state->edid.segments)) { + v4l2_dbg(1, debug, sd, "%s: hotplug and (rx-sense or edid)\n", __func__); + if (!state->have_monitor) { + v4l2_dbg(1, debug, sd, "%s: monitor detected\n", __func__); + state->have_monitor = true; + adv7511_set_isr(sd, true); + if (!adv7511_s_power(sd, true)) { + v4l2_dbg(1, debug, sd, "%s: monitor detected, powerup failed\n", __func__); + return; + } + adv7511_setup(sd); + adv7511_notify_monitor_detect(sd); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + } + } else if (status & MASK_ADV7511_HPD_DETECT) { + v4l2_dbg(1, debug, sd, "%s: hotplug detected\n", __func__); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + } else if (!(status & MASK_ADV7511_HPD_DETECT)) { + v4l2_dbg(1, debug, sd, "%s: hotplug not detected\n", __func__); + if (state->have_monitor) { + v4l2_dbg(1, debug, sd, "%s: monitor not detected\n", __func__); + state->have_monitor = false; + adv7511_notify_monitor_detect(sd); + } + adv7511_s_power(sd, false); + memset(&state->edid, 0, sizeof(struct adv7511_state_edid)); + adv7511_notify_no_edid(sd); + } +} + +static bool edid_block_verify_crc(u8 *edid_block) +{ + u8 sum = 0; + int i; + + for (i = 0; i < 128; i++) + sum += edid_block[i]; + return sum == 0; +} + +static bool edid_verify_crc(struct v4l2_subdev *sd, u32 segment) +{ + struct adv7511_state *state = get_adv7511_state(sd); + u32 blocks = state->edid.blocks; + u8 *data = state->edid.data; + + if (!edid_block_verify_crc(&data[segment * 256])) + return false; + if ((segment + 1) * 2 <= blocks) + return edid_block_verify_crc(&data[segment * 256 + 128]); + return true; +} + +static bool edid_verify_header(struct v4l2_subdev *sd, u32 segment) +{ + static const u8 hdmi_header[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 + }; + struct adv7511_state *state = get_adv7511_state(sd); + u8 *data = state->edid.data; + + if (segment != 0) + return true; + return !memcmp(data, hdmi_header, sizeof(hdmi_header)); +} + +static bool adv7511_check_edid_status(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + u8 edidRdy = adv7511_rd(sd, 0xc5); + + v4l2_dbg(1, debug, sd, "%s: edid ready (retries: %d)\n", + __func__, EDID_MAX_RETRIES - state->edid.read_retries); + + if (state->edid.complete) + return true; + + if (edidRdy & MASK_ADV7511_EDID_RDY) { + int segment = adv7511_rd(sd, 0xc4); + struct adv7511_edid_detect ed; + + if (segment >= EDID_MAX_SEGM) { + v4l2_err(sd, "edid segment number too big\n"); + return false; + } + v4l2_dbg(1, debug, sd, "%s: got segment %d\n", __func__, segment); + adv7511_edid_rd(sd, 256, &state->edid.data[segment * 256]); + adv7511_dbg_dump_edid(2, debug, sd, segment, &state->edid.data[segment * 256]); + if (segment == 0) { + state->edid.blocks = state->edid.data[0x7e] + 1; + v4l2_dbg(1, debug, sd, "%s: %d blocks in total\n", __func__, state->edid.blocks); + } + if (!edid_verify_crc(sd, segment) || + !edid_verify_header(sd, segment)) { + /* edid crc error, force reread of edid segment */ + v4l2_err(sd, "%s: edid crc or header error\n", __func__); + state->have_monitor = false; + adv7511_s_power(sd, false); + adv7511_s_power(sd, true); + return false; + } + /* one more segment read ok */ + state->edid.segments = segment + 1; + v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, 0x1); + if (((state->edid.data[0x7e] >> 1) + 1) > state->edid.segments) { + /* Request next EDID segment */ + v4l2_dbg(1, debug, sd, "%s: request segment %d\n", __func__, state->edid.segments); + adv7511_wr(sd, 0xc9, 0xf); + adv7511_wr(sd, 0xc4, state->edid.segments); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + return false; + } + + v4l2_dbg(1, debug, sd, "%s: edid complete with %d segment(s)\n", __func__, state->edid.segments); + state->edid.complete = true; + ed.phys_addr = cec_get_edid_phys_addr(state->edid.data, + state->edid.segments * 256, + NULL); + /* report when we have all segments + but report only for segment 0 + */ + ed.present = true; + ed.segment = 0; + state->edid_detect_counter++; + cec_s_phys_addr(state->cec_adap, ed.phys_addr, false); + v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed); + return ed.present; + } + + return false; +} + +static int adv7511_registered(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err; + + err = cec_register_adapter(state->cec_adap, &client->dev); + if (err) + cec_delete_adapter(state->cec_adap); + return err; +} + +static void adv7511_unregistered(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + cec_unregister_adapter(state->cec_adap); +} + +static const struct v4l2_subdev_internal_ops adv7511_int_ops = { + .registered = adv7511_registered, + .unregistered = adv7511_unregistered, +}; + +/* ----------------------------------------------------------------------- */ +/* Setup ADV7511 */ +static void adv7511_init_setup(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct adv7511_state_edid *edid = &state->edid; + u32 cec_clk = state->pdata.cec_clk; + u8 ratio; + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* clear all interrupts */ + adv7511_wr(sd, 0x96, 0xff); + adv7511_wr(sd, 0x97, 0xff); + /* + * Stop HPD from resetting a lot of registers. + * It might leave the chip in a partly un-initialized state, + * in particular with regards to hotplug bounces. + */ + adv7511_wr_and_or(sd, 0xd6, 0x3f, 0xc0); + memset(edid, 0, sizeof(struct adv7511_state_edid)); + state->have_monitor = false; + adv7511_set_isr(sd, false); + adv7511_s_stream(sd, false); + adv7511_s_audio_stream(sd, false); + + if (state->i2c_cec == NULL) + return; + + v4l2_dbg(1, debug, sd, "%s: cec_clk %d\n", __func__, cec_clk); + + /* cec soft reset */ + adv7511_cec_write(sd, 0x50, 0x01); + adv7511_cec_write(sd, 0x50, 0x00); + + /* legacy mode */ + adv7511_cec_write(sd, 0x4a, 0x00); + adv7511_cec_write(sd, 0x4a, 0x07); + + if (cec_clk % 750000 != 0) + v4l2_err(sd, "%s: cec_clk %d, not multiple of 750 Khz\n", + __func__, cec_clk); + + ratio = (cec_clk / 750000) - 1; + adv7511_cec_write(sd, 0x4e, ratio << 2); +} + +static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct adv7511_state *state; + struct adv7511_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; + struct v4l2_subdev *sd; + u8 chip_id[2]; + int err = -EIO; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + state = devm_kzalloc(&client->dev, sizeof(struct adv7511_state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + /* Platform data */ + if (!pdata) { + v4l_err(client, "No platform data!\n"); + return -ENODEV; + } + memcpy(&state->pdata, pdata, sizeof(state->pdata)); + state->fmt_code = MEDIA_BUS_FMT_RGB888_1X24; + state->colorspace = V4L2_COLORSPACE_SRGB; + + sd = &state->sd; + + v4l2_dbg(1, debug, sd, "detecting adv7511 client on address 0x%x\n", + client->addr << 1); + + v4l2_i2c_subdev_init(sd, client, &adv7511_ops); + sd->internal_ops = &adv7511_int_ops; + + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 10); + /* add in ascending ID order */ + state->hdmi_mode_ctrl = v4l2_ctrl_new_std_menu(hdl, &adv7511_ctrl_ops, + V4L2_CID_DV_TX_MODE, V4L2_DV_TX_MODE_HDMI, + 0, V4L2_DV_TX_MODE_DVI_D); + state->hotplug_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_HOTPLUG, 0, 1, 0, 0); + state->rx_sense_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_RXSENSE, 0, 1, 0, 0); + state->have_edid0_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_EDID_PRESENT, 0, 1, 0, 0); + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv7511_ctrl_ops, + V4L2_CID_DV_TX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + state->content_type_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv7511_ctrl_ops, + V4L2_CID_DV_TX_IT_CONTENT_TYPE, V4L2_DV_IT_CONTENT_TYPE_NO_ITC, + 0, V4L2_DV_IT_CONTENT_TYPE_NO_ITC); + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + goto err_hdl; + } + state->pad.flags = MEDIA_PAD_FL_SINK; + sd->entity.function = MEDIA_ENT_F_DV_ENCODER; + err = media_entity_pads_init(&sd->entity, 1, &state->pad); + if (err) + goto err_hdl; + + /* EDID and CEC i2c addr */ + state->i2c_edid_addr = state->pdata.i2c_edid << 1; + state->i2c_cec_addr = state->pdata.i2c_cec << 1; + state->i2c_pktmem_addr = state->pdata.i2c_pktmem << 1; + + state->chip_revision = adv7511_rd(sd, 0x0); + chip_id[0] = adv7511_rd(sd, 0xf5); + chip_id[1] = adv7511_rd(sd, 0xf6); + if (chip_id[0] != 0x75 || chip_id[1] != 0x11) { + v4l2_err(sd, "chip_id != 0x7511, read 0x%02x%02x\n", chip_id[0], + chip_id[1]); + err = -EIO; + goto err_entity; + } + + state->i2c_edid = i2c_new_dummy_device(client->adapter, + state->i2c_edid_addr >> 1); + if (IS_ERR(state->i2c_edid)) { + v4l2_err(sd, "failed to register edid i2c client\n"); + err = PTR_ERR(state->i2c_edid); + goto err_entity; + } + + adv7511_wr(sd, 0xe1, state->i2c_cec_addr); + if (state->pdata.cec_clk < 3000000 || + state->pdata.cec_clk > 100000000) { + v4l2_err(sd, "%s: cec_clk %u outside range, disabling cec\n", + __func__, state->pdata.cec_clk); + state->pdata.cec_clk = 0; + } + + if (state->pdata.cec_clk) { + state->i2c_cec = i2c_new_dummy_device(client->adapter, + state->i2c_cec_addr >> 1); + if (IS_ERR(state->i2c_cec)) { + v4l2_err(sd, "failed to register cec i2c client\n"); + err = PTR_ERR(state->i2c_cec); + goto err_unreg_edid; + } + adv7511_wr(sd, 0xe2, 0x00); /* power up cec section */ + } else { + adv7511_wr(sd, 0xe2, 0x01); /* power down cec section */ + } + + state->i2c_pktmem = i2c_new_dummy_device(client->adapter, state->i2c_pktmem_addr >> 1); + if (IS_ERR(state->i2c_pktmem)) { + v4l2_err(sd, "failed to register pktmem i2c client\n"); + err = PTR_ERR(state->i2c_pktmem); + goto err_unreg_cec; + } + + state->work_queue = create_singlethread_workqueue(sd->name); + if (state->work_queue == NULL) { + v4l2_err(sd, "could not create workqueue\n"); + err = -ENOMEM; + goto err_unreg_pktmem; + } + + INIT_DELAYED_WORK(&state->edid_handler, adv7511_edid_handler); + + adv7511_init_setup(sd); + +#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC) + state->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops, + state, dev_name(&client->dev), CEC_CAP_DEFAULTS, + ADV7511_MAX_ADDRS); + err = PTR_ERR_OR_ZERO(state->cec_adap); + if (err) { + destroy_workqueue(state->work_queue); + goto err_unreg_pktmem; + } +#endif + + adv7511_set_isr(sd, true); + adv7511_check_monitor_present_status(sd); + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + return 0; + +err_unreg_pktmem: + i2c_unregister_device(state->i2c_pktmem); +err_unreg_cec: + i2c_unregister_device(state->i2c_cec); +err_unreg_edid: + i2c_unregister_device(state->i2c_edid); +err_entity: + media_entity_cleanup(&sd->entity); +err_hdl: + v4l2_ctrl_handler_free(&state->hdl); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static int adv7511_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7511_state *state = get_adv7511_state(sd); + + state->chip_revision = -1; + + v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + adv7511_set_isr(sd, false); + adv7511_init_setup(sd); + cancel_delayed_work_sync(&state->edid_handler); + i2c_unregister_device(state->i2c_edid); + i2c_unregister_device(state->i2c_cec); + i2c_unregister_device(state->i2c_pktmem); + destroy_workqueue(state->work_queue); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id adv7511_id[] = { + { "adv7511-v4l2", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7511_id); + +static struct i2c_driver adv7511_driver = { + .driver = { + .name = "adv7511-v4l2", + }, + .probe = adv7511_probe, + .remove = adv7511_remove, + .id_table = adv7511_id, +}; + +module_i2c_driver(adv7511_driver); diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c new file mode 100644 index 000000000..8cf170430 --- /dev/null +++ b/drivers/media/i2c/adv7604.c @@ -0,0 +1,3639 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adv7604 - Analog Devices ADV7604 video decoder driver + * + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + */ + +/* + * References (c = chapter, p = page): + * REF_01 - Analog devices, ADV7604, Register Settings Recommendations, + * Revision 2.5, June 2010 + * REF_02 - Analog devices, Register map documentation, Documentation of + * the register maps, Software manual, Rev. F, June 2010 + * REF_03 - Analog devices, ADV7604, Hardware Manual, Rev. F, August 2010 + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/hdmi.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/slab.h> +#include <linux/v4l2-dv-timings.h> +#include <linux/videodev2.h> +#include <linux/workqueue.h> +#include <linux/regmap.h> +#include <linux/interrupt.h> + +#include <media/i2c/adv7604.h> +#include <media/cec.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-fwnode.h> + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices ADV7604 video decoder driver"); +MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>"); +MODULE_AUTHOR("Mats Randgaard <mats.randgaard@cisco.com>"); +MODULE_LICENSE("GPL"); + +/* ADV7604 system clock frequency */ +#define ADV76XX_FSC (28636360) + +#define ADV76XX_RGB_OUT (1 << 1) + +#define ADV76XX_OP_FORMAT_SEL_8BIT (0 << 0) +#define ADV7604_OP_FORMAT_SEL_10BIT (1 << 0) +#define ADV76XX_OP_FORMAT_SEL_12BIT (2 << 0) + +#define ADV76XX_OP_MODE_SEL_SDR_422 (0 << 5) +#define ADV7604_OP_MODE_SEL_DDR_422 (1 << 5) +#define ADV76XX_OP_MODE_SEL_SDR_444 (2 << 5) +#define ADV7604_OP_MODE_SEL_DDR_444 (3 << 5) +#define ADV76XX_OP_MODE_SEL_SDR_422_2X (4 << 5) +#define ADV7604_OP_MODE_SEL_ADI_CM (5 << 5) + +#define ADV76XX_OP_CH_SEL_GBR (0 << 5) +#define ADV76XX_OP_CH_SEL_GRB (1 << 5) +#define ADV76XX_OP_CH_SEL_BGR (2 << 5) +#define ADV76XX_OP_CH_SEL_RGB (3 << 5) +#define ADV76XX_OP_CH_SEL_BRG (4 << 5) +#define ADV76XX_OP_CH_SEL_RBG (5 << 5) + +#define ADV76XX_OP_SWAP_CB_CR (1 << 0) + +#define ADV76XX_MAX_ADDRS (3) + +enum adv76xx_type { + ADV7604, + ADV7611, + ADV7612, +}; + +struct adv76xx_reg_seq { + unsigned int reg; + u8 val; +}; + +struct adv76xx_format_info { + u32 code; + u8 op_ch_sel; + bool rgb_out; + bool swap_cb_cr; + u8 op_format_sel; +}; + +struct adv76xx_cfg_read_infoframe { + const char *desc; + u8 present_mask; + u8 head_addr; + u8 payload_addr; +}; + +struct adv76xx_chip_info { + enum adv76xx_type type; + + bool has_afe; + unsigned int max_port; + unsigned int num_dv_ports; + + unsigned int edid_enable_reg; + unsigned int edid_status_reg; + unsigned int lcf_reg; + + unsigned int cable_det_mask; + unsigned int tdms_lock_mask; + unsigned int fmt_change_digital_mask; + unsigned int cp_csc; + + unsigned int cec_irq_status; + unsigned int cec_rx_enable; + unsigned int cec_rx_enable_mask; + bool cec_irq_swap; + + const struct adv76xx_format_info *formats; + unsigned int nformats; + + void (*set_termination)(struct v4l2_subdev *sd, bool enable); + void (*setup_irqs)(struct v4l2_subdev *sd); + unsigned int (*read_hdmi_pixelclock)(struct v4l2_subdev *sd); + unsigned int (*read_cable_det)(struct v4l2_subdev *sd); + + /* 0 = AFE, 1 = HDMI */ + const struct adv76xx_reg_seq *recommended_settings[2]; + unsigned int num_recommended_settings[2]; + + unsigned long page_mask; + + /* Masks for timings */ + unsigned int linewidth_mask; + unsigned int field0_height_mask; + unsigned int field1_height_mask; + unsigned int hfrontporch_mask; + unsigned int hsync_mask; + unsigned int hbackporch_mask; + unsigned int field0_vfrontporch_mask; + unsigned int field1_vfrontporch_mask; + unsigned int field0_vsync_mask; + unsigned int field1_vsync_mask; + unsigned int field0_vbackporch_mask; + unsigned int field1_vbackporch_mask; +}; + +/* + ********************************************************************** + * + * Arrays with configuration parameters for the ADV7604 + * + ********************************************************************** + */ + +struct adv76xx_state { + const struct adv76xx_chip_info *info; + struct adv76xx_platform_data pdata; + + struct gpio_desc *hpd_gpio[4]; + struct gpio_desc *reset_gpio; + + struct v4l2_subdev sd; + struct media_pad pads[ADV76XX_PAD_MAX]; + unsigned int source_pad; + + struct v4l2_ctrl_handler hdl; + + enum adv76xx_pad selected_input; + + struct v4l2_dv_timings timings; + const struct adv76xx_format_info *format; + + struct { + u8 edid[256]; + u32 present; + unsigned blocks; + } edid; + u16 spa_port_a[2]; + struct v4l2_fract aspect_ratio; + u32 rgb_quantization_range; + struct delayed_work delayed_work_enable_hotplug; + bool restart_stdi_once; + + /* CEC */ + struct cec_adapter *cec_adap; + u8 cec_addr[ADV76XX_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + + /* i2c clients */ + struct i2c_client *i2c_clients[ADV76XX_PAGE_MAX]; + + /* Regmaps */ + struct regmap *regmap[ADV76XX_PAGE_MAX]; + + /* controls */ + struct v4l2_ctrl *detect_tx_5v_ctrl; + struct v4l2_ctrl *analog_sampling_phase_ctrl; + struct v4l2_ctrl *free_run_color_manual_ctrl; + struct v4l2_ctrl *free_run_color_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; +}; + +static bool adv76xx_has_afe(struct adv76xx_state *state) +{ + return state->info->has_afe; +} + +/* Unsupported timings. This device cannot support 720p30. */ +static const struct v4l2_dv_timings adv76xx_timings_exceptions[] = { + V4L2_DV_BT_CEA_1280X720P30, + { } +}; + +static bool adv76xx_check_dv_timings(const struct v4l2_dv_timings *t, void *hdl) +{ + int i; + + for (i = 0; adv76xx_timings_exceptions[i].bt.width; i++) + if (v4l2_match_dv_timings(t, adv76xx_timings_exceptions + i, 0, false)) + return false; + return true; +} + +struct adv76xx_video_standards { + struct v4l2_dv_timings timings; + u8 vid_std; + u8 v_freq; +}; + +/* sorted by number of lines */ +static const struct adv76xx_video_standards adv7604_prim_mode_comp[] = { + /* { V4L2_DV_BT_CEA_720X480P59_94, 0x0a, 0x00 }, TODO flickering */ + { V4L2_DV_BT_CEA_720X576P50, 0x0b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x19, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x19, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x1e, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x1e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x1e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x1e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x1e, 0x00 }, + /* TODO add 1920x1080P60_RB (CVT timing) */ + { }, +}; + +/* sorted by number of lines */ +static const struct adv76xx_video_standards adv7604_prim_mode_gr[] = { + { V4L2_DV_BT_DMT_640X480P60, 0x08, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x09, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x0a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x0b, 0x00 }, + { V4L2_DV_BT_DMT_800X600P56, 0x00, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x01, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x02, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x03, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x04, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P60, 0x0c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x0d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x0e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x0f, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P60, 0x05, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x06, 0x00 }, + { V4L2_DV_BT_DMT_1360X768P60, 0x12, 0x00 }, + { V4L2_DV_BT_DMT_1366X768P60, 0x13, 0x00 }, + { V4L2_DV_BT_DMT_1400X1050P60, 0x14, 0x00 }, + { V4L2_DV_BT_DMT_1400X1050P75, 0x15, 0x00 }, + { V4L2_DV_BT_DMT_1600X1200P60, 0x16, 0x00 }, /* TODO not tested */ + /* TODO add 1600X1200P60_RB (not a DMT timing) */ + { V4L2_DV_BT_DMT_1680X1050P60, 0x18, 0x00 }, + { V4L2_DV_BT_DMT_1920X1200P60_RB, 0x19, 0x00 }, /* TODO not tested */ + { }, +}; + +/* sorted by number of lines */ +static const struct adv76xx_video_standards adv76xx_prim_mode_hdmi_comp[] = { + { V4L2_DV_BT_CEA_720X480P59_94, 0x0a, 0x00 }, + { V4L2_DV_BT_CEA_720X576P50, 0x0b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x13, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x13, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x1e, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x1e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x1e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x1e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x1e, 0x00 }, + { }, +}; + +/* sorted by number of lines */ +static const struct adv76xx_video_standards adv76xx_prim_mode_hdmi_gr[] = { + { V4L2_DV_BT_DMT_640X480P60, 0x08, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x09, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x0a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x0b, 0x00 }, + { V4L2_DV_BT_DMT_800X600P56, 0x00, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x01, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x02, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x03, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x04, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P60, 0x0c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x0d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x0e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x0f, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P60, 0x05, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x06, 0x00 }, + { }, +}; + +static const struct v4l2_event adv76xx_ev_fmt = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, +}; + +/* ----------------------------------------------------------------------- */ + +static inline struct adv76xx_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv76xx_state, sd); +} + +static inline unsigned htotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_WIDTH(t); +} + +static inline unsigned vtotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_HEIGHT(t); +} + +/* ----------------------------------------------------------------------- */ + +static int adv76xx_read_check(struct adv76xx_state *state, + int client_page, u8 reg) +{ + struct i2c_client *client = state->i2c_clients[client_page]; + int err; + unsigned int val; + + err = regmap_read(state->regmap[client_page], reg, &val); + + if (err) { + v4l_err(client, "error reading %02x, %02x\n", + client->addr, reg); + return err; + } + return val; +} + +/* adv76xx_write_block(): Write raw data with a maximum of I2C_SMBUS_BLOCK_MAX + * size to one or more registers. + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +static int adv76xx_write_block(struct adv76xx_state *state, int client_page, + unsigned int init_reg, const void *val, + size_t val_len) +{ + struct regmap *regmap = state->regmap[client_page]; + + if (val_len > I2C_SMBUS_BLOCK_MAX) + val_len = I2C_SMBUS_BLOCK_MAX; + + return regmap_raw_write(regmap, init_reg, val, val_len); +} + +/* ----------------------------------------------------------------------- */ + +static inline int io_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_IO, reg); +} + +static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_IO], reg, val); +} + +static inline int io_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return io_write(sd, reg, (io_read(sd, reg) & ~mask) | val); +} + +static inline int avlink_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV7604_PAGE_AVLINK, reg); +} + +static inline int avlink_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV7604_PAGE_AVLINK], reg, val); +} + +static inline int cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_CEC, reg); +} + +static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_CEC], reg, val); +} + +static inline int cec_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return cec_write(sd, reg, (cec_read(sd, reg) & ~mask) | val); +} + +static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_INFOFRAME, reg); +} + +static inline int infoframe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_INFOFRAME], reg, val); +} + +static inline int afe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_AFE, reg); +} + +static inline int afe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_AFE], reg, val); +} + +static inline int rep_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_REP, reg); +} + +static inline int rep_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_REP], reg, val); +} + +static inline int rep_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return rep_write(sd, reg, (rep_read(sd, reg) & ~mask) | val); +} + +static inline int edid_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_EDID, reg); +} + +static inline int edid_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_EDID], reg, val); +} + +static inline int edid_write_block(struct v4l2_subdev *sd, + unsigned int total_len, const u8 *val) +{ + struct adv76xx_state *state = to_state(sd); + int err = 0; + int i = 0; + int len = 0; + + v4l2_dbg(2, debug, sd, "%s: write EDID block (%d byte)\n", + __func__, total_len); + + while (!err && i < total_len) { + len = (total_len - i) > I2C_SMBUS_BLOCK_MAX ? + I2C_SMBUS_BLOCK_MAX : + (total_len - i); + + err = adv76xx_write_block(state, ADV76XX_PAGE_EDID, + i, val + i, len); + i += len; + } + + return err; +} + +static void adv76xx_set_hpd(struct adv76xx_state *state, unsigned int hpd) +{ + unsigned int i; + + for (i = 0; i < state->info->num_dv_ports; ++i) + gpiod_set_value_cansleep(state->hpd_gpio[i], hpd & BIT(i)); + + v4l2_subdev_notify(&state->sd, ADV76XX_HOTPLUG, &hpd); +} + +static void adv76xx_delayed_work_enable_hotplug(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adv76xx_state *state = container_of(dwork, struct adv76xx_state, + delayed_work_enable_hotplug); + struct v4l2_subdev *sd = &state->sd; + + v4l2_dbg(2, debug, sd, "%s: enable hotplug\n", __func__); + + adv76xx_set_hpd(state, state->edid.present); +} + +static inline int hdmi_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_HDMI, reg); +} + +static u16 hdmi_read16(struct v4l2_subdev *sd, u8 reg, u16 mask) +{ + return ((hdmi_read(sd, reg) << 8) | hdmi_read(sd, reg + 1)) & mask; +} + +static inline int hdmi_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_HDMI], reg, val); +} + +static inline int hdmi_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return hdmi_write(sd, reg, (hdmi_read(sd, reg) & ~mask) | val); +} + +static inline int test_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_TEST], reg, val); +} + +static inline int cp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_CP, reg); +} + +static u16 cp_read16(struct v4l2_subdev *sd, u8 reg, u16 mask) +{ + return ((cp_read(sd, reg) << 8) | cp_read(sd, reg + 1)) & mask; +} + +static inline int cp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_CP], reg, val); +} + +static inline int cp_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return cp_write(sd, reg, (cp_read(sd, reg) & ~mask) | val); +} + +static inline int vdp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV7604_PAGE_VDP, reg); +} + +static inline int vdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV7604_PAGE_VDP], reg, val); +} + +#define ADV76XX_REG(page, offset) (((page) << 8) | (offset)) +#define ADV76XX_REG_SEQ_TERM 0xffff + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int adv76xx_read_reg(struct v4l2_subdev *sd, unsigned int reg) +{ + struct adv76xx_state *state = to_state(sd); + unsigned int page = reg >> 8; + unsigned int val; + int err; + + if (page >= ADV76XX_PAGE_MAX || !(BIT(page) & state->info->page_mask)) + return -EINVAL; + + reg &= 0xff; + err = regmap_read(state->regmap[page], reg, &val); + + return err ? err : val; +} +#endif + +static int adv76xx_write_reg(struct v4l2_subdev *sd, unsigned int reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + unsigned int page = reg >> 8; + + if (page >= ADV76XX_PAGE_MAX || !(BIT(page) & state->info->page_mask)) + return -EINVAL; + + reg &= 0xff; + + return regmap_write(state->regmap[page], reg, val); +} + +static void adv76xx_write_reg_seq(struct v4l2_subdev *sd, + const struct adv76xx_reg_seq *reg_seq) +{ + unsigned int i; + + for (i = 0; reg_seq[i].reg != ADV76XX_REG_SEQ_TERM; i++) + adv76xx_write_reg(sd, reg_seq[i].reg, reg_seq[i].val); +} + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +static const struct adv76xx_format_info adv7604_formats[] = { + { MEDIA_BUS_FMT_RGB888_1X24, ADV76XX_OP_CH_SEL_RGB, true, false, + ADV76XX_OP_MODE_SEL_SDR_444 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_2X8, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_2X8, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV10_2X10, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YVYU10_2X10, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YUYV12_2X12, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_2X12, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_UYVY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_VYUY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_1X16, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_1X16, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_UYVY10_1X20, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_VYUY10_1X20, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YUYV10_1X20, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YVYU10_1X20, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_UYVY12_1X24, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_VYUY12_1X24, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YUYV12_1X24, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_1X24, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, +}; + +static const struct adv76xx_format_info adv7611_formats[] = { + { MEDIA_BUS_FMT_RGB888_1X24, ADV76XX_OP_CH_SEL_RGB, true, false, + ADV76XX_OP_MODE_SEL_SDR_444 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_2X8, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_2X8, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV12_2X12, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_2X12, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_UYVY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_VYUY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_1X16, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_1X16, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_UYVY12_1X24, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_VYUY12_1X24, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YUYV12_1X24, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_1X24, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, +}; + +static const struct adv76xx_format_info adv7612_formats[] = { + { MEDIA_BUS_FMT_RGB888_1X24, ADV76XX_OP_CH_SEL_RGB, true, false, + ADV76XX_OP_MODE_SEL_SDR_444 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_2X8, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_2X8, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_UYVY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_VYUY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_1X16, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_1X16, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, +}; + +static const struct adv76xx_format_info * +adv76xx_format_info(struct adv76xx_state *state, u32 code) +{ + unsigned int i; + + for (i = 0; i < state->info->nformats; ++i) { + if (state->info->formats[i].code == code) + return &state->info->formats[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------- */ + +static inline bool is_analog_input(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + return state->selected_input == ADV7604_PAD_VGA_RGB || + state->selected_input == ADV7604_PAD_VGA_COMP; +} + +static inline bool is_digital_input(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + return state->selected_input == ADV76XX_PAD_HDMI_PORT_A || + state->selected_input == ADV7604_PAD_HDMI_PORT_B || + state->selected_input == ADV7604_PAD_HDMI_PORT_C || + state->selected_input == ADV7604_PAD_HDMI_PORT_D; +} + +static const struct v4l2_dv_timings_cap adv7604_timings_cap_analog = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1200, 25000000, 170000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +static const struct v4l2_dv_timings_cap adv76xx_timings_cap_digital = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1200, 25000000, 225000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +/* + * Return the DV timings capabilities for the requested sink pad. As a special + * case, pad value -1 returns the capabilities for the currently selected input. + */ +static const struct v4l2_dv_timings_cap * +adv76xx_get_dv_timings_cap(struct v4l2_subdev *sd, int pad) +{ + if (pad == -1) { + struct adv76xx_state *state = to_state(sd); + + pad = state->selected_input; + } + + switch (pad) { + case ADV76XX_PAD_HDMI_PORT_A: + case ADV7604_PAD_HDMI_PORT_B: + case ADV7604_PAD_HDMI_PORT_C: + case ADV7604_PAD_HDMI_PORT_D: + return &adv76xx_timings_cap_digital; + + case ADV7604_PAD_VGA_RGB: + case ADV7604_PAD_VGA_COMP: + default: + return &adv7604_timings_cap_analog; + } +} + + +/* ----------------------------------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static void adv76xx_inv_register(struct v4l2_subdev *sd) +{ + v4l2_info(sd, "0x000-0x0ff: IO Map\n"); + v4l2_info(sd, "0x100-0x1ff: AVLink Map\n"); + v4l2_info(sd, "0x200-0x2ff: CEC Map\n"); + v4l2_info(sd, "0x300-0x3ff: InfoFrame Map\n"); + v4l2_info(sd, "0x400-0x4ff: ESDP Map\n"); + v4l2_info(sd, "0x500-0x5ff: DPP Map\n"); + v4l2_info(sd, "0x600-0x6ff: AFE Map\n"); + v4l2_info(sd, "0x700-0x7ff: Repeater Map\n"); + v4l2_info(sd, "0x800-0x8ff: EDID Map\n"); + v4l2_info(sd, "0x900-0x9ff: HDMI Map\n"); + v4l2_info(sd, "0xa00-0xaff: Test Map\n"); + v4l2_info(sd, "0xb00-0xbff: CP Map\n"); + v4l2_info(sd, "0xc00-0xcff: VDP Map\n"); +} + +static int adv76xx_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + int ret; + + ret = adv76xx_read_reg(sd, reg->reg); + if (ret < 0) { + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv76xx_inv_register(sd); + return ret; + } + + reg->size = 1; + reg->val = ret; + + return 0; +} + +static int adv76xx_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + int ret; + + ret = adv76xx_write_reg(sd, reg->reg, reg->val); + if (ret < 0) { + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv76xx_inv_register(sd); + return ret; + } + + return 0; +} +#endif + +static unsigned int adv7604_read_cable_det(struct v4l2_subdev *sd) +{ + u8 value = io_read(sd, 0x6f); + + return ((value & 0x10) >> 4) + | ((value & 0x08) >> 2) + | ((value & 0x04) << 0) + | ((value & 0x02) << 2); +} + +static unsigned int adv7611_read_cable_det(struct v4l2_subdev *sd) +{ + u8 value = io_read(sd, 0x6f); + + return value & 1; +} + +static unsigned int adv7612_read_cable_det(struct v4l2_subdev *sd) +{ + /* Reads CABLE_DET_A_RAW. For input B support, need to + * account for bit 7 [MSB] of 0x6a (ie. CABLE_DET_B_RAW) + */ + u8 value = io_read(sd, 0x6f); + + return value & 1; +} + +static int adv76xx_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + u16 cable_det = info->read_cable_det(sd); + + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, cable_det); +} + +static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, + u8 prim_mode, + const struct adv76xx_video_standards *predef_vid_timings, + const struct v4l2_dv_timings *timings) +{ + int i; + + for (i = 0; predef_vid_timings[i].timings.bt.width; i++) { + if (!v4l2_match_dv_timings(timings, &predef_vid_timings[i].timings, + is_digital_input(sd) ? 250000 : 1000000, false)) + continue; + io_write(sd, 0x00, predef_vid_timings[i].vid_std); /* video std */ + io_write(sd, 0x01, (predef_vid_timings[i].v_freq << 4) + + prim_mode); /* v_freq and prim mode */ + return 0; + } + + return -1; +} + +static int configure_predefined_video_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + int err; + + v4l2_dbg(1, debug, sd, "%s", __func__); + + if (adv76xx_has_afe(state)) { + /* reset to default values */ + io_write(sd, 0x16, 0x43); + io_write(sd, 0x17, 0x5a); + } + /* disable embedded syncs for auto graphics mode */ + cp_write_clr_set(sd, 0x81, 0x10, 0x00); + cp_write(sd, 0x8f, 0x00); + cp_write(sd, 0x90, 0x00); + cp_write(sd, 0xa2, 0x00); + cp_write(sd, 0xa3, 0x00); + cp_write(sd, 0xa4, 0x00); + cp_write(sd, 0xa5, 0x00); + cp_write(sd, 0xa6, 0x00); + cp_write(sd, 0xa7, 0x00); + cp_write(sd, 0xab, 0x00); + cp_write(sd, 0xac, 0x00); + + if (is_analog_input(sd)) { + err = find_and_set_predefined_video_timings(sd, + 0x01, adv7604_prim_mode_comp, timings); + if (err) + err = find_and_set_predefined_video_timings(sd, + 0x02, adv7604_prim_mode_gr, timings); + } else if (is_digital_input(sd)) { + err = find_and_set_predefined_video_timings(sd, + 0x05, adv76xx_prim_mode_hdmi_comp, timings); + if (err) + err = find_and_set_predefined_video_timings(sd, + 0x06, adv76xx_prim_mode_hdmi_gr, timings); + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); + err = -1; + } + + + return err; +} + +static void configure_custom_video_timings(struct v4l2_subdev *sd, + const struct v4l2_bt_timings *bt) +{ + struct adv76xx_state *state = to_state(sd); + u32 width = htotal(bt); + u32 height = vtotal(bt); + u16 cp_start_sav = bt->hsync + bt->hbackporch - 4; + u16 cp_start_eav = width - bt->hfrontporch; + u16 cp_start_vbi = height - bt->vfrontporch; + u16 cp_end_vbi = bt->vsync + bt->vbackporch; + u16 ch1_fr_ll = (((u32)bt->pixelclock / 100) > 0) ? + ((width * (ADV76XX_FSC / 100)) / ((u32)bt->pixelclock / 100)) : 0; + const u8 pll[2] = { + 0xc0 | ((width >> 8) & 0x1f), + width & 0xff + }; + + v4l2_dbg(2, debug, sd, "%s\n", __func__); + + if (is_analog_input(sd)) { + /* auto graphics */ + io_write(sd, 0x00, 0x07); /* video std */ + io_write(sd, 0x01, 0x02); /* prim mode */ + /* enable embedded syncs for auto graphics mode */ + cp_write_clr_set(sd, 0x81, 0x10, 0x10); + + /* Should only be set in auto-graphics mode [REF_02, p. 91-92] */ + /* setup PLL_DIV_MAN_EN and PLL_DIV_RATIO */ + /* IO-map reg. 0x16 and 0x17 should be written in sequence */ + if (regmap_raw_write(state->regmap[ADV76XX_PAGE_IO], + 0x16, pll, 2)) + v4l2_err(sd, "writing to reg 0x16 and 0x17 failed\n"); + + /* active video - horizontal timing */ + cp_write(sd, 0xa2, (cp_start_sav >> 4) & 0xff); + cp_write(sd, 0xa3, ((cp_start_sav & 0x0f) << 4) | + ((cp_start_eav >> 8) & 0x0f)); + cp_write(sd, 0xa4, cp_start_eav & 0xff); + + /* active video - vertical timing */ + cp_write(sd, 0xa5, (cp_start_vbi >> 4) & 0xff); + cp_write(sd, 0xa6, ((cp_start_vbi & 0xf) << 4) | + ((cp_end_vbi >> 8) & 0xf)); + cp_write(sd, 0xa7, cp_end_vbi & 0xff); + } else if (is_digital_input(sd)) { + /* set default prim_mode/vid_std for HDMI + according to [REF_03, c. 4.2] */ + io_write(sd, 0x00, 0x02); /* video std */ + io_write(sd, 0x01, 0x06); /* prim mode */ + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); + } + + cp_write(sd, 0x8f, (ch1_fr_ll >> 8) & 0x7); + cp_write(sd, 0x90, ch1_fr_ll & 0xff); + cp_write(sd, 0xab, (height >> 4) & 0xff); + cp_write(sd, 0xac, (height & 0x0f) << 4); +} + +static void adv76xx_set_offset(struct v4l2_subdev *sd, bool auto_offset, u16 offset_a, u16 offset_b, u16 offset_c) +{ + struct adv76xx_state *state = to_state(sd); + u8 offset_buf[4]; + + if (auto_offset) { + offset_a = 0x3ff; + offset_b = 0x3ff; + offset_c = 0x3ff; + } + + v4l2_dbg(2, debug, sd, "%s: %s offset: a = 0x%x, b = 0x%x, c = 0x%x\n", + __func__, auto_offset ? "Auto" : "Manual", + offset_a, offset_b, offset_c); + + offset_buf[0] = (cp_read(sd, 0x77) & 0xc0) | ((offset_a & 0x3f0) >> 4); + offset_buf[1] = ((offset_a & 0x00f) << 4) | ((offset_b & 0x3c0) >> 6); + offset_buf[2] = ((offset_b & 0x03f) << 2) | ((offset_c & 0x300) >> 8); + offset_buf[3] = offset_c & 0x0ff; + + /* Registers must be written in this order with no i2c access in between */ + if (regmap_raw_write(state->regmap[ADV76XX_PAGE_CP], + 0x77, offset_buf, 4)) + v4l2_err(sd, "%s: i2c error writing to CP reg 0x77, 0x78, 0x79, 0x7a\n", __func__); +} + +static void adv76xx_set_gain(struct v4l2_subdev *sd, bool auto_gain, u16 gain_a, u16 gain_b, u16 gain_c) +{ + struct adv76xx_state *state = to_state(sd); + u8 gain_buf[4]; + u8 gain_man = 1; + u8 agc_mode_man = 1; + + if (auto_gain) { + gain_man = 0; + agc_mode_man = 0; + gain_a = 0x100; + gain_b = 0x100; + gain_c = 0x100; + } + + v4l2_dbg(2, debug, sd, "%s: %s gain: a = 0x%x, b = 0x%x, c = 0x%x\n", + __func__, auto_gain ? "Auto" : "Manual", + gain_a, gain_b, gain_c); + + gain_buf[0] = ((gain_man << 7) | (agc_mode_man << 6) | ((gain_a & 0x3f0) >> 4)); + gain_buf[1] = (((gain_a & 0x00f) << 4) | ((gain_b & 0x3c0) >> 6)); + gain_buf[2] = (((gain_b & 0x03f) << 2) | ((gain_c & 0x300) >> 8)); + gain_buf[3] = ((gain_c & 0x0ff)); + + /* Registers must be written in this order with no i2c access in between */ + if (regmap_raw_write(state->regmap[ADV76XX_PAGE_CP], + 0x73, gain_buf, 4)) + v4l2_err(sd, "%s: i2c error writing to CP reg 0x73, 0x74, 0x75, 0x76\n", __func__); +} + +static void set_rgb_quantization_range(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + bool rgb_output = io_read(sd, 0x02) & 0x02; + bool hdmi_signal = hdmi_read(sd, 0x05) & 0x80; + u8 y = HDMI_COLORSPACE_RGB; + + if (hdmi_signal && (io_read(sd, 0x60) & 1)) + y = infoframe_read(sd, 0x01) >> 5; + + v4l2_dbg(2, debug, sd, "%s: RGB quantization range: %d, RGB out: %d, HDMI: %d\n", + __func__, state->rgb_quantization_range, + rgb_output, hdmi_signal); + + adv76xx_set_gain(sd, true, 0x0, 0x0, 0x0); + adv76xx_set_offset(sd, true, 0x0, 0x0, 0x0); + io_write_clr_set(sd, 0x02, 0x04, rgb_output ? 0 : 4); + + switch (state->rgb_quantization_range) { + case V4L2_DV_RGB_RANGE_AUTO: + if (state->selected_input == ADV7604_PAD_VGA_RGB) { + /* Receiving analog RGB signal + * Set RGB full range (0-255) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x10); + break; + } + + if (state->selected_input == ADV7604_PAD_VGA_COMP) { + /* Receiving analog YPbPr signal + * Set automode */ + io_write_clr_set(sd, 0x02, 0xf0, 0xf0); + break; + } + + if (hdmi_signal) { + /* Receiving HDMI signal + * Set automode */ + io_write_clr_set(sd, 0x02, 0xf0, 0xf0); + break; + } + + /* Receiving DVI-D signal + * ADV7604 selects RGB limited range regardless of + * input format (CE/IT) in automatic mode */ + if (state->timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) { + /* RGB limited range (16-235) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x00); + } else { + /* RGB full range (0-255) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x10); + + if (is_digital_input(sd) && rgb_output) { + adv76xx_set_offset(sd, false, 0x40, 0x40, 0x40); + } else { + adv76xx_set_gain(sd, false, 0xe0, 0xe0, 0xe0); + adv76xx_set_offset(sd, false, 0x70, 0x70, 0x70); + } + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + if (state->selected_input == ADV7604_PAD_VGA_COMP) { + /* YCrCb limited range (16-235) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x20); + break; + } + + if (y != HDMI_COLORSPACE_RGB) + break; + + /* RGB limited range (16-235) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x00); + + break; + case V4L2_DV_RGB_RANGE_FULL: + if (state->selected_input == ADV7604_PAD_VGA_COMP) { + /* YCrCb full range (0-255) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x60); + break; + } + + if (y != HDMI_COLORSPACE_RGB) + break; + + /* RGB full range (0-255) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x10); + + if (is_analog_input(sd) || hdmi_signal) + break; + + /* Adjust gain/offset for DVI-D signals only */ + if (rgb_output) { + adv76xx_set_offset(sd, false, 0x40, 0x40, 0x40); + } else { + adv76xx_set_gain(sd, false, 0xe0, 0xe0, 0xe0); + adv76xx_set_offset(sd, false, 0x70, 0x70, 0x70); + } + break; + } +} + +static int adv76xx_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = + &container_of(ctrl->handler, struct adv76xx_state, hdl)->sd; + + struct adv76xx_state *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + cp_write(sd, 0x3c, ctrl->val); + return 0; + case V4L2_CID_CONTRAST: + cp_write(sd, 0x3a, ctrl->val); + return 0; + case V4L2_CID_SATURATION: + cp_write(sd, 0x3b, ctrl->val); + return 0; + case V4L2_CID_HUE: + cp_write(sd, 0x3d, ctrl->val); + return 0; + case V4L2_CID_DV_RX_RGB_RANGE: + state->rgb_quantization_range = ctrl->val; + set_rgb_quantization_range(sd); + return 0; + case V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE: + if (!adv76xx_has_afe(state)) + return -EINVAL; + /* Set the analog sampling phase. This is needed to find the + best sampling phase for analog video: an application or + driver has to try a number of phases and analyze the picture + quality before settling on the best performing phase. */ + afe_write(sd, 0xc8, ctrl->val); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL: + /* Use the default blue color for free running mode, + or supply your own. */ + cp_write_clr_set(sd, 0xbf, 0x04, ctrl->val << 2); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR: + cp_write(sd, 0xc0, (ctrl->val & 0xff0000) >> 16); + cp_write(sd, 0xc1, (ctrl->val & 0x00ff00) >> 8); + cp_write(sd, 0xc2, (u8)(ctrl->val & 0x0000ff)); + return 0; + } + return -EINVAL; +} + +static int adv76xx_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = + &container_of(ctrl->handler, struct adv76xx_state, hdl)->sd; + + if (ctrl->id == V4L2_CID_DV_RX_IT_CONTENT_TYPE) { + ctrl->val = V4L2_DV_IT_CONTENT_TYPE_NO_ITC; + if ((io_read(sd, 0x60) & 1) && (infoframe_read(sd, 0x03) & 0x80)) + ctrl->val = (infoframe_read(sd, 0x05) >> 4) & 3; + return 0; + } + return -EINVAL; +} + +/* ----------------------------------------------------------------------- */ + +static inline bool no_power(struct v4l2_subdev *sd) +{ + /* Entire chip or CP powered off */ + return io_read(sd, 0x0c) & 0x24; +} + +static inline bool no_signal_tmds(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + return !(io_read(sd, 0x6a) & (0x10 >> state->selected_input)); +} + +static inline bool no_lock_tmds(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + + return (io_read(sd, 0x6a) & info->tdms_lock_mask) != info->tdms_lock_mask; +} + +static inline bool is_hdmi(struct v4l2_subdev *sd) +{ + return hdmi_read(sd, 0x05) & 0x80; +} + +static inline bool no_lock_sspd(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + /* + * Chips without a AFE don't expose registers for the SSPD, so just assume + * that we have a lock. + */ + if (adv76xx_has_afe(state)) + return false; + + /* TODO channel 2 */ + return ((cp_read(sd, 0xb5) & 0xd0) != 0xd0); +} + +static inline bool no_lock_stdi(struct v4l2_subdev *sd) +{ + /* TODO channel 2 */ + return !(cp_read(sd, 0xb1) & 0x80); +} + +static inline bool no_signal(struct v4l2_subdev *sd) +{ + bool ret; + + ret = no_power(sd); + + ret |= no_lock_stdi(sd); + ret |= no_lock_sspd(sd); + + if (is_digital_input(sd)) { + ret |= no_lock_tmds(sd); + ret |= no_signal_tmds(sd); + } + + return ret; +} + +static inline bool no_lock_cp(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + if (!adv76xx_has_afe(state)) + return false; + + /* CP has detected a non standard number of lines on the incoming + video compared to what it is configured to receive by s_dv_timings */ + return io_read(sd, 0x12) & 0x01; +} + +static inline bool in_free_run(struct v4l2_subdev *sd) +{ + return cp_read(sd, 0xff) & 0x10; +} + +static int adv76xx_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + *status = 0; + *status |= no_power(sd) ? V4L2_IN_ST_NO_POWER : 0; + *status |= no_signal(sd) ? V4L2_IN_ST_NO_SIGNAL : 0; + if (!in_free_run(sd) && no_lock_cp(sd)) + *status |= is_digital_input(sd) ? + V4L2_IN_ST_NO_SYNC : V4L2_IN_ST_NO_H_LOCK; + + v4l2_dbg(1, debug, sd, "%s: status = 0x%x\n", __func__, *status); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +struct stdi_readback { + u16 bl, lcf, lcvs; + u8 hs_pol, vs_pol; + bool interlaced; +}; + +static int stdi2dv_timings(struct v4l2_subdev *sd, + struct stdi_readback *stdi, + struct v4l2_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + u32 hfreq = (ADV76XX_FSC * 8) / stdi->bl; + u32 pix_clk; + int i; + + for (i = 0; v4l2_dv_timings_presets[i].bt.width; i++) { + const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[i].bt; + + if (!v4l2_valid_dv_timings(&v4l2_dv_timings_presets[i], + adv76xx_get_dv_timings_cap(sd, -1), + adv76xx_check_dv_timings, NULL)) + continue; + if (vtotal(bt) != stdi->lcf + 1) + continue; + if (bt->vsync != stdi->lcvs) + continue; + + pix_clk = hfreq * htotal(bt); + + if ((pix_clk < bt->pixelclock + 1000000) && + (pix_clk > bt->pixelclock - 1000000)) { + *timings = v4l2_dv_timings_presets[i]; + return 0; + } + } + + if (v4l2_detect_cvt(stdi->lcf + 1, hfreq, stdi->lcvs, 0, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + false, timings)) + return 0; + if (v4l2_detect_gtf(stdi->lcf + 1, hfreq, stdi->lcvs, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + false, state->aspect_ratio, timings)) + return 0; + + v4l2_dbg(2, debug, sd, + "%s: No format candidate found for lcvs = %d, lcf=%d, bl = %d, %chsync, %cvsync\n", + __func__, stdi->lcvs, stdi->lcf, stdi->bl, + stdi->hs_pol, stdi->vs_pol); + return -1; +} + + +static int read_stdi(struct v4l2_subdev *sd, struct stdi_readback *stdi) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + u8 polarity; + + if (no_lock_stdi(sd) || no_lock_sspd(sd)) { + v4l2_dbg(2, debug, sd, "%s: STDI and/or SSPD not locked\n", __func__); + return -1; + } + + /* read STDI */ + stdi->bl = cp_read16(sd, 0xb1, 0x3fff); + stdi->lcf = cp_read16(sd, info->lcf_reg, 0x7ff); + stdi->lcvs = cp_read(sd, 0xb3) >> 3; + stdi->interlaced = io_read(sd, 0x12) & 0x10; + + if (adv76xx_has_afe(state)) { + /* read SSPD */ + polarity = cp_read(sd, 0xb5); + if ((polarity & 0x03) == 0x01) { + stdi->hs_pol = polarity & 0x10 + ? (polarity & 0x08 ? '+' : '-') : 'x'; + stdi->vs_pol = polarity & 0x40 + ? (polarity & 0x20 ? '+' : '-') : 'x'; + } else { + stdi->hs_pol = 'x'; + stdi->vs_pol = 'x'; + } + } else { + polarity = hdmi_read(sd, 0x05); + stdi->hs_pol = polarity & 0x20 ? '+' : '-'; + stdi->vs_pol = polarity & 0x10 ? '+' : '-'; + } + + if (no_lock_stdi(sd) || no_lock_sspd(sd)) { + v4l2_dbg(2, debug, sd, + "%s: signal lost during readout of STDI/SSPD\n", __func__); + return -1; + } + + if (stdi->lcf < 239 || stdi->bl < 8 || stdi->bl == 0x3fff) { + v4l2_dbg(2, debug, sd, "%s: invalid signal\n", __func__); + memset(stdi, 0, sizeof(struct stdi_readback)); + return -1; + } + + v4l2_dbg(2, debug, sd, + "%s: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %chsync, %cvsync, %s\n", + __func__, stdi->lcf, stdi->bl, stdi->lcvs, + stdi->hs_pol, stdi->vs_pol, + stdi->interlaced ? "interlaced" : "progressive"); + + return 0; +} + +static int adv76xx_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + + if (timings->pad >= state->source_pad) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, + adv76xx_get_dv_timings_cap(sd, timings->pad), + adv76xx_check_dv_timings, NULL); +} + +static int adv76xx_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + struct adv76xx_state *state = to_state(sd); + unsigned int pad = cap->pad; + + if (cap->pad >= state->source_pad) + return -EINVAL; + + *cap = *adv76xx_get_dv_timings_cap(sd, pad); + cap->pad = pad; + + return 0; +} + +/* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is listed in adv76xx_timings[] */ +static void adv76xx_fill_optional_dv_timings_fields(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + v4l2_find_dv_timings_cap(timings, adv76xx_get_dv_timings_cap(sd, -1), + is_digital_input(sd) ? 250000 : 1000000, + adv76xx_check_dv_timings, NULL); +} + +static unsigned int adv7604_read_hdmi_pixelclock(struct v4l2_subdev *sd) +{ + int a, b; + + a = hdmi_read(sd, 0x06); + b = hdmi_read(sd, 0x3b); + if (a < 0 || b < 0) + return 0; + + return a * 1000000 + ((b & 0x30) >> 4) * 250000; +} + +static unsigned int adv7611_read_hdmi_pixelclock(struct v4l2_subdev *sd) +{ + int a, b; + + a = hdmi_read(sd, 0x51); + b = hdmi_read(sd, 0x52); + if (a < 0 || b < 0) + return 0; + + return ((a << 1) | (b >> 7)) * 1000000 + (b & 0x7f) * 1000000 / 128; +} + +static unsigned int adv76xx_read_hdmi_pixelclock(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + unsigned int freq, bits_per_channel, pixelrepetition; + + freq = info->read_hdmi_pixelclock(sd); + if (is_hdmi(sd)) { + /* adjust for deep color mode and pixel repetition */ + bits_per_channel = ((hdmi_read(sd, 0x0b) & 0x60) >> 4) + 8; + pixelrepetition = (hdmi_read(sd, 0x05) & 0x0f) + 1; + + freq = freq * 8 / bits_per_channel / pixelrepetition; + } + + return freq; +} + +static int adv76xx_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + struct v4l2_bt_timings *bt = &timings->bt; + struct stdi_readback stdi; + + if (!timings) + return -EINVAL; + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + + if (no_signal(sd)) { + state->restart_stdi_once = true; + v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); + return -ENOLINK; + } + + /* read STDI */ + if (read_stdi(sd, &stdi)) { + v4l2_dbg(1, debug, sd, "%s: STDI/SSPD not locked\n", __func__); + return -ENOLINK; + } + bt->interlaced = stdi.interlaced ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + + if (is_digital_input(sd)) { + bool hdmi_signal = hdmi_read(sd, 0x05) & 0x80; + u8 vic = 0; + u32 w, h; + + w = hdmi_read16(sd, 0x07, info->linewidth_mask); + h = hdmi_read16(sd, 0x09, info->field0_height_mask); + + if (hdmi_signal && (io_read(sd, 0x60) & 1)) + vic = infoframe_read(sd, 0x04); + + if (vic && v4l2_find_dv_timings_cea861_vic(timings, vic) && + bt->width == w && bt->height == h) + goto found; + + timings->type = V4L2_DV_BT_656_1120; + + bt->width = w; + bt->height = h; + bt->pixelclock = adv76xx_read_hdmi_pixelclock(sd); + bt->hfrontporch = hdmi_read16(sd, 0x20, info->hfrontporch_mask); + bt->hsync = hdmi_read16(sd, 0x22, info->hsync_mask); + bt->hbackporch = hdmi_read16(sd, 0x24, info->hbackporch_mask); + bt->vfrontporch = hdmi_read16(sd, 0x2a, + info->field0_vfrontporch_mask) / 2; + bt->vsync = hdmi_read16(sd, 0x2e, info->field0_vsync_mask) / 2; + bt->vbackporch = hdmi_read16(sd, 0x32, + info->field0_vbackporch_mask) / 2; + bt->polarities = ((hdmi_read(sd, 0x05) & 0x10) ? V4L2_DV_VSYNC_POS_POL : 0) | + ((hdmi_read(sd, 0x05) & 0x20) ? V4L2_DV_HSYNC_POS_POL : 0); + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height += hdmi_read16(sd, 0x0b, + info->field1_height_mask); + bt->il_vfrontporch = hdmi_read16(sd, 0x2c, + info->field1_vfrontporch_mask) / 2; + bt->il_vsync = hdmi_read16(sd, 0x30, + info->field1_vsync_mask) / 2; + bt->il_vbackporch = hdmi_read16(sd, 0x34, + info->field1_vbackporch_mask) / 2; + } + adv76xx_fill_optional_dv_timings_fields(sd, timings); + } else { + /* find format + * Since LCVS values are inaccurate [REF_03, p. 275-276], + * stdi2dv_timings() is called with lcvs +-1 if the first attempt fails. + */ + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs += 1; + v4l2_dbg(1, debug, sd, "%s: lcvs + 1 = %d\n", __func__, stdi.lcvs); + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs -= 2; + v4l2_dbg(1, debug, sd, "%s: lcvs - 1 = %d\n", __func__, stdi.lcvs); + if (stdi2dv_timings(sd, &stdi, timings)) { + /* + * The STDI block may measure wrong values, especially + * for lcvs and lcf. If the driver can not find any + * valid timing, the STDI block is restarted to measure + * the video timings again. The function will return an + * error, but the restart of STDI will generate a new + * STDI interrupt and the format detection process will + * restart. + */ + if (state->restart_stdi_once) { + v4l2_dbg(1, debug, sd, "%s: restart STDI\n", __func__); + /* TODO restart STDI for Sync Channel 2 */ + /* enter one-shot mode */ + cp_write_clr_set(sd, 0x86, 0x06, 0x00); + /* trigger STDI restart */ + cp_write_clr_set(sd, 0x86, 0x06, 0x04); + /* reset to continuous mode */ + cp_write_clr_set(sd, 0x86, 0x06, 0x02); + state->restart_stdi_once = false; + return -ENOLINK; + } + v4l2_dbg(1, debug, sd, "%s: format not supported\n", __func__); + return -ERANGE; + } + state->restart_stdi_once = true; + } +found: + + if (no_signal(sd)) { + v4l2_dbg(1, debug, sd, "%s: signal lost during readout\n", __func__); + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + return -ENOLINK; + } + + if ((is_analog_input(sd) && bt->pixelclock > 170000000) || + (is_digital_input(sd) && bt->pixelclock > 225000000)) { + v4l2_dbg(1, debug, sd, "%s: pixelclock out of range %d\n", + __func__, (u32)bt->pixelclock); + return -ERANGE; + } + + if (debug > 1) + v4l2_print_dv_timings(sd->name, "adv76xx_query_dv_timings: ", + timings, true); + + return 0; +} + +static int adv76xx_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + struct v4l2_bt_timings *bt; + int err; + + if (!timings) + return -EINVAL; + + if (v4l2_match_dv_timings(&state->timings, timings, 0, false)) { + v4l2_dbg(1, debug, sd, "%s: no change\n", __func__); + return 0; + } + + bt = &timings->bt; + + if (!v4l2_valid_dv_timings(timings, adv76xx_get_dv_timings_cap(sd, -1), + adv76xx_check_dv_timings, NULL)) + return -ERANGE; + + adv76xx_fill_optional_dv_timings_fields(sd, timings); + + state->timings = *timings; + + cp_write_clr_set(sd, 0x91, 0x40, bt->interlaced ? 0x40 : 0x00); + + /* Use prim_mode and vid_std when available */ + err = configure_predefined_video_timings(sd, timings); + if (err) { + /* custom settings when the video format + does not have prim_mode/vid_std */ + configure_custom_video_timings(sd, bt); + } + + set_rgb_quantization_range(sd); + + if (debug > 1) + v4l2_print_dv_timings(sd->name, "adv76xx_s_dv_timings: ", + timings, true); + return 0; +} + +static int adv76xx_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + + *timings = state->timings; + return 0; +} + +static void adv7604_set_termination(struct v4l2_subdev *sd, bool enable) +{ + hdmi_write(sd, 0x01, enable ? 0x00 : 0x78); +} + +static void adv7611_set_termination(struct v4l2_subdev *sd, bool enable) +{ + hdmi_write(sd, 0x83, enable ? 0xfe : 0xff); +} + +static void enable_input(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + if (is_analog_input(sd)) { + io_write(sd, 0x15, 0xb0); /* Disable Tristate of Pins (no audio) */ + } else if (is_digital_input(sd)) { + hdmi_write_clr_set(sd, 0x00, 0x03, state->selected_input); + state->info->set_termination(sd, true); + io_write(sd, 0x15, 0xa0); /* Disable Tristate of Pins */ + hdmi_write_clr_set(sd, 0x1a, 0x10, 0x00); /* Unmute audio */ + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); + } +} + +static void disable_input(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + hdmi_write_clr_set(sd, 0x1a, 0x10, 0x10); /* Mute audio */ + msleep(16); /* 512 samples with >= 32 kHz sample rate [REF_03, c. 7.16.10] */ + io_write(sd, 0x15, 0xbe); /* Tristate all outputs from video core */ + state->info->set_termination(sd, false); +} + +static void select_input(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + + if (is_analog_input(sd)) { + adv76xx_write_reg_seq(sd, info->recommended_settings[0]); + + afe_write(sd, 0x00, 0x08); /* power up ADC */ + afe_write(sd, 0x01, 0x06); /* power up Analog Front End */ + afe_write(sd, 0xc8, 0x00); /* phase control */ + } else if (is_digital_input(sd)) { + hdmi_write(sd, 0x00, state->selected_input & 0x03); + + adv76xx_write_reg_seq(sd, info->recommended_settings[1]); + + if (adv76xx_has_afe(state)) { + afe_write(sd, 0x00, 0xff); /* power down ADC */ + afe_write(sd, 0x01, 0xfe); /* power down Analog Front End */ + afe_write(sd, 0xc8, 0x40); /* phase control */ + } + + cp_write(sd, 0x3e, 0x00); /* CP core pre-gain control */ + cp_write(sd, 0xc3, 0x39); /* CP coast control. Graphics mode */ + cp_write(sd, 0x40, 0x80); /* CP core pre-gain control. Graphics mode */ + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); + } +} + +static int adv76xx_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv76xx_state *state = to_state(sd); + + v4l2_dbg(2, debug, sd, "%s: input %d, selected input %d", + __func__, input, state->selected_input); + + if (input == state->selected_input) + return 0; + + if (input > state->info->max_port) + return -EINVAL; + + state->selected_input = input; + + disable_input(sd); + select_input(sd); + enable_input(sd); + + v4l2_subdev_notify_event(sd, &adv76xx_ev_fmt); + + return 0; +} + +static int adv76xx_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct adv76xx_state *state = to_state(sd); + + if (code->index >= state->info->nformats) + return -EINVAL; + + code->code = state->info->formats[code->index].code; + + return 0; +} + +static void adv76xx_fill_format(struct adv76xx_state *state, + struct v4l2_mbus_framefmt *format) +{ + memset(format, 0, sizeof(*format)); + + format->width = state->timings.bt.width; + format->height = state->timings.bt.height; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + if (state->timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) + format->colorspace = (state->timings.bt.height <= 576) ? + V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; +} + +/* + * Compute the op_ch_sel value required to obtain on the bus the component order + * corresponding to the selected format taking into account bus reordering + * applied by the board at the output of the device. + * + * The following table gives the op_ch_value from the format component order + * (expressed as op_ch_sel value in column) and the bus reordering (expressed as + * adv76xx_bus_order value in row). + * + * | GBR(0) GRB(1) BGR(2) RGB(3) BRG(4) RBG(5) + * ----------+------------------------------------------------- + * RGB (NOP) | GBR GRB BGR RGB BRG RBG + * GRB (1-2) | BGR RGB GBR GRB RBG BRG + * RBG (2-3) | GRB GBR BRG RBG BGR RGB + * BGR (1-3) | RBG BRG RGB BGR GRB GBR + * BRG (ROR) | BRG RBG GRB GBR RGB BGR + * GBR (ROL) | RGB BGR RBG BRG GBR GRB + */ +static unsigned int adv76xx_op_ch_sel(struct adv76xx_state *state) +{ +#define _SEL(a,b,c,d,e,f) { \ + ADV76XX_OP_CH_SEL_##a, ADV76XX_OP_CH_SEL_##b, ADV76XX_OP_CH_SEL_##c, \ + ADV76XX_OP_CH_SEL_##d, ADV76XX_OP_CH_SEL_##e, ADV76XX_OP_CH_SEL_##f } +#define _BUS(x) [ADV7604_BUS_ORDER_##x] + + static const unsigned int op_ch_sel[6][6] = { + _BUS(RGB) /* NOP */ = _SEL(GBR, GRB, BGR, RGB, BRG, RBG), + _BUS(GRB) /* 1-2 */ = _SEL(BGR, RGB, GBR, GRB, RBG, BRG), + _BUS(RBG) /* 2-3 */ = _SEL(GRB, GBR, BRG, RBG, BGR, RGB), + _BUS(BGR) /* 1-3 */ = _SEL(RBG, BRG, RGB, BGR, GRB, GBR), + _BUS(BRG) /* ROR */ = _SEL(BRG, RBG, GRB, GBR, RGB, BGR), + _BUS(GBR) /* ROL */ = _SEL(RGB, BGR, RBG, BRG, GBR, GRB), + }; + + return op_ch_sel[state->pdata.bus_order][state->format->op_ch_sel >> 5]; +} + +static void adv76xx_setup_format(struct adv76xx_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + + io_write_clr_set(sd, 0x02, 0x02, + state->format->rgb_out ? ADV76XX_RGB_OUT : 0); + io_write(sd, 0x03, state->format->op_format_sel | + state->pdata.op_format_mode_sel); + io_write_clr_set(sd, 0x04, 0xe0, adv76xx_op_ch_sel(state)); + io_write_clr_set(sd, 0x05, 0x01, + state->format->swap_cb_cr ? ADV76XX_OP_SWAP_CB_CR : 0); + set_rgb_quantization_range(sd); +} + +static int adv76xx_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct adv76xx_state *state = to_state(sd); + + if (format->pad != state->source_pad) + return -EINVAL; + + adv76xx_fill_format(state, &format->format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + format->format.code = fmt->code; + } else { + format->format.code = state->format->code; + } + + return 0; +} + +static int adv76xx_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct adv76xx_state *state = to_state(sd); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + /* Only CROP, CROP_DEFAULT and CROP_BOUNDS are supported */ + if (sel->target > V4L2_SEL_TGT_CROP_BOUNDS) + return -EINVAL; + + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = state->timings.bt.width; + sel->r.height = state->timings.bt.height; + + return 0; +} + +static int adv76xx_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_format_info *info; + + if (format->pad != state->source_pad) + return -EINVAL; + + info = adv76xx_format_info(state, format->format.code); + if (!info) + info = adv76xx_format_info(state, MEDIA_BUS_FMT_YUYV8_2X8); + + adv76xx_fill_format(state, &format->format); + format->format.code = info->code; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + fmt->code = format->format.code; + } else { + state->format = info; + adv76xx_setup_format(state); + } + + return 0; +} + +#if IS_ENABLED(CONFIG_VIDEO_ADV7604_CEC) +static void adv76xx_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + struct adv76xx_state *state = to_state(sd); + + if ((cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x02) { + v4l2_dbg(1, debug, sd, "%s: tx raw: arbitration lost\n", + __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + return; + } + if (tx_raw_status & 0x04) { + u8 status; + u8 nack_cnt; + u8 low_drive_cnt; + + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + nack_cnt = cec_read(sd, 0x14) & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = cec_read(sd, 0x14) >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + cec_transmit_done(state->cec_adap, status, + 0, nack_cnt, low_drive_cnt, 0); + return; + } + if (tx_raw_status & 0x01) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + return; + } +} + +static void adv76xx_cec_isr(struct v4l2_subdev *sd, bool *handled) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + u8 cec_irq; + + /* cec controller */ + cec_irq = io_read(sd, info->cec_irq_status) & 0x0f; + if (!cec_irq) + return; + + v4l2_dbg(1, debug, sd, "%s: cec: irq 0x%x\n", __func__, cec_irq); + adv76xx_cec_tx_raw_status(sd, cec_irq); + if (cec_irq & 0x08) { + struct cec_msg msg; + + msg.len = cec_read(sd, 0x25) & 0x1f; + if (msg.len > 16) + msg.len = 16; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = cec_read(sd, i + 0x15); + cec_write(sd, info->cec_rx_enable, + info->cec_rx_enable_mask); /* re-enable rx */ + cec_received_msg(state->cec_adap, &msg); + } + } + + if (info->cec_irq_swap) { + /* + * Note: the bit order is swapped between 0x4d and 0x4e + * on adv7604 + */ + cec_irq = ((cec_irq & 0x08) >> 3) | ((cec_irq & 0x04) >> 1) | + ((cec_irq & 0x02) << 1) | ((cec_irq & 0x01) << 3); + } + io_write(sd, info->cec_irq_status + 1, cec_irq); + + if (handled) + *handled = true; +} + +static int adv76xx_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv76xx_state *state = cec_get_drvdata(adap); + const struct adv76xx_chip_info *info = state->info; + struct v4l2_subdev *sd = &state->sd; + + if (!state->cec_enabled_adap && enable) { + cec_write_clr_set(sd, 0x2a, 0x01, 0x01); /* power up cec */ + cec_write(sd, 0x2c, 0x01); /* cec soft reset */ + cec_write_clr_set(sd, 0x11, 0x01, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready */ + io_write_clr_set(sd, info->cec_irq_status + 3, 0x0f, 0x0f); + cec_write(sd, info->cec_rx_enable, info->cec_rx_enable_mask); + } else if (state->cec_enabled_adap && !enable) { + /* disable cec interrupts */ + io_write_clr_set(sd, info->cec_irq_status + 3, 0x0f, 0x00); + /* disable address mask 1-3 */ + cec_write_clr_set(sd, 0x27, 0x70, 0x00); + /* power down cec section */ + cec_write_clr_set(sd, 0x2a, 0x01, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + adv76xx_s_detect_tx_5v_ctrl(sd); + return 0; +} + +static int adv76xx_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv76xx_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + unsigned int i, free_idx = ADV76XX_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + cec_write_clr_set(sd, 0x27, 0x70, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV76XX_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV76XX_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV76XX_MAX_ADDRS) { + i = free_idx; + if (i == ADV76XX_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + cec_write_clr_set(sd, 0x27, 0x10, 0x10); + /* set address for mask 0 */ + cec_write_clr_set(sd, 0x28, 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + cec_write_clr_set(sd, 0x27, 0x20, 0x20); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x28, 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + cec_write_clr_set(sd, 0x27, 0x40, 0x40); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x29, 0x0f, addr); + break; + } + return 0; +} + +static int adv76xx_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv76xx_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + u8 len = msg->len; + unsigned int i; + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + cec_write_clr_set(sd, 0x12, 0x70, max(1, attempts - 1) << 4); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* write data */ + for (i = 0; i < len; i++) + cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + cec_write(sd, 0x11, 0x01); + return 0; +} + +static const struct cec_adap_ops adv76xx_cec_adap_ops = { + .adap_enable = adv76xx_cec_adap_enable, + .adap_log_addr = adv76xx_cec_adap_log_addr, + .adap_transmit = adv76xx_cec_adap_transmit, +}; +#endif + +static int adv76xx_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + const u8 irq_reg_0x43 = io_read(sd, 0x43); + const u8 irq_reg_0x6b = io_read(sd, 0x6b); + const u8 irq_reg_0x70 = io_read(sd, 0x70); + u8 fmt_change_digital; + u8 fmt_change; + u8 tx_5v; + + if (irq_reg_0x43) + io_write(sd, 0x44, irq_reg_0x43); + if (irq_reg_0x70) + io_write(sd, 0x71, irq_reg_0x70); + if (irq_reg_0x6b) + io_write(sd, 0x6c, irq_reg_0x6b); + + v4l2_dbg(2, debug, sd, "%s: ", __func__); + + /* format change */ + fmt_change = irq_reg_0x43 & 0x98; + fmt_change_digital = is_digital_input(sd) + ? irq_reg_0x6b & info->fmt_change_digital_mask + : 0; + + if (fmt_change || fmt_change_digital) { + v4l2_dbg(1, debug, sd, + "%s: fmt_change = 0x%x, fmt_change_digital = 0x%x\n", + __func__, fmt_change, fmt_change_digital); + + v4l2_subdev_notify_event(sd, &adv76xx_ev_fmt); + + if (handled) + *handled = true; + } + /* HDMI/DVI mode */ + if (irq_reg_0x6b & 0x01) { + v4l2_dbg(1, debug, sd, "%s: irq %s mode\n", __func__, + (io_read(sd, 0x6a) & 0x01) ? "HDMI" : "DVI"); + set_rgb_quantization_range(sd); + if (handled) + *handled = true; + } + +#if IS_ENABLED(CONFIG_VIDEO_ADV7604_CEC) + /* cec */ + adv76xx_cec_isr(sd, handled); +#endif + + /* tx 5v detect */ + tx_5v = irq_reg_0x70 & info->cable_det_mask; + if (tx_5v) { + v4l2_dbg(1, debug, sd, "%s: tx_5v: 0x%x\n", __func__, tx_5v); + adv76xx_s_detect_tx_5v_ctrl(sd); + if (handled) + *handled = true; + } + return 0; +} + +static irqreturn_t adv76xx_irq_handler(int irq, void *dev_id) +{ + struct adv76xx_state *state = dev_id; + bool handled = false; + + adv76xx_isr(&state->sd, 0, &handled); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static int adv76xx_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv76xx_state *state = to_state(sd); + u8 *data = NULL; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + switch (edid->pad) { + case ADV76XX_PAD_HDMI_PORT_A: + case ADV7604_PAD_HDMI_PORT_B: + case ADV7604_PAD_HDMI_PORT_C: + case ADV7604_PAD_HDMI_PORT_D: + if (state->edid.present & (1 << edid->pad)) + data = state->edid.edid; + break; + default: + return -EINVAL; + } + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = data ? state->edid.blocks : 0; + return 0; + } + + if (!data) + return -ENODATA; + + if (edid->start_block >= state->edid.blocks) + return -EINVAL; + + if (edid->start_block + edid->blocks > state->edid.blocks) + edid->blocks = state->edid.blocks - edid->start_block; + + memcpy(edid->edid, data + edid->start_block * 128, edid->blocks * 128); + + return 0; +} + +static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + unsigned int spa_loc; + u16 pa; + int err; + int i; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->pad > ADV7604_PAD_HDMI_PORT_D) + return -EINVAL; + if (edid->start_block != 0) + return -EINVAL; + if (edid->blocks == 0) { + /* Disable hotplug and I2C access to EDID RAM from DDC port */ + state->edid.present &= ~(1 << edid->pad); + adv76xx_set_hpd(state, state->edid.present); + rep_write_clr_set(sd, info->edid_enable_reg, 0x0f, state->edid.present); + + /* Fall back to a 16:9 aspect ratio */ + state->aspect_ratio.numerator = 16; + state->aspect_ratio.denominator = 9; + + if (!state->edid.present) { + state->edid.blocks = 0; + cec_phys_addr_invalidate(state->cec_adap); + } + + v4l2_dbg(2, debug, sd, "%s: clear EDID pad %d, edid.present = 0x%x\n", + __func__, edid->pad, state->edid.present); + return 0; + } + if (edid->blocks > 2) { + edid->blocks = 2; + return -E2BIG; + } + pa = v4l2_get_edid_phys_addr(edid->edid, edid->blocks * 128, &spa_loc); + err = v4l2_phys_addr_validate(pa, &pa, NULL); + if (err) + return err; + + v4l2_dbg(2, debug, sd, "%s: write EDID pad %d, edid.present = 0x%x\n", + __func__, edid->pad, state->edid.present); + + /* Disable hotplug and I2C access to EDID RAM from DDC port */ + cancel_delayed_work_sync(&state->delayed_work_enable_hotplug); + adv76xx_set_hpd(state, 0); + rep_write_clr_set(sd, info->edid_enable_reg, 0x0f, 0x00); + + /* + * Return an error if no location of the source physical address + * was found. + */ + if (spa_loc == 0) + return -EINVAL; + + switch (edid->pad) { + case ADV76XX_PAD_HDMI_PORT_A: + state->spa_port_a[0] = edid->edid[spa_loc]; + state->spa_port_a[1] = edid->edid[spa_loc + 1]; + break; + case ADV7604_PAD_HDMI_PORT_B: + rep_write(sd, 0x70, edid->edid[spa_loc]); + rep_write(sd, 0x71, edid->edid[spa_loc + 1]); + break; + case ADV7604_PAD_HDMI_PORT_C: + rep_write(sd, 0x72, edid->edid[spa_loc]); + rep_write(sd, 0x73, edid->edid[spa_loc + 1]); + break; + case ADV7604_PAD_HDMI_PORT_D: + rep_write(sd, 0x74, edid->edid[spa_loc]); + rep_write(sd, 0x75, edid->edid[spa_loc + 1]); + break; + default: + return -EINVAL; + } + + if (info->type == ADV7604) { + rep_write(sd, 0x76, spa_loc & 0xff); + rep_write_clr_set(sd, 0x77, 0x40, (spa_loc & 0x100) >> 2); + } else { + /* ADV7612 Software Manual Rev. A, p. 15 */ + rep_write(sd, 0x70, spa_loc & 0xff); + rep_write_clr_set(sd, 0x71, 0x01, (spa_loc & 0x100) >> 8); + } + + edid->edid[spa_loc] = state->spa_port_a[0]; + edid->edid[spa_loc + 1] = state->spa_port_a[1]; + + memcpy(state->edid.edid, edid->edid, 128 * edid->blocks); + state->edid.blocks = edid->blocks; + state->aspect_ratio = v4l2_calc_aspect_ratio(edid->edid[0x15], + edid->edid[0x16]); + state->edid.present |= 1 << edid->pad; + + err = edid_write_block(sd, 128 * edid->blocks, state->edid.edid); + if (err < 0) { + v4l2_err(sd, "error %d writing edid pad %d\n", err, edid->pad); + return err; + } + + /* adv76xx calculates the checksums and enables I2C access to internal + EDID RAM from DDC port. */ + rep_write_clr_set(sd, info->edid_enable_reg, 0x0f, state->edid.present); + + for (i = 0; i < 1000; i++) { + if (rep_read(sd, info->edid_status_reg) & state->edid.present) + break; + mdelay(1); + } + if (i == 1000) { + v4l2_err(sd, "error enabling edid (0x%x)\n", state->edid.present); + return -EIO; + } + cec_s_phys_addr(state->cec_adap, pa, false); + + /* enable hotplug after 100 ms */ + schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 10); + return 0; +} + +/*********** avi info frame CEA-861-E **************/ + +static const struct adv76xx_cfg_read_infoframe adv76xx_cri[] = { + { "AVI", 0x01, 0xe0, 0x00 }, + { "Audio", 0x02, 0xe3, 0x1c }, + { "SDP", 0x04, 0xe6, 0x2a }, + { "Vendor", 0x10, 0xec, 0x54 } +}; + +static int adv76xx_read_infoframe(struct v4l2_subdev *sd, int index, + union hdmi_infoframe *frame) +{ + uint8_t buffer[32]; + u8 len; + int i; + + if (!(io_read(sd, 0x60) & adv76xx_cri[index].present_mask)) { + v4l2_info(sd, "%s infoframe not received\n", + adv76xx_cri[index].desc); + return -ENOENT; + } + + for (i = 0; i < 3; i++) + buffer[i] = infoframe_read(sd, + adv76xx_cri[index].head_addr + i); + + len = buffer[2] + 1; + + if (len + 3 > sizeof(buffer)) { + v4l2_err(sd, "%s: invalid %s infoframe length %d\n", __func__, + adv76xx_cri[index].desc, len); + return -ENOENT; + } + + for (i = 0; i < len; i++) + buffer[i + 3] = infoframe_read(sd, + adv76xx_cri[index].payload_addr + i); + + if (hdmi_infoframe_unpack(frame, buffer, len + 3) < 0) { + v4l2_err(sd, "%s: unpack of %s infoframe failed\n", __func__, + adv76xx_cri[index].desc); + return -ENOENT; + } + return 0; +} + +static void adv76xx_log_infoframes(struct v4l2_subdev *sd) +{ + int i; + + if (!is_hdmi(sd)) { + v4l2_info(sd, "receive DVI-D signal, no infoframes\n"); + return; + } + + for (i = 0; i < ARRAY_SIZE(adv76xx_cri); i++) { + union hdmi_infoframe frame; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (adv76xx_read_infoframe(sd, i, &frame)) + return; + hdmi_infoframe_log(KERN_INFO, &client->dev, &frame); + } +} + +static int adv76xx_log_status(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + struct v4l2_dv_timings timings; + struct stdi_readback stdi; + u8 reg_io_0x02 = io_read(sd, 0x02); + u8 edid_enabled; + u8 cable_det; + + static const char * const csc_coeff_sel_rb[16] = { + "bypassed", "YPbPr601 -> RGB", "reserved", "YPbPr709 -> RGB", + "reserved", "RGB -> YPbPr601", "reserved", "RGB -> YPbPr709", + "reserved", "YPbPr709 -> YPbPr601", "YPbPr601 -> YPbPr709", + "reserved", "reserved", "reserved", "reserved", "manual" + }; + static const char * const input_color_space_txt[16] = { + "RGB limited range (16-235)", "RGB full range (0-255)", + "YCbCr Bt.601 (16-235)", "YCbCr Bt.709 (16-235)", + "xvYCC Bt.601", "xvYCC Bt.709", + "YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)", + "invalid", "invalid", "invalid", "invalid", "invalid", + "invalid", "invalid", "automatic" + }; + static const char * const hdmi_color_space_txt[16] = { + "RGB limited range (16-235)", "RGB full range (0-255)", + "YCbCr Bt.601 (16-235)", "YCbCr Bt.709 (16-235)", + "xvYCC Bt.601", "xvYCC Bt.709", + "YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)", + "sYCC", "opYCC 601", "opRGB", "invalid", "invalid", + "invalid", "invalid", "invalid" + }; + static const char * const rgb_quantization_range_txt[] = { + "Automatic", + "RGB limited range (16-235)", + "RGB full range (0-255)", + }; + static const char * const deep_color_mode_txt[4] = { + "8-bits per channel", + "10-bits per channel", + "12-bits per channel", + "16-bits per channel (not supported)" + }; + + v4l2_info(sd, "-----Chip status-----\n"); + v4l2_info(sd, "Chip power: %s\n", no_power(sd) ? "off" : "on"); + edid_enabled = rep_read(sd, info->edid_status_reg); + v4l2_info(sd, "EDID enabled port A: %s, B: %s, C: %s, D: %s\n", + ((edid_enabled & 0x01) ? "Yes" : "No"), + ((edid_enabled & 0x02) ? "Yes" : "No"), + ((edid_enabled & 0x04) ? "Yes" : "No"), + ((edid_enabled & 0x08) ? "Yes" : "No")); + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? + "enabled" : "disabled"); + if (state->cec_enabled_adap) { + int i; + + for (i = 0; i < ADV76XX_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + } + + v4l2_info(sd, "-----Signal status-----\n"); + cable_det = info->read_cable_det(sd); + v4l2_info(sd, "Cable detected (+5V power) port A: %s, B: %s, C: %s, D: %s\n", + ((cable_det & 0x01) ? "Yes" : "No"), + ((cable_det & 0x02) ? "Yes" : "No"), + ((cable_det & 0x04) ? "Yes" : "No"), + ((cable_det & 0x08) ? "Yes" : "No")); + v4l2_info(sd, "TMDS signal detected: %s\n", + no_signal_tmds(sd) ? "false" : "true"); + v4l2_info(sd, "TMDS signal locked: %s\n", + no_lock_tmds(sd) ? "false" : "true"); + v4l2_info(sd, "SSPD locked: %s\n", no_lock_sspd(sd) ? "false" : "true"); + v4l2_info(sd, "STDI locked: %s\n", no_lock_stdi(sd) ? "false" : "true"); + v4l2_info(sd, "CP locked: %s\n", no_lock_cp(sd) ? "false" : "true"); + v4l2_info(sd, "CP free run: %s\n", + (in_free_run(sd)) ? "on" : "off"); + v4l2_info(sd, "Prim-mode = 0x%x, video std = 0x%x, v_freq = 0x%x\n", + io_read(sd, 0x01) & 0x0f, io_read(sd, 0x00) & 0x3f, + (io_read(sd, 0x01) & 0x70) >> 4); + + v4l2_info(sd, "-----Video Timings-----\n"); + if (read_stdi(sd, &stdi)) + v4l2_info(sd, "STDI: not locked\n"); + else + v4l2_info(sd, "STDI: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %s, %chsync, %cvsync\n", + stdi.lcf, stdi.bl, stdi.lcvs, + stdi.interlaced ? "interlaced" : "progressive", + stdi.hs_pol, stdi.vs_pol); + if (adv76xx_query_dv_timings(sd, &timings)) + v4l2_info(sd, "No video detected\n"); + else + v4l2_print_dv_timings(sd->name, "Detected format: ", + &timings, true); + v4l2_print_dv_timings(sd->name, "Configured format: ", + &state->timings, true); + + if (no_signal(sd)) + return 0; + + v4l2_info(sd, "-----Color space-----\n"); + v4l2_info(sd, "RGB quantization range ctrl: %s\n", + rgb_quantization_range_txt[state->rgb_quantization_range]); + v4l2_info(sd, "Input color space: %s\n", + input_color_space_txt[reg_io_0x02 >> 4]); + v4l2_info(sd, "Output color space: %s %s, alt-gamma %s\n", + (reg_io_0x02 & 0x02) ? "RGB" : "YCbCr", + (((reg_io_0x02 >> 2) & 0x01) ^ (reg_io_0x02 & 0x01)) ? + "(16-235)" : "(0-255)", + (reg_io_0x02 & 0x08) ? "enabled" : "disabled"); + v4l2_info(sd, "Color space conversion: %s\n", + csc_coeff_sel_rb[cp_read(sd, info->cp_csc) >> 4]); + + if (!is_digital_input(sd)) + return 0; + + v4l2_info(sd, "-----%s status-----\n", is_hdmi(sd) ? "HDMI" : "DVI-D"); + v4l2_info(sd, "Digital video port selected: %c\n", + (hdmi_read(sd, 0x00) & 0x03) + 'A'); + v4l2_info(sd, "HDCP encrypted content: %s\n", + (hdmi_read(sd, 0x05) & 0x40) ? "true" : "false"); + v4l2_info(sd, "HDCP keys read: %s%s\n", + (hdmi_read(sd, 0x04) & 0x20) ? "yes" : "no", + (hdmi_read(sd, 0x04) & 0x10) ? "ERROR" : ""); + if (is_hdmi(sd)) { + bool audio_pll_locked = hdmi_read(sd, 0x04) & 0x01; + bool audio_sample_packet_detect = hdmi_read(sd, 0x18) & 0x01; + bool audio_mute = io_read(sd, 0x65) & 0x40; + + v4l2_info(sd, "Audio: pll %s, samples %s, %s\n", + audio_pll_locked ? "locked" : "not locked", + audio_sample_packet_detect ? "detected" : "not detected", + audio_mute ? "muted" : "enabled"); + if (audio_pll_locked && audio_sample_packet_detect) { + v4l2_info(sd, "Audio format: %s\n", + (hdmi_read(sd, 0x07) & 0x20) ? "multi-channel" : "stereo"); + } + v4l2_info(sd, "Audio CTS: %u\n", (hdmi_read(sd, 0x5b) << 12) + + (hdmi_read(sd, 0x5c) << 8) + + (hdmi_read(sd, 0x5d) & 0xf0)); + v4l2_info(sd, "Audio N: %u\n", ((hdmi_read(sd, 0x5d) & 0x0f) << 16) + + (hdmi_read(sd, 0x5e) << 8) + + hdmi_read(sd, 0x5f)); + v4l2_info(sd, "AV Mute: %s\n", (hdmi_read(sd, 0x04) & 0x40) ? "on" : "off"); + + v4l2_info(sd, "Deep color mode: %s\n", deep_color_mode_txt[(hdmi_read(sd, 0x0b) & 0x60) >> 5]); + v4l2_info(sd, "HDMI colorspace: %s\n", hdmi_color_space_txt[hdmi_read(sd, 0x53) & 0xf]); + + adv76xx_log_infoframes(sd); + } + + return 0; +} + +static int adv76xx_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static int adv76xx_registered(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err; + + err = cec_register_adapter(state->cec_adap, &client->dev); + if (err) + cec_delete_adapter(state->cec_adap); + return err; +} + +static void adv76xx_unregistered(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + cec_unregister_adapter(state->cec_adap); +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops adv76xx_ctrl_ops = { + .s_ctrl = adv76xx_s_ctrl, + .g_volatile_ctrl = adv76xx_g_volatile_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv76xx_core_ops = { + .log_status = adv76xx_log_status, + .interrupt_service_routine = adv76xx_isr, + .subscribe_event = adv76xx_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv76xx_g_register, + .s_register = adv76xx_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops adv76xx_video_ops = { + .s_routing = adv76xx_s_routing, + .g_input_status = adv76xx_g_input_status, + .s_dv_timings = adv76xx_s_dv_timings, + .g_dv_timings = adv76xx_g_dv_timings, + .query_dv_timings = adv76xx_query_dv_timings, +}; + +static const struct v4l2_subdev_pad_ops adv76xx_pad_ops = { + .enum_mbus_code = adv76xx_enum_mbus_code, + .get_selection = adv76xx_get_selection, + .get_fmt = adv76xx_get_format, + .set_fmt = adv76xx_set_format, + .get_edid = adv76xx_get_edid, + .set_edid = adv76xx_set_edid, + .dv_timings_cap = adv76xx_dv_timings_cap, + .enum_dv_timings = adv76xx_enum_dv_timings, +}; + +static const struct v4l2_subdev_ops adv76xx_ops = { + .core = &adv76xx_core_ops, + .video = &adv76xx_video_ops, + .pad = &adv76xx_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops adv76xx_int_ops = { + .registered = adv76xx_registered, + .unregistered = adv76xx_unregistered, +}; + +/* -------------------------- custom ctrls ---------------------------------- */ + +static const struct v4l2_ctrl_config adv7604_ctrl_analog_sampling_phase = { + .ops = &adv76xx_ctrl_ops, + .id = V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE, + .name = "Analog Sampling Phase", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 0x1f, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config adv76xx_ctrl_free_run_color_manual = { + .ops = &adv76xx_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL, + .name = "Free Running Color, Manual", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = false, + .max = true, + .step = 1, + .def = false, +}; + +static const struct v4l2_ctrl_config adv76xx_ctrl_free_run_color = { + .ops = &adv76xx_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR, + .name = "Free Running Color", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0x0, + .max = 0xffffff, + .step = 0x1, + .def = 0x0, +}; + +/* ----------------------------------------------------------------------- */ + +struct adv76xx_register_map { + const char *name; + u8 default_addr; +}; + +static const struct adv76xx_register_map adv76xx_default_addresses[] = { + [ADV76XX_PAGE_IO] = { "main", 0x4c }, + [ADV7604_PAGE_AVLINK] = { "avlink", 0x42 }, + [ADV76XX_PAGE_CEC] = { "cec", 0x40 }, + [ADV76XX_PAGE_INFOFRAME] = { "infoframe", 0x3e }, + [ADV7604_PAGE_ESDP] = { "esdp", 0x38 }, + [ADV7604_PAGE_DPP] = { "dpp", 0x3c }, + [ADV76XX_PAGE_AFE] = { "afe", 0x26 }, + [ADV76XX_PAGE_REP] = { "rep", 0x32 }, + [ADV76XX_PAGE_EDID] = { "edid", 0x36 }, + [ADV76XX_PAGE_HDMI] = { "hdmi", 0x34 }, + [ADV76XX_PAGE_TEST] = { "test", 0x30 }, + [ADV76XX_PAGE_CP] = { "cp", 0x22 }, + [ADV7604_PAGE_VDP] = { "vdp", 0x24 }, +}; + +static int adv76xx_core_init(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + struct adv76xx_platform_data *pdata = &state->pdata; + + hdmi_write(sd, 0x48, + (pdata->disable_pwrdnb ? 0x80 : 0) | + (pdata->disable_cable_det_rst ? 0x40 : 0)); + + disable_input(sd); + + if (pdata->default_input >= 0 && + pdata->default_input < state->source_pad) { + state->selected_input = pdata->default_input; + select_input(sd); + enable_input(sd); + } + + /* power */ + io_write(sd, 0x0c, 0x42); /* Power up part and power down VDP */ + io_write(sd, 0x0b, 0x44); /* Power down ESDP block */ + cp_write(sd, 0xcf, 0x01); /* Power down macrovision */ + + /* video format */ + io_write_clr_set(sd, 0x02, 0x0f, pdata->alt_gamma << 3); + io_write_clr_set(sd, 0x05, 0x0e, pdata->blank_data << 3 | + pdata->insert_av_codes << 2 | + pdata->replicate_av_codes << 1); + adv76xx_setup_format(state); + + cp_write(sd, 0x69, 0x30); /* Enable CP CSC */ + + /* VS, HS polarities */ + io_write(sd, 0x06, 0xa0 | pdata->inv_vs_pol << 2 | + pdata->inv_hs_pol << 1 | pdata->inv_llc_pol); + + /* Adjust drive strength */ + io_write(sd, 0x14, 0x40 | pdata->dr_str_data << 4 | + pdata->dr_str_clk << 2 | + pdata->dr_str_sync); + + cp_write(sd, 0xba, (pdata->hdmi_free_run_mode << 1) | 0x01); /* HDMI free run */ + cp_write(sd, 0xf3, 0xdc); /* Low threshold to enter/exit free run mode */ + cp_write(sd, 0xf9, 0x23); /* STDI ch. 1 - LCVS change threshold - + ADI recommended setting [REF_01, c. 2.3.3] */ + cp_write(sd, 0x45, 0x23); /* STDI ch. 2 - LCVS change threshold - + ADI recommended setting [REF_01, c. 2.3.3] */ + cp_write(sd, 0xc9, 0x2d); /* use prim_mode and vid_std as free run resolution + for digital formats */ + + /* HDMI audio */ + hdmi_write_clr_set(sd, 0x15, 0x03, 0x03); /* Mute on FIFO over-/underflow [REF_01, c. 1.2.18] */ + hdmi_write_clr_set(sd, 0x1a, 0x0e, 0x08); /* Wait 1 s before unmute */ + hdmi_write_clr_set(sd, 0x68, 0x06, 0x06); /* FIFO reset on over-/underflow [REF_01, c. 1.2.19] */ + + /* TODO from platform data */ + afe_write(sd, 0xb5, 0x01); /* Setting MCLK to 256Fs */ + + if (adv76xx_has_afe(state)) { + afe_write(sd, 0x02, pdata->ain_sel); /* Select analog input muxing mode */ + io_write_clr_set(sd, 0x30, 1 << 4, pdata->output_bus_lsb_to_msb << 4); + } + + /* interrupts */ + io_write(sd, 0x40, 0xc0 | pdata->int1_config); /* Configure INT1 */ + io_write(sd, 0x46, 0x98); /* Enable SSPD, STDI and CP unlocked interrupts */ + io_write(sd, 0x6e, info->fmt_change_digital_mask); /* Enable V_LOCKED and DE_REGEN_LCK interrupts */ + io_write(sd, 0x73, info->cable_det_mask); /* Enable cable detection (+5v) interrupts */ + info->setup_irqs(sd); + + return v4l2_ctrl_handler_setup(sd->ctrl_handler); +} + +static void adv7604_setup_irqs(struct v4l2_subdev *sd) +{ + io_write(sd, 0x41, 0xd7); /* STDI irq for any change, disable INT2 */ +} + +static void adv7611_setup_irqs(struct v4l2_subdev *sd) +{ + io_write(sd, 0x41, 0xd0); /* STDI irq for any change, disable INT2 */ +} + +static void adv7612_setup_irqs(struct v4l2_subdev *sd) +{ + io_write(sd, 0x41, 0xd0); /* disable INT2 */ +} + +static void adv76xx_unregister_clients(struct adv76xx_state *state) +{ + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(state->i2c_clients); ++i) + i2c_unregister_device(state->i2c_clients[i]); +} + +static struct i2c_client *adv76xx_dummy_client(struct v4l2_subdev *sd, + unsigned int page) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv76xx_state *state = to_state(sd); + struct adv76xx_platform_data *pdata = &state->pdata; + unsigned int io_reg = 0xf2 + page; + struct i2c_client *new_client; + + if (pdata && pdata->i2c_addresses[page]) + new_client = i2c_new_dummy_device(client->adapter, + pdata->i2c_addresses[page]); + else + new_client = i2c_new_ancillary_device(client, + adv76xx_default_addresses[page].name, + adv76xx_default_addresses[page].default_addr); + + if (!IS_ERR(new_client)) + io_write(sd, io_reg, new_client->addr << 1); + + return new_client; +} + +static const struct adv76xx_reg_seq adv7604_recommended_settings_afe[] = { + /* reset ADI recommended settings for HDMI: */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 4. */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x0d), 0x04 }, /* HDMI filter optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x0d), 0x04 }, /* HDMI filter optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x3d), 0x00 }, /* DDC bus active pull-up control */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x3e), 0x74 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x4e), 0x3b }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x57), 0x74 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x58), 0x63 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8d), 0x18 }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8e), 0x34 }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x93), 0x88 }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x94), 0x2e }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x96), 0x00 }, /* enable automatic EQ changing */ + + /* set ADI recommended settings for digitizer */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 17. */ + { ADV76XX_REG(ADV76XX_PAGE_AFE, 0x12), 0x7b }, /* ADC noise shaping filter controls */ + { ADV76XX_REG(ADV76XX_PAGE_AFE, 0x0c), 0x1f }, /* CP core gain controls */ + { ADV76XX_REG(ADV76XX_PAGE_CP, 0x3e), 0x04 }, /* CP core pre-gain control */ + { ADV76XX_REG(ADV76XX_PAGE_CP, 0xc3), 0x39 }, /* CP coast control. Graphics mode */ + { ADV76XX_REG(ADV76XX_PAGE_CP, 0x40), 0x5c }, /* CP core pre-gain control. Graphics mode */ + + { ADV76XX_REG_SEQ_TERM, 0 }, +}; + +static const struct adv76xx_reg_seq adv7604_recommended_settings_hdmi[] = { + /* set ADI recommended settings for HDMI: */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 4. */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x0d), 0x84 }, /* HDMI filter optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x3d), 0x10 }, /* DDC bus active pull-up control */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x3e), 0x39 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x4e), 0x3b }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x57), 0xb6 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x58), 0x03 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8d), 0x18 }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8e), 0x34 }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x93), 0x8b }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x94), 0x2d }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x96), 0x01 }, /* enable automatic EQ changing */ + + /* reset ADI recommended settings for digitizer */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 17. */ + { ADV76XX_REG(ADV76XX_PAGE_AFE, 0x12), 0xfb }, /* ADC noise shaping filter controls */ + { ADV76XX_REG(ADV76XX_PAGE_AFE, 0x0c), 0x0d }, /* CP core gain controls */ + + { ADV76XX_REG_SEQ_TERM, 0 }, +}; + +static const struct adv76xx_reg_seq adv7611_recommended_settings_hdmi[] = { + /* ADV7611 Register Settings Recommendations Rev 1.5, May 2014 */ + { ADV76XX_REG(ADV76XX_PAGE_CP, 0x6c), 0x00 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x9b), 0x03 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x6f), 0x08 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x85), 0x1f }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x87), 0x70 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x57), 0xda }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x58), 0x01 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x03), 0x98 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x4c), 0x44 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8d), 0x04 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8e), 0x1e }, + + { ADV76XX_REG_SEQ_TERM, 0 }, +}; + +static const struct adv76xx_reg_seq adv7612_recommended_settings_hdmi[] = { + { ADV76XX_REG(ADV76XX_PAGE_CP, 0x6c), 0x00 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x9b), 0x03 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x6f), 0x08 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x85), 0x1f }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x87), 0x70 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x57), 0xda }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x58), 0x01 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x03), 0x98 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x4c), 0x44 }, + { ADV76XX_REG_SEQ_TERM, 0 }, +}; + +static const struct adv76xx_chip_info adv76xx_chip_info[] = { + [ADV7604] = { + .type = ADV7604, + .has_afe = true, + .max_port = ADV7604_PAD_VGA_COMP, + .num_dv_ports = 4, + .edid_enable_reg = 0x77, + .edid_status_reg = 0x7d, + .lcf_reg = 0xb3, + .tdms_lock_mask = 0xe0, + .cable_det_mask = 0x1e, + .fmt_change_digital_mask = 0xc1, + .cp_csc = 0xfc, + .cec_irq_status = 0x4d, + .cec_rx_enable = 0x26, + .cec_rx_enable_mask = 0x01, + .cec_irq_swap = true, + .formats = adv7604_formats, + .nformats = ARRAY_SIZE(adv7604_formats), + .set_termination = adv7604_set_termination, + .setup_irqs = adv7604_setup_irqs, + .read_hdmi_pixelclock = adv7604_read_hdmi_pixelclock, + .read_cable_det = adv7604_read_cable_det, + .recommended_settings = { + [0] = adv7604_recommended_settings_afe, + [1] = adv7604_recommended_settings_hdmi, + }, + .num_recommended_settings = { + [0] = ARRAY_SIZE(adv7604_recommended_settings_afe), + [1] = ARRAY_SIZE(adv7604_recommended_settings_hdmi), + }, + .page_mask = BIT(ADV76XX_PAGE_IO) | BIT(ADV7604_PAGE_AVLINK) | + BIT(ADV76XX_PAGE_CEC) | BIT(ADV76XX_PAGE_INFOFRAME) | + BIT(ADV7604_PAGE_ESDP) | BIT(ADV7604_PAGE_DPP) | + BIT(ADV76XX_PAGE_AFE) | BIT(ADV76XX_PAGE_REP) | + BIT(ADV76XX_PAGE_EDID) | BIT(ADV76XX_PAGE_HDMI) | + BIT(ADV76XX_PAGE_TEST) | BIT(ADV76XX_PAGE_CP) | + BIT(ADV7604_PAGE_VDP), + .linewidth_mask = 0xfff, + .field0_height_mask = 0xfff, + .field1_height_mask = 0xfff, + .hfrontporch_mask = 0x3ff, + .hsync_mask = 0x3ff, + .hbackporch_mask = 0x3ff, + .field0_vfrontporch_mask = 0x1fff, + .field0_vsync_mask = 0x1fff, + .field0_vbackporch_mask = 0x1fff, + .field1_vfrontporch_mask = 0x1fff, + .field1_vsync_mask = 0x1fff, + .field1_vbackporch_mask = 0x1fff, + }, + [ADV7611] = { + .type = ADV7611, + .has_afe = false, + .max_port = ADV76XX_PAD_HDMI_PORT_A, + .num_dv_ports = 1, + .edid_enable_reg = 0x74, + .edid_status_reg = 0x76, + .lcf_reg = 0xa3, + .tdms_lock_mask = 0x43, + .cable_det_mask = 0x01, + .fmt_change_digital_mask = 0x03, + .cp_csc = 0xf4, + .cec_irq_status = 0x93, + .cec_rx_enable = 0x2c, + .cec_rx_enable_mask = 0x02, + .formats = adv7611_formats, + .nformats = ARRAY_SIZE(adv7611_formats), + .set_termination = adv7611_set_termination, + .setup_irqs = adv7611_setup_irqs, + .read_hdmi_pixelclock = adv7611_read_hdmi_pixelclock, + .read_cable_det = adv7611_read_cable_det, + .recommended_settings = { + [1] = adv7611_recommended_settings_hdmi, + }, + .num_recommended_settings = { + [1] = ARRAY_SIZE(adv7611_recommended_settings_hdmi), + }, + .page_mask = BIT(ADV76XX_PAGE_IO) | BIT(ADV76XX_PAGE_CEC) | + BIT(ADV76XX_PAGE_INFOFRAME) | BIT(ADV76XX_PAGE_AFE) | + BIT(ADV76XX_PAGE_REP) | BIT(ADV76XX_PAGE_EDID) | + BIT(ADV76XX_PAGE_HDMI) | BIT(ADV76XX_PAGE_CP), + .linewidth_mask = 0x1fff, + .field0_height_mask = 0x1fff, + .field1_height_mask = 0x1fff, + .hfrontporch_mask = 0x1fff, + .hsync_mask = 0x1fff, + .hbackporch_mask = 0x1fff, + .field0_vfrontporch_mask = 0x3fff, + .field0_vsync_mask = 0x3fff, + .field0_vbackporch_mask = 0x3fff, + .field1_vfrontporch_mask = 0x3fff, + .field1_vsync_mask = 0x3fff, + .field1_vbackporch_mask = 0x3fff, + }, + [ADV7612] = { + .type = ADV7612, + .has_afe = false, + .max_port = ADV76XX_PAD_HDMI_PORT_A, /* B not supported */ + .num_dv_ports = 1, /* normally 2 */ + .edid_enable_reg = 0x74, + .edid_status_reg = 0x76, + .lcf_reg = 0xa3, + .tdms_lock_mask = 0x43, + .cable_det_mask = 0x01, + .fmt_change_digital_mask = 0x03, + .cp_csc = 0xf4, + .cec_irq_status = 0x93, + .cec_rx_enable = 0x2c, + .cec_rx_enable_mask = 0x02, + .formats = adv7612_formats, + .nformats = ARRAY_SIZE(adv7612_formats), + .set_termination = adv7611_set_termination, + .setup_irqs = adv7612_setup_irqs, + .read_hdmi_pixelclock = adv7611_read_hdmi_pixelclock, + .read_cable_det = adv7612_read_cable_det, + .recommended_settings = { + [1] = adv7612_recommended_settings_hdmi, + }, + .num_recommended_settings = { + [1] = ARRAY_SIZE(adv7612_recommended_settings_hdmi), + }, + .page_mask = BIT(ADV76XX_PAGE_IO) | BIT(ADV76XX_PAGE_CEC) | + BIT(ADV76XX_PAGE_INFOFRAME) | BIT(ADV76XX_PAGE_AFE) | + BIT(ADV76XX_PAGE_REP) | BIT(ADV76XX_PAGE_EDID) | + BIT(ADV76XX_PAGE_HDMI) | BIT(ADV76XX_PAGE_CP), + .linewidth_mask = 0x1fff, + .field0_height_mask = 0x1fff, + .field1_height_mask = 0x1fff, + .hfrontporch_mask = 0x1fff, + .hsync_mask = 0x1fff, + .hbackporch_mask = 0x1fff, + .field0_vfrontporch_mask = 0x3fff, + .field0_vsync_mask = 0x3fff, + .field0_vbackporch_mask = 0x3fff, + .field1_vfrontporch_mask = 0x3fff, + .field1_vsync_mask = 0x3fff, + .field1_vbackporch_mask = 0x3fff, + }, +}; + +static const struct i2c_device_id adv76xx_i2c_id[] = { + { "adv7604", (kernel_ulong_t)&adv76xx_chip_info[ADV7604] }, + { "adv7611", (kernel_ulong_t)&adv76xx_chip_info[ADV7611] }, + { "adv7612", (kernel_ulong_t)&adv76xx_chip_info[ADV7612] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv76xx_i2c_id); + +static const struct of_device_id adv76xx_of_id[] __maybe_unused = { + { .compatible = "adi,adv7611", .data = &adv76xx_chip_info[ADV7611] }, + { .compatible = "adi,adv7612", .data = &adv76xx_chip_info[ADV7612] }, + { } +}; +MODULE_DEVICE_TABLE(of, adv76xx_of_id); + +static int adv76xx_parse_dt(struct adv76xx_state *state) +{ + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + struct device_node *endpoint; + struct device_node *np; + unsigned int flags; + int ret; + u32 v; + + np = state->i2c_clients[ADV76XX_PAGE_IO]->dev.of_node; + + /* Parse the endpoint. */ + endpoint = of_graph_get_next_endpoint(np, NULL); + if (!endpoint) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), &bus_cfg); + of_node_put(endpoint); + if (ret) + return ret; + + if (!of_property_read_u32(np, "default-input", &v)) + state->pdata.default_input = v; + else + state->pdata.default_input = -1; + + flags = bus_cfg.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + state->pdata.inv_hs_pol = 1; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + state->pdata.inv_vs_pol = 1; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + state->pdata.inv_llc_pol = 1; + + if (bus_cfg.bus_type == V4L2_MBUS_BT656) + state->pdata.insert_av_codes = 1; + + /* Disable the interrupt for now as no DT-based board uses it. */ + state->pdata.int1_config = ADV76XX_INT1_CONFIG_ACTIVE_HIGH; + + /* Hardcode the remaining platform data fields. */ + state->pdata.disable_pwrdnb = 0; + state->pdata.disable_cable_det_rst = 0; + state->pdata.blank_data = 1; + state->pdata.op_format_mode_sel = ADV7604_OP_FORMAT_MODE0; + state->pdata.bus_order = ADV7604_BUS_ORDER_RGB; + state->pdata.dr_str_data = ADV76XX_DR_STR_MEDIUM_HIGH; + state->pdata.dr_str_clk = ADV76XX_DR_STR_MEDIUM_HIGH; + state->pdata.dr_str_sync = ADV76XX_DR_STR_MEDIUM_HIGH; + + return 0; +} + +static const struct regmap_config adv76xx_regmap_cnf[] = { + { + .name = "io", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "avlink", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "cec", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "infoframe", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "esdp", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "epp", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "afe", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "rep", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "edid", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + + { + .name = "hdmi", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "test", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "cp", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "vdp", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, +}; + +static int configure_regmap(struct adv76xx_state *state, int region) +{ + int err; + + if (!state->i2c_clients[region]) + return -ENODEV; + + state->regmap[region] = + devm_regmap_init_i2c(state->i2c_clients[region], + &adv76xx_regmap_cnf[region]); + + if (IS_ERR(state->regmap[region])) { + err = PTR_ERR(state->regmap[region]); + v4l_err(state->i2c_clients[region], + "Error initializing regmap %d with error %d\n", + region, err); + return -EINVAL; + } + + return 0; +} + +static int configure_regmaps(struct adv76xx_state *state) +{ + int i, err; + + for (i = ADV7604_PAGE_AVLINK ; i < ADV76XX_PAGE_MAX; i++) { + err = configure_regmap(state, i); + if (err && (err != -ENODEV)) + return err; + } + return 0; +} + +static void adv76xx_reset(struct adv76xx_state *state) +{ + if (state->reset_gpio) { + /* ADV76XX can be reset by a low reset pulse of minimum 5 ms. */ + gpiod_set_value_cansleep(state->reset_gpio, 0); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(state->reset_gpio, 1); + /* It is recommended to wait 5 ms after the low pulse before */ + /* an I2C write is performed to the ADV76XX. */ + usleep_range(5000, 10000); + } +} + +static int adv76xx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + static const struct v4l2_dv_timings cea640x480 = + V4L2_DV_BT_CEA_640X480P59_94; + struct adv76xx_state *state; + struct v4l2_ctrl_handler *hdl; + struct v4l2_ctrl *ctrl; + struct v4l2_subdev *sd; + unsigned int i; + unsigned int val, val2; + int err; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + v4l_dbg(1, debug, client, "detecting adv76xx client on address 0x%x\n", + client->addr << 1); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->i2c_clients[ADV76XX_PAGE_IO] = client; + + /* initialize variables */ + state->restart_stdi_once = true; + state->selected_input = ~0; + + if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) { + const struct of_device_id *oid; + + oid = of_match_node(adv76xx_of_id, client->dev.of_node); + state->info = oid->data; + + err = adv76xx_parse_dt(state); + if (err < 0) { + v4l_err(client, "DT parsing error\n"); + return err; + } + } else if (client->dev.platform_data) { + struct adv76xx_platform_data *pdata = client->dev.platform_data; + + state->info = (const struct adv76xx_chip_info *)id->driver_data; + state->pdata = *pdata; + } else { + v4l_err(client, "No platform data!\n"); + return -ENODEV; + } + + /* Request GPIOs. */ + for (i = 0; i < state->info->num_dv_ports; ++i) { + state->hpd_gpio[i] = + devm_gpiod_get_index_optional(&client->dev, "hpd", i, + GPIOD_OUT_LOW); + if (IS_ERR(state->hpd_gpio[i])) + return PTR_ERR(state->hpd_gpio[i]); + + if (state->hpd_gpio[i]) + v4l_info(client, "Handling HPD %u GPIO\n", i); + } + state->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(state->reset_gpio)) + return PTR_ERR(state->reset_gpio); + + adv76xx_reset(state); + + state->timings = cea640x480; + state->format = adv76xx_format_info(state, MEDIA_BUS_FMT_YUYV8_2X8); + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &adv76xx_ops); + snprintf(sd->name, sizeof(sd->name), "%s %d-%04x", + id->name, i2c_adapter_id(client->adapter), + client->addr); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->internal_ops = &adv76xx_int_ops; + + /* Configure IO Regmap region */ + err = configure_regmap(state, ADV76XX_PAGE_IO); + + if (err) { + v4l2_err(sd, "Error configuring IO regmap region\n"); + return -ENODEV; + } + + /* + * Verify that the chip is present. On ADV7604 the RD_INFO register only + * identifies the revision, while on ADV7611 it identifies the model as + * well. Use the HDMI slave address on ADV7604 and RD_INFO on ADV7611. + */ + switch (state->info->type) { + case ADV7604: + err = regmap_read(state->regmap[ADV76XX_PAGE_IO], 0xfb, &val); + if (err) { + v4l2_err(sd, "Error %d reading IO Regmap\n", err); + return -ENODEV; + } + if (val != 0x68) { + v4l2_err(sd, "not an adv7604 on address 0x%x\n", + client->addr << 1); + return -ENODEV; + } + break; + case ADV7611: + case ADV7612: + err = regmap_read(state->regmap[ADV76XX_PAGE_IO], + 0xea, + &val); + if (err) { + v4l2_err(sd, "Error %d reading IO Regmap\n", err); + return -ENODEV; + } + val2 = val << 8; + err = regmap_read(state->regmap[ADV76XX_PAGE_IO], + 0xeb, + &val); + if (err) { + v4l2_err(sd, "Error %d reading IO Regmap\n", err); + return -ENODEV; + } + val |= val2; + if ((state->info->type == ADV7611 && val != 0x2051) || + (state->info->type == ADV7612 && val != 0x2041)) { + v4l2_err(sd, "not an adv761x on address 0x%x\n", + client->addr << 1); + return -ENODEV; + } + break; + } + + /* control handlers */ + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, adv76xx_has_afe(state) ? 9 : 8); + + v4l2_ctrl_new_std(hdl, &adv76xx_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &adv76xx_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv76xx_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv76xx_ctrl_ops, + V4L2_CID_HUE, 0, 128, 1, 0); + ctrl = v4l2_ctrl_new_std_menu(hdl, &adv76xx_ctrl_ops, + V4L2_CID_DV_RX_IT_CONTENT_TYPE, V4L2_DV_IT_CONTENT_TYPE_NO_ITC, + 0, V4L2_DV_IT_CONTENT_TYPE_NO_ITC); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_RX_POWER_PRESENT, 0, + (1 << state->info->num_dv_ports) - 1, 0, 0); + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv76xx_ctrl_ops, + V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + + /* custom controls */ + if (adv76xx_has_afe(state)) + state->analog_sampling_phase_ctrl = + v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_analog_sampling_phase, NULL); + state->free_run_color_manual_ctrl = + v4l2_ctrl_new_custom(hdl, &adv76xx_ctrl_free_run_color_manual, NULL); + state->free_run_color_ctrl = + v4l2_ctrl_new_custom(hdl, &adv76xx_ctrl_free_run_color, NULL); + + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + goto err_hdl; + } + if (adv76xx_s_detect_tx_5v_ctrl(sd)) { + err = -ENODEV; + goto err_hdl; + } + + for (i = 1; i < ADV76XX_PAGE_MAX; ++i) { + struct i2c_client *dummy_client; + + if (!(BIT(i) & state->info->page_mask)) + continue; + + dummy_client = adv76xx_dummy_client(sd, i); + if (IS_ERR(dummy_client)) { + err = PTR_ERR(dummy_client); + v4l2_err(sd, "failed to create i2c client %u\n", i); + goto err_i2c; + } + + state->i2c_clients[i] = dummy_client; + } + + INIT_DELAYED_WORK(&state->delayed_work_enable_hotplug, + adv76xx_delayed_work_enable_hotplug); + + state->source_pad = state->info->num_dv_ports + + (state->info->has_afe ? 2 : 0); + for (i = 0; i < state->source_pad; ++i) + state->pads[i].flags = MEDIA_PAD_FL_SINK; + state->pads[state->source_pad].flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_DV_DECODER; + + err = media_entity_pads_init(&sd->entity, state->source_pad + 1, + state->pads); + if (err) + goto err_work_queues; + + /* Configure regmaps */ + err = configure_regmaps(state); + if (err) + goto err_entity; + + err = adv76xx_core_init(sd); + if (err) + goto err_entity; + + if (client->irq) { + err = devm_request_threaded_irq(&client->dev, + client->irq, + NULL, adv76xx_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + client->name, state); + if (err) + goto err_entity; + } + +#if IS_ENABLED(CONFIG_VIDEO_ADV7604_CEC) + state->cec_adap = cec_allocate_adapter(&adv76xx_cec_adap_ops, + state, dev_name(&client->dev), + CEC_CAP_DEFAULTS, ADV76XX_MAX_ADDRS); + err = PTR_ERR_OR_ZERO(state->cec_adap); + if (err) + goto err_entity; +#endif + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + err = v4l2_async_register_subdev(sd); + if (err) + goto err_entity; + + return 0; + +err_entity: + media_entity_cleanup(&sd->entity); +err_work_queues: + cancel_delayed_work(&state->delayed_work_enable_hotplug); +err_i2c: + adv76xx_unregister_clients(state); +err_hdl: + v4l2_ctrl_handler_free(hdl); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static int adv76xx_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv76xx_state *state = to_state(sd); + + /* disable interrupts */ + io_write(sd, 0x40, 0); + io_write(sd, 0x41, 0); + io_write(sd, 0x46, 0); + io_write(sd, 0x6e, 0); + io_write(sd, 0x73, 0); + + cancel_delayed_work_sync(&state->delayed_work_enable_hotplug); + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + adv76xx_unregister_clients(to_state(sd)); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver adv76xx_driver = { + .driver = { + .name = "adv7604", + .of_match_table = of_match_ptr(adv76xx_of_id), + }, + .probe = adv76xx_probe, + .remove = adv76xx_remove, + .id_table = adv76xx_i2c_id, +}; + +module_i2c_driver(adv76xx_driver); diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c new file mode 100644 index 000000000..a870117fe --- /dev/null +++ b/drivers/media/i2c/adv7842.c @@ -0,0 +1,3616 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adv7842 - Analog Devices ADV7842 video decoder driver + * + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + */ + +/* + * References (c = chapter, p = page): + * REF_01 - Analog devices, ADV7842, + * Register Settings Recommendations, Rev. 1.9, April 2011 + * REF_02 - Analog devices, Software User Guide, UG-206, + * ADV7842 I2C Register Maps, Rev. 0, November 2010 + * REF_03 - Analog devices, Hardware User Guide, UG-214, + * ADV7842 Fast Switching 2:1 HDMI 1.4 Receiver with 3D-Comb + * Decoder and Digitizer , Rev. 0, January 2011 + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <linux/workqueue.h> +#include <linux/v4l2-dv-timings.h> +#include <linux/hdmi.h> +#include <media/cec.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dv-timings.h> +#include <media/i2c/adv7842.h> + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices ADV7842 video decoder driver"); +MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>"); +MODULE_AUTHOR("Martin Bugge <marbugge@cisco.com>"); +MODULE_LICENSE("GPL"); + +/* ADV7842 system clock frequency */ +#define ADV7842_fsc (28636360) + +#define ADV7842_RGB_OUT (1 << 1) + +#define ADV7842_OP_FORMAT_SEL_8BIT (0 << 0) +#define ADV7842_OP_FORMAT_SEL_10BIT (1 << 0) +#define ADV7842_OP_FORMAT_SEL_12BIT (2 << 0) + +#define ADV7842_OP_MODE_SEL_SDR_422 (0 << 5) +#define ADV7842_OP_MODE_SEL_DDR_422 (1 << 5) +#define ADV7842_OP_MODE_SEL_SDR_444 (2 << 5) +#define ADV7842_OP_MODE_SEL_DDR_444 (3 << 5) +#define ADV7842_OP_MODE_SEL_SDR_422_2X (4 << 5) +#define ADV7842_OP_MODE_SEL_ADI_CM (5 << 5) + +#define ADV7842_OP_CH_SEL_GBR (0 << 5) +#define ADV7842_OP_CH_SEL_GRB (1 << 5) +#define ADV7842_OP_CH_SEL_BGR (2 << 5) +#define ADV7842_OP_CH_SEL_RGB (3 << 5) +#define ADV7842_OP_CH_SEL_BRG (4 << 5) +#define ADV7842_OP_CH_SEL_RBG (5 << 5) + +#define ADV7842_OP_SWAP_CB_CR (1 << 0) + +#define ADV7842_MAX_ADDRS (3) + +/* +********************************************************************** +* +* Arrays with configuration parameters for the ADV7842 +* +********************************************************************** +*/ + +struct adv7842_format_info { + u32 code; + u8 op_ch_sel; + bool rgb_out; + bool swap_cb_cr; + u8 op_format_sel; +}; + +struct adv7842_state { + struct adv7842_platform_data pdata; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + enum adv7842_mode mode; + struct v4l2_dv_timings timings; + enum adv7842_vid_std_select vid_std_select; + + const struct adv7842_format_info *format; + + v4l2_std_id norm; + struct { + u8 edid[256]; + u32 present; + } hdmi_edid; + struct { + u8 edid[256]; + u32 present; + } vga_edid; + struct v4l2_fract aspect_ratio; + u32 rgb_quantization_range; + bool is_cea_format; + struct delayed_work delayed_work_enable_hotplug; + bool restart_stdi_once; + bool hdmi_port_a; + + /* i2c clients */ + struct i2c_client *i2c_sdp_io; + struct i2c_client *i2c_sdp; + struct i2c_client *i2c_cp; + struct i2c_client *i2c_vdp; + struct i2c_client *i2c_afe; + struct i2c_client *i2c_hdmi; + struct i2c_client *i2c_repeater; + struct i2c_client *i2c_edid; + struct i2c_client *i2c_infoframe; + struct i2c_client *i2c_cec; + struct i2c_client *i2c_avlink; + + /* controls */ + struct v4l2_ctrl *detect_tx_5v_ctrl; + struct v4l2_ctrl *analog_sampling_phase_ctrl; + struct v4l2_ctrl *free_run_color_ctrl_manual; + struct v4l2_ctrl *free_run_color_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; + + struct cec_adapter *cec_adap; + u8 cec_addr[ADV7842_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; +}; + +/* Unsupported timings. This device cannot support 720p30. */ +static const struct v4l2_dv_timings adv7842_timings_exceptions[] = { + V4L2_DV_BT_CEA_1280X720P30, + { } +}; + +static bool adv7842_check_dv_timings(const struct v4l2_dv_timings *t, void *hdl) +{ + int i; + + for (i = 0; adv7842_timings_exceptions[i].bt.width; i++) + if (v4l2_match_dv_timings(t, adv7842_timings_exceptions + i, 0, false)) + return false; + return true; +} + +struct adv7842_video_standards { + struct v4l2_dv_timings timings; + u8 vid_std; + u8 v_freq; +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_comp[] = { + /* { V4L2_DV_BT_CEA_720X480P59_94, 0x0a, 0x00 }, TODO flickering */ + { V4L2_DV_BT_CEA_720X576P50, 0x0b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x19, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x19, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x1e, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x1e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x1e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x1e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x1e, 0x00 }, + /* TODO add 1920x1080P60_RB (CVT timing) */ + { }, +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_gr[] = { + { V4L2_DV_BT_DMT_640X480P60, 0x08, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x09, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x0a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x0b, 0x00 }, + { V4L2_DV_BT_DMT_800X600P56, 0x00, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x01, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x02, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x03, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x04, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P60, 0x0c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x0d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x0e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x0f, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P60, 0x05, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x06, 0x00 }, + { V4L2_DV_BT_DMT_1360X768P60, 0x12, 0x00 }, + { V4L2_DV_BT_DMT_1366X768P60, 0x13, 0x00 }, + { V4L2_DV_BT_DMT_1400X1050P60, 0x14, 0x00 }, + { V4L2_DV_BT_DMT_1400X1050P75, 0x15, 0x00 }, + { V4L2_DV_BT_DMT_1600X1200P60, 0x16, 0x00 }, /* TODO not tested */ + /* TODO add 1600X1200P60_RB (not a DMT timing) */ + { V4L2_DV_BT_DMT_1680X1050P60, 0x18, 0x00 }, + { V4L2_DV_BT_DMT_1920X1200P60_RB, 0x19, 0x00 }, /* TODO not tested */ + { }, +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_hdmi_comp[] = { + { V4L2_DV_BT_CEA_720X480P59_94, 0x0a, 0x00 }, + { V4L2_DV_BT_CEA_720X576P50, 0x0b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x13, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x13, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x1e, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x1e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x1e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x1e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x1e, 0x00 }, + { }, +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_hdmi_gr[] = { + { V4L2_DV_BT_DMT_640X480P60, 0x08, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x09, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x0a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x0b, 0x00 }, + { V4L2_DV_BT_DMT_800X600P56, 0x00, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x01, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x02, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x03, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x04, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P60, 0x0c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x0d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x0e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x0f, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P60, 0x05, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x06, 0x00 }, + { }, +}; + +static const struct v4l2_event adv7842_ev_fmt = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, +}; + +/* ----------------------------------------------------------------------- */ + +static inline struct adv7842_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7842_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7842_state, hdl)->sd; +} + +static inline unsigned hblanking(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_BLANKING_WIDTH(t); +} + +static inline unsigned htotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_WIDTH(t); +} + +static inline unsigned vblanking(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_BLANKING_HEIGHT(t); +} + +static inline unsigned vtotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_HEIGHT(t); +} + + +/* ----------------------------------------------------------------------- */ + +static s32 adv_smbus_read_byte_data_check(struct i2c_client *client, + u8 command, bool check) +{ + union i2c_smbus_data data; + + if (!i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_BYTE_DATA, &data)) + return data.byte; + if (check) + v4l_err(client, "error reading %02x, %02x\n", + client->addr, command); + return -EIO; +} + +static s32 adv_smbus_read_byte_data(struct i2c_client *client, u8 command) +{ + int i; + + for (i = 0; i < 3; i++) { + int ret = adv_smbus_read_byte_data_check(client, command, true); + + if (ret >= 0) { + if (i) + v4l_err(client, "read ok after %d retries\n", i); + return ret; + } + } + v4l_err(client, "read failed\n"); + return -EIO; +} + +static s32 adv_smbus_write_byte_data(struct i2c_client *client, + u8 command, u8 value) +{ + union i2c_smbus_data data; + int err; + int i; + + data.byte = value; + for (i = 0; i < 3; i++) { + err = i2c_smbus_xfer(client->adapter, client->addr, + client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_BYTE_DATA, &data); + if (!err) + break; + } + if (err < 0) + v4l_err(client, "error writing %02x, %02x, %02x\n", + client->addr, command, value); + return err; +} + +static void adv_smbus_write_byte_no_check(struct i2c_client *client, + u8 command, u8 value) +{ + union i2c_smbus_data data; + data.byte = value; + + i2c_smbus_xfer(client->adapter, client->addr, + client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_BYTE_DATA, &data); +} + +static s32 adv_smbus_write_i2c_block_data(struct i2c_client *client, + u8 command, unsigned length, const u8 *values) +{ + union i2c_smbus_data data; + + if (length > I2C_SMBUS_BLOCK_MAX) + length = I2C_SMBUS_BLOCK_MAX; + data.block[0] = length; + memcpy(data.block + 1, values, length); + return i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_I2C_BLOCK_DATA, &data); +} + +/* ----------------------------------------------------------------------- */ + +static inline int io_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_read_byte_data(client, reg); +} + +static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_write_byte_data(client, reg, val); +} + +static inline int io_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return io_write(sd, reg, (io_read(sd, reg) & mask) | val); +} + +static inline int io_write_clr_set(struct v4l2_subdev *sd, + u8 reg, u8 mask, u8 val) +{ + return io_write(sd, reg, (io_read(sd, reg) & ~mask) | val); +} + +static inline int avlink_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_avlink, reg); +} + +static inline int avlink_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_avlink, reg, val); +} + +static inline int cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_cec, reg); +} + +static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_cec, reg, val); +} + +static inline int cec_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return cec_write(sd, reg, (cec_read(sd, reg) & ~mask) | val); +} + +static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_infoframe, reg); +} + +static inline int infoframe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_infoframe, reg, val); +} + +static inline int sdp_io_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_sdp_io, reg); +} + +static inline int sdp_io_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_sdp_io, reg, val); +} + +static inline int sdp_io_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return sdp_io_write(sd, reg, (sdp_io_read(sd, reg) & mask) | val); +} + +static inline int sdp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_sdp, reg); +} + +static inline int sdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_sdp, reg, val); +} + +static inline int sdp_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return sdp_write(sd, reg, (sdp_read(sd, reg) & mask) | val); +} + +static inline int afe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_afe, reg); +} + +static inline int afe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_afe, reg, val); +} + +static inline int afe_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return afe_write(sd, reg, (afe_read(sd, reg) & mask) | val); +} + +static inline int rep_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_repeater, reg); +} + +static inline int rep_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_repeater, reg, val); +} + +static inline int rep_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return rep_write(sd, reg, (rep_read(sd, reg) & mask) | val); +} + +static inline int edid_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_edid, reg); +} + +static inline int edid_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_edid, reg, val); +} + +static inline int hdmi_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_hdmi, reg); +} + +static inline int hdmi_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_hdmi, reg, val); +} + +static inline int hdmi_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return hdmi_write(sd, reg, (hdmi_read(sd, reg) & mask) | val); +} + +static inline int cp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_cp, reg); +} + +static inline int cp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_cp, reg, val); +} + +static inline int cp_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return cp_write(sd, reg, (cp_read(sd, reg) & mask) | val); +} + +static inline int vdp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_vdp, reg); +} + +static inline int vdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_vdp, reg, val); +} + +static void main_reset(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + adv_smbus_write_byte_no_check(client, 0xff, 0x80); + + mdelay(5); +} + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +static const struct adv7842_format_info adv7842_formats[] = { + { MEDIA_BUS_FMT_RGB888_1X24, ADV7842_OP_CH_SEL_RGB, true, false, + ADV7842_OP_MODE_SEL_SDR_444 | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_2X8, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_2X8, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV10_2X10, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YVYU10_2X10, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YUYV12_2X12, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_2X12, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_UYVY8_1X16, ADV7842_OP_CH_SEL_RBG, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_VYUY8_1X16, ADV7842_OP_CH_SEL_RBG, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_1X16, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_1X16, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_UYVY10_1X20, ADV7842_OP_CH_SEL_RBG, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_VYUY10_1X20, ADV7842_OP_CH_SEL_RBG, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YUYV10_1X20, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YVYU10_1X20, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_UYVY12_1X24, ADV7842_OP_CH_SEL_RBG, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_VYUY12_1X24, ADV7842_OP_CH_SEL_RBG, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YUYV12_1X24, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_1X24, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_12BIT }, +}; + +static const struct adv7842_format_info * +adv7842_format_info(struct adv7842_state *state, u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(adv7842_formats); ++i) { + if (adv7842_formats[i].code == code) + return &adv7842_formats[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------- */ + +static inline bool is_analog_input(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + return ((state->mode == ADV7842_MODE_RGB) || + (state->mode == ADV7842_MODE_COMP)); +} + +static inline bool is_digital_input(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + return state->mode == ADV7842_MODE_HDMI; +} + +static const struct v4l2_dv_timings_cap adv7842_timings_cap_analog = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1200, 25000000, 170000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +static const struct v4l2_dv_timings_cap adv7842_timings_cap_digital = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1200, 25000000, 225000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +static inline const struct v4l2_dv_timings_cap * +adv7842_get_dv_timings_cap(struct v4l2_subdev *sd) +{ + return is_digital_input(sd) ? &adv7842_timings_cap_digital : + &adv7842_timings_cap_analog; +} + +/* ----------------------------------------------------------------------- */ + +static u16 adv7842_read_cable_det(struct v4l2_subdev *sd) +{ + u8 reg = io_read(sd, 0x6f); + u16 val = 0; + + if (reg & 0x02) + val |= 1; /* port A */ + if (reg & 0x01) + val |= 2; /* port B */ + return val; +} + +static void adv7842_delayed_work_enable_hotplug(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adv7842_state *state = container_of(dwork, + struct adv7842_state, delayed_work_enable_hotplug); + struct v4l2_subdev *sd = &state->sd; + int present = state->hdmi_edid.present; + u8 mask = 0; + + v4l2_dbg(2, debug, sd, "%s: enable hotplug on ports: 0x%x\n", + __func__, present); + + if (present & (0x04 << ADV7842_EDID_PORT_A)) + mask |= 0x20; + if (present & (0x04 << ADV7842_EDID_PORT_B)) + mask |= 0x10; + io_write_and_or(sd, 0x20, 0xcf, mask); +} + +static int edid_write_vga_segment(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7842_state *state = to_state(sd); + const u8 *val = state->vga_edid.edid; + int err = 0; + int i; + + v4l2_dbg(2, debug, sd, "%s: write EDID on VGA port\n", __func__); + + /* HPA disable on port A and B */ + io_write_and_or(sd, 0x20, 0xcf, 0x00); + + /* Disable I2C access to internal EDID ram from VGA DDC port */ + rep_write_and_or(sd, 0x7f, 0x7f, 0x00); + + /* edid segment pointer '1' for VGA port */ + rep_write_and_or(sd, 0x77, 0xef, 0x10); + + for (i = 0; !err && i < 256; i += I2C_SMBUS_BLOCK_MAX) + err = adv_smbus_write_i2c_block_data(state->i2c_edid, i, + I2C_SMBUS_BLOCK_MAX, val + i); + if (err) + return err; + + /* Calculates the checksums and enables I2C access + * to internal EDID ram from VGA DDC port. + */ + rep_write_and_or(sd, 0x7f, 0x7f, 0x80); + + for (i = 0; i < 1000; i++) { + if (rep_read(sd, 0x79) & 0x20) + break; + mdelay(1); + } + if (i == 1000) { + v4l_err(client, "error enabling edid on VGA port\n"); + return -EIO; + } + + /* enable hotplug after 200 ms */ + schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 5); + + return 0; +} + +static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7842_state *state = to_state(sd); + const u8 *edid = state->hdmi_edid.edid; + int spa_loc; + u16 pa; + int err = 0; + int i; + + v4l2_dbg(2, debug, sd, "%s: write EDID on port %c\n", + __func__, (port == ADV7842_EDID_PORT_A) ? 'A' : 'B'); + + /* HPA disable on port A and B */ + io_write_and_or(sd, 0x20, 0xcf, 0x00); + + /* Disable I2C access to internal EDID ram from HDMI DDC ports */ + rep_write_and_or(sd, 0x77, 0xf3, 0x00); + + if (!state->hdmi_edid.present) { + cec_phys_addr_invalidate(state->cec_adap); + return 0; + } + + pa = v4l2_get_edid_phys_addr(edid, 256, &spa_loc); + err = v4l2_phys_addr_validate(pa, &pa, NULL); + if (err) + return err; + + /* + * Return an error if no location of the source physical address + * was found. + */ + if (spa_loc == 0) + return -EINVAL; + + /* edid segment pointer '0' for HDMI ports */ + rep_write_and_or(sd, 0x77, 0xef, 0x00); + + for (i = 0; !err && i < 256; i += I2C_SMBUS_BLOCK_MAX) + err = adv_smbus_write_i2c_block_data(state->i2c_edid, i, + I2C_SMBUS_BLOCK_MAX, edid + i); + if (err) + return err; + + if (port == ADV7842_EDID_PORT_A) { + rep_write(sd, 0x72, edid[spa_loc]); + rep_write(sd, 0x73, edid[spa_loc + 1]); + } else { + rep_write(sd, 0x74, edid[spa_loc]); + rep_write(sd, 0x75, edid[spa_loc + 1]); + } + rep_write(sd, 0x76, spa_loc & 0xff); + rep_write_and_or(sd, 0x77, 0xbf, (spa_loc >> 2) & 0x40); + + /* Calculates the checksums and enables I2C access to internal + * EDID ram from HDMI DDC ports + */ + rep_write_and_or(sd, 0x77, 0xf3, state->hdmi_edid.present); + + for (i = 0; i < 1000; i++) { + if (rep_read(sd, 0x7d) & state->hdmi_edid.present) + break; + mdelay(1); + } + if (i == 1000) { + v4l_err(client, "error enabling edid on port %c\n", + (port == ADV7842_EDID_PORT_A) ? 'A' : 'B'); + return -EIO; + } + cec_s_phys_addr(state->cec_adap, pa, false); + + /* enable hotplug after 200 ms */ + schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 5); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static void adv7842_inv_register(struct v4l2_subdev *sd) +{ + v4l2_info(sd, "0x000-0x0ff: IO Map\n"); + v4l2_info(sd, "0x100-0x1ff: AVLink Map\n"); + v4l2_info(sd, "0x200-0x2ff: CEC Map\n"); + v4l2_info(sd, "0x300-0x3ff: InfoFrame Map\n"); + v4l2_info(sd, "0x400-0x4ff: SDP_IO Map\n"); + v4l2_info(sd, "0x500-0x5ff: SDP Map\n"); + v4l2_info(sd, "0x600-0x6ff: AFE Map\n"); + v4l2_info(sd, "0x700-0x7ff: Repeater Map\n"); + v4l2_info(sd, "0x800-0x8ff: EDID Map\n"); + v4l2_info(sd, "0x900-0x9ff: HDMI Map\n"); + v4l2_info(sd, "0xa00-0xaff: CP Map\n"); + v4l2_info(sd, "0xb00-0xbff: VDP Map\n"); +} + +static int adv7842_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + reg->size = 1; + switch (reg->reg >> 8) { + case 0: + reg->val = io_read(sd, reg->reg & 0xff); + break; + case 1: + reg->val = avlink_read(sd, reg->reg & 0xff); + break; + case 2: + reg->val = cec_read(sd, reg->reg & 0xff); + break; + case 3: + reg->val = infoframe_read(sd, reg->reg & 0xff); + break; + case 4: + reg->val = sdp_io_read(sd, reg->reg & 0xff); + break; + case 5: + reg->val = sdp_read(sd, reg->reg & 0xff); + break; + case 6: + reg->val = afe_read(sd, reg->reg & 0xff); + break; + case 7: + reg->val = rep_read(sd, reg->reg & 0xff); + break; + case 8: + reg->val = edid_read(sd, reg->reg & 0xff); + break; + case 9: + reg->val = hdmi_read(sd, reg->reg & 0xff); + break; + case 0xa: + reg->val = cp_read(sd, reg->reg & 0xff); + break; + case 0xb: + reg->val = vdp_read(sd, reg->reg & 0xff); + break; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7842_inv_register(sd); + break; + } + return 0; +} + +static int adv7842_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + u8 val = reg->val & 0xff; + + switch (reg->reg >> 8) { + case 0: + io_write(sd, reg->reg & 0xff, val); + break; + case 1: + avlink_write(sd, reg->reg & 0xff, val); + break; + case 2: + cec_write(sd, reg->reg & 0xff, val); + break; + case 3: + infoframe_write(sd, reg->reg & 0xff, val); + break; + case 4: + sdp_io_write(sd, reg->reg & 0xff, val); + break; + case 5: + sdp_write(sd, reg->reg & 0xff, val); + break; + case 6: + afe_write(sd, reg->reg & 0xff, val); + break; + case 7: + rep_write(sd, reg->reg & 0xff, val); + break; + case 8: + edid_write(sd, reg->reg & 0xff, val); + break; + case 9: + hdmi_write(sd, reg->reg & 0xff, val); + break; + case 0xa: + cp_write(sd, reg->reg & 0xff, val); + break; + case 0xb: + vdp_write(sd, reg->reg & 0xff, val); + break; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7842_inv_register(sd); + break; + } + return 0; +} +#endif + +static int adv7842_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + u16 cable_det = adv7842_read_cable_det(sd); + + v4l2_dbg(1, debug, sd, "%s: 0x%x\n", __func__, cable_det); + + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, cable_det); +} + +static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, + u8 prim_mode, + const struct adv7842_video_standards *predef_vid_timings, + const struct v4l2_dv_timings *timings) +{ + int i; + + for (i = 0; predef_vid_timings[i].timings.bt.width; i++) { + if (!v4l2_match_dv_timings(timings, &predef_vid_timings[i].timings, + is_digital_input(sd) ? 250000 : 1000000, false)) + continue; + /* video std */ + io_write(sd, 0x00, predef_vid_timings[i].vid_std); + /* v_freq and prim mode */ + io_write(sd, 0x01, (predef_vid_timings[i].v_freq << 4) + prim_mode); + return 0; + } + + return -1; +} + +static int configure_predefined_video_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + int err; + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* reset to default values */ + io_write(sd, 0x16, 0x43); + io_write(sd, 0x17, 0x5a); + /* disable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x00); + cp_write(sd, 0x26, 0x00); + cp_write(sd, 0x27, 0x00); + cp_write(sd, 0x28, 0x00); + cp_write(sd, 0x29, 0x00); + cp_write(sd, 0x8f, 0x40); + cp_write(sd, 0x90, 0x00); + cp_write(sd, 0xa5, 0x00); + cp_write(sd, 0xa6, 0x00); + cp_write(sd, 0xa7, 0x00); + cp_write(sd, 0xab, 0x00); + cp_write(sd, 0xac, 0x00); + + switch (state->mode) { + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + err = find_and_set_predefined_video_timings(sd, + 0x01, adv7842_prim_mode_comp, timings); + if (err) + err = find_and_set_predefined_video_timings(sd, + 0x02, adv7842_prim_mode_gr, timings); + break; + case ADV7842_MODE_HDMI: + err = find_and_set_predefined_video_timings(sd, + 0x05, adv7842_prim_mode_hdmi_comp, timings); + if (err) + err = find_and_set_predefined_video_timings(sd, + 0x06, adv7842_prim_mode_hdmi_gr, timings); + break; + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + err = -1; + break; + } + + + return err; +} + +static void configure_custom_video_timings(struct v4l2_subdev *sd, + const struct v4l2_bt_timings *bt) +{ + struct adv7842_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u32 width = htotal(bt); + u32 height = vtotal(bt); + u16 cp_start_sav = bt->hsync + bt->hbackporch - 4; + u16 cp_start_eav = width - bt->hfrontporch; + u16 cp_start_vbi = height - bt->vfrontporch + 1; + u16 cp_end_vbi = bt->vsync + bt->vbackporch + 1; + u16 ch1_fr_ll = (((u32)bt->pixelclock / 100) > 0) ? + ((width * (ADV7842_fsc / 100)) / ((u32)bt->pixelclock / 100)) : 0; + const u8 pll[2] = { + 0xc0 | ((width >> 8) & 0x1f), + width & 0xff + }; + + v4l2_dbg(2, debug, sd, "%s\n", __func__); + + switch (state->mode) { + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + /* auto graphics */ + io_write(sd, 0x00, 0x07); /* video std */ + io_write(sd, 0x01, 0x02); /* prim mode */ + /* enable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x10); + + /* Should only be set in auto-graphics mode [REF_02, p. 91-92] */ + /* setup PLL_DIV_MAN_EN and PLL_DIV_RATIO */ + /* IO-map reg. 0x16 and 0x17 should be written in sequence */ + if (adv_smbus_write_i2c_block_data(client, 0x16, 2, pll)) { + v4l2_err(sd, "writing to reg 0x16 and 0x17 failed\n"); + break; + } + + /* active video - horizontal timing */ + cp_write(sd, 0x26, (cp_start_sav >> 8) & 0xf); + cp_write(sd, 0x27, (cp_start_sav & 0xff)); + cp_write(sd, 0x28, (cp_start_eav >> 8) & 0xf); + cp_write(sd, 0x29, (cp_start_eav & 0xff)); + + /* active video - vertical timing */ + cp_write(sd, 0xa5, (cp_start_vbi >> 4) & 0xff); + cp_write(sd, 0xa6, ((cp_start_vbi & 0xf) << 4) | + ((cp_end_vbi >> 8) & 0xf)); + cp_write(sd, 0xa7, cp_end_vbi & 0xff); + break; + case ADV7842_MODE_HDMI: + /* set default prim_mode/vid_std for HDMI + according to [REF_03, c. 4.2] */ + io_write(sd, 0x00, 0x02); /* video std */ + io_write(sd, 0x01, 0x06); /* prim mode */ + break; + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + break; + } + + cp_write(sd, 0x8f, (ch1_fr_ll >> 8) & 0x7); + cp_write(sd, 0x90, ch1_fr_ll & 0xff); + cp_write(sd, 0xab, (height >> 4) & 0xff); + cp_write(sd, 0xac, (height & 0x0f) << 4); +} + +static void adv7842_set_offset(struct v4l2_subdev *sd, bool auto_offset, u16 offset_a, u16 offset_b, u16 offset_c) +{ + struct adv7842_state *state = to_state(sd); + u8 offset_buf[4]; + + if (auto_offset) { + offset_a = 0x3ff; + offset_b = 0x3ff; + offset_c = 0x3ff; + } + + v4l2_dbg(2, debug, sd, "%s: %s offset: a = 0x%x, b = 0x%x, c = 0x%x\n", + __func__, auto_offset ? "Auto" : "Manual", + offset_a, offset_b, offset_c); + + offset_buf[0]= (cp_read(sd, 0x77) & 0xc0) | ((offset_a & 0x3f0) >> 4); + offset_buf[1] = ((offset_a & 0x00f) << 4) | ((offset_b & 0x3c0) >> 6); + offset_buf[2] = ((offset_b & 0x03f) << 2) | ((offset_c & 0x300) >> 8); + offset_buf[3] = offset_c & 0x0ff; + + /* Registers must be written in this order with no i2c access in between */ + if (adv_smbus_write_i2c_block_data(state->i2c_cp, 0x77, 4, offset_buf)) + v4l2_err(sd, "%s: i2c error writing to CP reg 0x77, 0x78, 0x79, 0x7a\n", __func__); +} + +static void adv7842_set_gain(struct v4l2_subdev *sd, bool auto_gain, u16 gain_a, u16 gain_b, u16 gain_c) +{ + struct adv7842_state *state = to_state(sd); + u8 gain_buf[4]; + u8 gain_man = 1; + u8 agc_mode_man = 1; + + if (auto_gain) { + gain_man = 0; + agc_mode_man = 0; + gain_a = 0x100; + gain_b = 0x100; + gain_c = 0x100; + } + + v4l2_dbg(2, debug, sd, "%s: %s gain: a = 0x%x, b = 0x%x, c = 0x%x\n", + __func__, auto_gain ? "Auto" : "Manual", + gain_a, gain_b, gain_c); + + gain_buf[0] = ((gain_man << 7) | (agc_mode_man << 6) | ((gain_a & 0x3f0) >> 4)); + gain_buf[1] = (((gain_a & 0x00f) << 4) | ((gain_b & 0x3c0) >> 6)); + gain_buf[2] = (((gain_b & 0x03f) << 2) | ((gain_c & 0x300) >> 8)); + gain_buf[3] = ((gain_c & 0x0ff)); + + /* Registers must be written in this order with no i2c access in between */ + if (adv_smbus_write_i2c_block_data(state->i2c_cp, 0x73, 4, gain_buf)) + v4l2_err(sd, "%s: i2c error writing to CP reg 0x73, 0x74, 0x75, 0x76\n", __func__); +} + +static void set_rgb_quantization_range(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + bool rgb_output = io_read(sd, 0x02) & 0x02; + bool hdmi_signal = hdmi_read(sd, 0x05) & 0x80; + u8 y = HDMI_COLORSPACE_RGB; + + if (hdmi_signal && (io_read(sd, 0x60) & 1)) + y = infoframe_read(sd, 0x01) >> 5; + + v4l2_dbg(2, debug, sd, "%s: RGB quantization range: %d, RGB out: %d, HDMI: %d\n", + __func__, state->rgb_quantization_range, + rgb_output, hdmi_signal); + + adv7842_set_gain(sd, true, 0x0, 0x0, 0x0); + adv7842_set_offset(sd, true, 0x0, 0x0, 0x0); + io_write_clr_set(sd, 0x02, 0x04, rgb_output ? 0 : 4); + + switch (state->rgb_quantization_range) { + case V4L2_DV_RGB_RANGE_AUTO: + if (state->mode == ADV7842_MODE_RGB) { + /* Receiving analog RGB signal + * Set RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + break; + } + + if (state->mode == ADV7842_MODE_COMP) { + /* Receiving analog YPbPr signal + * Set automode */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + } + + if (hdmi_signal) { + /* Receiving HDMI signal + * Set automode */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + } + + /* Receiving DVI-D signal + * ADV7842 selects RGB limited range regardless of + * input format (CE/IT) in automatic mode */ + if (state->timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) { + /* RGB limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x00); + } else { + /* RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + + if (is_digital_input(sd) && rgb_output) { + adv7842_set_offset(sd, false, 0x40, 0x40, 0x40); + } else { + adv7842_set_gain(sd, false, 0xe0, 0xe0, 0xe0); + adv7842_set_offset(sd, false, 0x70, 0x70, 0x70); + } + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + if (state->mode == ADV7842_MODE_COMP) { + /* YCrCb limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x20); + break; + } + + if (y != HDMI_COLORSPACE_RGB) + break; + + /* RGB limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x00); + + break; + case V4L2_DV_RGB_RANGE_FULL: + if (state->mode == ADV7842_MODE_COMP) { + /* YCrCb full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x60); + break; + } + + if (y != HDMI_COLORSPACE_RGB) + break; + + /* RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + + if (is_analog_input(sd) || hdmi_signal) + break; + + /* Adjust gain/offset for DVI-D signals only */ + if (rgb_output) { + adv7842_set_offset(sd, false, 0x40, 0x40, 0x40); + } else { + adv7842_set_gain(sd, false, 0xe0, 0xe0, 0xe0); + adv7842_set_offset(sd, false, 0x70, 0x70, 0x70); + } + break; + } +} + +static int adv7842_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct adv7842_state *state = to_state(sd); + + /* TODO SDP ctrls + contrast/brightness/hue/free run is acting a bit strange, + not sure if sdp csc is correct. + */ + switch (ctrl->id) { + /* standard ctrls */ + case V4L2_CID_BRIGHTNESS: + cp_write(sd, 0x3c, ctrl->val); + sdp_write(sd, 0x14, ctrl->val); + /* ignore lsb sdp 0x17[3:2] */ + return 0; + case V4L2_CID_CONTRAST: + cp_write(sd, 0x3a, ctrl->val); + sdp_write(sd, 0x13, ctrl->val); + /* ignore lsb sdp 0x17[1:0] */ + return 0; + case V4L2_CID_SATURATION: + cp_write(sd, 0x3b, ctrl->val); + sdp_write(sd, 0x15, ctrl->val); + /* ignore lsb sdp 0x17[5:4] */ + return 0; + case V4L2_CID_HUE: + cp_write(sd, 0x3d, ctrl->val); + sdp_write(sd, 0x16, ctrl->val); + /* ignore lsb sdp 0x17[7:6] */ + return 0; + /* custom ctrls */ + case V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE: + afe_write(sd, 0xc8, ctrl->val); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL: + cp_write_and_or(sd, 0xbf, ~0x04, (ctrl->val << 2)); + sdp_write_and_or(sd, 0xdd, ~0x04, (ctrl->val << 2)); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR: { + u8 R = (ctrl->val & 0xff0000) >> 16; + u8 G = (ctrl->val & 0x00ff00) >> 8; + u8 B = (ctrl->val & 0x0000ff); + /* RGB -> YUV, numerical approximation */ + int Y = 66 * R + 129 * G + 25 * B; + int U = -38 * R - 74 * G + 112 * B; + int V = 112 * R - 94 * G - 18 * B; + + /* Scale down to 8 bits with rounding */ + Y = (Y + 128) >> 8; + U = (U + 128) >> 8; + V = (V + 128) >> 8; + /* make U,V positive */ + Y += 16; + U += 128; + V += 128; + + v4l2_dbg(1, debug, sd, "R %x, G %x, B %x\n", R, G, B); + v4l2_dbg(1, debug, sd, "Y %x, U %x, V %x\n", Y, U, V); + + /* CP */ + cp_write(sd, 0xc1, R); + cp_write(sd, 0xc0, G); + cp_write(sd, 0xc2, B); + /* SDP */ + sdp_write(sd, 0xde, Y); + sdp_write(sd, 0xdf, (V & 0xf0) | ((U >> 4) & 0x0f)); + return 0; + } + case V4L2_CID_DV_RX_RGB_RANGE: + state->rgb_quantization_range = ctrl->val; + set_rgb_quantization_range(sd); + return 0; + } + return -EINVAL; +} + +static int adv7842_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + if (ctrl->id == V4L2_CID_DV_RX_IT_CONTENT_TYPE) { + ctrl->val = V4L2_DV_IT_CONTENT_TYPE_NO_ITC; + if ((io_read(sd, 0x60) & 1) && (infoframe_read(sd, 0x03) & 0x80)) + ctrl->val = (infoframe_read(sd, 0x05) >> 4) & 3; + return 0; + } + return -EINVAL; +} + +static inline bool no_power(struct v4l2_subdev *sd) +{ + return io_read(sd, 0x0c) & 0x24; +} + +static inline bool no_cp_signal(struct v4l2_subdev *sd) +{ + return ((cp_read(sd, 0xb5) & 0xd0) != 0xd0) || !(cp_read(sd, 0xb1) & 0x80); +} + +static inline bool is_hdmi(struct v4l2_subdev *sd) +{ + return hdmi_read(sd, 0x05) & 0x80; +} + +static int adv7842_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct adv7842_state *state = to_state(sd); + + *status = 0; + + if (io_read(sd, 0x0c) & 0x24) + *status |= V4L2_IN_ST_NO_POWER; + + if (state->mode == ADV7842_MODE_SDP) { + /* status from SDP block */ + if (!(sdp_read(sd, 0x5A) & 0x01)) + *status |= V4L2_IN_ST_NO_SIGNAL; + + v4l2_dbg(1, debug, sd, "%s: SDP status = 0x%x\n", + __func__, *status); + return 0; + } + /* status from CP block */ + if ((cp_read(sd, 0xb5) & 0xd0) != 0xd0 || + !(cp_read(sd, 0xb1) & 0x80)) + /* TODO channel 2 */ + *status |= V4L2_IN_ST_NO_SIGNAL; + + if (is_digital_input(sd) && ((io_read(sd, 0x74) & 0x03) != 0x03)) + *status |= V4L2_IN_ST_NO_SIGNAL; + + v4l2_dbg(1, debug, sd, "%s: CP status = 0x%x\n", + __func__, *status); + + return 0; +} + +struct stdi_readback { + u16 bl, lcf, lcvs; + u8 hs_pol, vs_pol; + bool interlaced; +}; + +static int stdi2dv_timings(struct v4l2_subdev *sd, + struct stdi_readback *stdi, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + u32 hfreq = (ADV7842_fsc * 8) / stdi->bl; + u32 pix_clk; + int i; + + for (i = 0; v4l2_dv_timings_presets[i].bt.width; i++) { + const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[i].bt; + + if (!v4l2_valid_dv_timings(&v4l2_dv_timings_presets[i], + adv7842_get_dv_timings_cap(sd), + adv7842_check_dv_timings, NULL)) + continue; + if (vtotal(bt) != stdi->lcf + 1) + continue; + if (bt->vsync != stdi->lcvs) + continue; + + pix_clk = hfreq * htotal(bt); + + if ((pix_clk < bt->pixelclock + 1000000) && + (pix_clk > bt->pixelclock - 1000000)) { + *timings = v4l2_dv_timings_presets[i]; + return 0; + } + } + + if (v4l2_detect_cvt(stdi->lcf + 1, hfreq, stdi->lcvs, 0, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + false, timings)) + return 0; + if (v4l2_detect_gtf(stdi->lcf + 1, hfreq, stdi->lcvs, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + false, state->aspect_ratio, timings)) + return 0; + + v4l2_dbg(2, debug, sd, + "%s: No format candidate found for lcvs = %d, lcf=%d, bl = %d, %chsync, %cvsync\n", + __func__, stdi->lcvs, stdi->lcf, stdi->bl, + stdi->hs_pol, stdi->vs_pol); + return -1; +} + +static int read_stdi(struct v4l2_subdev *sd, struct stdi_readback *stdi) +{ + u32 status; + + adv7842_g_input_status(sd, &status); + if (status & V4L2_IN_ST_NO_SIGNAL) { + v4l2_dbg(2, debug, sd, "%s: no signal\n", __func__); + return -ENOLINK; + } + + stdi->bl = ((cp_read(sd, 0xb1) & 0x3f) << 8) | cp_read(sd, 0xb2); + stdi->lcf = ((cp_read(sd, 0xb3) & 0x7) << 8) | cp_read(sd, 0xb4); + stdi->lcvs = cp_read(sd, 0xb3) >> 3; + + if ((cp_read(sd, 0xb5) & 0x80) && ((cp_read(sd, 0xb5) & 0x03) == 0x01)) { + stdi->hs_pol = ((cp_read(sd, 0xb5) & 0x10) ? + ((cp_read(sd, 0xb5) & 0x08) ? '+' : '-') : 'x'); + stdi->vs_pol = ((cp_read(sd, 0xb5) & 0x40) ? + ((cp_read(sd, 0xb5) & 0x20) ? '+' : '-') : 'x'); + } else { + stdi->hs_pol = 'x'; + stdi->vs_pol = 'x'; + } + stdi->interlaced = (cp_read(sd, 0xb1) & 0x40) ? true : false; + + if (stdi->lcf < 239 || stdi->bl < 8 || stdi->bl == 0x3fff) { + v4l2_dbg(2, debug, sd, "%s: invalid signal\n", __func__); + return -ENOLINK; + } + + v4l2_dbg(2, debug, sd, + "%s: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %chsync, %cvsync, %s\n", + __func__, stdi->lcf, stdi->bl, stdi->lcvs, + stdi->hs_pol, stdi->vs_pol, + stdi->interlaced ? "interlaced" : "progressive"); + + return 0; +} + +static int adv7842_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->pad != 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, + adv7842_get_dv_timings_cap(sd), adv7842_check_dv_timings, NULL); +} + +static int adv7842_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + if (cap->pad != 0) + return -EINVAL; + + *cap = *adv7842_get_dv_timings_cap(sd); + return 0; +} + +/* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is listed in adv7842_timings[] */ +static void adv7842_fill_optional_dv_timings_fields(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + v4l2_find_dv_timings_cap(timings, adv7842_get_dv_timings_cap(sd), + is_digital_input(sd) ? 250000 : 1000000, + adv7842_check_dv_timings, NULL); + timings->bt.flags |= V4L2_DV_FL_CAN_DETECT_REDUCED_FPS; +} + +static int adv7842_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + struct v4l2_bt_timings *bt = &timings->bt; + struct stdi_readback stdi = { 0 }; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + + /* SDP block */ + if (state->mode == ADV7842_MODE_SDP) + return -ENODATA; + + /* read STDI */ + if (read_stdi(sd, &stdi)) { + state->restart_stdi_once = true; + v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); + return -ENOLINK; + } + bt->interlaced = stdi.interlaced ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + bt->standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT; + + if (is_digital_input(sd)) { + u32 freq; + + timings->type = V4L2_DV_BT_656_1120; + + bt->width = (hdmi_read(sd, 0x07) & 0x0f) * 256 + hdmi_read(sd, 0x08); + bt->height = (hdmi_read(sd, 0x09) & 0x0f) * 256 + hdmi_read(sd, 0x0a); + freq = ((hdmi_read(sd, 0x51) << 1) + (hdmi_read(sd, 0x52) >> 7)) * 1000000; + freq += ((hdmi_read(sd, 0x52) & 0x7f) * 7813); + if (is_hdmi(sd)) { + /* adjust for deep color mode */ + freq = freq * 8 / (((hdmi_read(sd, 0x0b) & 0xc0) >> 6) * 2 + 8); + } + bt->pixelclock = freq; + bt->hfrontporch = (hdmi_read(sd, 0x20) & 0x03) * 256 + + hdmi_read(sd, 0x21); + bt->hsync = (hdmi_read(sd, 0x22) & 0x03) * 256 + + hdmi_read(sd, 0x23); + bt->hbackporch = (hdmi_read(sd, 0x24) & 0x03) * 256 + + hdmi_read(sd, 0x25); + bt->vfrontporch = ((hdmi_read(sd, 0x2a) & 0x1f) * 256 + + hdmi_read(sd, 0x2b)) / 2; + bt->vsync = ((hdmi_read(sd, 0x2e) & 0x1f) * 256 + + hdmi_read(sd, 0x2f)) / 2; + bt->vbackporch = ((hdmi_read(sd, 0x32) & 0x1f) * 256 + + hdmi_read(sd, 0x33)) / 2; + bt->polarities = ((hdmi_read(sd, 0x05) & 0x10) ? V4L2_DV_VSYNC_POS_POL : 0) | + ((hdmi_read(sd, 0x05) & 0x20) ? V4L2_DV_HSYNC_POS_POL : 0); + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height += (hdmi_read(sd, 0x0b) & 0x0f) * 256 + + hdmi_read(sd, 0x0c); + bt->il_vfrontporch = ((hdmi_read(sd, 0x2c) & 0x1f) * 256 + + hdmi_read(sd, 0x2d)) / 2; + bt->il_vsync = ((hdmi_read(sd, 0x30) & 0x1f) * 256 + + hdmi_read(sd, 0x31)) / 2; + bt->il_vbackporch = ((hdmi_read(sd, 0x34) & 0x1f) * 256 + + hdmi_read(sd, 0x35)) / 2; + } else { + bt->il_vfrontporch = 0; + bt->il_vsync = 0; + bt->il_vbackporch = 0; + } + adv7842_fill_optional_dv_timings_fields(sd, timings); + if ((timings->bt.flags & V4L2_DV_FL_CAN_REDUCE_FPS) && + freq < bt->pixelclock) { + u32 reduced_freq = ((u32)bt->pixelclock / 1001) * 1000; + u32 delta_freq = abs(freq - reduced_freq); + + if (delta_freq < ((u32)bt->pixelclock - reduced_freq) / 2) + timings->bt.flags |= V4L2_DV_FL_REDUCED_FPS; + } + } else { + /* find format + * Since LCVS values are inaccurate [REF_03, p. 339-340], + * stdi2dv_timings() is called with lcvs +-1 if the first attempt fails. + */ + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs += 1; + v4l2_dbg(1, debug, sd, "%s: lcvs + 1 = %d\n", __func__, stdi.lcvs); + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs -= 2; + v4l2_dbg(1, debug, sd, "%s: lcvs - 1 = %d\n", __func__, stdi.lcvs); + if (stdi2dv_timings(sd, &stdi, timings)) { + /* + * The STDI block may measure wrong values, especially + * for lcvs and lcf. If the driver can not find any + * valid timing, the STDI block is restarted to measure + * the video timings again. The function will return an + * error, but the restart of STDI will generate a new + * STDI interrupt and the format detection process will + * restart. + */ + if (state->restart_stdi_once) { + v4l2_dbg(1, debug, sd, "%s: restart STDI\n", __func__); + /* TODO restart STDI for Sync Channel 2 */ + /* enter one-shot mode */ + cp_write_and_or(sd, 0x86, 0xf9, 0x00); + /* trigger STDI restart */ + cp_write_and_or(sd, 0x86, 0xf9, 0x04); + /* reset to continuous mode */ + cp_write_and_or(sd, 0x86, 0xf9, 0x02); + state->restart_stdi_once = false; + return -ENOLINK; + } + v4l2_dbg(1, debug, sd, "%s: format not supported\n", __func__); + return -ERANGE; + } + state->restart_stdi_once = true; + } +found: + + if (debug > 1) + v4l2_print_dv_timings(sd->name, "adv7842_query_dv_timings:", + timings, true); + return 0; +} + +static int adv7842_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + struct v4l2_bt_timings *bt; + int err; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (state->mode == ADV7842_MODE_SDP) + return -ENODATA; + + if (v4l2_match_dv_timings(&state->timings, timings, 0, false)) { + v4l2_dbg(1, debug, sd, "%s: no change\n", __func__); + return 0; + } + + bt = &timings->bt; + + if (!v4l2_valid_dv_timings(timings, adv7842_get_dv_timings_cap(sd), + adv7842_check_dv_timings, NULL)) + return -ERANGE; + + adv7842_fill_optional_dv_timings_fields(sd, timings); + + state->timings = *timings; + + cp_write(sd, 0x91, bt->interlaced ? 0x40 : 0x00); + + /* Use prim_mode and vid_std when available */ + err = configure_predefined_video_timings(sd, timings); + if (err) { + /* custom settings when the video format + does not have prim_mode/vid_std */ + configure_custom_video_timings(sd, bt); + } + + set_rgb_quantization_range(sd); + + + if (debug > 1) + v4l2_print_dv_timings(sd->name, "adv7842_s_dv_timings: ", + timings, true); + return 0; +} + +static int adv7842_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + + if (state->mode == ADV7842_MODE_SDP) + return -ENODATA; + *timings = state->timings; + return 0; +} + +static void enable_input(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + set_rgb_quantization_range(sd); + switch (state->mode) { + case ADV7842_MODE_SDP: + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + io_write(sd, 0x15, 0xb0); /* Disable Tristate of Pins (no audio) */ + break; + case ADV7842_MODE_HDMI: + hdmi_write(sd, 0x01, 0x00); /* Enable HDMI clock terminators */ + io_write(sd, 0x15, 0xa0); /* Disable Tristate of Pins */ + hdmi_write_and_or(sd, 0x1a, 0xef, 0x00); /* Unmute audio */ + break; + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + break; + } +} + +static void disable_input(struct v4l2_subdev *sd) +{ + hdmi_write_and_or(sd, 0x1a, 0xef, 0x10); /* Mute audio [REF_01, c. 2.2.2] */ + msleep(16); /* 512 samples with >= 32 kHz sample rate [REF_03, c. 8.29] */ + io_write(sd, 0x15, 0xbe); /* Tristate all outputs from video core */ + hdmi_write(sd, 0x01, 0x78); /* Disable HDMI clock terminators */ +} + +static void sdp_csc_coeff(struct v4l2_subdev *sd, + const struct adv7842_sdp_csc_coeff *c) +{ + /* csc auto/manual */ + sdp_io_write_and_or(sd, 0xe0, 0xbf, c->manual ? 0x00 : 0x40); + + if (!c->manual) + return; + + /* csc scaling */ + sdp_io_write_and_or(sd, 0xe0, 0x7f, c->scaling == 2 ? 0x80 : 0x00); + + /* A coeff */ + sdp_io_write_and_or(sd, 0xe0, 0xe0, c->A1 >> 8); + sdp_io_write(sd, 0xe1, c->A1); + sdp_io_write_and_or(sd, 0xe2, 0xe0, c->A2 >> 8); + sdp_io_write(sd, 0xe3, c->A2); + sdp_io_write_and_or(sd, 0xe4, 0xe0, c->A3 >> 8); + sdp_io_write(sd, 0xe5, c->A3); + + /* A scale */ + sdp_io_write_and_or(sd, 0xe6, 0x80, c->A4 >> 8); + sdp_io_write(sd, 0xe7, c->A4); + + /* B coeff */ + sdp_io_write_and_or(sd, 0xe8, 0xe0, c->B1 >> 8); + sdp_io_write(sd, 0xe9, c->B1); + sdp_io_write_and_or(sd, 0xea, 0xe0, c->B2 >> 8); + sdp_io_write(sd, 0xeb, c->B2); + sdp_io_write_and_or(sd, 0xec, 0xe0, c->B3 >> 8); + sdp_io_write(sd, 0xed, c->B3); + + /* B scale */ + sdp_io_write_and_or(sd, 0xee, 0x80, c->B4 >> 8); + sdp_io_write(sd, 0xef, c->B4); + + /* C coeff */ + sdp_io_write_and_or(sd, 0xf0, 0xe0, c->C1 >> 8); + sdp_io_write(sd, 0xf1, c->C1); + sdp_io_write_and_or(sd, 0xf2, 0xe0, c->C2 >> 8); + sdp_io_write(sd, 0xf3, c->C2); + sdp_io_write_and_or(sd, 0xf4, 0xe0, c->C3 >> 8); + sdp_io_write(sd, 0xf5, c->C3); + + /* C scale */ + sdp_io_write_and_or(sd, 0xf6, 0x80, c->C4 >> 8); + sdp_io_write(sd, 0xf7, c->C4); +} + +static void select_input(struct v4l2_subdev *sd, + enum adv7842_vid_std_select vid_std_select) +{ + struct adv7842_state *state = to_state(sd); + + switch (state->mode) { + case ADV7842_MODE_SDP: + io_write(sd, 0x00, vid_std_select); /* video std: CVBS or YC mode */ + io_write(sd, 0x01, 0); /* prim mode */ + /* enable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x10); + + afe_write(sd, 0x00, 0x00); /* power up ADC */ + afe_write(sd, 0xc8, 0x00); /* phase control */ + + io_write(sd, 0xdd, 0x90); /* Manual 2x output clock */ + /* script says register 0xde, which don't exist in manual */ + + /* Manual analog input muxing mode, CVBS (6.4)*/ + afe_write_and_or(sd, 0x02, 0x7f, 0x80); + if (vid_std_select == ADV7842_SDP_VID_STD_CVBS_SD_4x1) { + afe_write(sd, 0x03, 0xa0); /* ADC0 to AIN10 (CVBS), ADC1 N/C*/ + afe_write(sd, 0x04, 0x00); /* ADC2 N/C,ADC3 N/C*/ + } else { + afe_write(sd, 0x03, 0xa0); /* ADC0 to AIN10 (CVBS), ADC1 N/C*/ + afe_write(sd, 0x04, 0xc0); /* ADC2 to AIN12, ADC3 N/C*/ + } + afe_write(sd, 0x0c, 0x1f); /* ADI recommend write */ + afe_write(sd, 0x12, 0x63); /* ADI recommend write */ + + sdp_io_write(sd, 0xb2, 0x60); /* Disable AV codes */ + sdp_io_write(sd, 0xc8, 0xe3); /* Disable Ancillary data */ + + /* SDP recommended settings */ + sdp_write(sd, 0x00, 0x3F); /* Autodetect PAL NTSC (not SECAM) */ + sdp_write(sd, 0x01, 0x00); /* Pedestal Off */ + + sdp_write(sd, 0x03, 0xE4); /* Manual VCR Gain Luma 0x40B */ + sdp_write(sd, 0x04, 0x0B); /* Manual Luma setting */ + sdp_write(sd, 0x05, 0xC3); /* Manual Chroma setting 0x3FE */ + sdp_write(sd, 0x06, 0xFE); /* Manual Chroma setting */ + sdp_write(sd, 0x12, 0x0D); /* Frame TBC,I_P, 3D comb enabled */ + sdp_write(sd, 0xA7, 0x00); /* ADI Recommended Write */ + sdp_io_write(sd, 0xB0, 0x00); /* Disable H and v blanking */ + + /* deinterlacer enabled and 3D comb */ + sdp_write_and_or(sd, 0x12, 0xf6, 0x09); + + break; + + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + /* Automatic analog input muxing mode */ + afe_write_and_or(sd, 0x02, 0x7f, 0x00); + /* set mode and select free run resolution */ + io_write(sd, 0x00, vid_std_select); /* video std */ + io_write(sd, 0x01, 0x02); /* prim mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x10); /* enable embedded syncs + for auto graphics mode */ + + afe_write(sd, 0x00, 0x00); /* power up ADC */ + afe_write(sd, 0xc8, 0x00); /* phase control */ + if (state->mode == ADV7842_MODE_COMP) { + /* force to YCrCb */ + io_write_and_or(sd, 0x02, 0x0f, 0x60); + } else { + /* force to RGB */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + } + + /* set ADI recommended settings for digitizer */ + /* "ADV7842 Register Settings Recommendations + * (rev. 1.8, November 2010)" p. 9. */ + afe_write(sd, 0x0c, 0x1f); /* ADC Range improvement */ + afe_write(sd, 0x12, 0x63); /* ADC Range improvement */ + + /* set to default gain for RGB */ + cp_write(sd, 0x73, 0x10); + cp_write(sd, 0x74, 0x04); + cp_write(sd, 0x75, 0x01); + cp_write(sd, 0x76, 0x00); + + cp_write(sd, 0x3e, 0x04); /* CP core pre-gain control */ + cp_write(sd, 0xc3, 0x39); /* CP coast control. Graphics mode */ + cp_write(sd, 0x40, 0x5c); /* CP core pre-gain control. Graphics mode */ + break; + + case ADV7842_MODE_HDMI: + /* Automatic analog input muxing mode */ + afe_write_and_or(sd, 0x02, 0x7f, 0x00); + /* set mode and select free run resolution */ + if (state->hdmi_port_a) + hdmi_write(sd, 0x00, 0x02); /* select port A */ + else + hdmi_write(sd, 0x00, 0x03); /* select port B */ + io_write(sd, 0x00, vid_std_select); /* video std */ + io_write(sd, 0x01, 5); /* prim mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x00); /* disable embedded syncs + for auto graphics mode */ + + /* set ADI recommended settings for HDMI: */ + /* "ADV7842 Register Settings Recommendations + * (rev. 1.8, November 2010)" p. 3. */ + hdmi_write(sd, 0xc0, 0x00); + hdmi_write(sd, 0x0d, 0x34); /* ADI recommended write */ + hdmi_write(sd, 0x3d, 0x10); /* ADI recommended write */ + hdmi_write(sd, 0x44, 0x85); /* TMDS PLL optimization */ + hdmi_write(sd, 0x46, 0x1f); /* ADI recommended write */ + hdmi_write(sd, 0x57, 0xb6); /* TMDS PLL optimization */ + hdmi_write(sd, 0x58, 0x03); /* TMDS PLL optimization */ + hdmi_write(sd, 0x60, 0x88); /* TMDS PLL optimization */ + hdmi_write(sd, 0x61, 0x88); /* TMDS PLL optimization */ + hdmi_write(sd, 0x6c, 0x18); /* Disable ISRC clearing bit, + Improve robustness */ + hdmi_write(sd, 0x75, 0x10); /* DDC drive strength */ + hdmi_write(sd, 0x85, 0x1f); /* equaliser */ + hdmi_write(sd, 0x87, 0x70); /* ADI recommended write */ + hdmi_write(sd, 0x89, 0x04); /* equaliser */ + hdmi_write(sd, 0x8a, 0x1e); /* equaliser */ + hdmi_write(sd, 0x93, 0x04); /* equaliser */ + hdmi_write(sd, 0x94, 0x1e); /* equaliser */ + hdmi_write(sd, 0x99, 0xa1); /* ADI recommended write */ + hdmi_write(sd, 0x9b, 0x09); /* ADI recommended write */ + hdmi_write(sd, 0x9d, 0x02); /* equaliser */ + + afe_write(sd, 0x00, 0xff); /* power down ADC */ + afe_write(sd, 0xc8, 0x40); /* phase control */ + + /* set to default gain for HDMI */ + cp_write(sd, 0x73, 0x10); + cp_write(sd, 0x74, 0x04); + cp_write(sd, 0x75, 0x01); + cp_write(sd, 0x76, 0x00); + + /* reset ADI recommended settings for digitizer */ + /* "ADV7842 Register Settings Recommendations + * (rev. 2.5, June 2010)" p. 17. */ + afe_write(sd, 0x12, 0xfb); /* ADC noise shaping filter controls */ + afe_write(sd, 0x0c, 0x0d); /* CP core gain controls */ + cp_write(sd, 0x3e, 0x00); /* CP core pre-gain control */ + + /* CP coast control */ + cp_write(sd, 0xc3, 0x33); /* Component mode */ + + /* color space conversion, autodetect color space */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + break; + } +} + +static int adv7842_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7842_state *state = to_state(sd); + + v4l2_dbg(2, debug, sd, "%s: input %d\n", __func__, input); + + switch (input) { + case ADV7842_SELECT_HDMI_PORT_A: + state->mode = ADV7842_MODE_HDMI; + state->vid_std_select = ADV7842_HDMI_COMP_VID_STD_HD_1250P; + state->hdmi_port_a = true; + break; + case ADV7842_SELECT_HDMI_PORT_B: + state->mode = ADV7842_MODE_HDMI; + state->vid_std_select = ADV7842_HDMI_COMP_VID_STD_HD_1250P; + state->hdmi_port_a = false; + break; + case ADV7842_SELECT_VGA_COMP: + state->mode = ADV7842_MODE_COMP; + state->vid_std_select = ADV7842_RGB_VID_STD_AUTO_GRAPH_MODE; + break; + case ADV7842_SELECT_VGA_RGB: + state->mode = ADV7842_MODE_RGB; + state->vid_std_select = ADV7842_RGB_VID_STD_AUTO_GRAPH_MODE; + break; + case ADV7842_SELECT_SDP_CVBS: + state->mode = ADV7842_MODE_SDP; + state->vid_std_select = ADV7842_SDP_VID_STD_CVBS_SD_4x1; + break; + case ADV7842_SELECT_SDP_YC: + state->mode = ADV7842_MODE_SDP; + state->vid_std_select = ADV7842_SDP_VID_STD_YC_SD4_x1; + break; + default: + return -EINVAL; + } + + disable_input(sd); + select_input(sd, state->vid_std_select); + enable_input(sd); + + v4l2_subdev_notify_event(sd, &adv7842_ev_fmt); + + return 0; +} + +static int adv7842_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(adv7842_formats)) + return -EINVAL; + code->code = adv7842_formats[code->index].code; + return 0; +} + +static void adv7842_fill_format(struct adv7842_state *state, + struct v4l2_mbus_framefmt *format) +{ + memset(format, 0, sizeof(*format)); + + format->width = state->timings.bt.width; + format->height = state->timings.bt.height; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + if (state->timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) + format->colorspace = (state->timings.bt.height <= 576) ? + V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; +} + +/* + * Compute the op_ch_sel value required to obtain on the bus the component order + * corresponding to the selected format taking into account bus reordering + * applied by the board at the output of the device. + * + * The following table gives the op_ch_value from the format component order + * (expressed as op_ch_sel value in column) and the bus reordering (expressed as + * adv7842_bus_order value in row). + * + * | GBR(0) GRB(1) BGR(2) RGB(3) BRG(4) RBG(5) + * ----------+------------------------------------------------- + * RGB (NOP) | GBR GRB BGR RGB BRG RBG + * GRB (1-2) | BGR RGB GBR GRB RBG BRG + * RBG (2-3) | GRB GBR BRG RBG BGR RGB + * BGR (1-3) | RBG BRG RGB BGR GRB GBR + * BRG (ROR) | BRG RBG GRB GBR RGB BGR + * GBR (ROL) | RGB BGR RBG BRG GBR GRB + */ +static unsigned int adv7842_op_ch_sel(struct adv7842_state *state) +{ +#define _SEL(a, b, c, d, e, f) { \ + ADV7842_OP_CH_SEL_##a, ADV7842_OP_CH_SEL_##b, ADV7842_OP_CH_SEL_##c, \ + ADV7842_OP_CH_SEL_##d, ADV7842_OP_CH_SEL_##e, ADV7842_OP_CH_SEL_##f } +#define _BUS(x) [ADV7842_BUS_ORDER_##x] + + static const unsigned int op_ch_sel[6][6] = { + _BUS(RGB) /* NOP */ = _SEL(GBR, GRB, BGR, RGB, BRG, RBG), + _BUS(GRB) /* 1-2 */ = _SEL(BGR, RGB, GBR, GRB, RBG, BRG), + _BUS(RBG) /* 2-3 */ = _SEL(GRB, GBR, BRG, RBG, BGR, RGB), + _BUS(BGR) /* 1-3 */ = _SEL(RBG, BRG, RGB, BGR, GRB, GBR), + _BUS(BRG) /* ROR */ = _SEL(BRG, RBG, GRB, GBR, RGB, BGR), + _BUS(GBR) /* ROL */ = _SEL(RGB, BGR, RBG, BRG, GBR, GRB), + }; + + return op_ch_sel[state->pdata.bus_order][state->format->op_ch_sel >> 5]; +} + +static void adv7842_setup_format(struct adv7842_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + + io_write_clr_set(sd, 0x02, 0x02, + state->format->rgb_out ? ADV7842_RGB_OUT : 0); + io_write(sd, 0x03, state->format->op_format_sel | + state->pdata.op_format_mode_sel); + io_write_clr_set(sd, 0x04, 0xe0, adv7842_op_ch_sel(state)); + io_write_clr_set(sd, 0x05, 0x01, + state->format->swap_cb_cr ? ADV7842_OP_SWAP_CB_CR : 0); + set_rgb_quantization_range(sd); +} + +static int adv7842_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct adv7842_state *state = to_state(sd); + + if (format->pad != ADV7842_PAD_SOURCE) + return -EINVAL; + + if (state->mode == ADV7842_MODE_SDP) { + /* SPD block */ + if (!(sdp_read(sd, 0x5a) & 0x01)) + return -EINVAL; + format->format.code = MEDIA_BUS_FMT_YUYV8_2X8; + format->format.width = 720; + /* valid signal */ + if (state->norm & V4L2_STD_525_60) + format->format.height = 480; + else + format->format.height = 576; + format->format.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; + } + + adv7842_fill_format(state, &format->format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + format->format.code = fmt->code; + } else { + format->format.code = state->format->code; + } + + return 0; +} + +static int adv7842_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct adv7842_state *state = to_state(sd); + const struct adv7842_format_info *info; + + if (format->pad != ADV7842_PAD_SOURCE) + return -EINVAL; + + if (state->mode == ADV7842_MODE_SDP) + return adv7842_get_format(sd, cfg, format); + + info = adv7842_format_info(state, format->format.code); + if (info == NULL) + info = adv7842_format_info(state, MEDIA_BUS_FMT_YUYV8_2X8); + + adv7842_fill_format(state, &format->format); + format->format.code = info->code; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + fmt->code = format->format.code; + } else { + state->format = info; + adv7842_setup_format(state); + } + + return 0; +} + +static void adv7842_irq_enable(struct v4l2_subdev *sd, bool enable) +{ + if (enable) { + /* Enable SSPD, STDI and CP locked/unlocked interrupts */ + io_write(sd, 0x46, 0x9c); + /* ESDP_50HZ_DET interrupt */ + io_write(sd, 0x5a, 0x10); + /* Enable CABLE_DET_A/B_ST (+5v) interrupt */ + io_write(sd, 0x73, 0x03); + /* Enable V_LOCKED and DE_REGEN_LCK interrupts */ + io_write(sd, 0x78, 0x03); + /* Enable SDP Standard Detection Change and SDP Video Detected */ + io_write(sd, 0xa0, 0x09); + /* Enable HDMI_MODE interrupt */ + io_write(sd, 0x69, 0x08); + } else { + io_write(sd, 0x46, 0x0); + io_write(sd, 0x5a, 0x0); + io_write(sd, 0x73, 0x0); + io_write(sd, 0x78, 0x0); + io_write(sd, 0xa0, 0x0); + io_write(sd, 0x69, 0x0); + } +} + +#if IS_ENABLED(CONFIG_VIDEO_ADV7842_CEC) +static void adv7842_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + struct adv7842_state *state = to_state(sd); + + if ((cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x02) { + v4l2_dbg(1, debug, sd, "%s: tx raw: arbitration lost\n", + __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + return; + } + if (tx_raw_status & 0x04) { + u8 status; + u8 nack_cnt; + u8 low_drive_cnt; + + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + nack_cnt = cec_read(sd, 0x14) & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = cec_read(sd, 0x14) >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + cec_transmit_done(state->cec_adap, status, + 0, nack_cnt, low_drive_cnt, 0); + return; + } + if (tx_raw_status & 0x01) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + return; + } +} + +static void adv7842_cec_isr(struct v4l2_subdev *sd, bool *handled) +{ + u8 cec_irq; + + /* cec controller */ + cec_irq = io_read(sd, 0x93) & 0x0f; + if (!cec_irq) + return; + + v4l2_dbg(1, debug, sd, "%s: cec: irq 0x%x\n", __func__, cec_irq); + adv7842_cec_tx_raw_status(sd, cec_irq); + if (cec_irq & 0x08) { + struct adv7842_state *state = to_state(sd); + struct cec_msg msg; + + msg.len = cec_read(sd, 0x25) & 0x1f; + if (msg.len > 16) + msg.len = 16; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = cec_read(sd, i + 0x15); + cec_write(sd, 0x26, 0x01); /* re-enable rx */ + cec_received_msg(state->cec_adap, &msg); + } + } + + io_write(sd, 0x94, cec_irq); + + if (handled) + *handled = true; +} + +static int adv7842_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv7842_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + + if (!state->cec_enabled_adap && enable) { + cec_write_clr_set(sd, 0x2a, 0x01, 0x01); /* power up cec */ + cec_write(sd, 0x2c, 0x01); /* cec soft reset */ + cec_write_clr_set(sd, 0x11, 0x01, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready */ + io_write_clr_set(sd, 0x96, 0x0f, 0x0f); + cec_write(sd, 0x26, 0x01); /* enable rx */ + } else if (state->cec_enabled_adap && !enable) { + /* disable cec interrupts */ + io_write_clr_set(sd, 0x96, 0x0f, 0x00); + /* disable address mask 1-3 */ + cec_write_clr_set(sd, 0x27, 0x70, 0x00); + /* power down cec section */ + cec_write_clr_set(sd, 0x2a, 0x01, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + return 0; +} + +static int adv7842_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv7842_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + unsigned int i, free_idx = ADV7842_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + cec_write_clr_set(sd, 0x27, 0x70, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7842_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7842_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV7842_MAX_ADDRS) { + i = free_idx; + if (i == ADV7842_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + cec_write_clr_set(sd, 0x27, 0x10, 0x10); + /* set address for mask 0 */ + cec_write_clr_set(sd, 0x28, 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + cec_write_clr_set(sd, 0x27, 0x20, 0x20); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x28, 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + cec_write_clr_set(sd, 0x27, 0x40, 0x40); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x29, 0x0f, addr); + break; + } + return 0; +} + +static int adv7842_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv7842_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + u8 len = msg->len; + unsigned int i; + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + cec_write_clr_set(sd, 0x12, 0x70, max(1, attempts - 1) << 4); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* write data */ + for (i = 0; i < len; i++) + cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + cec_write(sd, 0x11, 0x01); + return 0; +} + +static const struct cec_adap_ops adv7842_cec_adap_ops = { + .adap_enable = adv7842_cec_adap_enable, + .adap_log_addr = adv7842_cec_adap_log_addr, + .adap_transmit = adv7842_cec_adap_transmit, +}; +#endif + +static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct adv7842_state *state = to_state(sd); + u8 fmt_change_cp, fmt_change_digital, fmt_change_sdp; + u8 irq_status[6]; + + adv7842_irq_enable(sd, false); + + /* read status */ + irq_status[0] = io_read(sd, 0x43); + irq_status[1] = io_read(sd, 0x57); + irq_status[2] = io_read(sd, 0x70); + irq_status[3] = io_read(sd, 0x75); + irq_status[4] = io_read(sd, 0x9d); + irq_status[5] = io_read(sd, 0x66); + + /* and clear */ + if (irq_status[0]) + io_write(sd, 0x44, irq_status[0]); + if (irq_status[1]) + io_write(sd, 0x58, irq_status[1]); + if (irq_status[2]) + io_write(sd, 0x71, irq_status[2]); + if (irq_status[3]) + io_write(sd, 0x76, irq_status[3]); + if (irq_status[4]) + io_write(sd, 0x9e, irq_status[4]); + if (irq_status[5]) + io_write(sd, 0x67, irq_status[5]); + + adv7842_irq_enable(sd, true); + + v4l2_dbg(1, debug, sd, "%s: irq %x, %x, %x, %x, %x, %x\n", __func__, + irq_status[0], irq_status[1], irq_status[2], + irq_status[3], irq_status[4], irq_status[5]); + + /* format change CP */ + fmt_change_cp = irq_status[0] & 0x9c; + + /* format change SDP */ + if (state->mode == ADV7842_MODE_SDP) + fmt_change_sdp = (irq_status[1] & 0x30) | (irq_status[4] & 0x09); + else + fmt_change_sdp = 0; + + /* digital format CP */ + if (is_digital_input(sd)) + fmt_change_digital = irq_status[3] & 0x03; + else + fmt_change_digital = 0; + + /* format change */ + if (fmt_change_cp || fmt_change_digital || fmt_change_sdp) { + v4l2_dbg(1, debug, sd, + "%s: fmt_change_cp = 0x%x, fmt_change_digital = 0x%x, fmt_change_sdp = 0x%x\n", + __func__, fmt_change_cp, fmt_change_digital, + fmt_change_sdp); + v4l2_subdev_notify_event(sd, &adv7842_ev_fmt); + if (handled) + *handled = true; + } + + /* HDMI/DVI mode */ + if (irq_status[5] & 0x08) { + v4l2_dbg(1, debug, sd, "%s: irq %s mode\n", __func__, + (io_read(sd, 0x65) & 0x08) ? "HDMI" : "DVI"); + set_rgb_quantization_range(sd); + if (handled) + *handled = true; + } + +#if IS_ENABLED(CONFIG_VIDEO_ADV7842_CEC) + /* cec */ + adv7842_cec_isr(sd, handled); +#endif + + /* tx 5v detect */ + if (irq_status[2] & 0x3) { + v4l2_dbg(1, debug, sd, "%s: irq tx_5v\n", __func__); + adv7842_s_detect_tx_5v_ctrl(sd); + if (handled) + *handled = true; + } + return 0; +} + +static int adv7842_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv7842_state *state = to_state(sd); + u8 *data = NULL; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + switch (edid->pad) { + case ADV7842_EDID_PORT_A: + case ADV7842_EDID_PORT_B: + if (state->hdmi_edid.present & (0x04 << edid->pad)) + data = state->hdmi_edid.edid; + break; + case ADV7842_EDID_PORT_VGA: + if (state->vga_edid.present) + data = state->vga_edid.edid; + break; + default: + return -EINVAL; + } + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = data ? 2 : 0; + return 0; + } + + if (!data) + return -ENODATA; + + if (edid->start_block >= 2) + return -EINVAL; + + if (edid->start_block + edid->blocks > 2) + edid->blocks = 2 - edid->start_block; + + memcpy(edid->edid, data + edid->start_block * 128, edid->blocks * 128); + + return 0; +} + +static int adv7842_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *e) +{ + struct adv7842_state *state = to_state(sd); + int err = 0; + + memset(e->reserved, 0, sizeof(e->reserved)); + + if (e->pad > ADV7842_EDID_PORT_VGA) + return -EINVAL; + if (e->start_block != 0) + return -EINVAL; + if (e->blocks > 2) { + e->blocks = 2; + return -E2BIG; + } + + /* todo, per edid */ + state->aspect_ratio = v4l2_calc_aspect_ratio(e->edid[0x15], + e->edid[0x16]); + + switch (e->pad) { + case ADV7842_EDID_PORT_VGA: + memset(&state->vga_edid.edid, 0, 256); + state->vga_edid.present = e->blocks ? 0x1 : 0x0; + memcpy(&state->vga_edid.edid, e->edid, 128 * e->blocks); + err = edid_write_vga_segment(sd); + break; + case ADV7842_EDID_PORT_A: + case ADV7842_EDID_PORT_B: + memset(&state->hdmi_edid.edid, 0, 256); + if (e->blocks) { + state->hdmi_edid.present |= 0x04 << e->pad; + } else { + state->hdmi_edid.present &= ~(0x04 << e->pad); + adv7842_s_detect_tx_5v_ctrl(sd); + } + memcpy(&state->hdmi_edid.edid, e->edid, 128 * e->blocks); + err = edid_write_hdmi_segment(sd, e->pad); + break; + default: + return -EINVAL; + } + if (err < 0) + v4l2_err(sd, "error %d writing edid on port %d\n", err, e->pad); + return err; +} + +struct adv7842_cfg_read_infoframe { + const char *desc; + u8 present_mask; + u8 head_addr; + u8 payload_addr; +}; + +static void log_infoframe(struct v4l2_subdev *sd, const struct adv7842_cfg_read_infoframe *cri) +{ + int i; + u8 buffer[32]; + union hdmi_infoframe frame; + u8 len; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct device *dev = &client->dev; + + if (!(io_read(sd, 0x60) & cri->present_mask)) { + v4l2_info(sd, "%s infoframe not received\n", cri->desc); + return; + } + + for (i = 0; i < 3; i++) + buffer[i] = infoframe_read(sd, cri->head_addr + i); + + len = buffer[2] + 1; + + if (len + 3 > sizeof(buffer)) { + v4l2_err(sd, "%s: invalid %s infoframe length %d\n", __func__, cri->desc, len); + return; + } + + for (i = 0; i < len; i++) + buffer[i + 3] = infoframe_read(sd, cri->payload_addr + i); + + if (hdmi_infoframe_unpack(&frame, buffer, len + 3) < 0) { + v4l2_err(sd, "%s: unpack of %s infoframe failed\n", __func__, cri->desc); + return; + } + + hdmi_infoframe_log(KERN_INFO, dev, &frame); +} + +static void adv7842_log_infoframes(struct v4l2_subdev *sd) +{ + int i; + static const struct adv7842_cfg_read_infoframe cri[] = { + { "AVI", 0x01, 0xe0, 0x00 }, + { "Audio", 0x02, 0xe3, 0x1c }, + { "SDP", 0x04, 0xe6, 0x2a }, + { "Vendor", 0x10, 0xec, 0x54 } + }; + + if (!(hdmi_read(sd, 0x05) & 0x80)) { + v4l2_info(sd, "receive DVI-D signal, no infoframes\n"); + return; + } + + for (i = 0; i < ARRAY_SIZE(cri); i++) + log_infoframe(sd, &cri[i]); +} + +#if 0 +/* Let's keep it here for now, as it could be useful for debug */ +static const char * const prim_mode_txt[] = { + "SDP", + "Component", + "Graphics", + "Reserved", + "CVBS & HDMI AUDIO", + "HDMI-Comp", + "HDMI-GR", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", +}; +#endif + +static int adv7842_sdp_log_status(struct v4l2_subdev *sd) +{ + /* SDP (Standard definition processor) block */ + u8 sdp_signal_detected = sdp_read(sd, 0x5A) & 0x01; + + v4l2_info(sd, "Chip powered %s\n", no_power(sd) ? "off" : "on"); + v4l2_info(sd, "Prim-mode = 0x%x, video std = 0x%x\n", + io_read(sd, 0x01) & 0x0f, io_read(sd, 0x00) & 0x3f); + + v4l2_info(sd, "SDP: free run: %s\n", + (sdp_read(sd, 0x56) & 0x01) ? "on" : "off"); + v4l2_info(sd, "SDP: %s\n", sdp_signal_detected ? + "valid SD/PR signal detected" : "invalid/no signal"); + if (sdp_signal_detected) { + static const char * const sdp_std_txt[] = { + "NTSC-M/J", + "1?", + "NTSC-443", + "60HzSECAM", + "PAL-M", + "5?", + "PAL-60", + "7?", "8?", "9?", "a?", "b?", + "PAL-CombN", + "d?", + "PAL-BGHID", + "SECAM" + }; + v4l2_info(sd, "SDP: standard %s\n", + sdp_std_txt[sdp_read(sd, 0x52) & 0x0f]); + v4l2_info(sd, "SDP: %s\n", + (sdp_read(sd, 0x59) & 0x08) ? "50Hz" : "60Hz"); + v4l2_info(sd, "SDP: %s\n", + (sdp_read(sd, 0x57) & 0x08) ? "Interlaced" : "Progressive"); + v4l2_info(sd, "SDP: deinterlacer %s\n", + (sdp_read(sd, 0x12) & 0x08) ? "enabled" : "disabled"); + v4l2_info(sd, "SDP: csc %s mode\n", + (sdp_io_read(sd, 0xe0) & 0x40) ? "auto" : "manual"); + } + return 0; +} + +static int adv7842_cp_log_status(struct v4l2_subdev *sd) +{ + /* CP block */ + struct adv7842_state *state = to_state(sd); + struct v4l2_dv_timings timings; + u8 reg_io_0x02 = io_read(sd, 0x02); + u8 reg_io_0x21 = io_read(sd, 0x21); + u8 reg_rep_0x77 = rep_read(sd, 0x77); + u8 reg_rep_0x7d = rep_read(sd, 0x7d); + bool audio_pll_locked = hdmi_read(sd, 0x04) & 0x01; + bool audio_sample_packet_detect = hdmi_read(sd, 0x18) & 0x01; + bool audio_mute = io_read(sd, 0x65) & 0x40; + + static const char * const csc_coeff_sel_rb[16] = { + "bypassed", "YPbPr601 -> RGB", "reserved", "YPbPr709 -> RGB", + "reserved", "RGB -> YPbPr601", "reserved", "RGB -> YPbPr709", + "reserved", "YPbPr709 -> YPbPr601", "YPbPr601 -> YPbPr709", + "reserved", "reserved", "reserved", "reserved", "manual" + }; + static const char * const input_color_space_txt[16] = { + "RGB limited range (16-235)", "RGB full range (0-255)", + "YCbCr Bt.601 (16-235)", "YCbCr Bt.709 (16-235)", + "xvYCC Bt.601", "xvYCC Bt.709", + "YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)", + "invalid", "invalid", "invalid", "invalid", "invalid", + "invalid", "invalid", "automatic" + }; + static const char * const rgb_quantization_range_txt[] = { + "Automatic", + "RGB limited range (16-235)", + "RGB full range (0-255)", + }; + static const char * const deep_color_mode_txt[4] = { + "8-bits per channel", + "10-bits per channel", + "12-bits per channel", + "16-bits per channel (not supported)" + }; + + v4l2_info(sd, "-----Chip status-----\n"); + v4l2_info(sd, "Chip power: %s\n", no_power(sd) ? "off" : "on"); + v4l2_info(sd, "HDMI/DVI-D port selected: %s\n", + state->hdmi_port_a ? "A" : "B"); + v4l2_info(sd, "EDID A %s, B %s\n", + ((reg_rep_0x7d & 0x04) && (reg_rep_0x77 & 0x04)) ? + "enabled" : "disabled", + ((reg_rep_0x7d & 0x08) && (reg_rep_0x77 & 0x08)) ? + "enabled" : "disabled"); + v4l2_info(sd, "HPD A %s, B %s\n", + reg_io_0x21 & 0x02 ? "enabled" : "disabled", + reg_io_0x21 & 0x01 ? "enabled" : "disabled"); + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? + "enabled" : "disabled"); + if (state->cec_enabled_adap) { + int i; + + for (i = 0; i < ADV7842_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + } + + v4l2_info(sd, "-----Signal status-----\n"); + if (state->hdmi_port_a) { + v4l2_info(sd, "Cable detected (+5V power): %s\n", + io_read(sd, 0x6f) & 0x02 ? "true" : "false"); + v4l2_info(sd, "TMDS signal detected: %s\n", + (io_read(sd, 0x6a) & 0x02) ? "true" : "false"); + v4l2_info(sd, "TMDS signal locked: %s\n", + (io_read(sd, 0x6a) & 0x20) ? "true" : "false"); + } else { + v4l2_info(sd, "Cable detected (+5V power):%s\n", + io_read(sd, 0x6f) & 0x01 ? "true" : "false"); + v4l2_info(sd, "TMDS signal detected: %s\n", + (io_read(sd, 0x6a) & 0x01) ? "true" : "false"); + v4l2_info(sd, "TMDS signal locked: %s\n", + (io_read(sd, 0x6a) & 0x10) ? "true" : "false"); + } + v4l2_info(sd, "CP free run: %s\n", + (!!(cp_read(sd, 0xff) & 0x10) ? "on" : "off")); + v4l2_info(sd, "Prim-mode = 0x%x, video std = 0x%x, v_freq = 0x%x\n", + io_read(sd, 0x01) & 0x0f, io_read(sd, 0x00) & 0x3f, + (io_read(sd, 0x01) & 0x70) >> 4); + + v4l2_info(sd, "-----Video Timings-----\n"); + if (no_cp_signal(sd)) { + v4l2_info(sd, "STDI: not locked\n"); + } else { + u32 bl = ((cp_read(sd, 0xb1) & 0x3f) << 8) | cp_read(sd, 0xb2); + u32 lcf = ((cp_read(sd, 0xb3) & 0x7) << 8) | cp_read(sd, 0xb4); + u32 lcvs = cp_read(sd, 0xb3) >> 3; + u32 fcl = ((cp_read(sd, 0xb8) & 0x1f) << 8) | cp_read(sd, 0xb9); + char hs_pol = ((cp_read(sd, 0xb5) & 0x10) ? + ((cp_read(sd, 0xb5) & 0x08) ? '+' : '-') : 'x'); + char vs_pol = ((cp_read(sd, 0xb5) & 0x40) ? + ((cp_read(sd, 0xb5) & 0x20) ? '+' : '-') : 'x'); + v4l2_info(sd, + "STDI: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, fcl = %d, %s, %chsync, %cvsync\n", + lcf, bl, lcvs, fcl, + (cp_read(sd, 0xb1) & 0x40) ? + "interlaced" : "progressive", + hs_pol, vs_pol); + } + if (adv7842_query_dv_timings(sd, &timings)) + v4l2_info(sd, "No video detected\n"); + else + v4l2_print_dv_timings(sd->name, "Detected format: ", + &timings, true); + v4l2_print_dv_timings(sd->name, "Configured format: ", + &state->timings, true); + + if (no_cp_signal(sd)) + return 0; + + v4l2_info(sd, "-----Color space-----\n"); + v4l2_info(sd, "RGB quantization range ctrl: %s\n", + rgb_quantization_range_txt[state->rgb_quantization_range]); + v4l2_info(sd, "Input color space: %s\n", + input_color_space_txt[reg_io_0x02 >> 4]); + v4l2_info(sd, "Output color space: %s %s, alt-gamma %s\n", + (reg_io_0x02 & 0x02) ? "RGB" : "YCbCr", + (((reg_io_0x02 >> 2) & 0x01) ^ (reg_io_0x02 & 0x01)) ? + "(16-235)" : "(0-255)", + (reg_io_0x02 & 0x08) ? "enabled" : "disabled"); + v4l2_info(sd, "Color space conversion: %s\n", + csc_coeff_sel_rb[cp_read(sd, 0xf4) >> 4]); + + if (!is_digital_input(sd)) + return 0; + + v4l2_info(sd, "-----%s status-----\n", is_hdmi(sd) ? "HDMI" : "DVI-D"); + v4l2_info(sd, "HDCP encrypted content: %s\n", + (hdmi_read(sd, 0x05) & 0x40) ? "true" : "false"); + v4l2_info(sd, "HDCP keys read: %s%s\n", + (hdmi_read(sd, 0x04) & 0x20) ? "yes" : "no", + (hdmi_read(sd, 0x04) & 0x10) ? "ERROR" : ""); + if (!is_hdmi(sd)) + return 0; + + v4l2_info(sd, "Audio: pll %s, samples %s, %s\n", + audio_pll_locked ? "locked" : "not locked", + audio_sample_packet_detect ? "detected" : "not detected", + audio_mute ? "muted" : "enabled"); + if (audio_pll_locked && audio_sample_packet_detect) { + v4l2_info(sd, "Audio format: %s\n", + (hdmi_read(sd, 0x07) & 0x40) ? "multi-channel" : "stereo"); + } + v4l2_info(sd, "Audio CTS: %u\n", (hdmi_read(sd, 0x5b) << 12) + + (hdmi_read(sd, 0x5c) << 8) + + (hdmi_read(sd, 0x5d) & 0xf0)); + v4l2_info(sd, "Audio N: %u\n", ((hdmi_read(sd, 0x5d) & 0x0f) << 16) + + (hdmi_read(sd, 0x5e) << 8) + + hdmi_read(sd, 0x5f)); + v4l2_info(sd, "AV Mute: %s\n", + (hdmi_read(sd, 0x04) & 0x40) ? "on" : "off"); + v4l2_info(sd, "Deep color mode: %s\n", + deep_color_mode_txt[hdmi_read(sd, 0x0b) >> 6]); + + adv7842_log_infoframes(sd); + + return 0; +} + +static int adv7842_log_status(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + if (state->mode == ADV7842_MODE_SDP) + return adv7842_sdp_log_status(sd); + return adv7842_cp_log_status(sd); +} + +static int adv7842_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7842_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (state->mode != ADV7842_MODE_SDP) + return -ENODATA; + + if (!(sdp_read(sd, 0x5A) & 0x01)) { + *std = 0; + v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); + return 0; + } + + switch (sdp_read(sd, 0x52) & 0x0f) { + case 0: + /* NTSC-M/J */ + *std &= V4L2_STD_NTSC; + break; + case 2: + /* NTSC-443 */ + *std &= V4L2_STD_NTSC_443; + break; + case 3: + /* 60HzSECAM */ + *std &= V4L2_STD_SECAM; + break; + case 4: + /* PAL-M */ + *std &= V4L2_STD_PAL_M; + break; + case 6: + /* PAL-60 */ + *std &= V4L2_STD_PAL_60; + break; + case 0xc: + /* PAL-CombN */ + *std &= V4L2_STD_PAL_Nc; + break; + case 0xe: + /* PAL-BGHID */ + *std &= V4L2_STD_PAL; + break; + case 0xf: + /* SECAM */ + *std &= V4L2_STD_SECAM; + break; + default: + *std &= V4L2_STD_ALL; + break; + } + return 0; +} + +static void adv7842_s_sdp_io(struct v4l2_subdev *sd, struct adv7842_sdp_io_sync_adjustment *s) +{ + if (s && s->adjust) { + sdp_io_write(sd, 0x94, (s->hs_beg >> 8) & 0xf); + sdp_io_write(sd, 0x95, s->hs_beg & 0xff); + sdp_io_write(sd, 0x96, (s->hs_width >> 8) & 0xf); + sdp_io_write(sd, 0x97, s->hs_width & 0xff); + sdp_io_write(sd, 0x98, (s->de_beg >> 8) & 0xf); + sdp_io_write(sd, 0x99, s->de_beg & 0xff); + sdp_io_write(sd, 0x9a, (s->de_end >> 8) & 0xf); + sdp_io_write(sd, 0x9b, s->de_end & 0xff); + sdp_io_write(sd, 0xa8, s->vs_beg_o); + sdp_io_write(sd, 0xa9, s->vs_beg_e); + sdp_io_write(sd, 0xaa, s->vs_end_o); + sdp_io_write(sd, 0xab, s->vs_end_e); + sdp_io_write(sd, 0xac, s->de_v_beg_o); + sdp_io_write(sd, 0xad, s->de_v_beg_e); + sdp_io_write(sd, 0xae, s->de_v_end_o); + sdp_io_write(sd, 0xaf, s->de_v_end_e); + } else { + /* set to default */ + sdp_io_write(sd, 0x94, 0x00); + sdp_io_write(sd, 0x95, 0x00); + sdp_io_write(sd, 0x96, 0x00); + sdp_io_write(sd, 0x97, 0x20); + sdp_io_write(sd, 0x98, 0x00); + sdp_io_write(sd, 0x99, 0x00); + sdp_io_write(sd, 0x9a, 0x00); + sdp_io_write(sd, 0x9b, 0x00); + sdp_io_write(sd, 0xa8, 0x04); + sdp_io_write(sd, 0xa9, 0x04); + sdp_io_write(sd, 0xaa, 0x04); + sdp_io_write(sd, 0xab, 0x04); + sdp_io_write(sd, 0xac, 0x04); + sdp_io_write(sd, 0xad, 0x04); + sdp_io_write(sd, 0xae, 0x04); + sdp_io_write(sd, 0xaf, 0x04); + } +} + +static int adv7842_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = &state->pdata; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (state->mode != ADV7842_MODE_SDP) + return -ENODATA; + + if (norm & V4L2_STD_625_50) + adv7842_s_sdp_io(sd, &pdata->sdp_io_sync_625); + else if (norm & V4L2_STD_525_60) + adv7842_s_sdp_io(sd, &pdata->sdp_io_sync_525); + else + adv7842_s_sdp_io(sd, NULL); + + if (norm & V4L2_STD_ALL) { + state->norm = norm; + return 0; + } + return -EINVAL; +} + +static int adv7842_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + struct adv7842_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (state->mode != ADV7842_MODE_SDP) + return -ENODATA; + + *norm = state->norm; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int adv7842_core_init(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = &state->pdata; + hdmi_write(sd, 0x48, + (pdata->disable_pwrdnb ? 0x80 : 0) | + (pdata->disable_cable_det_rst ? 0x40 : 0)); + + disable_input(sd); + + /* + * Disable I2C access to internal EDID ram from HDMI DDC ports + * Disable auto edid enable when leaving powerdown mode + */ + rep_write_and_or(sd, 0x77, 0xd3, 0x20); + + /* power */ + io_write(sd, 0x0c, 0x42); /* Power up part and power down VDP */ + io_write(sd, 0x15, 0x80); /* Power up pads */ + + /* video format */ + io_write(sd, 0x02, 0xf0 | pdata->alt_gamma << 3); + io_write_and_or(sd, 0x05, 0xf0, pdata->blank_data << 3 | + pdata->insert_av_codes << 2 | + pdata->replicate_av_codes << 1); + adv7842_setup_format(state); + + /* HDMI audio */ + hdmi_write_and_or(sd, 0x1a, 0xf1, 0x08); /* Wait 1 s before unmute */ + + /* Drive strength */ + io_write_and_or(sd, 0x14, 0xc0, + pdata->dr_str_data << 4 | + pdata->dr_str_clk << 2 | + pdata->dr_str_sync); + + /* HDMI free run */ + cp_write_and_or(sd, 0xba, 0xfc, pdata->hdmi_free_run_enable | + (pdata->hdmi_free_run_mode << 1)); + + /* SPD free run */ + sdp_write_and_or(sd, 0xdd, 0xf0, pdata->sdp_free_run_force | + (pdata->sdp_free_run_cbar_en << 1) | + (pdata->sdp_free_run_man_col_en << 2) | + (pdata->sdp_free_run_auto << 3)); + + /* TODO from platform data */ + cp_write(sd, 0x69, 0x14); /* Enable CP CSC */ + io_write(sd, 0x06, 0xa6); /* positive VS and HS and DE */ + cp_write(sd, 0xf3, 0xdc); /* Low threshold to enter/exit free run mode */ + afe_write(sd, 0xb5, 0x01); /* Setting MCLK to 256Fs */ + + afe_write(sd, 0x02, pdata->ain_sel); /* Select analog input muxing mode */ + io_write_and_or(sd, 0x30, ~(1 << 4), pdata->output_bus_lsb_to_msb << 4); + + sdp_csc_coeff(sd, &pdata->sdp_csc_coeff); + + /* todo, improve settings for sdram */ + if (pdata->sd_ram_size >= 128) { + sdp_write(sd, 0x12, 0x0d); /* Frame TBC,3D comb enabled */ + if (pdata->sd_ram_ddr) { + /* SDP setup for the AD eval board */ + sdp_io_write(sd, 0x6f, 0x00); /* DDR mode */ + sdp_io_write(sd, 0x75, 0x0a); /* 128 MB memory size */ + sdp_io_write(sd, 0x7a, 0xa5); /* Timing Adjustment */ + sdp_io_write(sd, 0x7b, 0x8f); /* Timing Adjustment */ + sdp_io_write(sd, 0x60, 0x01); /* SDRAM reset */ + } else { + sdp_io_write(sd, 0x75, 0x0a); /* 64 MB memory size ?*/ + sdp_io_write(sd, 0x74, 0x00); /* must be zero for sdr sdram */ + sdp_io_write(sd, 0x79, 0x33); /* CAS latency to 3, + depends on memory */ + sdp_io_write(sd, 0x6f, 0x01); /* SDR mode */ + sdp_io_write(sd, 0x7a, 0xa5); /* Timing Adjustment */ + sdp_io_write(sd, 0x7b, 0x8f); /* Timing Adjustment */ + sdp_io_write(sd, 0x60, 0x01); /* SDRAM reset */ + } + } else { + /* + * Manual UG-214, rev 0 is bit confusing on this bit + * but a '1' disables any signal if the Ram is active. + */ + sdp_io_write(sd, 0x29, 0x10); /* Tristate memory interface */ + } + + select_input(sd, pdata->vid_std_select); + + enable_input(sd); + + if (pdata->hpa_auto) { + /* HPA auto, HPA 0.5s after Edid set and Cable detect */ + hdmi_write(sd, 0x69, 0x5c); + } else { + /* HPA manual */ + hdmi_write(sd, 0x69, 0xa3); + /* HPA disable on port A and B */ + io_write_and_or(sd, 0x20, 0xcf, 0x00); + } + + /* LLC */ + io_write(sd, 0x19, 0x80 | pdata->llc_dll_phase); + io_write(sd, 0x33, 0x40); + + /* interrupts */ + io_write(sd, 0x40, 0xf2); /* Configure INT1 */ + + adv7842_irq_enable(sd, true); + + return v4l2_ctrl_handler_setup(sd->ctrl_handler); +} + +/* ----------------------------------------------------------------------- */ + +static int adv7842_ddr_ram_test(struct v4l2_subdev *sd) +{ + /* + * From ADV784x external Memory test.pdf + * + * Reset must just been performed before running test. + * Recommended to reset after test. + */ + int i; + int pass = 0; + int fail = 0; + int complete = 0; + + io_write(sd, 0x00, 0x01); /* Program SDP 4x1 */ + io_write(sd, 0x01, 0x00); /* Program SDP mode */ + afe_write(sd, 0x80, 0x92); /* SDP Recommended Write */ + afe_write(sd, 0x9B, 0x01); /* SDP Recommended Write ADV7844ES1 */ + afe_write(sd, 0x9C, 0x60); /* SDP Recommended Write ADV7844ES1 */ + afe_write(sd, 0x9E, 0x02); /* SDP Recommended Write ADV7844ES1 */ + afe_write(sd, 0xA0, 0x0B); /* SDP Recommended Write ADV7844ES1 */ + afe_write(sd, 0xC3, 0x02); /* Memory BIST Initialisation */ + io_write(sd, 0x0C, 0x40); /* Power up ADV7844 */ + io_write(sd, 0x15, 0xBA); /* Enable outputs */ + sdp_write(sd, 0x12, 0x00); /* Disable 3D comb, Frame TBC & 3DNR */ + io_write(sd, 0xFF, 0x04); /* Reset memory controller */ + + usleep_range(5000, 6000); + + sdp_write(sd, 0x12, 0x00); /* Disable 3D Comb, Frame TBC & 3DNR */ + sdp_io_write(sd, 0x2A, 0x01); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x7c, 0x19); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x80, 0x87); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x81, 0x4a); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x82, 0x2c); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x83, 0x0e); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x84, 0x94); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x85, 0x62); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x7d, 0x00); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x7e, 0x1a); /* Memory BIST Initialisation */ + + usleep_range(5000, 6000); + + sdp_io_write(sd, 0xd9, 0xd5); /* Enable BIST Test */ + sdp_write(sd, 0x12, 0x05); /* Enable FRAME TBC & 3D COMB */ + + msleep(20); + + for (i = 0; i < 10; i++) { + u8 result = sdp_io_read(sd, 0xdb); + if (result & 0x10) { + complete++; + if (result & 0x20) + fail++; + else + pass++; + } + msleep(20); + } + + v4l2_dbg(1, debug, sd, + "Ram Test: completed %d of %d: pass %d, fail %d\n", + complete, i, pass, fail); + + if (!complete || fail) + return -EIO; + return 0; +} + +static void adv7842_rewrite_i2c_addresses(struct v4l2_subdev *sd, + struct adv7842_platform_data *pdata) +{ + io_write(sd, 0xf1, pdata->i2c_sdp << 1); + io_write(sd, 0xf2, pdata->i2c_sdp_io << 1); + io_write(sd, 0xf3, pdata->i2c_avlink << 1); + io_write(sd, 0xf4, pdata->i2c_cec << 1); + io_write(sd, 0xf5, pdata->i2c_infoframe << 1); + + io_write(sd, 0xf8, pdata->i2c_afe << 1); + io_write(sd, 0xf9, pdata->i2c_repeater << 1); + io_write(sd, 0xfa, pdata->i2c_edid << 1); + io_write(sd, 0xfb, pdata->i2c_hdmi << 1); + + io_write(sd, 0xfd, pdata->i2c_cp << 1); + io_write(sd, 0xfe, pdata->i2c_vdp << 1); +} + +static int adv7842_command_ram_test(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = client->dev.platform_data; + struct v4l2_dv_timings timings; + int ret = 0; + + if (!pdata) + return -ENODEV; + + if (!pdata->sd_ram_size || !pdata->sd_ram_ddr) { + v4l2_info(sd, "no sdram or no ddr sdram\n"); + return -EINVAL; + } + + main_reset(sd); + + adv7842_rewrite_i2c_addresses(sd, pdata); + + /* run ram test */ + ret = adv7842_ddr_ram_test(sd); + + main_reset(sd); + + adv7842_rewrite_i2c_addresses(sd, pdata); + + /* and re-init chip and state */ + adv7842_core_init(sd); + + disable_input(sd); + + select_input(sd, state->vid_std_select); + + enable_input(sd); + + edid_write_vga_segment(sd); + edid_write_hdmi_segment(sd, ADV7842_EDID_PORT_A); + edid_write_hdmi_segment(sd, ADV7842_EDID_PORT_B); + + timings = state->timings; + + memset(&state->timings, 0, sizeof(struct v4l2_dv_timings)); + + adv7842_s_dv_timings(sd, &timings); + + return ret; +} + +static long adv7842_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + switch (cmd) { + case ADV7842_CMD_RAM_TEST: + return adv7842_command_ram_test(sd); + } + return -ENOTTY; +} + +static int adv7842_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static int adv7842_registered(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err; + + err = cec_register_adapter(state->cec_adap, &client->dev); + if (err) + cec_delete_adapter(state->cec_adap); + return err; +} + +static void adv7842_unregistered(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + cec_unregister_adapter(state->cec_adap); +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops adv7842_ctrl_ops = { + .s_ctrl = adv7842_s_ctrl, + .g_volatile_ctrl = adv7842_g_volatile_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7842_core_ops = { + .log_status = adv7842_log_status, + .ioctl = adv7842_ioctl, + .interrupt_service_routine = adv7842_isr, + .subscribe_event = adv7842_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7842_g_register, + .s_register = adv7842_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops adv7842_video_ops = { + .g_std = adv7842_g_std, + .s_std = adv7842_s_std, + .s_routing = adv7842_s_routing, + .querystd = adv7842_querystd, + .g_input_status = adv7842_g_input_status, + .s_dv_timings = adv7842_s_dv_timings, + .g_dv_timings = adv7842_g_dv_timings, + .query_dv_timings = adv7842_query_dv_timings, +}; + +static const struct v4l2_subdev_pad_ops adv7842_pad_ops = { + .enum_mbus_code = adv7842_enum_mbus_code, + .get_fmt = adv7842_get_format, + .set_fmt = adv7842_set_format, + .get_edid = adv7842_get_edid, + .set_edid = adv7842_set_edid, + .enum_dv_timings = adv7842_enum_dv_timings, + .dv_timings_cap = adv7842_dv_timings_cap, +}; + +static const struct v4l2_subdev_ops adv7842_ops = { + .core = &adv7842_core_ops, + .video = &adv7842_video_ops, + .pad = &adv7842_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops adv7842_int_ops = { + .registered = adv7842_registered, + .unregistered = adv7842_unregistered, +}; + +/* -------------------------- custom ctrls ---------------------------------- */ + +static const struct v4l2_ctrl_config adv7842_ctrl_analog_sampling_phase = { + .ops = &adv7842_ctrl_ops, + .id = V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE, + .name = "Analog Sampling Phase", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 0x1f, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config adv7842_ctrl_free_run_color_manual = { + .ops = &adv7842_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL, + .name = "Free Running Color, Manual", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .max = 1, + .step = 1, + .def = 1, +}; + +static const struct v4l2_ctrl_config adv7842_ctrl_free_run_color = { + .ops = &adv7842_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR, + .name = "Free Running Color", + .type = V4L2_CTRL_TYPE_INTEGER, + .max = 0xffffff, + .step = 0x1, +}; + + +static void adv7842_unregister_clients(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + i2c_unregister_device(state->i2c_avlink); + i2c_unregister_device(state->i2c_cec); + i2c_unregister_device(state->i2c_infoframe); + i2c_unregister_device(state->i2c_sdp_io); + i2c_unregister_device(state->i2c_sdp); + i2c_unregister_device(state->i2c_afe); + i2c_unregister_device(state->i2c_repeater); + i2c_unregister_device(state->i2c_edid); + i2c_unregister_device(state->i2c_hdmi); + i2c_unregister_device(state->i2c_cp); + i2c_unregister_device(state->i2c_vdp); + + state->i2c_avlink = NULL; + state->i2c_cec = NULL; + state->i2c_infoframe = NULL; + state->i2c_sdp_io = NULL; + state->i2c_sdp = NULL; + state->i2c_afe = NULL; + state->i2c_repeater = NULL; + state->i2c_edid = NULL; + state->i2c_hdmi = NULL; + state->i2c_cp = NULL; + state->i2c_vdp = NULL; +} + +static struct i2c_client *adv7842_dummy_client(struct v4l2_subdev *sd, const char *desc, + u8 addr, u8 io_reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct i2c_client *cp; + + io_write(sd, io_reg, addr << 1); + + if (addr == 0) { + v4l2_err(sd, "no %s i2c addr configured\n", desc); + return NULL; + } + + cp = i2c_new_dummy_device(client->adapter, io_read(sd, io_reg) >> 1); + if (IS_ERR(cp)) { + v4l2_err(sd, "register %s on i2c addr 0x%x failed with %ld\n", + desc, addr, PTR_ERR(cp)); + cp = NULL; + } + + return cp; +} + +static int adv7842_register_clients(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = &state->pdata; + + state->i2c_avlink = adv7842_dummy_client(sd, "avlink", pdata->i2c_avlink, 0xf3); + state->i2c_cec = adv7842_dummy_client(sd, "cec", pdata->i2c_cec, 0xf4); + state->i2c_infoframe = adv7842_dummy_client(sd, "infoframe", pdata->i2c_infoframe, 0xf5); + state->i2c_sdp_io = adv7842_dummy_client(sd, "sdp_io", pdata->i2c_sdp_io, 0xf2); + state->i2c_sdp = adv7842_dummy_client(sd, "sdp", pdata->i2c_sdp, 0xf1); + state->i2c_afe = adv7842_dummy_client(sd, "afe", pdata->i2c_afe, 0xf8); + state->i2c_repeater = adv7842_dummy_client(sd, "repeater", pdata->i2c_repeater, 0xf9); + state->i2c_edid = adv7842_dummy_client(sd, "edid", pdata->i2c_edid, 0xfa); + state->i2c_hdmi = adv7842_dummy_client(sd, "hdmi", pdata->i2c_hdmi, 0xfb); + state->i2c_cp = adv7842_dummy_client(sd, "cp", pdata->i2c_cp, 0xfd); + state->i2c_vdp = adv7842_dummy_client(sd, "vdp", pdata->i2c_vdp, 0xfe); + + if (!state->i2c_avlink || + !state->i2c_cec || + !state->i2c_infoframe || + !state->i2c_sdp_io || + !state->i2c_sdp || + !state->i2c_afe || + !state->i2c_repeater || + !state->i2c_edid || + !state->i2c_hdmi || + !state->i2c_cp || + !state->i2c_vdp) + return -1; + + return 0; +} + +static int adv7842_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adv7842_state *state; + static const struct v4l2_dv_timings cea640x480 = + V4L2_DV_BT_CEA_640X480P59_94; + struct adv7842_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; + struct v4l2_ctrl *ctrl; + struct v4l2_subdev *sd; + u16 rev; + int err; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_dbg(1, debug, client, "detecting adv7842 client on address 0x%x\n", + client->addr << 1); + + if (!pdata) { + v4l_err(client, "No platform data!\n"); + return -ENODEV; + } + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + /* platform data */ + state->pdata = *pdata; + state->timings = cea640x480; + state->format = adv7842_format_info(state, MEDIA_BUS_FMT_YUYV8_2X8); + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &adv7842_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->internal_ops = &adv7842_int_ops; + state->mode = pdata->mode; + + state->hdmi_port_a = pdata->input == ADV7842_SELECT_HDMI_PORT_A; + state->restart_stdi_once = true; + + /* i2c access to adv7842? */ + rev = adv_smbus_read_byte_data_check(client, 0xea, false) << 8 | + adv_smbus_read_byte_data_check(client, 0xeb, false); + if (rev != 0x2012) { + v4l2_info(sd, "got rev=0x%04x on first read attempt\n", rev); + rev = adv_smbus_read_byte_data_check(client, 0xea, false) << 8 | + adv_smbus_read_byte_data_check(client, 0xeb, false); + } + if (rev != 0x2012) { + v4l2_info(sd, "not an adv7842 on address 0x%x (rev=0x%04x)\n", + client->addr << 1, rev); + return -ENODEV; + } + + if (pdata->chip_reset) + main_reset(sd); + + /* control handlers */ + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 6); + + /* add in ascending ID order */ + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_HUE, 0, 128, 1, 0); + ctrl = v4l2_ctrl_new_std_menu(hdl, &adv7842_ctrl_ops, + V4L2_CID_DV_RX_IT_CONTENT_TYPE, V4L2_DV_IT_CONTENT_TYPE_NO_ITC, + 0, V4L2_DV_IT_CONTENT_TYPE_NO_ITC); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + /* custom controls */ + state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_RX_POWER_PRESENT, 0, 3, 0, 0); + state->analog_sampling_phase_ctrl = v4l2_ctrl_new_custom(hdl, + &adv7842_ctrl_analog_sampling_phase, NULL); + state->free_run_color_ctrl_manual = v4l2_ctrl_new_custom(hdl, + &adv7842_ctrl_free_run_color_manual, NULL); + state->free_run_color_ctrl = v4l2_ctrl_new_custom(hdl, + &adv7842_ctrl_free_run_color, NULL); + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv7842_ctrl_ops, + V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + goto err_hdl; + } + if (adv7842_s_detect_tx_5v_ctrl(sd)) { + err = -ENODEV; + goto err_hdl; + } + + if (adv7842_register_clients(sd) < 0) { + err = -ENOMEM; + v4l2_err(sd, "failed to create all i2c clients\n"); + goto err_i2c; + } + + + INIT_DELAYED_WORK(&state->delayed_work_enable_hotplug, + adv7842_delayed_work_enable_hotplug); + + sd->entity.function = MEDIA_ENT_F_DV_DECODER; + state->pad.flags = MEDIA_PAD_FL_SOURCE; + err = media_entity_pads_init(&sd->entity, 1, &state->pad); + if (err) + goto err_work_queues; + + err = adv7842_core_init(sd); + if (err) + goto err_entity; + +#if IS_ENABLED(CONFIG_VIDEO_ADV7842_CEC) + state->cec_adap = cec_allocate_adapter(&adv7842_cec_adap_ops, + state, dev_name(&client->dev), + CEC_CAP_DEFAULTS, ADV7842_MAX_ADDRS); + err = PTR_ERR_OR_ZERO(state->cec_adap); + if (err) + goto err_entity; +#endif + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + return 0; + +err_entity: + media_entity_cleanup(&sd->entity); +err_work_queues: + cancel_delayed_work(&state->delayed_work_enable_hotplug); +err_i2c: + adv7842_unregister_clients(sd); +err_hdl: + v4l2_ctrl_handler_free(hdl); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static int adv7842_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7842_state *state = to_state(sd); + + adv7842_irq_enable(sd, false); + cancel_delayed_work_sync(&state->delayed_work_enable_hotplug); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + adv7842_unregister_clients(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id adv7842_id[] = { + { "adv7842", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7842_id); + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver adv7842_driver = { + .driver = { + .name = "adv7842", + }, + .probe = adv7842_probe, + .remove = adv7842_remove, + .id_table = adv7842_id, +}; + +module_i2c_driver(adv7842_driver); diff --git a/drivers/media/i2c/ak7375.c b/drivers/media/i2c/ak7375.c new file mode 100644 index 000000000..7b14b1160 --- /dev/null +++ b/drivers/media/i2c/ak7375.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Intel Corporation + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#define AK7375_MAX_FOCUS_POS 4095 +/* + * This sets the minimum granularity for the focus positions. + * A value of 1 gives maximum accuracy for a desired focus position + */ +#define AK7375_FOCUS_STEPS 1 +/* + * This acts as the minimum granularity of lens movement. + * Keep this value power of 2, so the control steps can be + * uniformly adjusted for gradual lens movement, with desired + * number of control steps. + */ +#define AK7375_CTRL_STEPS 64 +#define AK7375_CTRL_DELAY_US 1000 + +#define AK7375_REG_POSITION 0x0 +#define AK7375_REG_CONT 0x2 +#define AK7375_MODE_ACTIVE 0x0 +#define AK7375_MODE_STANDBY 0x40 + +/* ak7375 device structure */ +struct ak7375_device { + struct v4l2_ctrl_handler ctrls_vcm; + struct v4l2_subdev sd; + struct v4l2_ctrl *focus; + /* active or standby mode */ + bool active; +}; + +static inline struct ak7375_device *to_ak7375_vcm(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct ak7375_device, ctrls_vcm); +} + +static inline struct ak7375_device *sd_to_ak7375_vcm(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct ak7375_device, sd); +} + +static int ak7375_i2c_write(struct ak7375_device *ak7375, + u8 addr, u16 data, u8 size) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ak7375->sd); + u8 buf[3]; + int ret; + + if (size != 1 && size != 2) + return -EINVAL; + buf[0] = addr; + buf[size] = data & 0xff; + if (size == 2) + buf[1] = (data >> 8) & 0xff; + ret = i2c_master_send(client, (const char *)buf, size + 1); + if (ret < 0) + return ret; + if (ret != size + 1) + return -EIO; + + return 0; +} + +static int ak7375_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ak7375_device *dev_vcm = to_ak7375_vcm(ctrl); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) + return ak7375_i2c_write(dev_vcm, AK7375_REG_POSITION, + ctrl->val << 4, 2); + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops ak7375_vcm_ctrl_ops = { + .s_ctrl = ak7375_set_ctrl, +}; + +static int ak7375_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + int ret; + + ret = pm_runtime_get_sync(sd->dev); + if (ret < 0) { + pm_runtime_put_noidle(sd->dev); + return ret; + } + + return 0; +} + +static int ak7375_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops ak7375_int_ops = { + .open = ak7375_open, + .close = ak7375_close, +}; + +static const struct v4l2_subdev_ops ak7375_ops = { }; + +static void ak7375_subdev_cleanup(struct ak7375_device *ak7375_dev) +{ + v4l2_async_unregister_subdev(&ak7375_dev->sd); + v4l2_ctrl_handler_free(&ak7375_dev->ctrls_vcm); + media_entity_cleanup(&ak7375_dev->sd.entity); +} + +static int ak7375_init_controls(struct ak7375_device *dev_vcm) +{ + struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm; + const struct v4l2_ctrl_ops *ops = &ak7375_vcm_ctrl_ops; + + v4l2_ctrl_handler_init(hdl, 1); + + dev_vcm->focus = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, + 0, AK7375_MAX_FOCUS_POS, AK7375_FOCUS_STEPS, 0); + + if (hdl->error) + dev_err(dev_vcm->sd.dev, "%s fail error: 0x%x\n", + __func__, hdl->error); + dev_vcm->sd.ctrl_handler = hdl; + + return hdl->error; +} + +static int ak7375_probe(struct i2c_client *client) +{ + struct ak7375_device *ak7375_dev; + int ret; + + ak7375_dev = devm_kzalloc(&client->dev, sizeof(*ak7375_dev), + GFP_KERNEL); + if (!ak7375_dev) + return -ENOMEM; + + v4l2_i2c_subdev_init(&ak7375_dev->sd, client, &ak7375_ops); + ak7375_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ak7375_dev->sd.internal_ops = &ak7375_int_ops; + ak7375_dev->sd.entity.function = MEDIA_ENT_F_LENS; + + ret = ak7375_init_controls(ak7375_dev); + if (ret) + goto err_cleanup; + + ret = media_entity_pads_init(&ak7375_dev->sd.entity, 0, NULL); + if (ret < 0) + goto err_cleanup; + + ret = v4l2_async_register_subdev(&ak7375_dev->sd); + if (ret < 0) + goto err_cleanup; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_cleanup: + v4l2_ctrl_handler_free(&ak7375_dev->ctrls_vcm); + media_entity_cleanup(&ak7375_dev->sd.entity); + + return ret; +} + +static int ak7375_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ak7375_device *ak7375_dev = sd_to_ak7375_vcm(sd); + + ak7375_subdev_cleanup(ak7375_dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +/* + * This function sets the vcm position, so it consumes least current + * The lens position is gradually moved in units of AK7375_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused ak7375_vcm_suspend(struct device *dev) +{ + + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ak7375_device *ak7375_dev = sd_to_ak7375_vcm(sd); + int ret, val; + + if (!ak7375_dev->active) + return 0; + + for (val = ak7375_dev->focus->val & ~(AK7375_CTRL_STEPS - 1); + val >= 0; val -= AK7375_CTRL_STEPS) { + ret = ak7375_i2c_write(ak7375_dev, AK7375_REG_POSITION, + val << 4, 2); + if (ret) + dev_err_once(dev, "%s I2C failure: %d\n", + __func__, ret); + usleep_range(AK7375_CTRL_DELAY_US, AK7375_CTRL_DELAY_US + 10); + } + + ret = ak7375_i2c_write(ak7375_dev, AK7375_REG_CONT, + AK7375_MODE_STANDBY, 1); + if (ret) + dev_err(dev, "%s I2C failure: %d\n", __func__, ret); + + ak7375_dev->active = false; + + return 0; +} + +/* + * This function sets the vcm position to the value set by the user + * through v4l2_ctrl_ops s_ctrl handler + * The lens position is gradually moved in units of AK7375_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused ak7375_vcm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ak7375_device *ak7375_dev = sd_to_ak7375_vcm(sd); + int ret, val; + + if (ak7375_dev->active) + return 0; + + ret = ak7375_i2c_write(ak7375_dev, AK7375_REG_CONT, + AK7375_MODE_ACTIVE, 1); + if (ret) { + dev_err(dev, "%s I2C failure: %d\n", __func__, ret); + return ret; + } + + for (val = ak7375_dev->focus->val % AK7375_CTRL_STEPS; + val <= ak7375_dev->focus->val; + val += AK7375_CTRL_STEPS) { + ret = ak7375_i2c_write(ak7375_dev, AK7375_REG_POSITION, + val << 4, 2); + if (ret) + dev_err_ratelimited(dev, "%s I2C failure: %d\n", + __func__, ret); + usleep_range(AK7375_CTRL_DELAY_US, AK7375_CTRL_DELAY_US + 10); + } + + ak7375_dev->active = true; + + return 0; +} + +static const struct of_device_id ak7375_of_table[] = { + { .compatible = "asahi-kasei,ak7375" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ak7375_of_table); + +static const struct dev_pm_ops ak7375_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ak7375_vcm_suspend, ak7375_vcm_resume) + SET_RUNTIME_PM_OPS(ak7375_vcm_suspend, ak7375_vcm_resume, NULL) +}; + +static struct i2c_driver ak7375_i2c_driver = { + .driver = { + .name = "ak7375", + .pm = &ak7375_pm_ops, + .of_match_table = ak7375_of_table, + }, + .probe_new = ak7375_probe, + .remove = ak7375_remove, +}; +module_i2c_driver(ak7375_i2c_driver); + +MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); +MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>"); +MODULE_DESCRIPTION("AK7375 VCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ak881x.c b/drivers/media/i2c/ak881x.c new file mode 100644 index 000000000..1adaf470c --- /dev/null +++ b/drivers/media/i2c/ak881x.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for AK8813 / AK8814 TV-ecoders from Asahi Kasei Microsystems Co., Ltd. (AKM) + * + * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de> + */ + +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/module.h> + +#include <media/i2c/ak881x.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> + +#define AK881X_INTERFACE_MODE 0 +#define AK881X_VIDEO_PROCESS1 1 +#define AK881X_VIDEO_PROCESS2 2 +#define AK881X_VIDEO_PROCESS3 3 +#define AK881X_DAC_MODE 5 +#define AK881X_STATUS 0x24 +#define AK881X_DEVICE_ID 0x25 +#define AK881X_DEVICE_REVISION 0x26 + +struct ak881x { + struct v4l2_subdev subdev; + struct ak881x_pdata *pdata; + unsigned int lines; + char revision; /* DEVICE_REVISION content */ +}; + +static int reg_read(struct i2c_client *client, const u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int reg_write(struct i2c_client *client, const u8 reg, + const u8 data) +{ + return i2c_smbus_write_byte_data(client, reg, data); +} + +static int reg_set(struct i2c_client *client, const u8 reg, + const u8 data, u8 mask) +{ + int ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, (ret & ~mask) | (data & mask)); +} + +static struct ak881x *to_ak881x(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ak881x, subdev); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ak881x_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0x26) + return -EINVAL; + + reg->size = 1; + reg->val = reg_read(client, reg->reg); + + if (reg->val > 0xffff) + return -EIO; + + return 0; +} + +static int ak881x_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0x26) + return -EINVAL; + + if (reg_write(client, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static int ak881x_fill_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ak881x *ak881x = to_ak881x(client); + + if (format->pad) + return -EINVAL; + + v4l_bound_align_image(&mf->width, 0, 720, 2, + &mf->height, 0, ak881x->lines, 1, 0); + mf->field = V4L2_FIELD_INTERLACED; + mf->code = MEDIA_BUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int ak881x_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_YUYV8_2X8; + return 0; +} + +static int ak881x_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ak881x *ak881x = to_ak881x(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = 720; + sel->r.height = ak881x->lines; + return 0; + default: + return -EINVAL; + } +} + +static int ak881x_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ak881x *ak881x = to_ak881x(client); + u8 vp1; + + if (std == V4L2_STD_NTSC_443) { + vp1 = 3; + ak881x->lines = 480; + } else if (std == V4L2_STD_PAL_M) { + vp1 = 5; + ak881x->lines = 480; + } else if (std == V4L2_STD_PAL_60) { + vp1 = 7; + ak881x->lines = 480; + } else if (std & V4L2_STD_NTSC) { + vp1 = 0; + ak881x->lines = 480; + } else if (std & V4L2_STD_PAL) { + vp1 = 0xf; + ak881x->lines = 576; + } else { + /* No SECAM or PAL_N/Nc supported */ + return -EINVAL; + } + + reg_set(client, AK881X_VIDEO_PROCESS1, vp1, 0xf); + + return 0; +} + +static int ak881x_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ak881x *ak881x = to_ak881x(client); + + if (enable) { + u8 dac; + /* For colour-bar testing set bit 6 of AK881X_VIDEO_PROCESS1 */ + /* Default: composite output */ + if (ak881x->pdata->flags & AK881X_COMPONENT) + dac = 3; + else + dac = 4; + /* Turn on the DAC(s) */ + reg_write(client, AK881X_DAC_MODE, dac); + dev_dbg(&client->dev, "chip status 0x%x\n", + reg_read(client, AK881X_STATUS)); + } else { + /* ...and clear bit 6 of AK881X_VIDEO_PROCESS1 here */ + reg_write(client, AK881X_DAC_MODE, 0); + dev_dbg(&client->dev, "chip status 0x%x\n", + reg_read(client, AK881X_STATUS)); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops ak881x_subdev_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ak881x_g_register, + .s_register = ak881x_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops ak881x_subdev_video_ops = { + .s_std_output = ak881x_s_std_output, + .s_stream = ak881x_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ak881x_subdev_pad_ops = { + .enum_mbus_code = ak881x_enum_mbus_code, + .get_selection = ak881x_get_selection, + .set_fmt = ak881x_fill_fmt, + .get_fmt = ak881x_fill_fmt, +}; + +static const struct v4l2_subdev_ops ak881x_subdev_ops = { + .core = &ak881x_subdev_core_ops, + .video = &ak881x_subdev_video_ops, + .pad = &ak881x_subdev_pad_ops, +}; + +static int ak881x_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct i2c_adapter *adapter = client->adapter; + struct ak881x *ak881x; + u8 ifmode, data; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + ak881x = devm_kzalloc(&client->dev, sizeof(*ak881x), GFP_KERNEL); + if (!ak881x) + return -ENOMEM; + + v4l2_i2c_subdev_init(&ak881x->subdev, client, &ak881x_subdev_ops); + + data = reg_read(client, AK881X_DEVICE_ID); + + switch (data) { + case 0x13: + case 0x14: + break; + default: + dev_err(&client->dev, + "No ak881x chip detected, register read %x\n", data); + return -ENODEV; + } + + ak881x->revision = reg_read(client, AK881X_DEVICE_REVISION); + ak881x->pdata = client->dev.platform_data; + + if (ak881x->pdata) { + if (ak881x->pdata->flags & AK881X_FIELD) + ifmode = 4; + else + ifmode = 0; + + switch (ak881x->pdata->flags & AK881X_IF_MODE_MASK) { + case AK881X_IF_MODE_BT656: + ifmode |= 1; + break; + case AK881X_IF_MODE_MASTER: + ifmode |= 2; + break; + case AK881X_IF_MODE_SLAVE: + default: + break; + } + + dev_dbg(&client->dev, "IF mode %x\n", ifmode); + + /* + * "Line Blanking No." seems to be the same as the number of + * "black" lines on, e.g., SuperH VOU, whose default value of 20 + * "incidentally" matches ak881x' default + */ + reg_write(client, AK881X_INTERFACE_MODE, ifmode | (20 << 3)); + } + + /* Hardware default: NTSC-M */ + ak881x->lines = 480; + + dev_info(&client->dev, "Detected an ak881x chip ID %x, revision %x\n", + data, ak881x->revision); + + return 0; +} + +static int ak881x_remove(struct i2c_client *client) +{ + struct ak881x *ak881x = to_ak881x(client); + + v4l2_device_unregister_subdev(&ak881x->subdev); + + return 0; +} + +static const struct i2c_device_id ak881x_id[] = { + { "ak8813", 0 }, + { "ak8814", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak881x_id); + +static struct i2c_driver ak881x_i2c_driver = { + .driver = { + .name = "ak881x", + }, + .probe = ak881x_probe, + .remove = ak881x_remove, + .id_table = ak881x_id, +}; + +module_i2c_driver(ak881x_i2c_driver); + +MODULE_DESCRIPTION("TV-output driver for ak8813/ak8814"); +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/aptina-pll.c b/drivers/media/i2c/aptina-pll.c new file mode 100644 index 000000000..1423c04a1 --- /dev/null +++ b/drivers/media/i2c/aptina-pll.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Aptina Sensor PLL Configuration + * + * Copyright (C) 2012 Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <linux/device.h> +#include <linux/gcd.h> +#include <linux/kernel.h> +#include <linux/lcm.h> +#include <linux/module.h> + +#include "aptina-pll.h" + +int aptina_pll_calculate(struct device *dev, + const struct aptina_pll_limits *limits, + struct aptina_pll *pll) +{ + unsigned int mf_min; + unsigned int mf_max; + unsigned int p1_min; + unsigned int p1_max; + unsigned int p1; + unsigned int div; + + dev_dbg(dev, "PLL: ext clock %u pix clock %u\n", + pll->ext_clock, pll->pix_clock); + + if (pll->ext_clock < limits->ext_clock_min || + pll->ext_clock > limits->ext_clock_max) { + dev_err(dev, "pll: invalid external clock frequency.\n"); + return -EINVAL; + } + + if (pll->pix_clock == 0 || pll->pix_clock > limits->pix_clock_max) { + dev_err(dev, "pll: invalid pixel clock frequency.\n"); + return -EINVAL; + } + + /* Compute the multiplier M and combined N*P1 divisor. */ + div = gcd(pll->pix_clock, pll->ext_clock); + pll->m = pll->pix_clock / div; + div = pll->ext_clock / div; + + /* We now have the smallest M and N*P1 values that will result in the + * desired pixel clock frequency, but they might be out of the valid + * range. Compute the factor by which we should multiply them given the + * following constraints: + * + * - minimum/maximum multiplier + * - minimum/maximum multiplier output clock frequency assuming the + * minimum/maximum N value + * - minimum/maximum combined N*P1 divisor + */ + mf_min = DIV_ROUND_UP(limits->m_min, pll->m); + mf_min = max(mf_min, limits->out_clock_min / + (pll->ext_clock / limits->n_min * pll->m)); + mf_min = max(mf_min, limits->n_min * limits->p1_min / div); + mf_max = limits->m_max / pll->m; + mf_max = min(mf_max, limits->out_clock_max / + (pll->ext_clock / limits->n_max * pll->m)); + mf_max = min(mf_max, DIV_ROUND_UP(limits->n_max * limits->p1_max, div)); + + dev_dbg(dev, "pll: mf min %u max %u\n", mf_min, mf_max); + if (mf_min > mf_max) { + dev_err(dev, "pll: no valid combined N*P1 divisor.\n"); + return -EINVAL; + } + + /* + * We're looking for the highest acceptable P1 value for which a + * multiplier factor MF exists that fulfills the following conditions: + * + * 1. p1 is in the [p1_min, p1_max] range given by the limits and is + * even + * 2. mf is in the [mf_min, mf_max] range computed above + * 3. div * mf is a multiple of p1, in order to compute + * n = div * mf / p1 + * m = pll->m * mf + * 4. the internal clock frequency, given by ext_clock / n, is in the + * [int_clock_min, int_clock_max] range given by the limits + * 5. the output clock frequency, given by ext_clock / n * m, is in the + * [out_clock_min, out_clock_max] range given by the limits + * + * The first naive approach is to iterate over all p1 values acceptable + * according to (1) and all mf values acceptable according to (2), and + * stop at the first combination that fulfills (3), (4) and (5). This + * has a O(n^2) complexity. + * + * Instead of iterating over all mf values in the [mf_min, mf_max] range + * we can compute the mf increment between two acceptable values + * according to (3) with + * + * mf_inc = p1 / gcd(div, p1) (6) + * + * and round the minimum up to the nearest multiple of mf_inc. This will + * restrict the number of mf values to be checked. + * + * Furthermore, conditions (4) and (5) only restrict the range of + * acceptable p1 and mf values by modifying the minimum and maximum + * limits. (5) can be expressed as + * + * ext_clock / (div * mf / p1) * m * mf >= out_clock_min + * ext_clock / (div * mf / p1) * m * mf <= out_clock_max + * + * or + * + * p1 >= out_clock_min * div / (ext_clock * m) (7) + * p1 <= out_clock_max * div / (ext_clock * m) + * + * Similarly, (4) can be expressed as + * + * mf >= ext_clock * p1 / (int_clock_max * div) (8) + * mf <= ext_clock * p1 / (int_clock_min * div) + * + * We can thus iterate over the restricted p1 range defined by the + * combination of (1) and (7), and then compute the restricted mf range + * defined by the combination of (2), (6) and (8). If the resulting mf + * range is not empty, any value in the mf range is acceptable. We thus + * select the mf lwoer bound and the corresponding p1 value. + */ + if (limits->p1_min == 0) { + dev_err(dev, "pll: P1 minimum value must be >0.\n"); + return -EINVAL; + } + + p1_min = max(limits->p1_min, DIV_ROUND_UP(limits->out_clock_min * div, + pll->ext_clock * pll->m)); + p1_max = min(limits->p1_max, limits->out_clock_max * div / + (pll->ext_clock * pll->m)); + + for (p1 = p1_max & ~1; p1 >= p1_min; p1 -= 2) { + unsigned int mf_inc = p1 / gcd(div, p1); + unsigned int mf_high; + unsigned int mf_low; + + mf_low = roundup(max(mf_min, DIV_ROUND_UP(pll->ext_clock * p1, + limits->int_clock_max * div)), mf_inc); + mf_high = min(mf_max, pll->ext_clock * p1 / + (limits->int_clock_min * div)); + + if (mf_low > mf_high) + continue; + + pll->n = div * mf_low / p1; + pll->m *= mf_low; + pll->p1 = p1; + dev_dbg(dev, "PLL: N %u M %u P1 %u\n", pll->n, pll->m, pll->p1); + return 0; + } + + dev_err(dev, "pll: no valid N and P1 divisors found.\n"); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(aptina_pll_calculate); + +MODULE_DESCRIPTION("Aptina PLL Helpers"); +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/aptina-pll.h b/drivers/media/i2c/aptina-pll.h new file mode 100644 index 000000000..54c0e185a --- /dev/null +++ b/drivers/media/i2c/aptina-pll.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Aptina Sensor PLL Configuration + * + * Copyright (C) 2012 Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#ifndef __APTINA_PLL_H +#define __APTINA_PLL_H + +struct aptina_pll { + unsigned int ext_clock; + unsigned int pix_clock; + + unsigned int n; + unsigned int m; + unsigned int p1; +}; + +struct aptina_pll_limits { + unsigned int ext_clock_min; + unsigned int ext_clock_max; + unsigned int int_clock_min; + unsigned int int_clock_max; + unsigned int out_clock_min; + unsigned int out_clock_max; + unsigned int pix_clock_max; + + unsigned int n_min; + unsigned int n_max; + unsigned int m_min; + unsigned int m_max; + unsigned int p1_min; + unsigned int p1_max; +}; + +struct device; + +int aptina_pll_calculate(struct device *dev, + const struct aptina_pll_limits *limits, + struct aptina_pll *pll); + +#endif /* __APTINA_PLL_H */ diff --git a/drivers/media/i2c/bt819.c b/drivers/media/i2c/bt819.c new file mode 100644 index 000000000..73bc50c91 --- /dev/null +++ b/drivers/media/i2c/bt819.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * bt819 - BT819A VideoStream Decoder (Rockwell Part) + * + * Copyright (C) 1999 Mike Bernson <mike@mlb.org> + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Modifications for LML33/DC10plus unified driver + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + * + * This code was modify/ported from the saa7111 driver written + * by Dave Perks. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/i2c/bt819.h> + +MODULE_DESCRIPTION("Brooktree-819 video decoder driver"); +MODULE_AUTHOR("Mike Bernson & Dave Perks"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* ----------------------------------------------------------------------- */ + +struct bt819 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + unsigned char reg[32]; + + v4l2_std_id norm; + int input; + int enable; +}; + +static inline struct bt819 *to_bt819(struct v4l2_subdev *sd) +{ + return container_of(sd, struct bt819, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct bt819, hdl)->sd; +} + +struct timing { + int hactive; + int hdelay; + int vactive; + int vdelay; + int hscale; + int vscale; +}; + +/* for values, see the bt819 datasheet */ +static struct timing timing_data[] = { + {864 - 24, 20, 625 - 2, 1, 0x0504, 0x0000}, + {858 - 24, 20, 525 - 2, 1, 0x00f8, 0x0000}, +}; + +/* ----------------------------------------------------------------------- */ + +static inline int bt819_write(struct bt819 *decoder, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(&decoder->sd); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int bt819_setbit(struct bt819 *decoder, u8 reg, u8 bit, u8 value) +{ + return bt819_write(decoder, reg, + (decoder->reg[reg] & ~(1 << bit)) | (value ? (1 << bit) : 0)); +} + +static int bt819_write_block(struct bt819 *decoder, const u8 *data, unsigned int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&decoder->sd); + int ret = -1; + u8 reg; + + /* the bt819 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = + decoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && block_len < 32); + ret = i2c_master_send(client, block_data, block_len); + if (ret < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + ret = bt819_write(decoder, reg, *data++); + if (ret < 0) + break; + len -= 2; + } + } + + return ret; +} + +static inline int bt819_read(struct bt819 *decoder, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(&decoder->sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int bt819_init(struct v4l2_subdev *sd) +{ + static unsigned char init[] = { + /*0x1f, 0x00,*/ /* Reset */ + 0x01, 0x59, /* 0x01 input format */ + 0x02, 0x00, /* 0x02 temporal decimation */ + 0x03, 0x12, /* 0x03 Cropping msb */ + 0x04, 0x16, /* 0x04 Vertical Delay, lsb */ + 0x05, 0xe0, /* 0x05 Vertical Active lsb */ + 0x06, 0x80, /* 0x06 Horizontal Delay lsb */ + 0x07, 0xd0, /* 0x07 Horizontal Active lsb */ + 0x08, 0x00, /* 0x08 Horizontal Scaling msb */ + 0x09, 0xf8, /* 0x09 Horizontal Scaling lsb */ + 0x0a, 0x00, /* 0x0a Brightness control */ + 0x0b, 0x30, /* 0x0b Miscellaneous control */ + 0x0c, 0xd8, /* 0x0c Luma Gain lsb */ + 0x0d, 0xfe, /* 0x0d Chroma Gain (U) lsb */ + 0x0e, 0xb4, /* 0x0e Chroma Gain (V) msb */ + 0x0f, 0x00, /* 0x0f Hue control */ + 0x12, 0x04, /* 0x12 Output Format */ + 0x13, 0x20, /* 0x13 Vertical Scaling msb 0x00 + chroma comb OFF, line drop scaling, interlace scaling + BUG? Why does turning the chroma comb on screw up color? + Bug in the bt819 stepping on my board? + */ + 0x14, 0x00, /* 0x14 Vertical Scaling lsb */ + 0x16, 0x07, /* 0x16 Video Timing Polarity + ACTIVE=active low + FIELD: high=odd, + vreset=active high, + hreset=active high */ + 0x18, 0x68, /* 0x18 AGC Delay */ + 0x19, 0x5d, /* 0x19 Burst Gate Delay */ + 0x1a, 0x80, /* 0x1a ADC Interface */ + }; + + struct bt819 *decoder = to_bt819(sd); + struct timing *timing = &timing_data[(decoder->norm & V4L2_STD_525_60) ? 1 : 0]; + + init[0x03 * 2 - 1] = + (((timing->vdelay >> 8) & 0x03) << 6) | + (((timing->vactive >> 8) & 0x03) << 4) | + (((timing->hdelay >> 8) & 0x03) << 2) | + ((timing->hactive >> 8) & 0x03); + init[0x04 * 2 - 1] = timing->vdelay & 0xff; + init[0x05 * 2 - 1] = timing->vactive & 0xff; + init[0x06 * 2 - 1] = timing->hdelay & 0xff; + init[0x07 * 2 - 1] = timing->hactive & 0xff; + init[0x08 * 2 - 1] = timing->hscale >> 8; + init[0x09 * 2 - 1] = timing->hscale & 0xff; + /* 0x15 in array is address 0x19 */ + init[0x15 * 2 - 1] = (decoder->norm & V4L2_STD_625_50) ? 115 : 93; /* Chroma burst delay */ + /* reset */ + bt819_write(decoder, 0x1f, 0x00); + mdelay(1); + + /* init */ + return bt819_write_block(decoder, init, sizeof(init)); +} + +/* ----------------------------------------------------------------------- */ + +static int bt819_status(struct v4l2_subdev *sd, u32 *pstatus, v4l2_std_id *pstd) +{ + struct bt819 *decoder = to_bt819(sd); + int status = bt819_read(decoder, 0x00); + int res = V4L2_IN_ST_NO_SIGNAL; + v4l2_std_id std = pstd ? *pstd : V4L2_STD_ALL; + + if ((status & 0x80)) + res = 0; + else + std = V4L2_STD_UNKNOWN; + + if ((status & 0x10)) + std &= V4L2_STD_PAL; + else + std &= V4L2_STD_NTSC; + if (pstd) + *pstd = std; + if (pstatus) + *pstatus = res; + + v4l2_dbg(1, debug, sd, "get status %x\n", status); + return 0; +} + +static int bt819_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + return bt819_status(sd, NULL, std); +} + +static int bt819_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + return bt819_status(sd, status, NULL); +} + +static int bt819_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct bt819 *decoder = to_bt819(sd); + struct timing *timing = NULL; + + v4l2_dbg(1, debug, sd, "set norm %llx\n", (unsigned long long)std); + + if (sd->v4l2_dev == NULL || sd->v4l2_dev->notify == NULL) + v4l2_err(sd, "no notify found!\n"); + + if (std & V4L2_STD_NTSC) { + v4l2_subdev_notify(sd, BT819_FIFO_RESET_LOW, NULL); + bt819_setbit(decoder, 0x01, 0, 1); + bt819_setbit(decoder, 0x01, 1, 0); + bt819_setbit(decoder, 0x01, 5, 0); + bt819_write(decoder, 0x18, 0x68); + bt819_write(decoder, 0x19, 0x5d); + /* bt819_setbit(decoder, 0x1a, 5, 1); */ + timing = &timing_data[1]; + } else if (std & V4L2_STD_PAL) { + v4l2_subdev_notify(sd, BT819_FIFO_RESET_LOW, NULL); + bt819_setbit(decoder, 0x01, 0, 1); + bt819_setbit(decoder, 0x01, 1, 1); + bt819_setbit(decoder, 0x01, 5, 1); + bt819_write(decoder, 0x18, 0x7f); + bt819_write(decoder, 0x19, 0x72); + /* bt819_setbit(decoder, 0x1a, 5, 0); */ + timing = &timing_data[0]; + } else { + v4l2_dbg(1, debug, sd, "unsupported norm %llx\n", + (unsigned long long)std); + return -EINVAL; + } + bt819_write(decoder, 0x03, + (((timing->vdelay >> 8) & 0x03) << 6) | + (((timing->vactive >> 8) & 0x03) << 4) | + (((timing->hdelay >> 8) & 0x03) << 2) | + ((timing->hactive >> 8) & 0x03)); + bt819_write(decoder, 0x04, timing->vdelay & 0xff); + bt819_write(decoder, 0x05, timing->vactive & 0xff); + bt819_write(decoder, 0x06, timing->hdelay & 0xff); + bt819_write(decoder, 0x07, timing->hactive & 0xff); + bt819_write(decoder, 0x08, (timing->hscale >> 8) & 0xff); + bt819_write(decoder, 0x09, timing->hscale & 0xff); + decoder->norm = std; + v4l2_subdev_notify(sd, BT819_FIFO_RESET_HIGH, NULL); + return 0; +} + +static int bt819_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct bt819 *decoder = to_bt819(sd); + + v4l2_dbg(1, debug, sd, "set input %x\n", input); + + if (input > 7) + return -EINVAL; + + if (sd->v4l2_dev == NULL || sd->v4l2_dev->notify == NULL) + v4l2_err(sd, "no notify found!\n"); + + if (decoder->input != input) { + v4l2_subdev_notify(sd, BT819_FIFO_RESET_LOW, NULL); + decoder->input = input; + /* select mode */ + if (decoder->input == 0) { + bt819_setbit(decoder, 0x0b, 6, 0); + bt819_setbit(decoder, 0x1a, 1, 1); + } else { + bt819_setbit(decoder, 0x0b, 6, 1); + bt819_setbit(decoder, 0x1a, 1, 0); + } + v4l2_subdev_notify(sd, BT819_FIFO_RESET_HIGH, NULL); + } + return 0; +} + +static int bt819_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct bt819 *decoder = to_bt819(sd); + + v4l2_dbg(1, debug, sd, "enable output %x\n", enable); + + if (decoder->enable != enable) { + decoder->enable = enable; + bt819_setbit(decoder, 0x16, 7, !enable); + } + return 0; +} + +static int bt819_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct bt819 *decoder = to_bt819(sd); + int temp; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + bt819_write(decoder, 0x0a, ctrl->val); + break; + + case V4L2_CID_CONTRAST: + bt819_write(decoder, 0x0c, ctrl->val & 0xff); + bt819_setbit(decoder, 0x0b, 2, ((ctrl->val >> 8) & 0x01)); + break; + + case V4L2_CID_SATURATION: + bt819_write(decoder, 0x0d, (ctrl->val >> 7) & 0xff); + bt819_setbit(decoder, 0x0b, 1, ((ctrl->val >> 15) & 0x01)); + + /* Ratio between U gain and V gain must stay the same as + the ratio between the default U and V gain values. */ + temp = (ctrl->val * 180) / 254; + bt819_write(decoder, 0x0e, (temp >> 7) & 0xff); + bt819_setbit(decoder, 0x0b, 0, (temp >> 15) & 0x01); + break; + + case V4L2_CID_HUE: + bt819_write(decoder, 0x0f, ctrl->val); + break; + + default: + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops bt819_ctrl_ops = { + .s_ctrl = bt819_s_ctrl, +}; + +static const struct v4l2_subdev_video_ops bt819_video_ops = { + .s_std = bt819_s_std, + .s_routing = bt819_s_routing, + .s_stream = bt819_s_stream, + .querystd = bt819_querystd, + .g_input_status = bt819_g_input_status, +}; + +static const struct v4l2_subdev_ops bt819_ops = { + .video = &bt819_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int bt819_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i, ver; + struct bt819 *decoder; + struct v4l2_subdev *sd; + const char *name; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + decoder = devm_kzalloc(&client->dev, sizeof(*decoder), GFP_KERNEL); + if (decoder == NULL) + return -ENOMEM; + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &bt819_ops); + + ver = bt819_read(decoder, 0x17); + switch (ver & 0xf0) { + case 0x70: + name = "bt819a"; + break; + case 0x60: + name = "bt817a"; + break; + case 0x20: + name = "bt815a"; + break; + default: + v4l2_dbg(1, debug, sd, + "unknown chip version 0x%02x\n", ver); + return -ENODEV; + } + + v4l_info(client, "%s found @ 0x%x (%s)\n", name, + client->addr << 1, client->adapter->name); + + decoder->norm = V4L2_STD_NTSC; + decoder->input = 0; + decoder->enable = 1; + + i = bt819_init(sd); + if (i < 0) + v4l2_dbg(1, debug, sd, "init status %d\n", i); + + v4l2_ctrl_handler_init(&decoder->hdl, 4); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_CONTRAST, 0, 511, 1, 0xd8); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_SATURATION, 0, 511, 1, 0xfe); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + sd->ctrl_handler = &decoder->hdl; + if (decoder->hdl.error) { + int err = decoder->hdl.error; + + v4l2_ctrl_handler_free(&decoder->hdl); + return err; + } + v4l2_ctrl_handler_setup(&decoder->hdl); + return 0; +} + +static int bt819_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct bt819 *decoder = to_bt819(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&decoder->hdl); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id bt819_id[] = { + { "bt819a", 0 }, + { "bt817a", 0 }, + { "bt815a", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bt819_id); + +static struct i2c_driver bt819_driver = { + .driver = { + .name = "bt819", + }, + .probe = bt819_probe, + .remove = bt819_remove, + .id_table = bt819_id, +}; + +module_i2c_driver(bt819_driver); diff --git a/drivers/media/i2c/bt856.c b/drivers/media/i2c/bt856.c new file mode 100644 index 000000000..c134fda27 --- /dev/null +++ b/drivers/media/i2c/bt856.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * bt856 - BT856A Digital Video Encoder (Rockwell Part) + * + * Copyright (C) 1999 Mike Bernson <mike@mlb.org> + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Modifications for LML33/DC10plus unified driver + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * This code was modify/ported from the saa7111 driver written + * by Dave Perks. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> + +MODULE_DESCRIPTION("Brooktree-856A video encoder driver"); +MODULE_AUTHOR("Mike Bernson & Dave Perks"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* ----------------------------------------------------------------------- */ + +#define BT856_REG_OFFSET 0xDA +#define BT856_NR_REG 6 + +struct bt856 { + struct v4l2_subdev sd; + unsigned char reg[BT856_NR_REG]; + + v4l2_std_id norm; +}; + +static inline struct bt856 *to_bt856(struct v4l2_subdev *sd) +{ + return container_of(sd, struct bt856, sd); +} + +/* ----------------------------------------------------------------------- */ + +static inline int bt856_write(struct bt856 *encoder, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(&encoder->sd); + + encoder->reg[reg - BT856_REG_OFFSET] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int bt856_setbit(struct bt856 *encoder, u8 reg, u8 bit, u8 value) +{ + return bt856_write(encoder, reg, + (encoder->reg[reg - BT856_REG_OFFSET] & ~(1 << bit)) | + (value ? (1 << bit) : 0)); +} + +static void bt856_dump(struct bt856 *encoder) +{ + int i; + + v4l2_info(&encoder->sd, "register dump:\n"); + for (i = 0; i < BT856_NR_REG; i += 2) + printk(KERN_CONT " %02x", encoder->reg[i]); + printk(KERN_CONT "\n"); +} + +/* ----------------------------------------------------------------------- */ + +static int bt856_init(struct v4l2_subdev *sd, u32 arg) +{ + struct bt856 *encoder = to_bt856(sd); + + /* This is just for testing!!! */ + v4l2_dbg(1, debug, sd, "init\n"); + bt856_write(encoder, 0xdc, 0x18); + bt856_write(encoder, 0xda, 0); + bt856_write(encoder, 0xde, 0); + + bt856_setbit(encoder, 0xdc, 3, 1); + /*bt856_setbit(encoder, 0xdc, 6, 0);*/ + bt856_setbit(encoder, 0xdc, 4, 1); + + if (encoder->norm & V4L2_STD_NTSC) + bt856_setbit(encoder, 0xdc, 2, 0); + else + bt856_setbit(encoder, 0xdc, 2, 1); + + bt856_setbit(encoder, 0xdc, 1, 1); + bt856_setbit(encoder, 0xde, 4, 0); + bt856_setbit(encoder, 0xde, 3, 1); + if (debug != 0) + bt856_dump(encoder); + return 0; +} + +static int bt856_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct bt856 *encoder = to_bt856(sd); + + v4l2_dbg(1, debug, sd, "set norm %llx\n", (unsigned long long)std); + + if (std & V4L2_STD_NTSC) { + bt856_setbit(encoder, 0xdc, 2, 0); + } else if (std & V4L2_STD_PAL) { + bt856_setbit(encoder, 0xdc, 2, 1); + bt856_setbit(encoder, 0xda, 0, 0); + /*bt856_setbit(encoder, 0xda, 0, 1);*/ + } else { + return -EINVAL; + } + encoder->norm = std; + if (debug != 0) + bt856_dump(encoder); + return 0; +} + +static int bt856_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct bt856 *encoder = to_bt856(sd); + + v4l2_dbg(1, debug, sd, "set input %d\n", input); + + /* We only have video bus. + * input= 0: input is from bt819 + * input= 1: input is from ZR36060 */ + switch (input) { + case 0: + bt856_setbit(encoder, 0xde, 4, 0); + bt856_setbit(encoder, 0xde, 3, 1); + bt856_setbit(encoder, 0xdc, 3, 1); + bt856_setbit(encoder, 0xdc, 6, 0); + break; + case 1: + bt856_setbit(encoder, 0xde, 4, 0); + bt856_setbit(encoder, 0xde, 3, 1); + bt856_setbit(encoder, 0xdc, 3, 1); + bt856_setbit(encoder, 0xdc, 6, 1); + break; + case 2: /* Color bar */ + bt856_setbit(encoder, 0xdc, 3, 0); + bt856_setbit(encoder, 0xde, 4, 1); + break; + default: + return -EINVAL; + } + + if (debug != 0) + bt856_dump(encoder); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops bt856_core_ops = { + .init = bt856_init, +}; + +static const struct v4l2_subdev_video_ops bt856_video_ops = { + .s_std_output = bt856_s_std_output, + .s_routing = bt856_s_routing, +}; + +static const struct v4l2_subdev_ops bt856_ops = { + .core = &bt856_core_ops, + .video = &bt856_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int bt856_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bt856 *encoder; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + encoder = devm_kzalloc(&client->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + sd = &encoder->sd; + v4l2_i2c_subdev_init(sd, client, &bt856_ops); + encoder->norm = V4L2_STD_NTSC; + + bt856_write(encoder, 0xdc, 0x18); + bt856_write(encoder, 0xda, 0); + bt856_write(encoder, 0xde, 0); + + bt856_setbit(encoder, 0xdc, 3, 1); + /*bt856_setbit(encoder, 0xdc, 6, 0);*/ + bt856_setbit(encoder, 0xdc, 4, 1); + + if (encoder->norm & V4L2_STD_NTSC) + bt856_setbit(encoder, 0xdc, 2, 0); + else + bt856_setbit(encoder, 0xdc, 2, 1); + + bt856_setbit(encoder, 0xdc, 1, 1); + bt856_setbit(encoder, 0xde, 4, 0); + bt856_setbit(encoder, 0xde, 3, 1); + + if (debug != 0) + bt856_dump(encoder); + return 0; +} + +static int bt856_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +static const struct i2c_device_id bt856_id[] = { + { "bt856", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bt856_id); + +static struct i2c_driver bt856_driver = { + .driver = { + .name = "bt856", + }, + .probe = bt856_probe, + .remove = bt856_remove, + .id_table = bt856_id, +}; + +module_i2c_driver(bt856_driver); diff --git a/drivers/media/i2c/bt866.c b/drivers/media/i2c/bt866.c new file mode 100644 index 000000000..1a8df9f18 --- /dev/null +++ b/drivers/media/i2c/bt866.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + bt866 - BT866 Digital Video Encoder (Rockwell Part) + + Copyright (C) 1999 Mike Bernson <mike@mlb.org> + Copyright (C) 1998 Dave Perks <dperks@ibm.net> + + Modifications for LML33/DC10plus unified driver + Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + + This code was modify/ported from the saa7111 driver written + by Dave Perks. + + This code was adapted for the bt866 by Christer Weinigel and ported + to 2.6 by Martin Samuelsson. + +*/ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> + +MODULE_DESCRIPTION("Brooktree-866 video encoder driver"); +MODULE_AUTHOR("Mike Bernson & Dave Perks"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* ----------------------------------------------------------------------- */ + +struct bt866 { + struct v4l2_subdev sd; + u8 reg[256]; +}; + +static inline struct bt866 *to_bt866(struct v4l2_subdev *sd) +{ + return container_of(sd, struct bt866, sd); +} + +static int bt866_write(struct bt866 *encoder, u8 subaddr, u8 data) +{ + struct i2c_client *client = v4l2_get_subdevdata(&encoder->sd); + u8 buffer[2]; + int err; + + buffer[0] = subaddr; + buffer[1] = data; + + encoder->reg[subaddr] = data; + + v4l_dbg(1, debug, client, "write 0x%02x = 0x%02x\n", subaddr, data); + + for (err = 0; err < 3;) { + if (i2c_master_send(client, buffer, 2) == 2) + break; + err++; + v4l_warn(client, "error #%d writing to 0x%02x\n", + err, subaddr); + schedule_timeout_interruptible(msecs_to_jiffies(100)); + } + if (err == 3) { + v4l_warn(client, "giving up\n"); + return -1; + } + + return 0; +} + +static int bt866_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + v4l2_dbg(1, debug, sd, "set norm %llx\n", (unsigned long long)std); + + /* Only PAL supported by this driver at the moment! */ + if (!(std & V4L2_STD_NTSC)) + return -EINVAL; + return 0; +} + +static int bt866_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + static const __u8 init[] = { + 0xc8, 0xcc, /* CRSCALE */ + 0xca, 0x91, /* CBSCALE */ + 0xcc, 0x24, /* YC16 | OSDNUM */ + 0xda, 0x00, /* */ + 0xdc, 0x24, /* SETMODE | PAL */ + 0xde, 0x02, /* EACTIVE */ + + /* overlay colors */ + 0x70, 0xEB, 0x90, 0x80, 0xB0, 0x80, /* white */ + 0x72, 0xA2, 0x92, 0x8E, 0xB2, 0x2C, /* yellow */ + 0x74, 0x83, 0x94, 0x2C, 0xB4, 0x9C, /* cyan */ + 0x76, 0x70, 0x96, 0x3A, 0xB6, 0x48, /* green */ + 0x78, 0x54, 0x98, 0xC6, 0xB8, 0xB8, /* magenta */ + 0x7A, 0x41, 0x9A, 0xD4, 0xBA, 0x64, /* red */ + 0x7C, 0x23, 0x9C, 0x72, 0xBC, 0xD4, /* blue */ + 0x7E, 0x10, 0x9E, 0x80, 0xBE, 0x80, /* black */ + + 0x60, 0xEB, 0x80, 0x80, 0xc0, 0x80, /* white */ + 0x62, 0xA2, 0x82, 0x8E, 0xc2, 0x2C, /* yellow */ + 0x64, 0x83, 0x84, 0x2C, 0xc4, 0x9C, /* cyan */ + 0x66, 0x70, 0x86, 0x3A, 0xc6, 0x48, /* green */ + 0x68, 0x54, 0x88, 0xC6, 0xc8, 0xB8, /* magenta */ + 0x6A, 0x41, 0x8A, 0xD4, 0xcA, 0x64, /* red */ + 0x6C, 0x23, 0x8C, 0x72, 0xcC, 0xD4, /* blue */ + 0x6E, 0x10, 0x8E, 0x80, 0xcE, 0x80, /* black */ + }; + struct bt866 *encoder = to_bt866(sd); + u8 val; + int i; + + for (i = 0; i < ARRAY_SIZE(init) / 2; i += 2) + bt866_write(encoder, init[i], init[i+1]); + + val = encoder->reg[0xdc]; + + if (input == 0) + val |= 0x40; /* CBSWAP */ + else + val &= ~0x40; /* !CBSWAP */ + + bt866_write(encoder, 0xdc, val); + + val = encoder->reg[0xcc]; + if (input == 2) + val |= 0x01; /* OSDBAR */ + else + val &= ~0x01; /* !OSDBAR */ + bt866_write(encoder, 0xcc, val); + + v4l2_dbg(1, debug, sd, "set input %d\n", input); + + switch (input) { + case 0: + case 1: + case 2: + break; + default: + return -EINVAL; + } + return 0; +} + +#if 0 +/* Code to setup square pixels, might be of some use in the future, + but is currently unused. */ + val = encoder->reg[0xdc]; + if (*iarg) + val |= 1; /* SQUARE */ + else + val &= ~1; /* !SQUARE */ + bt866_write(client, 0xdc, val); +#endif + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_video_ops bt866_video_ops = { + .s_std_output = bt866_s_std_output, + .s_routing = bt866_s_routing, +}; + +static const struct v4l2_subdev_ops bt866_ops = { + .video = &bt866_video_ops, +}; + +static int bt866_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bt866 *encoder; + struct v4l2_subdev *sd; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + encoder = devm_kzalloc(&client->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + sd = &encoder->sd; + v4l2_i2c_subdev_init(sd, client, &bt866_ops); + return 0; +} + +static int bt866_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +static const struct i2c_device_id bt866_id[] = { + { "bt866", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bt866_id); + +static struct i2c_driver bt866_driver = { + .driver = { + .name = "bt866", + }, + .probe = bt866_probe, + .remove = bt866_remove, + .id_table = bt866_id, +}; + +module_i2c_driver(bt866_driver); diff --git a/drivers/media/i2c/cs3308.c b/drivers/media/i2c/cs3308.c new file mode 100644 index 000000000..ebe55e261 --- /dev/null +++ b/drivers/media/i2c/cs3308.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Cirrus Logic cs3308 8-Channel Analog Volume Control + * + * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> + * Copyright (C) 2012 Steven Toth <stoth@kernellabs.com> + * + * Derived from cs5345.c Copyright (C) 2007 Hans Verkuil + */ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> + +MODULE_DESCRIPTION("i2c device driver for cs3308 8-channel volume control"); +MODULE_AUTHOR("Devin Heitmueller"); +MODULE_LICENSE("GPL"); + +static inline int cs3308_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int cs3308_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cs3308_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->val = cs3308_read(sd, reg->reg & 0xffff); + reg->size = 1; + return 0; +} + +static int cs3308_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + cs3308_write(sd, reg->reg & 0xffff, reg->val & 0xff); + return 0; +} +#endif + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops cs3308_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = cs3308_g_register, + .s_register = cs3308_s_register, +#endif +}; + +static const struct v4l2_subdev_ops cs3308_ops = { + .core = &cs3308_core_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int cs3308_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct v4l2_subdev *sd; + unsigned i; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + if ((i2c_smbus_read_byte_data(client, 0x1c) & 0xf0) != 0xe0) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + sd = kzalloc(sizeof(struct v4l2_subdev), GFP_KERNEL); + if (sd == NULL) + return -ENOMEM; + + v4l2_i2c_subdev_init(sd, client, &cs3308_ops); + + /* Set some reasonable defaults */ + cs3308_write(sd, 0x0d, 0x00); /* Power up all channels */ + cs3308_write(sd, 0x0e, 0x00); /* Master Power */ + cs3308_write(sd, 0x0b, 0x00); /* Device Configuration */ + /* Set volume for each channel */ + for (i = 1; i <= 8; i++) + cs3308_write(sd, i, 0xd2); + cs3308_write(sd, 0x0a, 0x00); /* Unmute all channels */ + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int cs3308_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + kfree(sd); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id cs3308_id[] = { + { "cs3308", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs3308_id); + +static struct i2c_driver cs3308_driver = { + .driver = { + .name = "cs3308", + }, + .probe = cs3308_probe, + .remove = cs3308_remove, + .id_table = cs3308_id, +}; + +module_i2c_driver(cs3308_driver); diff --git a/drivers/media/i2c/cs5345.c b/drivers/media/i2c/cs5345.c new file mode 100644 index 000000000..f6dd5edf7 --- /dev/null +++ b/drivers/media/i2c/cs5345.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cs5345 Cirrus Logic 24-bit, 192 kHz Stereo Audio ADC + * Copyright (C) 2007 Hans Verkuil + */ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +MODULE_DESCRIPTION("i2c device driver for cs5345 Audio ADC"); +MODULE_AUTHOR("Hans Verkuil"); +MODULE_LICENSE("GPL"); + +static bool debug; + +module_param(debug, bool, 0644); + +MODULE_PARM_DESC(debug, "Debugging messages, 0=Off (default), 1=On"); + +struct cs5345_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; +}; + +static inline struct cs5345_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct cs5345_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct cs5345_state, hdl)->sd; +} + +/* ----------------------------------------------------------------------- */ + +static inline int cs5345_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int cs5345_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int cs5345_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + if ((input & 0xf) > 6) { + v4l2_err(sd, "Invalid input %d.\n", input); + return -EINVAL; + } + cs5345_write(sd, 0x09, input & 0xf); + cs5345_write(sd, 0x05, input & 0xf0); + return 0; +} + +static int cs5345_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + cs5345_write(sd, 0x04, ctrl->val ? 0x80 : 0); + return 0; + case V4L2_CID_AUDIO_VOLUME: + cs5345_write(sd, 0x07, ((u8)ctrl->val) & 0x3f); + cs5345_write(sd, 0x08, ((u8)ctrl->val) & 0x3f); + return 0; + } + return -EINVAL; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cs5345_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->size = 1; + reg->val = cs5345_read(sd, reg->reg & 0x1f); + return 0; +} + +static int cs5345_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + cs5345_write(sd, reg->reg & 0x1f, reg->val & 0xff); + return 0; +} +#endif + +static int cs5345_log_status(struct v4l2_subdev *sd) +{ + u8 v = cs5345_read(sd, 0x09) & 7; + u8 m = cs5345_read(sd, 0x04); + int vol = cs5345_read(sd, 0x08) & 0x3f; + + v4l2_info(sd, "Input: %d%s\n", v, + (m & 0x80) ? " (muted)" : ""); + if (vol >= 32) + vol = vol - 64; + v4l2_info(sd, "Volume: %d dB\n", vol); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops cs5345_ctrl_ops = { + .s_ctrl = cs5345_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops cs5345_core_ops = { + .log_status = cs5345_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = cs5345_g_register, + .s_register = cs5345_s_register, +#endif +}; + +static const struct v4l2_subdev_audio_ops cs5345_audio_ops = { + .s_routing = cs5345_s_routing, +}; + +static const struct v4l2_subdev_ops cs5345_ops = { + .core = &cs5345_core_ops, + .audio = &cs5345_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int cs5345_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cs5345_state *state; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &cs5345_ops); + + v4l2_ctrl_handler_init(&state->hdl, 2); + v4l2_ctrl_new_std(&state->hdl, &cs5345_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &cs5345_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, -24, 24, 1, 0); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + /* set volume/mute */ + v4l2_ctrl_handler_setup(&state->hdl); + + cs5345_write(sd, 0x02, 0x00); + cs5345_write(sd, 0x04, 0x01); + cs5345_write(sd, 0x09, 0x01); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int cs5345_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct cs5345_state *state = to_state(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id cs5345_id[] = { + { "cs5345", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs5345_id); + +static struct i2c_driver cs5345_driver = { + .driver = { + .name = "cs5345", + }, + .probe = cs5345_probe, + .remove = cs5345_remove, + .id_table = cs5345_id, +}; + +module_i2c_driver(cs5345_driver); diff --git a/drivers/media/i2c/cs53l32a.c b/drivers/media/i2c/cs53l32a.c new file mode 100644 index 000000000..9a411106c --- /dev/null +++ b/drivers/media/i2c/cs53l32a.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cs53l32a (Adaptec AVC-2010 and AVC-2410) i2c ivtv driver. + * Copyright (C) 2005 Martin Vaughan + * + * Audio source switching for Adaptec AVC-2410 added by Trev Jackson + */ + + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +MODULE_DESCRIPTION("i2c device driver for cs53l32a Audio ADC"); +MODULE_AUTHOR("Martin Vaughan"); +MODULE_LICENSE("GPL"); + +static bool debug; + +module_param(debug, bool, 0644); + +MODULE_PARM_DESC(debug, "Debugging messages, 0=Off (default), 1=On"); + + +struct cs53l32a_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; +}; + +static inline struct cs53l32a_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct cs53l32a_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct cs53l32a_state, hdl)->sd; +} + +/* ----------------------------------------------------------------------- */ + +static int cs53l32a_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int cs53l32a_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int cs53l32a_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + /* There are 2 physical inputs, but the second input can be + placed in two modes, the first mode bypasses the PGA (gain), + the second goes through the PGA. Hence there are three + possible inputs to choose from. */ + if (input > 2) { + v4l2_err(sd, "Invalid input %d.\n", input); + return -EINVAL; + } + cs53l32a_write(sd, 0x01, 0x01 + (input << 4)); + return 0; +} + +static int cs53l32a_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + cs53l32a_write(sd, 0x03, ctrl->val ? 0xf0 : 0x30); + return 0; + case V4L2_CID_AUDIO_VOLUME: + cs53l32a_write(sd, 0x04, (u8)ctrl->val); + cs53l32a_write(sd, 0x05, (u8)ctrl->val); + return 0; + } + return -EINVAL; +} + +static int cs53l32a_log_status(struct v4l2_subdev *sd) +{ + struct cs53l32a_state *state = to_state(sd); + u8 v = cs53l32a_read(sd, 0x01); + + v4l2_info(sd, "Input: %d\n", (v >> 4) & 3); + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops cs53l32a_ctrl_ops = { + .s_ctrl = cs53l32a_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops cs53l32a_core_ops = { + .log_status = cs53l32a_log_status, +}; + +static const struct v4l2_subdev_audio_ops cs53l32a_audio_ops = { + .s_routing = cs53l32a_s_routing, +}; + +static const struct v4l2_subdev_ops cs53l32a_ops = { + .core = &cs53l32a_core_ops, + .audio = &cs53l32a_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ + +/* i2c implementation */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ + +static int cs53l32a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cs53l32a_state *state; + struct v4l2_subdev *sd; + int i; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + if (!id) + strscpy(client->name, "cs53l32a", sizeof(client->name)); + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &cs53l32a_ops); + + for (i = 1; i <= 7; i++) { + u8 v = cs53l32a_read(sd, i); + + v4l2_dbg(1, debug, sd, "Read Reg %d %02x\n", i, v); + } + + v4l2_ctrl_handler_init(&state->hdl, 2); + v4l2_ctrl_new_std(&state->hdl, &cs53l32a_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, -96, 12, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &cs53l32a_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + + /* Set cs53l32a internal register for Adaptec 2010/2410 setup */ + + cs53l32a_write(sd, 0x01, 0x21); + cs53l32a_write(sd, 0x02, 0x29); + cs53l32a_write(sd, 0x03, 0x30); + cs53l32a_write(sd, 0x04, 0x00); + cs53l32a_write(sd, 0x05, 0x00); + cs53l32a_write(sd, 0x06, 0x00); + cs53l32a_write(sd, 0x07, 0x00); + + /* Display results, should be 0x21,0x29,0x30,0x00,0x00,0x00,0x00 */ + + for (i = 1; i <= 7; i++) { + u8 v = cs53l32a_read(sd, i); + + v4l2_dbg(1, debug, sd, "Read Reg %d %02x\n", i, v); + } + return 0; +} + +static int cs53l32a_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct cs53l32a_state *state = to_state(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); + return 0; +} + +static const struct i2c_device_id cs53l32a_id[] = { + { "cs53l32a", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs53l32a_id); + +static struct i2c_driver cs53l32a_driver = { + .driver = { + .name = "cs53l32a", + }, + .probe = cs53l32a_probe, + .remove = cs53l32a_remove, + .id_table = cs53l32a_id, +}; + +module_i2c_driver(cs53l32a_driver); diff --git a/drivers/media/i2c/cx25840/Kconfig b/drivers/media/i2c/cx25840/Kconfig new file mode 100644 index 000000000..e392f8e02 --- /dev/null +++ b/drivers/media/i2c/cx25840/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_CX25840 + tristate "Conexant CX2584x audio/video decoders" + depends on VIDEO_V4L2 && I2C + help + Support for the Conexant CX2584x audio/video decoders. + + To compile this driver as a module, choose M here: the + module will be called cx25840 diff --git a/drivers/media/i2c/cx25840/Makefile b/drivers/media/i2c/cx25840/Makefile new file mode 100644 index 000000000..3681df295 --- /dev/null +++ b/drivers/media/i2c/cx25840/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +cx25840-objs := cx25840-core.o cx25840-audio.o cx25840-firmware.o \ + cx25840-vbi.o cx25840-ir.o + +obj-$(CONFIG_VIDEO_CX25840) += cx25840.o diff --git a/drivers/media/i2c/cx25840/cx25840-audio.c b/drivers/media/i2c/cx25840/cx25840-audio.c new file mode 100644 index 000000000..eb77ba088 --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-audio.c @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* cx25840 audio functions + */ + + +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <media/v4l2-common.h> +#include <media/drv-intf/cx25840.h> + +#include "cx25840-core.h" + +/* + * Note: The PLL and SRC parameters are based on a reference frequency that + * would ideally be: + * + * NTSC Color subcarrier freq * 8 = 4.5 MHz/286 * 455/2 * 8 = 28.63636363... MHz + * + * However, it's not the exact reference frequency that matters, only that the + * firmware and modules that comprise the driver for a particular board all + * use the same value (close to the ideal value). + * + * Comments below will note which reference frequency is assumed for various + * parameters. They will usually be one of + * + * ref_freq = 28.636360 MHz + * or + * ref_freq = 28.636363 MHz + */ + +static int cx25840_set_audclk_freq(struct i2c_client *client, u32 freq) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (state->aud_input != CX25840_AUDIO_SERIAL) { + switch (freq) { + case 32000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x06, AUX PLL Post Divider = 0x10 + */ + cx25840_write4(client, 0x108, 0x1006040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x1bb39ee + * 28636363 * 0x6.dd9cf70/0x10 = 32000 * 384 + * 196.6 MHz pre-postdivide + * FIXME < 200 MHz is out of specified valid range + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x01bb39ee); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x10 = 384/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x50); + + if (is_cx2583x(state)) + break; + + /* src3/4/6_ctl */ + /* 0x1.f77f = (4 * 28636360/8 * 2/455) / 32000 */ + cx25840_write4(client, 0x900, 0x0801f77f); + cx25840_write4(client, 0x904, 0x0801f77f); + cx25840_write4(client, 0x90c, 0x0801f77f); + break; + + case 44100: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x09, AUX PLL Post Divider = 0x10 + */ + cx25840_write4(client, 0x108, 0x1009040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x0ec6bd6 + * 28636363 * 0x9.7635eb0/0x10 = 44100 * 384 + * 271 MHz pre-postdivide + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x00ec6bd6); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x10 = 384/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x50); + + if (is_cx2583x(state)) + break; + + /* src3/4/6_ctl */ + /* 0x1.6d59 = (4 * 28636360/8 * 2/455) / 44100 */ + cx25840_write4(client, 0x900, 0x08016d59); + cx25840_write4(client, 0x904, 0x08016d59); + cx25840_write4(client, 0x90c, 0x08016d59); + break; + + case 48000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x0a, AUX PLL Post Divider = 0x10 + */ + cx25840_write4(client, 0x108, 0x100a040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x098d6e5 + * 28636363 * 0xa.4c6b728/0x10 = 48000 * 384 + * 295 MHz pre-postdivide + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x0098d6e5); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x10 = 384/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x50); + + if (is_cx2583x(state)) + break; + + /* src3/4/6_ctl */ + /* 0x1.4faa = (4 * 28636360/8 * 2/455) / 48000 */ + cx25840_write4(client, 0x900, 0x08014faa); + cx25840_write4(client, 0x904, 0x08014faa); + cx25840_write4(client, 0x90c, 0x08014faa); + break; + } + } else { + switch (freq) { + case 32000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x08, AUX PLL Post Divider = 0x1e + */ + cx25840_write4(client, 0x108, 0x1e08040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x12a0869 + * 28636363 * 0x8.9504348/0x1e = 32000 * 256 + * 246 MHz pre-postdivide + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x012a0869); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x14 = 256/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x54); + + if (is_cx2583x(state)) + break; + + /* src1_ctl */ + /* 0x1.0000 = 32000/32000 */ + cx25840_write4(client, 0x8f8, 0x08010000); + + /* src3/4/6_ctl */ + /* 0x2.0000 = 2 * (32000/32000) */ + cx25840_write4(client, 0x900, 0x08020000); + cx25840_write4(client, 0x904, 0x08020000); + cx25840_write4(client, 0x90c, 0x08020000); + break; + + case 44100: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x09, AUX PLL Post Divider = 0x18 + */ + cx25840_write4(client, 0x108, 0x1809040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x0ec6bd6 + * 28636363 * 0x9.7635eb0/0x18 = 44100 * 256 + * 271 MHz pre-postdivide + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x00ec6bd6); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x10 = 256/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x50); + + if (is_cx2583x(state)) + break; + + /* src1_ctl */ + /* 0x1.60cd = 44100/32000 */ + cx25840_write4(client, 0x8f8, 0x080160cd); + + /* src3/4/6_ctl */ + /* 0x1.7385 = 2 * (32000/44100) */ + cx25840_write4(client, 0x900, 0x08017385); + cx25840_write4(client, 0x904, 0x08017385); + cx25840_write4(client, 0x90c, 0x08017385); + break; + + case 48000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x0a, AUX PLL Post Divider = 0x18 + */ + cx25840_write4(client, 0x108, 0x180a040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x098d6e5 + * 28636363 * 0xa.4c6b728/0x18 = 48000 * 256 + * 295 MHz pre-postdivide + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x0098d6e5); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x10 = 256/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x50); + + if (is_cx2583x(state)) + break; + + /* src1_ctl */ + /* 0x1.8000 = 48000/32000 */ + cx25840_write4(client, 0x8f8, 0x08018000); + + /* src3/4/6_ctl */ + /* 0x1.5555 = 2 * (32000/48000) */ + cx25840_write4(client, 0x900, 0x08015555); + cx25840_write4(client, 0x904, 0x08015555); + cx25840_write4(client, 0x90c, 0x08015555); + break; + } + } + + state->audclk_freq = freq; + + return 0; +} + +static inline int cx25836_set_audclk_freq(struct i2c_client *client, u32 freq) +{ + return cx25840_set_audclk_freq(client, freq); +} + +static int cx23885_set_audclk_freq(struct i2c_client *client, u32 freq) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (state->aud_input != CX25840_AUDIO_SERIAL) { + switch (freq) { + case 32000: + case 44100: + case 48000: + /* We don't have register values + * so avoid destroying registers. */ + /* FIXME return -EINVAL; */ + break; + } + } else { + switch (freq) { + case 32000: + case 44100: + /* We don't have register values + * so avoid destroying registers. */ + /* FIXME return -EINVAL; */ + break; + + case 48000: + /* src1_ctl */ + /* 0x1.867c = 48000 / (2 * 28636360/8 * 2/455) */ + cx25840_write4(client, 0x8f8, 0x0801867c); + + /* src3/4/6_ctl */ + /* 0x1.4faa = (4 * 28636360/8 * 2/455) / 48000 */ + cx25840_write4(client, 0x900, 0x08014faa); + cx25840_write4(client, 0x904, 0x08014faa); + cx25840_write4(client, 0x90c, 0x08014faa); + break; + } + } + + state->audclk_freq = freq; + + return 0; +} + +static int cx231xx_set_audclk_freq(struct i2c_client *client, u32 freq) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (state->aud_input != CX25840_AUDIO_SERIAL) { + switch (freq) { + case 32000: + /* src3/4/6_ctl */ + /* 0x1.f77f = (4 * 28636360/8 * 2/455) / 32000 */ + cx25840_write4(client, 0x900, 0x0801f77f); + cx25840_write4(client, 0x904, 0x0801f77f); + cx25840_write4(client, 0x90c, 0x0801f77f); + break; + + case 44100: + /* src3/4/6_ctl */ + /* 0x1.6d59 = (4 * 28636360/8 * 2/455) / 44100 */ + cx25840_write4(client, 0x900, 0x08016d59); + cx25840_write4(client, 0x904, 0x08016d59); + cx25840_write4(client, 0x90c, 0x08016d59); + break; + + case 48000: + /* src3/4/6_ctl */ + /* 0x1.4faa = (4 * 28636360/8 * 2/455) / 48000 */ + cx25840_write4(client, 0x900, 0x08014faa); + cx25840_write4(client, 0x904, 0x08014faa); + cx25840_write4(client, 0x90c, 0x08014faa); + break; + } + } else { + switch (freq) { + /* FIXME These cases make different assumptions about audclk */ + case 32000: + /* src1_ctl */ + /* 0x1.0000 = 32000/32000 */ + cx25840_write4(client, 0x8f8, 0x08010000); + + /* src3/4/6_ctl */ + /* 0x2.0000 = 2 * (32000/32000) */ + cx25840_write4(client, 0x900, 0x08020000); + cx25840_write4(client, 0x904, 0x08020000); + cx25840_write4(client, 0x90c, 0x08020000); + break; + + case 44100: + /* src1_ctl */ + /* 0x1.60cd = 44100/32000 */ + cx25840_write4(client, 0x8f8, 0x080160cd); + + /* src3/4/6_ctl */ + /* 0x1.7385 = 2 * (32000/44100) */ + cx25840_write4(client, 0x900, 0x08017385); + cx25840_write4(client, 0x904, 0x08017385); + cx25840_write4(client, 0x90c, 0x08017385); + break; + + case 48000: + /* src1_ctl */ + /* 0x1.867c = 48000 / (2 * 28636360/8 * 2/455) */ + cx25840_write4(client, 0x8f8, 0x0801867c); + + /* src3/4/6_ctl */ + /* 0x1.4faa = (4 * 28636360/8 * 2/455) / 48000 */ + cx25840_write4(client, 0x900, 0x08014faa); + cx25840_write4(client, 0x904, 0x08014faa); + cx25840_write4(client, 0x90c, 0x08014faa); + break; + } + } + + state->audclk_freq = freq; + + return 0; +} + +static int set_audclk_freq(struct i2c_client *client, u32 freq) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (freq != 32000 && freq != 44100 && freq != 48000) + return -EINVAL; + + if (is_cx231xx(state)) + return cx231xx_set_audclk_freq(client, freq); + + if (is_cx2388x(state)) + return cx23885_set_audclk_freq(client, freq); + + if (is_cx2583x(state)) + return cx25836_set_audclk_freq(client, freq); + + return cx25840_set_audclk_freq(client, freq); +} + +void cx25840_audio_set_path(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (!is_cx2583x(state)) { + /* assert soft reset */ + cx25840_and_or(client, 0x810, ~0x1, 0x01); + + /* stop microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0); + + /* Mute everything to prevent the PFFT! */ + cx25840_write(client, 0x8d3, 0x1f); + + if (state->aud_input == CX25840_AUDIO_SERIAL) { + /* Set Path1 to Serial Audio Input */ + cx25840_write4(client, 0x8d0, 0x01011012); + + /* The microcontroller should not be started for the + * non-tuner inputs: autodetection is specific for + * TV audio. */ + } else { + /* Set Path1 to Analog Demod Main Channel */ + cx25840_write4(client, 0x8d0, 0x1f063870); + } + } + + set_audclk_freq(client, state->audclk_freq); + + if (!is_cx2583x(state)) { + if (state->aud_input != CX25840_AUDIO_SERIAL) { + /* When the microcontroller detects the + * audio format, it will unmute the lines */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); + } + + /* deassert soft reset */ + cx25840_and_or(client, 0x810, ~0x1, 0x00); + + /* Ensure the controller is running when we exit */ + if (is_cx2388x(state) || is_cx231xx(state)) + cx25840_and_or(client, 0x803, ~0x10, 0x10); + } +} + +static void set_volume(struct i2c_client *client, int volume) +{ + int vol; + + /* Convert the volume to msp3400 values (0-127) */ + vol = volume >> 9; + + /* now scale it up to cx25840 values + * -114dB to -96dB maps to 0 + * this should be 19, but in my testing that was 4dB too loud */ + if (vol <= 23) { + vol = 0; + } else { + vol -= 23; + } + + /* PATH1_VOLUME */ + cx25840_write(client, 0x8d4, 228 - (vol * 2)); +} + +static void set_balance(struct i2c_client *client, int balance) +{ + int bal = balance >> 8; + if (bal > 0x80) { + /* PATH1_BAL_LEFT */ + cx25840_and_or(client, 0x8d5, 0x7f, 0x80); + /* PATH1_BAL_LEVEL */ + cx25840_and_or(client, 0x8d5, ~0x7f, bal & 0x7f); + } else { + /* PATH1_BAL_LEFT */ + cx25840_and_or(client, 0x8d5, 0x7f, 0x00); + /* PATH1_BAL_LEVEL */ + cx25840_and_or(client, 0x8d5, ~0x7f, 0x80 - bal); + } +} + +int cx25840_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + int retval; + + if (!is_cx2583x(state)) + cx25840_and_or(client, 0x810, ~0x1, 1); + if (state->aud_input != CX25840_AUDIO_SERIAL) { + cx25840_and_or(client, 0x803, ~0x10, 0); + cx25840_write(client, 0x8d3, 0x1f); + } + retval = set_audclk_freq(client, freq); + if (state->aud_input != CX25840_AUDIO_SERIAL) + cx25840_and_or(client, 0x803, ~0x10, 0x10); + if (!is_cx2583x(state)) + cx25840_and_or(client, 0x810, ~0x1, 0); + return retval; +} + +static int cx25840_audio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + if (state->mute->val) + set_volume(client, 0); + else + set_volume(client, state->volume->val); + break; + case V4L2_CID_AUDIO_BASS: + /* PATH1_EQ_BASS_VOL */ + cx25840_and_or(client, 0x8d9, ~0x3f, + 48 - (ctrl->val * 48 / 0xffff)); + break; + case V4L2_CID_AUDIO_TREBLE: + /* PATH1_EQ_TREBLE_VOL */ + cx25840_and_or(client, 0x8db, ~0x3f, + 48 - (ctrl->val * 48 / 0xffff)); + break; + case V4L2_CID_AUDIO_BALANCE: + set_balance(client, ctrl->val); + break; + default: + return -EINVAL; + } + return 0; +} + +const struct v4l2_ctrl_ops cx25840_audio_ctrl_ops = { + .s_ctrl = cx25840_audio_s_ctrl, +}; diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c new file mode 100644 index 000000000..e2e935f78 --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-core.c @@ -0,0 +1,6055 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* cx25840 - Conexant CX25840 audio/video decoder driver + * + * Copyright (C) 2004 Ulf Eklund + * + * Based on the saa7115 driver and on the first version of Chris Kennedy's + * cx25840 driver. + * + * Changes by Tyler Trafford <tatrafford@comcast.net> + * - cleanup/rewrite for V4L2 API (2005) + * + * VBI support by Hans Verkuil <hverkuil@xs4all.nl>. + * + * NTSC sliced VBI support by Christopher Neufeld <television@cneufeld.ca> + * with additional fixes by Hans Verkuil <hverkuil@xs4all.nl>. + * + * CX23885 support by Steven Toth <stoth@linuxtv.org>. + * + * CX2388[578] IRQ handling, IO Pin mux configuration and other small fixes are + * Copyright (C) 2010 Andy Walls <awalls@md.metrocast.net> + * + * CX23888 DIF support for the HVR1850 + * Copyright (C) 2011 Steven Toth <stoth@kernellabs.com> + * + * CX2584x pin to pad mapping and output format configuration support are + * Copyright (C) 2011 Maciej S. Szmigiero <mail@maciej.szmigiero.name> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/math64.h> +#include <media/v4l2-common.h> +#include <media/drv-intf/cx25840.h> + +#include "cx25840-core.h" + +MODULE_DESCRIPTION("Conexant CX25840 audio/video decoder driver"); +MODULE_AUTHOR("Ulf Eklund, Chris Kennedy, Hans Verkuil, Tyler Trafford"); +MODULE_LICENSE("GPL"); + +#define CX25840_VID_INT_STAT_REG 0x410 +#define CX25840_VID_INT_STAT_BITS 0x0000ffff +#define CX25840_VID_INT_MASK_BITS 0xffff0000 +#define CX25840_VID_INT_MASK_SHFT 16 +#define CX25840_VID_INT_MASK_REG 0x412 + +#define CX23885_AUD_MC_INT_MASK_REG 0x80c +#define CX23885_AUD_MC_INT_STAT_BITS 0xffff0000 +#define CX23885_AUD_MC_INT_CTRL_BITS 0x0000ffff +#define CX23885_AUD_MC_INT_STAT_SHFT 16 + +#define CX25840_AUD_INT_CTRL_REG 0x812 +#define CX25840_AUD_INT_STAT_REG 0x813 + +#define CX23885_PIN_CTRL_IRQ_REG 0x123 +#define CX23885_PIN_CTRL_IRQ_IR_STAT 0x40 +#define CX23885_PIN_CTRL_IRQ_AUD_STAT 0x20 +#define CX23885_PIN_CTRL_IRQ_VID_STAT 0x10 + +#define CX25840_IR_STATS_REG 0x210 +#define CX25840_IR_IRQEN_REG 0x214 + +static int cx25840_debug; + +module_param_named(debug, cx25840_debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debugging messages [0=Off (default) 1=On]"); + +/* ----------------------------------------------------------------------- */ +static void cx23888_std_setup(struct i2c_client *client); + +int cx25840_write(struct i2c_client *client, u16 addr, u8 value) +{ + u8 buffer[3]; + + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + buffer[2] = value; + return i2c_master_send(client, buffer, 3); +} + +int cx25840_write4(struct i2c_client *client, u16 addr, u32 value) +{ + u8 buffer[6]; + + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + buffer[2] = value & 0xff; + buffer[3] = (value >> 8) & 0xff; + buffer[4] = (value >> 16) & 0xff; + buffer[5] = value >> 24; + return i2c_master_send(client, buffer, 6); +} + +u8 cx25840_read(struct i2c_client *client, u16 addr) +{ + struct i2c_msg msgs[2]; + u8 tx_buf[2], rx_buf[1]; + + /* Write register address */ + tx_buf[0] = addr >> 8; + tx_buf[1] = addr & 0xff; + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (char *)tx_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 1; + msgs[1].buf = (char *)rx_buf; + + if (i2c_transfer(client->adapter, msgs, 2) < 2) + return 0; + + return rx_buf[0]; +} + +u32 cx25840_read4(struct i2c_client *client, u16 addr) +{ + struct i2c_msg msgs[2]; + u8 tx_buf[2], rx_buf[4]; + + /* Write register address */ + tx_buf[0] = addr >> 8; + tx_buf[1] = addr & 0xff; + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (char *)tx_buf; + + /* Read data from registers */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 4; + msgs[1].buf = (char *)rx_buf; + + if (i2c_transfer(client->adapter, msgs, 2) < 2) + return 0; + + return (rx_buf[3] << 24) | (rx_buf[2] << 16) | (rx_buf[1] << 8) | + rx_buf[0]; +} + +int cx25840_and_or(struct i2c_client *client, u16 addr, unsigned int and_mask, + u8 or_value) +{ + return cx25840_write(client, addr, + (cx25840_read(client, addr) & and_mask) | + or_value); +} + +int cx25840_and_or4(struct i2c_client *client, u16 addr, u32 and_mask, + u32 or_value) +{ + return cx25840_write4(client, addr, + (cx25840_read4(client, addr) & and_mask) | + or_value); +} + +/* ----------------------------------------------------------------------- */ + +static int set_input(struct i2c_client *client, + enum cx25840_video_input vid_input, + enum cx25840_audio_input aud_input); + +/* ----------------------------------------------------------------------- */ + +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 & + (BIT(V4L2_SUBDEV_IO_PIN_DISABLE) | + BIT(V4L2_SUBDEV_IO_PIN_INPUT))) { + pin_ctrl &= ~(0x1 << 25); + } else { + pin_ctrl |= (0x1 << 25); + } + if (p[i].flags & + BIT(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 & BIT(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 & BIT(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 & BIT(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 & BIT(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 & BIT(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 & BIT(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 u8 cx25840_function_to_pad(struct i2c_client *client, u8 function) +{ + if (function > CX25840_PAD_VRESET) { + v4l_err(client, "invalid function %u, assuming default\n", + (unsigned int)function); + return 0; + } + + return function; +} + +static void cx25840_set_invert(u8 *pinctrl3, u8 *voutctrl4, u8 function, + u8 pin, bool invert) +{ + switch (function) { + case CX25840_PAD_IRQ_N: + if (invert) + *pinctrl3 &= ~2; + else + *pinctrl3 |= 2; + break; + + case CX25840_PAD_ACTIVE: + if (invert) + *voutctrl4 |= BIT(2); + else + *voutctrl4 &= ~BIT(2); + break; + + case CX25840_PAD_VACTIVE: + if (invert) + *voutctrl4 |= BIT(5); + else + *voutctrl4 &= ~BIT(5); + break; + + case CX25840_PAD_CBFLAG: + if (invert) + *voutctrl4 |= BIT(4); + else + *voutctrl4 &= ~BIT(4); + break; + + case CX25840_PAD_VRESET: + if (invert) + *voutctrl4 |= BIT(0); + else + *voutctrl4 &= ~BIT(0); + break; + } + + if (function != CX25840_PAD_DEFAULT) + return; + + switch (pin) { + case CX25840_PIN_DVALID_PRGM0: + if (invert) + *voutctrl4 |= BIT(6); + else + *voutctrl4 &= ~BIT(6); + break; + + case CX25840_PIN_HRESET_PRGM2: + if (invert) + *voutctrl4 |= BIT(1); + else + *voutctrl4 &= ~BIT(1); + break; + } +} + +static int cx25840_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); + unsigned int i; + u8 pinctrl[6], pinconf[10], voutctrl4; + + for (i = 0; i < 6; i++) + pinctrl[i] = cx25840_read(client, 0x114 + i); + + for (i = 0; i < 10; i++) + pinconf[i] = cx25840_read(client, 0x11c + i); + + voutctrl4 = cx25840_read(client, 0x407); + + for (i = 0; i < n; i++) { + u8 strength = p[i].strength; + + if (strength != CX25840_PIN_DRIVE_SLOW && + strength != CX25840_PIN_DRIVE_MEDIUM && + strength != CX25840_PIN_DRIVE_FAST) { + v4l_err(client, + "invalid drive speed for pin %u (%u), assuming fast\n", + (unsigned int)p[i].pin, + (unsigned int)strength); + + strength = CX25840_PIN_DRIVE_FAST; + } + + switch (p[i].pin) { + case CX25840_PIN_DVALID_PRGM0: + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE)) + pinctrl[0] &= ~BIT(6); + else + pinctrl[0] |= BIT(6); + + pinconf[3] &= 0xf0; + pinconf[3] |= cx25840_function_to_pad(client, + p[i].function); + + cx25840_set_invert(&pinctrl[3], &voutctrl4, + p[i].function, + CX25840_PIN_DVALID_PRGM0, + p[i].flags & + BIT(V4L2_SUBDEV_IO_PIN_ACTIVE_LOW)); + + pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */ + switch (strength) { + case CX25840_PIN_DRIVE_SLOW: + pinctrl[4] |= 1 << 2; + break; + + case CX25840_PIN_DRIVE_FAST: + pinctrl[4] |= 2 << 2; + break; + } + + break; + + case CX25840_PIN_HRESET_PRGM2: + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE)) + pinctrl[1] &= ~BIT(0); + else + pinctrl[1] |= BIT(0); + + pinconf[4] &= 0xf0; + pinconf[4] |= cx25840_function_to_pad(client, + p[i].function); + + cx25840_set_invert(&pinctrl[3], &voutctrl4, + p[i].function, + CX25840_PIN_HRESET_PRGM2, + p[i].flags & + BIT(V4L2_SUBDEV_IO_PIN_ACTIVE_LOW)); + + pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */ + switch (strength) { + case CX25840_PIN_DRIVE_SLOW: + pinctrl[4] |= 1 << 2; + break; + + case CX25840_PIN_DRIVE_FAST: + pinctrl[4] |= 2 << 2; + break; + } + + break; + + case CX25840_PIN_PLL_CLK_PRGM7: + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE)) + pinctrl[2] &= ~BIT(2); + else + pinctrl[2] |= BIT(2); + + switch (p[i].function) { + case CX25840_PAD_XTI_X5_DLL: + pinconf[6] = 0; + break; + + case CX25840_PAD_AUX_PLL: + pinconf[6] = 1; + break; + + case CX25840_PAD_VID_PLL: + pinconf[6] = 5; + break; + + case CX25840_PAD_XTI: + pinconf[6] = 2; + break; + + default: + pinconf[6] = 3; + pinconf[6] |= + cx25840_function_to_pad(client, + p[i].function) + << 4; + } + + break; + + default: + v4l_err(client, "invalid or unsupported pin %u\n", + (unsigned int)p[i].pin); + break; + } + } + + cx25840_write(client, 0x407, voutctrl4); + + for (i = 0; i < 6; i++) + cx25840_write(client, 0x114 + i, pinctrl[i]); + + for (i = 0; i < 10; i++) + cx25840_write(client, 0x11c + i, pinconf[i]); + + 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); + else if (is_cx2584x(state)) + return cx25840_s_io_pin_config(sd, n, pincfg); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static void init_dll1(struct i2c_client *client) +{ + /* + * This is the Hauppauge sequence used to + * initialize the Delay Lock Loop 1 (ADC DLL). + */ + cx25840_write(client, 0x159, 0x23); + cx25840_write(client, 0x15a, 0x87); + cx25840_write(client, 0x15b, 0x06); + udelay(10); + cx25840_write(client, 0x159, 0xe1); + udelay(10); + cx25840_write(client, 0x15a, 0x86); + cx25840_write(client, 0x159, 0xe0); + cx25840_write(client, 0x159, 0xe1); + cx25840_write(client, 0x15b, 0x10); +} + +static void init_dll2(struct i2c_client *client) +{ + /* + * This is the Hauppauge sequence used to + * initialize the Delay Lock Loop 2 (ADC DLL). + */ + cx25840_write(client, 0x15d, 0xe3); + cx25840_write(client, 0x15e, 0x86); + cx25840_write(client, 0x15f, 0x06); + udelay(10); + cx25840_write(client, 0x15d, 0xe1); + cx25840_write(client, 0x15d, 0xe0); + cx25840_write(client, 0x15d, 0xe1); +} + +static void cx25836_initialize(struct i2c_client *client) +{ + /* + *reset configuration is described on page 3-77 + * of the CX25836 datasheet + */ + + /* 2. */ + cx25840_and_or(client, 0x000, ~0x01, 0x01); + cx25840_and_or(client, 0x000, ~0x01, 0x00); + /* 3a. */ + cx25840_and_or(client, 0x15a, ~0x70, 0x00); + /* 3b. */ + cx25840_and_or(client, 0x15b, ~0x1e, 0x06); + /* 3c. */ + cx25840_and_or(client, 0x159, ~0x02, 0x02); + /* 3d. */ + udelay(10); + /* 3e. */ + cx25840_and_or(client, 0x159, ~0x02, 0x00); + /* 3f. */ + cx25840_and_or(client, 0x159, ~0xc0, 0xc0); + /* 3g. */ + cx25840_and_or(client, 0x159, ~0x01, 0x00); + cx25840_and_or(client, 0x159, ~0x01, 0x01); + /* 3h. */ + cx25840_and_or(client, 0x15b, ~0x1e, 0x10); +} + +static void cx25840_work_handler(struct work_struct *work) +{ + struct cx25840_state *state = container_of(work, struct cx25840_state, fw_work); + + cx25840_loadfw(state->c); + wake_up(&state->fw_wait); +} + +#define CX25840_VCONFIG_SET_BIT(state, opt_msk, voc, idx, bit, oneval) \ + do { \ + if ((state)->vid_config & (opt_msk)) { \ + if (((state)->vid_config & (opt_msk)) == \ + (oneval)) \ + (voc)[idx] |= BIT(bit); \ + else \ + (voc)[idx] &= ~BIT(bit); \ + } \ + } while (0) + +/* apply current vconfig to hardware regs */ +static void cx25840_vconfig_apply(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u8 voutctrl[3]; + unsigned int i; + + for (i = 0; i < 3; i++) + voutctrl[i] = cx25840_read(client, 0x404 + i); + + if (state->vid_config & CX25840_VCONFIG_FMT_MASK) + voutctrl[0] &= ~3; + switch (state->vid_config & CX25840_VCONFIG_FMT_MASK) { + case CX25840_VCONFIG_FMT_BT656: + voutctrl[0] |= 1; + break; + + case CX25840_VCONFIG_FMT_VIP11: + voutctrl[0] |= 2; + break; + + case CX25840_VCONFIG_FMT_VIP2: + voutctrl[0] |= 3; + break; + + case CX25840_VCONFIG_FMT_BT601: + /* zero */ + default: + break; + } + + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_RES_MASK, voutctrl, + 0, 2, CX25840_VCONFIG_RES_10BIT); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VBIRAW_MASK, voutctrl, + 0, 3, CX25840_VCONFIG_VBIRAW_ENABLED); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ANCDATA_MASK, voutctrl, + 0, 4, CX25840_VCONFIG_ANCDATA_ENABLED); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_TASKBIT_MASK, voutctrl, + 0, 5, CX25840_VCONFIG_TASKBIT_ONE); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ACTIVE_MASK, voutctrl, + 1, 2, CX25840_VCONFIG_ACTIVE_HORIZONTAL); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VALID_MASK, voutctrl, + 1, 3, CX25840_VCONFIG_VALID_ANDACTIVE); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_HRESETW_MASK, voutctrl, + 1, 4, CX25840_VCONFIG_HRESETW_PIXCLK); + + if (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK) + voutctrl[1] &= ~(3 << 6); + switch (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK) { + case CX25840_VCONFIG_CLKGATE_VALID: + voutctrl[1] |= 2; + break; + + case CX25840_VCONFIG_CLKGATE_VALIDACTIVE: + voutctrl[1] |= 3; + break; + + case CX25840_VCONFIG_CLKGATE_NONE: + /* zero */ + default: + break; + } + + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_DCMODE_MASK, voutctrl, + 2, 0, CX25840_VCONFIG_DCMODE_BYTES); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_IDID0S_MASK, voutctrl, + 2, 1, CX25840_VCONFIG_IDID0S_LINECNT); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VIPCLAMP_MASK, voutctrl, + 2, 4, CX25840_VCONFIG_VIPCLAMP_ENABLED); + + for (i = 0; i < 3; i++) + cx25840_write(client, 0x404 + i, voutctrl[i]); +} + +static void cx25840_initialize(struct i2c_client *client) +{ + DEFINE_WAIT(wait); + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + struct workqueue_struct *q; + + /* datasheet startup in numbered steps, refer to page 3-77 */ + /* 2. */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + /* + * The default of this register should be 4, but I get 0 instead. + * Set this register to 4 manually. + */ + cx25840_write(client, 0x000, 0x04); + /* 3. */ + init_dll1(client); + init_dll2(client); + cx25840_write(client, 0x136, 0x0a); + /* 4. */ + cx25840_write(client, 0x13c, 0x01); + cx25840_write(client, 0x13c, 0x00); + /* 5. */ + /* + * Do the firmware load in a work handler to prevent. + * Otherwise the kernel is blocked waiting for the + * bit-banging i2c interface to finish uploading the + * firmware. + */ + INIT_WORK(&state->fw_work, cx25840_work_handler); + init_waitqueue_head(&state->fw_wait); + q = create_singlethread_workqueue("cx25840_fw"); + if (q) { + prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); + queue_work(q, &state->fw_work); + schedule(); + finish_wait(&state->fw_wait, &wait); + destroy_workqueue(q); + } + + /* 6. */ + cx25840_write(client, 0x115, 0x8c); + cx25840_write(client, 0x116, 0x07); + cx25840_write(client, 0x118, 0x02); + /* 7. */ + cx25840_write(client, 0x4a5, 0x80); + cx25840_write(client, 0x4a5, 0x00); + cx25840_write(client, 0x402, 0x00); + /* 8. */ + cx25840_and_or(client, 0x401, ~0x18, 0); + cx25840_and_or(client, 0x4a2, ~0x10, 0x10); + /* steps 8c and 8d are done in change_input() */ + /* 10. */ + cx25840_write(client, 0x8d3, 0x1f); + cx25840_write(client, 0x8e3, 0x03); + + cx25840_std_setup(client); + + /* trial and error says these are needed to get audio */ + cx25840_write(client, 0x914, 0xa0); + cx25840_write(client, 0x918, 0xa0); + cx25840_write(client, 0x919, 0x01); + + /* stereo preferred */ + cx25840_write(client, 0x809, 0x04); + /* AC97 shift */ + cx25840_write(client, 0x8cf, 0x0f); + + /* (re)set input */ + set_input(client, state->vid_input, state->aud_input); + + if (state->generic_mode) + cx25840_vconfig_apply(client); + + /* start microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); +} + +static void cx23885_initialize(struct i2c_client *client) +{ + DEFINE_WAIT(wait); + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u32 clk_freq = 0; + struct workqueue_struct *q; + + /* cx23885 sets hostdata to clk_freq pointer */ + if (v4l2_get_subdev_hostdata(&state->sd)) + clk_freq = *((u32 *)v4l2_get_subdev_hostdata(&state->sd)); + + /* + * Come out of digital power down + * The CX23888, at least, needs this, otherwise registers aside from + * 0x0-0x2 can't be read or written. + */ + cx25840_write(client, 0x000, 0); + + /* Internal Reset */ + cx25840_and_or(client, 0x102, ~0x01, 0x01); + cx25840_and_or(client, 0x102, ~0x01, 0x00); + + /* Stop microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + + /* DIF in reset? */ + cx25840_write(client, 0x398, 0); + + /* + * Trust the default xtal, no division + * '885: 28.636363... MHz + * '887: 25.000000 MHz + * '888: 50.000000 MHz + */ + cx25840_write(client, 0x2, 0x76); + + /* Power up all the PLL's and DLL */ + cx25840_write(client, 0x1, 0x40); + + /* Sys PLL */ + switch (state->id) { + case CX23888_AV: + /* + * 50.0 MHz * (0xb + 0xe8ba26/0x2000000)/4 = 5 * 28.636363 MHz + * 572.73 MHz before post divide + */ + if (clk_freq == 25000000) { + /* 888/ImpactVCBe or 25Mhz xtal */ + ; /* nothing to do */ + } else { + /* HVR1850 or 50MHz xtal */ + cx25840_write(client, 0x2, 0x71); + } + cx25840_write4(client, 0x11c, 0x01d1744c); + cx25840_write4(client, 0x118, 0x00000416); + cx25840_write4(client, 0x404, 0x0010253e); + cx25840_write4(client, 0x42c, 0x42600000); + cx25840_write4(client, 0x44c, 0x161f1000); + break; + case CX23887_AV: + /* + * 25.0 MHz * (0x16 + 0x1d1744c/0x2000000)/4 = 5 * 28.636363 MHz + * 572.73 MHz before post divide + */ + cx25840_write4(client, 0x11c, 0x01d1744c); + cx25840_write4(client, 0x118, 0x00000416); + break; + case CX23885_AV: + default: + /* + * 28.636363 MHz * (0x14 + 0x0/0x2000000)/4 = 5 * 28.636363 MHz + * 572.73 MHz before post divide + */ + cx25840_write4(client, 0x11c, 0x00000000); + cx25840_write4(client, 0x118, 0x00000414); + break; + } + + /* Disable DIF bypass */ + cx25840_write4(client, 0x33c, 0x00000001); + + /* DIF Src phase inc */ + cx25840_write4(client, 0x340, 0x0df7df83); + + /* + * Vid PLL + * Setup for a BT.656 pixel clock of 13.5 Mpixels/second + * + * 28.636363 MHz * (0xf + 0x02be2c9/0x2000000)/4 = 8 * 13.5 MHz + * 432.0 MHz before post divide + */ + + /* HVR1850 */ + switch (state->id) { + case CX23888_AV: + if (clk_freq == 25000000) { + /* 888/ImpactVCBe or 25MHz xtal */ + cx25840_write4(client, 0x10c, 0x01b6db7b); + cx25840_write4(client, 0x108, 0x00000512); + } else { + /* 888/HVR1250 or 50MHz xtal */ + cx25840_write4(client, 0x10c, 0x13333333); + cx25840_write4(client, 0x108, 0x00000515); + } + break; + default: + cx25840_write4(client, 0x10c, 0x002be2c9); + cx25840_write4(client, 0x108, 0x0000040f); + } + + /* Luma */ + cx25840_write4(client, 0x414, 0x00107d12); + + /* Chroma */ + if (is_cx23888(state)) + cx25840_write4(client, 0x418, 0x1d008282); + else + cx25840_write4(client, 0x420, 0x3d008282); + + /* + * Aux PLL + * Initial setup for audio sample clock: + * 48 ksps, 16 bits/sample, x160 multiplier = 122.88 MHz + * Initial I2S output/master clock(?): + * 48 ksps, 16 bits/sample, x16 multiplier = 12.288 MHz + */ + switch (state->id) { + case CX23888_AV: + /* + * 50.0 MHz * (0x7 + 0x0bedfa4/0x2000000)/3 = 122.88 MHz + * 368.64 MHz before post divide + * 122.88 MHz / 0xa = 12.288 MHz + */ + /* HVR1850 or 50MHz xtal or 25MHz xtal */ + cx25840_write4(client, 0x114, 0x017dbf48); + cx25840_write4(client, 0x110, 0x000a030e); + break; + case CX23887_AV: + /* + * 25.0 MHz * (0xe + 0x17dbf48/0x2000000)/3 = 122.88 MHz + * 368.64 MHz before post divide + * 122.88 MHz / 0xa = 12.288 MHz + */ + cx25840_write4(client, 0x114, 0x017dbf48); + cx25840_write4(client, 0x110, 0x000a030e); + break; + case CX23885_AV: + default: + /* + * 28.636363 MHz * (0xc + 0x1bf0c9e/0x2000000)/3 = 122.88 MHz + * 368.64 MHz before post divide + * 122.88 MHz / 0xa = 12.288 MHz + */ + cx25840_write4(client, 0x114, 0x01bf0c9e); + cx25840_write4(client, 0x110, 0x000a030c); + break; + } + + /* ADC2 input select */ + cx25840_write(client, 0x102, 0x10); + + /* VIN1 & VIN5 */ + cx25840_write(client, 0x103, 0x11); + + /* Enable format auto detect */ + cx25840_write(client, 0x400, 0); + /* Fast subchroma lock */ + /* White crush, Chroma AGC & Chroma Killer enabled */ + cx25840_write(client, 0x401, 0xe8); + + /* Select AFE clock pad output source */ + cx25840_write(client, 0x144, 0x05); + + /* Drive GPIO2 direction and values for HVR1700 + * where an onboard mux selects the output of demodulator + * vs the 417. Failure to set this results in no DTV. + * It's safe to set this across all Hauppauge boards + * currently, regardless of the board type. + */ + cx25840_write(client, 0x160, 0x1d); + cx25840_write(client, 0x164, 0x00); + + /* + * Do the firmware load in a work handler to prevent. + * Otherwise the kernel is blocked waiting for the + * bit-banging i2c interface to finish uploading the + * firmware. + */ + INIT_WORK(&state->fw_work, cx25840_work_handler); + init_waitqueue_head(&state->fw_wait); + q = create_singlethread_workqueue("cx25840_fw"); + if (q) { + prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); + queue_work(q, &state->fw_work); + schedule(); + finish_wait(&state->fw_wait, &wait); + destroy_workqueue(q); + } + + /* + * Call the cx23888 specific std setup func, we no longer rely on + * the generic cx24840 func. + */ + if (is_cx23888(state)) + cx23888_std_setup(client); + else + cx25840_std_setup(client); + + /* (re)set input */ + set_input(client, state->vid_input, state->aud_input); + + /* start microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); + + /* Disable and clear video interrupts - we don't use them */ + cx25840_write4(client, CX25840_VID_INT_STAT_REG, 0xffffffff); + + /* Disable and clear audio interrupts - we don't use them */ + cx25840_write(client, CX25840_AUD_INT_CTRL_REG, 0xff); + cx25840_write(client, CX25840_AUD_INT_STAT_REG, 0xff); + + /* CC raw enable */ + + /* + * - VIP 1.1 control codes - 10bit, blue field enable. + * - enable raw data during vertical blanking. + * - enable ancillary Data insertion for 656 or VIP. + */ + cx25840_write4(client, 0x404, 0x0010253e); + + /* CC on - VBI_LINE_CTRL3, FLD_VBI_MD_LINE12 */ + cx25840_write(client, state->vbi_regs_offset + 0x42f, 0x66); + + /* HVR-1250 / HVR1850 DIF related */ + /* Power everything up */ + cx25840_write4(client, 0x130, 0x0); + + /* SRC_COMB_CFG */ + if (is_cx23888(state)) + cx25840_write4(client, 0x454, 0x6628021F); + else + cx25840_write4(client, 0x478, 0x6628021F); + + /* AFE_CLK_OUT_CTRL - Select the clock output source as output */ + cx25840_write4(client, 0x144, 0x5); + + /* I2C_OUT_CTL - I2S output configuration as + * Master, Sony, Left justified, left sample on WS=1 + */ + cx25840_write4(client, 0x918, 0x1a0); + + /* AFE_DIAG_CTRL1 */ + cx25840_write4(client, 0x134, 0x000a1800); + + /* AFE_DIAG_CTRL3 - Inverted Polarity for Audio and Video */ + cx25840_write4(client, 0x13c, 0x00310000); +} + +/* ----------------------------------------------------------------------- */ + +static void cx231xx_initialize(struct i2c_client *client) +{ + DEFINE_WAIT(wait); + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + struct workqueue_struct *q; + + /* Internal Reset */ + cx25840_and_or(client, 0x102, ~0x01, 0x01); + cx25840_and_or(client, 0x102, ~0x01, 0x00); + + /* Stop microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + + /* DIF in reset? */ + cx25840_write(client, 0x398, 0); + + /* Trust the default xtal, no division */ + /* This changes for the cx23888 products */ + cx25840_write(client, 0x2, 0x76); + + /* Bring down the regulator for AUX clk */ + cx25840_write(client, 0x1, 0x40); + + /* Disable DIF bypass */ + cx25840_write4(client, 0x33c, 0x00000001); + + /* DIF Src phase inc */ + cx25840_write4(client, 0x340, 0x0df7df83); + + /* Luma */ + cx25840_write4(client, 0x414, 0x00107d12); + + /* Chroma */ + cx25840_write4(client, 0x420, 0x3d008282); + + /* ADC2 input select */ + cx25840_write(client, 0x102, 0x10); + + /* VIN1 & VIN5 */ + cx25840_write(client, 0x103, 0x11); + + /* Enable format auto detect */ + cx25840_write(client, 0x400, 0); + /* Fast subchroma lock */ + /* White crush, Chroma AGC & Chroma Killer enabled */ + cx25840_write(client, 0x401, 0xe8); + + /* + * Do the firmware load in a work handler to prevent. + * Otherwise the kernel is blocked waiting for the + * bit-banging i2c interface to finish uploading the + * firmware. + */ + INIT_WORK(&state->fw_work, cx25840_work_handler); + init_waitqueue_head(&state->fw_wait); + q = create_singlethread_workqueue("cx25840_fw"); + if (q) { + prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); + queue_work(q, &state->fw_work); + schedule(); + finish_wait(&state->fw_wait, &wait); + destroy_workqueue(q); + } + + cx25840_std_setup(client); + + /* (re)set input */ + set_input(client, state->vid_input, state->aud_input); + + /* start microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); + + /* CC raw enable */ + cx25840_write(client, 0x404, 0x0b); + + /* CC on */ + cx25840_write(client, 0x42f, 0x66); + cx25840_write4(client, 0x474, 0x1e1e601a); +} + +/* ----------------------------------------------------------------------- */ + +void cx25840_std_setup(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + v4l2_std_id std = state->std; + int hblank, hactive, burst, vblank, vactive, sc; + int vblank656, src_decimation; + int luma_lpf, uv_lpf, comb; + u32 pll_int, pll_frac, pll_post; + + /* datasheet startup, step 8d */ + if (std & ~V4L2_STD_NTSC) + cx25840_write(client, 0x49f, 0x11); + else + cx25840_write(client, 0x49f, 0x14); + + /* generic mode uses the values that the chip autoconfig would set */ + if (std & V4L2_STD_625_50) { + hblank = 132; + hactive = 720; + burst = 93; + if (state->generic_mode) { + vblank = 34; + vactive = 576; + vblank656 = 38; + } else { + vblank = 36; + vactive = 580; + vblank656 = 40; + } + src_decimation = 0x21f; + luma_lpf = 2; + + if (std & V4L2_STD_SECAM) { + uv_lpf = 0; + comb = 0; + sc = 0x0a425f; + } else if (std == V4L2_STD_PAL_Nc) { + if (state->generic_mode) { + burst = 95; + luma_lpf = 1; + } + uv_lpf = 1; + comb = 0x20; + sc = 556453; + } else { + uv_lpf = 1; + comb = 0x20; + sc = 688739; + } + } else { + hactive = 720; + hblank = 122; + vactive = 487; + luma_lpf = 1; + uv_lpf = 1; + if (state->generic_mode) { + vblank = 20; + vblank656 = 24; + } + + src_decimation = 0x21f; + if (std == V4L2_STD_PAL_60) { + if (!state->generic_mode) { + vblank = 26; + vblank656 = 26; + burst = 0x5b; + } else { + burst = 0x59; + } + luma_lpf = 2; + comb = 0x20; + sc = 688739; + } else if (std == V4L2_STD_PAL_M) { + vblank = 20; + vblank656 = 24; + burst = 0x61; + comb = 0x20; + sc = 555452; + } else { + if (!state->generic_mode) { + vblank = 26; + vblank656 = 26; + } + burst = 0x5b; + comb = 0x66; + sc = 556063; + } + } + + /* DEBUG: Displays configured PLL frequency */ + if (!is_cx231xx(state)) { + pll_int = cx25840_read(client, 0x108); + pll_frac = cx25840_read4(client, 0x10c) & 0x1ffffff; + pll_post = cx25840_read(client, 0x109); + v4l_dbg(1, cx25840_debug, client, + "PLL regs = int: %u, frac: %u, post: %u\n", + pll_int, pll_frac, pll_post); + + if (pll_post) { + int fin, fsc; + int pll = (28636363L * ((((u64)pll_int) << 25L) + pll_frac)) >> 25L; + + pll /= pll_post; + v4l_dbg(1, cx25840_debug, client, + "PLL = %d.%06d MHz\n", + pll / 1000000, pll % 1000000); + v4l_dbg(1, cx25840_debug, client, + "PLL/8 = %d.%06d MHz\n", + pll / 8000000, (pll / 8) % 1000000); + + fin = ((u64)src_decimation * pll) >> 12; + v4l_dbg(1, cx25840_debug, client, + "ADC Sampling freq = %d.%06d MHz\n", + fin / 1000000, fin % 1000000); + + fsc = (((u64)sc) * pll) >> 24L; + v4l_dbg(1, cx25840_debug, client, + "Chroma sub-carrier freq = %d.%06d MHz\n", + fsc / 1000000, fsc % 1000000); + + v4l_dbg(1, cx25840_debug, client, + "hblank %i, hactive %i, vblank %i, vactive %i, vblank656 %i, src_dec %i, burst 0x%02x, luma_lpf %i, uv_lpf %i, comb 0x%02x, sc 0x%06x\n", + hblank, hactive, vblank, vactive, vblank656, + src_decimation, burst, luma_lpf, uv_lpf, + comb, sc); + } + } + + /* Sets horizontal blanking delay and active lines */ + cx25840_write(client, 0x470, hblank); + cx25840_write(client, 0x471, + (((hblank >> 8) & 0x3) | (hactive << 4)) & 0xff); + cx25840_write(client, 0x472, hactive >> 4); + + /* Sets burst gate delay */ + cx25840_write(client, 0x473, burst); + + /* Sets vertical blanking delay and active duration */ + cx25840_write(client, 0x474, vblank); + cx25840_write(client, 0x475, + (((vblank >> 8) & 0x3) | (vactive << 4)) & 0xff); + cx25840_write(client, 0x476, vactive >> 4); + cx25840_write(client, 0x477, vblank656); + + /* Sets src decimation rate */ + cx25840_write(client, 0x478, src_decimation & 0xff); + cx25840_write(client, 0x479, (src_decimation >> 8) & 0xff); + + /* Sets Luma and UV Low pass filters */ + cx25840_write(client, 0x47a, luma_lpf << 6 | ((uv_lpf << 4) & 0x30)); + + /* Enables comb filters */ + cx25840_write(client, 0x47b, comb); + + /* Sets SC Step*/ + cx25840_write(client, 0x47c, sc); + cx25840_write(client, 0x47d, (sc >> 8) & 0xff); + cx25840_write(client, 0x47e, (sc >> 16) & 0xff); + + /* Sets VBI parameters */ + if (std & V4L2_STD_625_50) { + cx25840_write(client, 0x47f, 0x01); + state->vbi_line_offset = 5; + } else { + cx25840_write(client, 0x47f, 0x00); + state->vbi_line_offset = 8; + } +} + +/* ----------------------------------------------------------------------- */ + +static void input_change(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + v4l2_std_id std = state->std; + + /* Follow step 8c and 8d of section 3.16 in the cx25840 datasheet */ + if (std & V4L2_STD_SECAM) { + cx25840_write(client, 0x402, 0); + } else { + cx25840_write(client, 0x402, 0x04); + cx25840_write(client, 0x49f, + (std & V4L2_STD_NTSC) ? 0x14 : 0x11); + } + cx25840_and_or(client, 0x401, ~0x60, 0); + cx25840_and_or(client, 0x401, ~0x60, 0x60); + + /* Don't write into audio registers on cx2583x chips */ + if (is_cx2583x(state)) + return; + + cx25840_and_or(client, 0x810, ~0x01, 1); + + if (state->radio) { + cx25840_write(client, 0x808, 0xf9); + cx25840_write(client, 0x80b, 0x00); + } else if (std & V4L2_STD_525_60) { + /* + * Certain Hauppauge PVR150 models have a hardware bug + * that causes audio to drop out. For these models the + * audio standard must be set explicitly. + * To be precise: it affects cards with tuner models + * 85, 99 and 112 (model numbers from tveeprom). + */ + int hw_fix = state->pvr150_workaround; + + if (std == V4L2_STD_NTSC_M_JP) { + /* Japan uses EIAJ audio standard */ + cx25840_write(client, 0x808, hw_fix ? 0x2f : 0xf7); + } else if (std == V4L2_STD_NTSC_M_KR) { + /* South Korea uses A2 audio standard */ + cx25840_write(client, 0x808, hw_fix ? 0x3f : 0xf8); + } else { + /* Others use the BTSC audio standard */ + cx25840_write(client, 0x808, hw_fix ? 0x1f : 0xf6); + } + cx25840_write(client, 0x80b, 0x00); + } else if (std & V4L2_STD_PAL) { + /* Autodetect audio standard and audio system */ + cx25840_write(client, 0x808, 0xff); + /* + * Since system PAL-L is pretty much non-existent and + * not used by any public broadcast network, force + * 6.5 MHz carrier to be interpreted as System DK, + * this avoids DK audio detection instability + */ + cx25840_write(client, 0x80b, 0x00); + } else if (std & V4L2_STD_SECAM) { + /* Autodetect audio standard and audio system */ + cx25840_write(client, 0x808, 0xff); + /* + * If only one of SECAM-DK / SECAM-L is required, then force + * 6.5MHz carrier, else autodetect it + */ + if ((std & V4L2_STD_SECAM_DK) && + !(std & (V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC))) { + /* 6.5 MHz carrier to be interpreted as System DK */ + cx25840_write(client, 0x80b, 0x00); + } else if (!(std & V4L2_STD_SECAM_DK) && + (std & (V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC))) { + /* 6.5 MHz carrier to be interpreted as System L */ + cx25840_write(client, 0x80b, 0x08); + } else { + /* 6.5 MHz carrier to be autodetected */ + cx25840_write(client, 0x80b, 0x10); + } + } + + cx25840_and_or(client, 0x810, ~0x01, 0); +} + +static int set_input(struct i2c_client *client, + enum cx25840_video_input vid_input, + enum cx25840_audio_input aud_input) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u8 is_composite = (vid_input >= CX25840_COMPOSITE1 && + vid_input <= CX25840_COMPOSITE8); + u8 is_component = (vid_input & CX25840_COMPONENT_ON) == + CX25840_COMPONENT_ON; + u8 is_dif = (vid_input & CX25840_DIF_ON) == + CX25840_DIF_ON; + u8 is_svideo = (vid_input & CX25840_SVIDEO_ON) == + CX25840_SVIDEO_ON; + int luma = vid_input & 0xf0; + int chroma = vid_input & 0xf00; + u8 reg; + u32 val; + + v4l_dbg(1, cx25840_debug, client, + "decoder set video input %d, audio input %d\n", + vid_input, aud_input); + + if (vid_input >= CX25840_VIN1_CH1) { + v4l_dbg(1, cx25840_debug, client, "vid_input 0x%x\n", + vid_input); + reg = vid_input & 0xff; + is_composite = !is_component && + ((vid_input & CX25840_SVIDEO_ON) != CX25840_SVIDEO_ON); + + v4l_dbg(1, cx25840_debug, client, "mux cfg 0x%x comp=%d\n", + reg, is_composite); + } else if (is_composite) { + reg = 0xf0 + (vid_input - CX25840_COMPOSITE1); + } else { + if ((vid_input & ~0xff0) || + luma < CX25840_SVIDEO_LUMA1 || + luma > CX25840_SVIDEO_LUMA8 || + chroma < CX25840_SVIDEO_CHROMA4 || + chroma > CX25840_SVIDEO_CHROMA8) { + v4l_err(client, "0x%04x is not a valid video input!\n", + vid_input); + return -EINVAL; + } + reg = 0xf0 + ((luma - CX25840_SVIDEO_LUMA1) >> 4); + if (chroma >= CX25840_SVIDEO_CHROMA7) { + reg &= 0x3f; + reg |= (chroma - CX25840_SVIDEO_CHROMA7) >> 2; + } else { + reg &= 0xcf; + reg |= (chroma - CX25840_SVIDEO_CHROMA4) >> 4; + } + } + + /* The caller has previously prepared the correct routing + * configuration in reg (for the cx23885) so we have no + * need to attempt to flip bits for earlier av decoders. + */ + if (!is_cx2388x(state) && !is_cx231xx(state)) { + switch (aud_input) { + case CX25840_AUDIO_SERIAL: + /* do nothing, use serial audio input */ + break; + case CX25840_AUDIO4: + reg &= ~0x30; + break; + case CX25840_AUDIO5: + reg &= ~0x30; + reg |= 0x10; + break; + case CX25840_AUDIO6: + reg &= ~0x30; + reg |= 0x20; + break; + case CX25840_AUDIO7: + reg &= ~0xc0; + break; + case CX25840_AUDIO8: + reg &= ~0xc0; + reg |= 0x40; + break; + default: + v4l_err(client, "0x%04x is not a valid audio input!\n", + aud_input); + return -EINVAL; + } + } + + cx25840_write(client, 0x103, reg); + + /* Set INPUT_MODE to Composite, S-Video or Component */ + if (is_component) + cx25840_and_or(client, 0x401, ~0x6, 0x6); + else + cx25840_and_or(client, 0x401, ~0x6, is_composite ? 0 : 0x02); + + if (is_cx2388x(state)) { + /* Enable or disable the DIF for tuner use */ + if (is_dif) { + cx25840_and_or(client, 0x102, ~0x80, 0x80); + + /* Set of defaults for NTSC and PAL */ + cx25840_write4(client, 0x31c, 0xc2262600); + cx25840_write4(client, 0x320, 0xc2262600); + + /* 18271 IF - Nobody else yet uses a different + * tuner with the DIF, so these are reasonable + * assumptions (HVR1250 and HVR1850 specific). + */ + cx25840_write4(client, 0x318, 0xda262600); + cx25840_write4(client, 0x33c, 0x2a24c800); + cx25840_write4(client, 0x104, 0x0704dd00); + } else { + cx25840_write4(client, 0x300, 0x015c28f5); + + cx25840_and_or(client, 0x102, ~0x80, 0); + cx25840_write4(client, 0x340, 0xdf7df83); + cx25840_write4(client, 0x104, 0x0704dd80); + cx25840_write4(client, 0x314, 0x22400600); + cx25840_write4(client, 0x318, 0x40002600); + cx25840_write4(client, 0x324, 0x40002600); + cx25840_write4(client, 0x32c, 0x0250e620); + cx25840_write4(client, 0x39c, 0x01FF0B00); + + cx25840_write4(client, 0x410, 0xffff0dbf); + cx25840_write4(client, 0x414, 0x00137d03); + + if (is_cx23888(state)) { + /* 888 MISC_TIM_CTRL */ + cx25840_write4(client, 0x42c, 0x42600000); + /* 888 FIELD_COUNT */ + cx25840_write4(client, 0x430, 0x0000039b); + /* 888 VSCALE_CTRL */ + cx25840_write4(client, 0x438, 0x00000000); + /* 888 DFE_CTRL1 */ + cx25840_write4(client, 0x440, 0xF8E3E824); + /* 888 DFE_CTRL2 */ + cx25840_write4(client, 0x444, 0x401040dc); + /* 888 DFE_CTRL3 */ + cx25840_write4(client, 0x448, 0xcd3f02a0); + /* 888 PLL_CTRL */ + cx25840_write4(client, 0x44c, 0x161f1000); + /* 888 HTL_CTRL */ + cx25840_write4(client, 0x450, 0x00000802); + } + cx25840_write4(client, 0x91c, 0x01000000); + cx25840_write4(client, 0x8e0, 0x03063870); + cx25840_write4(client, 0x8d4, 0x7FFF0024); + cx25840_write4(client, 0x8d0, 0x00063073); + + cx25840_write4(client, 0x8c8, 0x00010000); + cx25840_write4(client, 0x8cc, 0x00080023); + + /* DIF BYPASS */ + cx25840_write4(client, 0x33c, 0x2a04c800); + } + + /* Reset the DIF */ + cx25840_write4(client, 0x398, 0); + } + + if (!is_cx2388x(state) && !is_cx231xx(state)) { + /* Set CH_SEL_ADC2 to 1 if input comes from CH3 */ + cx25840_and_or(client, 0x102, ~0x2, (reg & 0x80) == 0 ? 2 : 0); + /* Set DUAL_MODE_ADC2 to 1 if input comes from both CH2&CH3 */ + if ((reg & 0xc0) != 0xc0 && (reg & 0x30) != 0x30) + cx25840_and_or(client, 0x102, ~0x4, 4); + else + cx25840_and_or(client, 0x102, ~0x4, 0); + } else { + /* Set DUAL_MODE_ADC2 to 1 if component*/ + cx25840_and_or(client, 0x102, ~0x4, is_component ? 0x4 : 0x0); + if (is_composite) { + /* ADC2 input select channel 2 */ + cx25840_and_or(client, 0x102, ~0x2, 0); + } else if (!is_component) { + /* S-Video */ + if (chroma >= CX25840_SVIDEO_CHROMA7) { + /* ADC2 input select channel 3 */ + cx25840_and_or(client, 0x102, ~0x2, 2); + } else { + /* ADC2 input select channel 2 */ + cx25840_and_or(client, 0x102, ~0x2, 0); + } + } + + /* cx23885 / SVIDEO */ + if (is_cx2388x(state) && is_svideo) { +#define AFE_CTRL (0x104) +#define MODE_CTRL (0x400) + cx25840_and_or(client, 0x102, ~0x2, 0x2); + + val = cx25840_read4(client, MODE_CTRL); + val &= 0xFFFFF9FF; + + /* YC */ + val |= 0x00000200; + val &= ~0x2000; + cx25840_write4(client, MODE_CTRL, val); + + val = cx25840_read4(client, AFE_CTRL); + + /* Chroma in select */ + val |= 0x00001000; + val &= 0xfffffe7f; + /* Clear VGA_SEL_CH2 and VGA_SEL_CH3 (bits 7 and 8). + * This sets them to use video rather than audio. + * Only one of the two will be in use. + */ + cx25840_write4(client, AFE_CTRL, val); + } else { + cx25840_and_or(client, 0x102, ~0x2, 0); + } + } + + state->vid_input = vid_input; + state->aud_input = aud_input; + cx25840_audio_set_path(client); + input_change(client); + + if (is_cx2388x(state)) { + /* Audio channel 1 src : Parallel 1 */ + cx25840_write(client, 0x124, 0x03); + + /* Select AFE clock pad output source */ + cx25840_write(client, 0x144, 0x05); + + /* I2S_IN_CTL: I2S_IN_SONY_MODE, LEFT SAMPLE on WS=1 */ + cx25840_write(client, 0x914, 0xa0); + + /* I2S_OUT_CTL: + * I2S_IN_SONY_MODE, LEFT SAMPLE on WS=1 + * I2S_OUT_MASTER_MODE = Master + */ + cx25840_write(client, 0x918, 0xa0); + cx25840_write(client, 0x919, 0x01); + } else if (is_cx231xx(state)) { + /* Audio channel 1 src : Parallel 1 */ + cx25840_write(client, 0x124, 0x03); + + /* I2S_IN_CTL: I2S_IN_SONY_MODE, LEFT SAMPLE on WS=1 */ + cx25840_write(client, 0x914, 0xa0); + + /* I2S_OUT_CTL: + * I2S_IN_SONY_MODE, LEFT SAMPLE on WS=1 + * I2S_OUT_MASTER_MODE = Master + */ + cx25840_write(client, 0x918, 0xa0); + cx25840_write(client, 0x919, 0x01); + } + + if (is_cx2388x(state) && + ((aud_input == CX25840_AUDIO7) || (aud_input == CX25840_AUDIO6))) { + /* Configure audio from LR1 or LR2 input */ + cx25840_write4(client, 0x910, 0); + cx25840_write4(client, 0x8d0, 0x63073); + } else if (is_cx2388x(state) && (aud_input == CX25840_AUDIO8)) { + /* Configure audio from tuner/sif input */ + cx25840_write4(client, 0x910, 0x12b000c9); + cx25840_write4(client, 0x8d0, 0x1f063870); + } + + if (is_cx23888(state)) { + /* + * HVR1850 + * + * AUD_IO_CTRL - I2S Input, Parallel1 + * - Channel 1 src - Parallel1 (Merlin out) + * - Channel 2 src - Parallel2 (Merlin out) + * - Channel 3 src - Parallel3 (Merlin AC97 out) + * - I2S source and dir - Merlin, output + */ + cx25840_write4(client, 0x124, 0x100); + + if (!is_dif) { + /* + * Stop microcontroller if we don't need it + * to avoid audio popping on svideo/composite use. + */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + } + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int set_v4lstd(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u8 fmt = 0; /* zero is autodetect */ + u8 pal_m = 0; + + /* First tests should be against specific std */ + if (state->std == V4L2_STD_NTSC_M_JP) { + fmt = 0x2; + } else if (state->std == V4L2_STD_NTSC_443) { + fmt = 0x3; + } else if (state->std == V4L2_STD_PAL_M) { + pal_m = 1; + fmt = 0x5; + } else if (state->std == V4L2_STD_PAL_N) { + fmt = 0x6; + } else if (state->std == V4L2_STD_PAL_Nc) { + fmt = 0x7; + } else if (state->std == V4L2_STD_PAL_60) { + fmt = 0x8; + } else { + /* Then, test against generic ones */ + if (state->std & V4L2_STD_NTSC) + fmt = 0x1; + else if (state->std & V4L2_STD_PAL) + fmt = 0x4; + else if (state->std & V4L2_STD_SECAM) + fmt = 0xc; + } + + v4l_dbg(1, cx25840_debug, client, + "changing video std to fmt %i\n", fmt); + + /* + * Follow step 9 of section 3.16 in the cx25840 datasheet. + * Without this PAL may display a vertical ghosting effect. + * This happens for example with the Yuan MPC622. + */ + if (fmt >= 4 && fmt < 8) { + /* Set format to NTSC-M */ + cx25840_and_or(client, 0x400, ~0xf, 1); + /* Turn off LCOMB */ + cx25840_and_or(client, 0x47b, ~6, 0); + } + cx25840_and_or(client, 0x400, ~0xf, fmt); + cx25840_and_or(client, 0x403, ~0x3, pal_m); + if (is_cx23888(state)) + cx23888_std_setup(client); + else + cx25840_std_setup(client); + if (!is_cx2583x(state)) + input_change(client); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int cx25840_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + cx25840_write(client, 0x414, ctrl->val - 128); + break; + + case V4L2_CID_CONTRAST: + cx25840_write(client, 0x415, ctrl->val << 1); + break; + + case V4L2_CID_SATURATION: + if (is_cx23888(state)) { + cx25840_write(client, 0x418, ctrl->val << 1); + cx25840_write(client, 0x419, ctrl->val << 1); + } else { + cx25840_write(client, 0x420, ctrl->val << 1); + cx25840_write(client, 0x421, ctrl->val << 1); + } + break; + + case V4L2_CID_HUE: + if (is_cx23888(state)) + cx25840_write(client, 0x41a, ctrl->val); + else + cx25840_write(client, 0x422, ctrl->val); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int cx25840_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u32 hsc, vsc, v_src, h_src, v_add; + int filter; + int is_50hz = !(state->std & V4L2_STD_525_60); + + if (format->pad || fmt->code != MEDIA_BUS_FMT_FIXED) + return -EINVAL; + + fmt->field = V4L2_FIELD_INTERLACED; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + + if (is_cx23888(state)) { + v_src = (cx25840_read(client, 0x42a) & 0x3f) << 4; + v_src |= (cx25840_read(client, 0x429) & 0xf0) >> 4; + } else { + v_src = (cx25840_read(client, 0x476) & 0x3f) << 4; + v_src |= (cx25840_read(client, 0x475) & 0xf0) >> 4; + } + + if (is_cx23888(state)) { + h_src = (cx25840_read(client, 0x426) & 0x3f) << 4; + h_src |= (cx25840_read(client, 0x425) & 0xf0) >> 4; + } else { + h_src = (cx25840_read(client, 0x472) & 0x3f) << 4; + h_src |= (cx25840_read(client, 0x471) & 0xf0) >> 4; + } + + if (!state->generic_mode) { + v_add = is_50hz ? 4 : 7; + + /* + * cx23888 in 525-line mode is programmed for 486 active lines + * while other chips use 487 active lines. + * + * See reg 0x428 bits [21:12] in cx23888_std_setup() vs + * vactive in cx25840_std_setup(). + */ + if (is_cx23888(state) && !is_50hz) + v_add--; + } else { + v_add = 0; + } + + if (h_src == 0 || + v_src <= v_add) { + v4l_err(client, + "chip reported picture size (%u x %u) is far too small\n", + (unsigned int)h_src, (unsigned int)v_src); + /* + * that's the best we can do since the output picture + * size is completely unknown in this case + */ + return -EINVAL; + } + + fmt->width = clamp(fmt->width, (h_src + 15) / 16, h_src); + + if (v_add * 8 >= v_src) + fmt->height = clamp(fmt->height, (u32)1, v_src - v_add); + else + fmt->height = clamp(fmt->height, (v_src - v_add * 8 + 7) / 8, + v_src - v_add); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + hsc = (h_src * (1 << 20)) / fmt->width - (1 << 20); + vsc = (1 << 16) - (v_src * (1 << 9) / (fmt->height + v_add) - (1 << 9)); + vsc &= 0x1fff; + + if (fmt->width >= 385) + filter = 0; + else if (fmt->width > 192) + filter = 1; + else if (fmt->width > 96) + filter = 2; + else + filter = 3; + + v4l_dbg(1, cx25840_debug, client, + "decoder set size %u x %u with scale %x x %x\n", + (unsigned int)fmt->width, (unsigned int)fmt->height, + (unsigned int)hsc, (unsigned int)vsc); + + /* HSCALE=hsc */ + if (is_cx23888(state)) { + cx25840_write4(client, 0x434, hsc | (1 << 24)); + /* VSCALE=vsc VS_INTRLACE=1 VFILT=filter */ + cx25840_write4(client, 0x438, vsc | (1 << 19) | (filter << 16)); + } else { + cx25840_write(client, 0x418, hsc & 0xff); + cx25840_write(client, 0x419, (hsc >> 8) & 0xff); + cx25840_write(client, 0x41a, hsc >> 16); + /* VSCALE=vsc */ + cx25840_write(client, 0x41c, vsc & 0xff); + cx25840_write(client, 0x41d, vsc >> 8); + /* VS_INTRLACE=1 VFILT=filter */ + cx25840_write(client, 0x41e, 0x8 | filter); + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static void log_video_status(struct i2c_client *client) +{ + static const char *const fmt_strs[] = { + "0x0", + "NTSC-M", "NTSC-J", "NTSC-4.43", + "PAL-BDGHI", "PAL-M", "PAL-N", "PAL-Nc", "PAL-60", + "0x9", "0xA", "0xB", + "SECAM", + "0xD", "0xE", "0xF" + }; + + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u8 vidfmt_sel = cx25840_read(client, 0x400) & 0xf; + u8 gen_stat1 = cx25840_read(client, 0x40d); + u8 gen_stat2 = cx25840_read(client, 0x40e); + int vid_input = state->vid_input; + + v4l_info(client, "Video signal: %spresent\n", + (gen_stat2 & 0x20) ? "" : "not "); + v4l_info(client, "Detected format: %s\n", + fmt_strs[gen_stat1 & 0xf]); + + v4l_info(client, "Specified standard: %s\n", + vidfmt_sel ? fmt_strs[vidfmt_sel] : "automatic detection"); + + if (vid_input >= CX25840_COMPOSITE1 && + vid_input <= CX25840_COMPOSITE8) { + v4l_info(client, "Specified video input: Composite %d\n", + vid_input - CX25840_COMPOSITE1 + 1); + } else { + v4l_info(client, + "Specified video input: S-Video (Luma In%d, Chroma In%d)\n", + (vid_input & 0xf0) >> 4, (vid_input & 0xf00) >> 8); + } + + v4l_info(client, "Specified audioclock freq: %d Hz\n", + state->audclk_freq); +} + +/* ----------------------------------------------------------------------- */ + +static void log_audio_status(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u8 download_ctl = cx25840_read(client, 0x803); + u8 mod_det_stat0 = cx25840_read(client, 0x804); + u8 mod_det_stat1 = cx25840_read(client, 0x805); + u8 audio_config = cx25840_read(client, 0x808); + u8 pref_mode = cx25840_read(client, 0x809); + u8 afc0 = cx25840_read(client, 0x80b); + u8 mute_ctl = cx25840_read(client, 0x8d3); + int aud_input = state->aud_input; + char *p; + + switch (mod_det_stat0) { + case 0x00: + p = "mono"; + break; + case 0x01: + p = "stereo"; + break; + case 0x02: + p = "dual"; + break; + case 0x04: + p = "tri"; + break; + case 0x10: + p = "mono with SAP"; + break; + case 0x11: + p = "stereo with SAP"; + break; + case 0x12: + p = "dual with SAP"; + break; + case 0x14: + p = "tri with SAP"; + break; + case 0xfe: + p = "forced mode"; + break; + default: + p = "not defined"; + } + v4l_info(client, "Detected audio mode: %s\n", p); + + switch (mod_det_stat1) { + case 0x00: + p = "not defined"; + break; + case 0x01: + p = "EIAJ"; + break; + case 0x02: + p = "A2-M"; + break; + case 0x03: + p = "A2-BG"; + break; + case 0x04: + p = "A2-DK1"; + break; + case 0x05: + p = "A2-DK2"; + break; + case 0x06: + p = "A2-DK3"; + break; + case 0x07: + p = "A1 (6.0 MHz FM Mono)"; + break; + case 0x08: + p = "AM-L"; + break; + case 0x09: + p = "NICAM-BG"; + break; + case 0x0a: + p = "NICAM-DK"; + break; + case 0x0b: + p = "NICAM-I"; + break; + case 0x0c: + p = "NICAM-L"; + break; + case 0x0d: + p = "BTSC/EIAJ/A2-M Mono (4.5 MHz FMMono)"; + break; + case 0x0e: + p = "IF FM Radio"; + break; + case 0x0f: + p = "BTSC"; + break; + case 0x10: + p = "high-deviation FM"; + break; + case 0x11: + p = "very high-deviation FM"; + break; + case 0xfd: + p = "unknown audio standard"; + break; + case 0xfe: + p = "forced audio standard"; + break; + case 0xff: + p = "no detected audio standard"; + break; + default: + p = "not defined"; + } + v4l_info(client, "Detected audio standard: %s\n", p); + v4l_info(client, "Audio microcontroller: %s\n", + (download_ctl & 0x10) ? + ((mute_ctl & 0x2) ? "detecting" : "running") : "stopped"); + + switch (audio_config >> 4) { + case 0x00: + p = "undefined"; + break; + case 0x01: + p = "BTSC"; + break; + case 0x02: + p = "EIAJ"; + break; + case 0x03: + p = "A2-M"; + break; + case 0x04: + p = "A2-BG"; + break; + case 0x05: + p = "A2-DK1"; + break; + case 0x06: + p = "A2-DK2"; + break; + case 0x07: + p = "A2-DK3"; + break; + case 0x08: + p = "A1 (6.0 MHz FM Mono)"; + break; + case 0x09: + p = "AM-L"; + break; + case 0x0a: + p = "NICAM-BG"; + break; + case 0x0b: + p = "NICAM-DK"; + break; + case 0x0c: + p = "NICAM-I"; + break; + case 0x0d: + p = "NICAM-L"; + break; + case 0x0e: + p = "FM radio"; + break; + case 0x0f: + p = "automatic detection"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Configured audio standard: %s\n", p); + + if ((audio_config >> 4) < 0xF) { + switch (audio_config & 0xF) { + case 0x00: + p = "MONO1 (LANGUAGE A/Mono L+R channel for BTSC, EIAJ, A2)"; + break; + case 0x01: + p = "MONO2 (LANGUAGE B)"; + break; + case 0x02: + p = "MONO3 (STEREO forced MONO)"; + break; + case 0x03: + p = "MONO4 (NICAM ANALOG-Language C/Analog Fallback)"; + break; + case 0x04: + p = "STEREO"; + break; + case 0x05: + p = "DUAL1 (AB)"; + break; + case 0x06: + p = "DUAL2 (AC) (FM)"; + break; + case 0x07: + p = "DUAL3 (BC) (FM)"; + break; + case 0x08: + p = "DUAL4 (AC) (AM)"; + break; + case 0x09: + p = "DUAL5 (BC) (AM)"; + break; + case 0x0a: + p = "SAP"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Configured audio mode: %s\n", p); + } else { + switch (audio_config & 0xF) { + case 0x00: + p = "BG"; + break; + case 0x01: + p = "DK1"; + break; + case 0x02: + p = "DK2"; + break; + case 0x03: + p = "DK3"; + break; + case 0x04: + p = "I"; + break; + case 0x05: + p = "L"; + break; + case 0x06: + p = "BTSC"; + break; + case 0x07: + p = "EIAJ"; + break; + case 0x08: + p = "A2-M"; + break; + case 0x09: + p = "FM Radio"; + break; + case 0x0f: + p = "automatic standard and mode detection"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Configured audio system: %s\n", p); + } + + if (aud_input) { + v4l_info(client, "Specified audio input: Tuner (In%d)\n", + aud_input); + } else { + v4l_info(client, "Specified audio input: External\n"); + } + + switch (pref_mode & 0xf) { + case 0: + p = "mono/language A"; + break; + case 1: + p = "language B"; + break; + case 2: + p = "language C"; + break; + case 3: + p = "analog fallback"; + break; + case 4: + p = "stereo"; + break; + case 5: + p = "language AC"; + break; + case 6: + p = "language BC"; + break; + case 7: + p = "language AB"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Preferred audio mode: %s\n", p); + + if ((audio_config & 0xf) == 0xf) { + switch ((afc0 >> 3) & 0x3) { + case 0: + p = "system DK"; + break; + case 1: + p = "system L"; + break; + case 2: + p = "autodetect"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Selected 65 MHz format: %s\n", p); + + switch (afc0 & 0x7) { + case 0: + p = "chroma"; + break; + case 1: + p = "BTSC"; + break; + case 2: + p = "EIAJ"; + break; + case 3: + p = "A2-M"; + break; + case 4: + p = "autodetect"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Selected 45 MHz format: %s\n", p); + } +} + +#define CX25840_VCONFIG_OPTION(state, cfg_in, opt_msk) \ + do { \ + if ((cfg_in) & (opt_msk)) { \ + (state)->vid_config &= ~(opt_msk); \ + (state)->vid_config |= (cfg_in) & (opt_msk); \ + } \ + } while (0) + +/* apply incoming options to the current vconfig */ +static void cx25840_vconfig_add(struct cx25840_state *state, u32 cfg_in) +{ + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_FMT_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_RES_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VBIRAW_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ANCDATA_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_TASKBIT_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ACTIVE_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VALID_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_HRESETW_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_CLKGATE_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_DCMODE_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_IDID0S_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VIPCLAMP_MASK); +} + +/* ----------------------------------------------------------------------- */ + +/* + * Initializes the device in the generic mode. + * For cx2584x chips also adds additional video output settings provided + * in @val parameter (CX25840_VCONFIG_*). + * + * The generic mode disables some of the ivtv-related hacks in this driver. + * For cx2584x chips it also enables setting video output configuration while + * setting it according to datasheet defaults by default. + */ +static int cx25840_init(struct v4l2_subdev *sd, u32 val) +{ + struct cx25840_state *state = to_state(sd); + + state->generic_mode = true; + + if (is_cx2584x(state)) { + /* set datasheet video output defaults */ + state->vid_config = CX25840_VCONFIG_FMT_BT656 | + CX25840_VCONFIG_RES_8BIT | + CX25840_VCONFIG_VBIRAW_DISABLED | + CX25840_VCONFIG_ANCDATA_ENABLED | + CX25840_VCONFIG_TASKBIT_ONE | + CX25840_VCONFIG_ACTIVE_HORIZONTAL | + CX25840_VCONFIG_VALID_NORMAL | + CX25840_VCONFIG_HRESETW_NORMAL | + CX25840_VCONFIG_CLKGATE_NONE | + CX25840_VCONFIG_DCMODE_DWORDS | + CX25840_VCONFIG_IDID0S_NORMAL | + CX25840_VCONFIG_VIPCLAMP_DISABLED; + + /* add additional settings */ + cx25840_vconfig_add(state, val); + } else { + /* TODO: generic mode needs to be developed for other chips */ + WARN_ON(1); + } + + return 0; +} + +static int cx25840_reset(struct v4l2_subdev *sd, u32 val) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (is_cx2583x(state)) + cx25836_initialize(client); + else if (is_cx2388x(state)) + cx23885_initialize(client); + else if (is_cx231xx(state)) + cx231xx_initialize(client); + else + cx25840_initialize(client); + + state->is_initialized = 1; + + return 0; +} + +/* + * This load_fw operation must be called to load the driver's firmware. + * This will load the firmware on the first invocation (further ones are NOP). + * Without this the audio standard detection will fail and you will + * only get mono. + * Alternatively, you can call the reset operation instead of this one. + * + * Since loading the firmware is often problematic when the driver is + * compiled into the kernel I recommend postponing calling this function + * until the first open of the video device. Another reason for + * postponing it is that loading this firmware takes a long time (seconds) + * due to the slow i2c bus speed. So it will speed up the boot process if + * you can avoid loading the fw as long as the video device isn't used. + */ +static int cx25840_load_fw(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + + if (!state->is_initialized) { + /* initialize and load firmware */ + cx25840_reset(sd, 0); + } + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cx25840_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + reg->size = 1; + reg->val = cx25840_read(client, reg->reg & 0x0fff); + return 0; +} + +static int cx25840_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + cx25840_write(client, reg->reg & 0x0fff, reg->val & 0xff); + return 0; +} +#endif + +static int cx25840_s_audio_stream(struct v4l2_subdev *sd, int enable) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 v; + + if (is_cx2583x(state) || is_cx2388x(state) || is_cx231xx(state)) + return 0; + + v4l_dbg(1, cx25840_debug, client, "%s audio output\n", + enable ? "enable" : "disable"); + + if (enable) { + v = cx25840_read(client, 0x115) | 0x80; + cx25840_write(client, 0x115, v); + v = cx25840_read(client, 0x116) | 0x03; + cx25840_write(client, 0x116, v); + } else { + v = cx25840_read(client, 0x115) & ~(0x80); + cx25840_write(client, 0x115, v); + v = cx25840_read(client, 0x116) & ~(0x03); + cx25840_write(client, 0x116, v); + } + return 0; +} + +static int cx25840_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 v; + + v4l_dbg(1, cx25840_debug, client, "%s video output\n", + enable ? "enable" : "disable"); + + /* + * It's not clear what should be done for these devices. + * The original code used the same addresses as for the cx25840, but + * those addresses do something else entirely on the cx2388x and + * cx231xx. Since it never did anything in the first place, just do + * nothing. + */ + if (is_cx2388x(state) || is_cx231xx(state)) + return 0; + + if (enable) { + v = cx25840_read(client, 0x115) | 0x0c; + cx25840_write(client, 0x115, v); + v = cx25840_read(client, 0x116) | 0x04; + cx25840_write(client, 0x116, v); + } else { + v = cx25840_read(client, 0x115) & ~(0x0c); + cx25840_write(client, 0x115, v); + v = cx25840_read(client, 0x116) & ~(0x04); + cx25840_write(client, 0x116, v); + } + return 0; +} + +/* Query the current detected video format */ +static int cx25840_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + static const v4l2_std_id stds[] = { + /* 0000 */ V4L2_STD_UNKNOWN, + + /* 0001 */ V4L2_STD_NTSC_M, + /* 0010 */ V4L2_STD_NTSC_M_JP, + /* 0011 */ V4L2_STD_NTSC_443, + /* 0100 */ V4L2_STD_PAL, + /* 0101 */ V4L2_STD_PAL_M, + /* 0110 */ V4L2_STD_PAL_N, + /* 0111 */ V4L2_STD_PAL_Nc, + /* 1000 */ V4L2_STD_PAL_60, + + /* 1001 */ V4L2_STD_UNKNOWN, + /* 1010 */ V4L2_STD_UNKNOWN, + /* 1011 */ V4L2_STD_UNKNOWN, + /* 1100 */ V4L2_STD_SECAM, + /* 1101 */ V4L2_STD_UNKNOWN, + /* 1110 */ V4L2_STD_UNKNOWN, + /* 1111 */ V4L2_STD_UNKNOWN + }; + + u32 fmt = (cx25840_read4(client, 0x40c) >> 8) & 0xf; + *std = stds[fmt]; + + v4l_dbg(1, cx25840_debug, client, + "querystd fmt = %x, v4l2_std_id = 0x%x\n", + fmt, (unsigned int)stds[fmt]); + + return 0; +} + +static int cx25840_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* + * A limited function that checks for signal status and returns + * the state. + */ + + /* Check for status of Horizontal lock (SRC lock isn't reliable) */ + if ((cx25840_read4(client, 0x40c) & 0x00010000) == 0) + *status |= V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +static int cx25840_g_std(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct cx25840_state *state = to_state(sd); + + *std = state->std; + + return 0; +} + +static int cx25840_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (state->radio == 0 && state->std == std) + return 0; + state->radio = 0; + state->std = std; + return set_v4lstd(client); +} + +static int cx25840_s_radio(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + + state->radio = 1; + return 0; +} + +static int cx25840_s_video_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (is_cx23888(state)) + cx23888_std_setup(client); + + if (is_cx2584x(state) && state->generic_mode && config) { + cx25840_vconfig_add(state, config); + cx25840_vconfig_apply(client); + } + + return set_input(client, input, state->aud_input); +} + +static int cx25840_s_audio_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (is_cx23888(state)) + cx23888_std_setup(client); + return set_input(client, state->vid_input, input); +} + +static int cx25840_s_frequency(struct v4l2_subdev *sd, + const struct v4l2_frequency *freq) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + input_change(client); + return 0; +} + +static int cx25840_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 vpres = cx25840_read(client, 0x40e) & 0x20; + u8 mode; + int val = 0; + + if (state->radio) + return 0; + + vt->signal = vpres ? 0xffff : 0x0; + if (is_cx2583x(state)) + return 0; + + vt->capability |= V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + + mode = cx25840_read(client, 0x804); + + /* get rxsubchans and audmode */ + if ((mode & 0xf) == 1) + val |= V4L2_TUNER_SUB_STEREO; + else + val |= V4L2_TUNER_SUB_MONO; + + if (mode == 2 || mode == 4) + val = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + + if (mode & 0x10) + val |= V4L2_TUNER_SUB_SAP; + + vt->rxsubchans = val; + vt->audmode = state->audmode; + return 0; +} + +static int cx25840_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (state->radio || is_cx2583x(state)) + return 0; + + switch (vt->audmode) { + case V4L2_TUNER_MODE_MONO: + /* + * mono -> mono + * stereo -> mono + * bilingual -> lang1 + */ + cx25840_and_or(client, 0x809, ~0xf, 0x00); + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1: + /* + * mono -> mono + * stereo -> stereo + * bilingual -> lang1 + */ + cx25840_and_or(client, 0x809, ~0xf, 0x04); + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + /* + * mono -> mono + * stereo -> stereo + * bilingual -> lang1/lang2 + */ + cx25840_and_or(client, 0x809, ~0xf, 0x07); + break; + case V4L2_TUNER_MODE_LANG2: + /* + * mono -> mono + * stereo -> stereo + * bilingual -> lang2 + */ + cx25840_and_or(client, 0x809, ~0xf, 0x01); + break; + default: + return -EINVAL; + } + state->audmode = vt->audmode; + return 0; +} + +static int cx25840_log_status(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + log_video_status(client); + if (!is_cx2583x(state)) + log_audio_status(client); + cx25840_ir_log_status(sd); + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +static int cx23885_irq_handler(struct v4l2_subdev *sd, u32 status, + bool *handled) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *c = v4l2_get_subdevdata(sd); + u8 irq_stat, aud_stat, aud_en, ir_stat, ir_en; + u32 vid_stat, aud_mc_stat; + bool block_handled; + int ret = 0; + + irq_stat = cx25840_read(c, CX23885_PIN_CTRL_IRQ_REG); + v4l_dbg(2, cx25840_debug, c, "AV Core IRQ status (entry): %s %s %s\n", + irq_stat & CX23885_PIN_CTRL_IRQ_IR_STAT ? "ir" : " ", + irq_stat & CX23885_PIN_CTRL_IRQ_AUD_STAT ? "aud" : " ", + irq_stat & CX23885_PIN_CTRL_IRQ_VID_STAT ? "vid" : " "); + + if ((is_cx23885(state) || is_cx23887(state))) { + ir_stat = cx25840_read(c, CX25840_IR_STATS_REG); + ir_en = cx25840_read(c, CX25840_IR_IRQEN_REG); + v4l_dbg(2, cx25840_debug, c, + "AV Core ir IRQ status: %#04x disables: %#04x\n", + ir_stat, ir_en); + if (irq_stat & CX23885_PIN_CTRL_IRQ_IR_STAT) { + block_handled = false; + ret = cx25840_ir_irq_handler(sd, + status, &block_handled); + if (block_handled) + *handled = true; + } + } + + aud_stat = cx25840_read(c, CX25840_AUD_INT_STAT_REG); + aud_en = cx25840_read(c, CX25840_AUD_INT_CTRL_REG); + v4l_dbg(2, cx25840_debug, c, + "AV Core audio IRQ status: %#04x disables: %#04x\n", + aud_stat, aud_en); + aud_mc_stat = cx25840_read4(c, CX23885_AUD_MC_INT_MASK_REG); + v4l_dbg(2, cx25840_debug, c, + "AV Core audio MC IRQ status: %#06x enables: %#06x\n", + aud_mc_stat >> CX23885_AUD_MC_INT_STAT_SHFT, + aud_mc_stat & CX23885_AUD_MC_INT_CTRL_BITS); + if (irq_stat & CX23885_PIN_CTRL_IRQ_AUD_STAT) { + if (aud_stat) { + cx25840_write(c, CX25840_AUD_INT_STAT_REG, aud_stat); + *handled = true; + } + } + + vid_stat = cx25840_read4(c, CX25840_VID_INT_STAT_REG); + v4l_dbg(2, cx25840_debug, c, + "AV Core video IRQ status: %#06x disables: %#06x\n", + vid_stat & CX25840_VID_INT_STAT_BITS, + vid_stat >> CX25840_VID_INT_MASK_SHFT); + if (irq_stat & CX23885_PIN_CTRL_IRQ_VID_STAT) { + if (vid_stat & CX25840_VID_INT_STAT_BITS) { + cx25840_write4(c, CX25840_VID_INT_STAT_REG, vid_stat); + *handled = true; + } + } + + irq_stat = cx25840_read(c, CX23885_PIN_CTRL_IRQ_REG); + v4l_dbg(2, cx25840_debug, c, "AV Core IRQ status (exit): %s %s %s\n", + irq_stat & CX23885_PIN_CTRL_IRQ_IR_STAT ? "ir" : " ", + irq_stat & CX23885_PIN_CTRL_IRQ_AUD_STAT ? "aud" : " ", + irq_stat & CX23885_PIN_CTRL_IRQ_VID_STAT ? "vid" : " "); + + return ret; +} + +static int cx25840_irq_handler(struct v4l2_subdev *sd, u32 status, + bool *handled) +{ + struct cx25840_state *state = to_state(sd); + + *handled = false; + + /* Only support the CX2388[578] AV Core for now */ + if (is_cx2388x(state)) + return cx23885_irq_handler(sd, status, handled); + + return -ENODEV; +} + +/* ----------------------------------------------------------------------- */ + +#define DIF_PLL_FREQ_WORD (0x300) +#define DIF_BPF_COEFF01 (0x348) +#define DIF_BPF_COEFF23 (0x34c) +#define DIF_BPF_COEFF45 (0x350) +#define DIF_BPF_COEFF67 (0x354) +#define DIF_BPF_COEFF89 (0x358) +#define DIF_BPF_COEFF1011 (0x35c) +#define DIF_BPF_COEFF1213 (0x360) +#define DIF_BPF_COEFF1415 (0x364) +#define DIF_BPF_COEFF1617 (0x368) +#define DIF_BPF_COEFF1819 (0x36c) +#define DIF_BPF_COEFF2021 (0x370) +#define DIF_BPF_COEFF2223 (0x374) +#define DIF_BPF_COEFF2425 (0x378) +#define DIF_BPF_COEFF2627 (0x37c) +#define DIF_BPF_COEFF2829 (0x380) +#define DIF_BPF_COEFF3031 (0x384) +#define DIF_BPF_COEFF3233 (0x388) +#define DIF_BPF_COEFF3435 (0x38c) +#define DIF_BPF_COEFF36 (0x390) + +static void cx23885_dif_setup(struct i2c_client *client, u32 ifHz) +{ + u64 pll_freq; + u32 pll_freq_word; + + v4l_dbg(1, cx25840_debug, client, "%s(%d)\n", __func__, ifHz); + + /* Assuming TV */ + /* Calculate the PLL frequency word based on the adjusted ifHz */ + pll_freq = div_u64((u64)ifHz * 268435456, 50000000); + pll_freq_word = (u32)pll_freq; + + cx25840_write4(client, DIF_PLL_FREQ_WORD, pll_freq_word); + + /* Round down to the nearest 100KHz */ + ifHz = (ifHz / 100000) * 100000; + + if (ifHz < 3000000) + ifHz = 3000000; + + if (ifHz > 16000000) + ifHz = 16000000; + + v4l_dbg(1, cx25840_debug, client, "%s(%d) again\n", __func__, ifHz); + + switch (ifHz) { + case 3000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00080012); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001e0024); + cx25840_write4(client, DIF_BPF_COEFF67, 0x001bfff8); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffb4ff50); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfed8fe68); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe24fe34); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfebaffc7); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014d031f); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x04f0065d); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x07010688); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x04c901d6); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfe00f9d3); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf600f342); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf235f337); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf64efb22); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0105070f); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x0c460fce); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 3100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000001); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00070012); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00220032); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00370026); + cx25840_write4(client, DIF_BPF_COEFF89, 0xfff0ff91); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff0efe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe01fdcc); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfe0afedb); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x00440224); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0434060c); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0738074e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x06090361); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xff99fb39); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf6fef3b6); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf21af2a5); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf573fa33); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0034067d); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x0bfb0fb9); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 3200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000000); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0004000e); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00200038); + cx25840_write4(client, DIF_BPF_COEFF67, 0x004c004f); + cx25840_write4(client, DIF_BPF_COEFF89, 0x002fffdf); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff5cfeb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe0dfd92); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd7ffe03); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff36010a); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x03410575); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x072607d2); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x071804d5); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0134fcb7); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf81ff451); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf223f22e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf4a7f94b); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xff6405e8); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x0bae0fa4); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 3300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000ffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00000008); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001a0036); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0056006d); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00670030); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffbdff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe46fd8d); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd25fd4f); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe35ffe0); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0224049f); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x06c9080e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x07ef0627); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x02c9fe45); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf961f513); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf250f1d2); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf3ecf869); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfe930552); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x0b5f0f8f); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 3400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffd0001); + cx25840_write4(client, DIF_BPF_COEFF45, 0x000f002c); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0054007d); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0093007c); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0024ff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfea6fdbb); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd03fcca); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd51feb9); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x00eb0392); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x06270802); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08880750); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x044dffdb); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfabdf5f8); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf2a0f193); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf342f78f); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfdc404b9); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x0b0e0f78); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 3500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffafff9); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0002001b); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0046007d); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00ad00ba); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00870000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff26fe1a); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd1bfc7e); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc99fda4); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xffa5025c); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x054507ad); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08dd0847); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x05b80172); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfc2ef6ff); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf313f170); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf2abf6bd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfcf6041f); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x0abc0f61); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 3600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8fff3); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff50006); + cx25840_write4(client, DIF_BPF_COEFF67, 0x002f006c); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00b200e3); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00dc007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xffb9fea0); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd6bfc71); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc17fcb1); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfe65010b); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x042d0713); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08ec0906); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x07020302); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfdaff823); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf3a7f16a); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf228f5f5); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfc2a0384); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x0a670f4a); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 3700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff7ffef); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe9fff1); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0010004d); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00a100f2); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x011a00f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0053ff44); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfdedfca2); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfbd3fbef); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfd39ffae); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x02ea0638); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08b50987); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x08230483); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xff39f960); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf45bf180); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf1b8f537); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfb6102e7); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x0a110f32); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 3800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff9ffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe1ffdd); + cx25840_write4(client, DIF_BPF_COEFF67, 0xfff00024); + cx25840_write4(client, DIF_BPF_COEFF89, 0x007c00e5); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x013a014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x00e6fff8); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfe98fd0f); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfbd3fb67); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfc32fe54); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x01880525); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x083909c7); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x091505ee); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x00c7fab3); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf52df1b4); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf15df484); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfa9b0249); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x09ba0f19); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 3900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000000); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffbfff0); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffdeffcf); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffd1fff6); + cx25840_write4(client, DIF_BPF_COEFF89, 0x004800be); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x01390184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x016300ac); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xff5efdb1); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc17fb23); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfb5cfd0d); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x001703e4); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x077b09c4); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x09d2073c); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0251fc18); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf61cf203); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf118f3dc); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf9d801aa); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x09600eff); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 4000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000001); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffefff4); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe1ffc8); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffbaffca); + cx25840_write4(client, DIF_BPF_COEFF89, 0x000b0082); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x01170198); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01c10152); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0030fe7b); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc99fb24); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfac3fbe9); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfea5027f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x0683097f); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a560867); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x03d2fd89); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf723f26f); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf0e8f341); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf919010a); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x09060ee5); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 4100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0002fffb); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe8ffca); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffacffa4); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffcd0036); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00d70184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01f601dc); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x00ffff60); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd51fb6d); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa6efaf5); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfd410103); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x055708f9); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a9e0969); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0543ff02); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf842f2f5); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf0cef2b2); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf85e006b); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x08aa0ecb); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 4200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00050003); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff3ffd3); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffaaff8b); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff95ffe5); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0080014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01fe023f); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x01ba0050); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe35fbf8); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa62fa3b); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfbf9ff7e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x04010836); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0aa90a3d); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x069f007f); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf975f395); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf0cbf231); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf7a9ffcb); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x084c0eaf); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 4300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0008000a); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0000ffe4); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffb4ff81); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff6aff96); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x001c00f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01d70271); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0254013b); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff36fcbd); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa9ff9c5); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfadbfdfe); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x028c073b); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a750adf); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x07e101fa); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfab8f44e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf0ddf1be); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf6f9ff2b); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x07ed0e94); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 4400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0009000f); + cx25840_write4(client, DIF_BPF_COEFF45, 0x000efff8); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffc9ff87); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff52ff54); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffb5007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01860270); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02c00210); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0044fdb2); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfb22f997); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf9f2fc90); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x0102060f); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a050b4c); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0902036e); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfc0af51e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf106f15a); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf64efe8b); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x078d0e77); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 4500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00080012); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0019000e); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffe5ff9e); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff4fff25); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff560000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0112023b); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02f702c0); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014dfec8); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfbe5f9b3); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf947fb41); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xff7004b9); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x095a0b81); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0a0004d8); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfd65f603); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf144f104); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf5aafdec); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x072b0e5a); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 4600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000001); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00060012); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00200022); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0005ffc1); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff61ff10); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff09ff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x008601d7); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02f50340); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0241fff0); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfcddfa19); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf8e2fa1e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfde30343); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x08790b7f); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0ad50631); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfec7f6fc); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf198f0bd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf50dfd4e); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x06c90e3d); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 4700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000ffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0003000f); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00220030); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0025ffed); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff87ff15); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfed6ff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xffed014c); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02b90386); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03110119); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfdfefac4); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf8c6f92f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfc6701b7); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x07670b44); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0b7e0776); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x002df807); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf200f086); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf477fcb1); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x06650e1e); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 4800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xffff0009); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001e0038); + cx25840_write4(client, DIF_BPF_COEFF67, 0x003f001b); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffbcff36); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfec2feb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff5600a5); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0248038d); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03b00232); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xff39fbab); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf8f4f87f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfb060020); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x062a0ad2); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0bf908a3); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0192f922); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf27df05e); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf3e8fc14); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x06000e00); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 4900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffc0002); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00160037); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00510046); + cx25840_write4(client, DIF_BPF_COEFF89, 0xfff9ff6d); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfed0fe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfecefff0); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x01aa0356); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0413032b); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x007ffcc5); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf96cf812); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf9cefe87); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x04c90a2c); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0c4309b4); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x02f3fa4a); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf30ef046); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf361fb7a); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x059b0de0); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 5000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff9fffa); + cx25840_write4(client, DIF_BPF_COEFF45, 0x000a002d); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00570067); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0037ffb5); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfefffe68); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe62ff3d); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x00ec02e3); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x043503f6); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x01befe05); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfa27f7ee); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf8c6fcf8); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x034c0954); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0c5c0aa4); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x044cfb7e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf3b1f03f); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf2e2fae1); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x05340dc0); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 5100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8fff4); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfffd001e); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0051007b); + cx25840_write4(client, DIF_BPF_COEFF89, 0x006e0006); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff48fe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe1bfe9a); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x001d023e); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x04130488); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x02e6ff5b); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfb1ef812); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf7f7fb7f); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x01bc084e); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0c430b72); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x059afcba); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf467f046); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf26cfa4a); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x04cd0da0); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 5200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8ffef); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff00009); + cx25840_write4(client, DIF_BPF_COEFF67, 0x003f007f); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00980056); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffa5feb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe00fe15); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xff4b0170); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03b004d7); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x03e800b9); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfc48f87f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf768fa23); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0022071f); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0bf90c1b); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x06dafdfd); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf52df05e); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf1fef9b5); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x04640d7f); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 5300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000ffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff9ffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe6fff3); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00250072); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00af009c); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x000cff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe13fdb8); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfe870089); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x031104e1); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x04b8020f); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfd98f92f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf71df8f0); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfe8805ce); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0b7e0c9c); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0808ff44); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf603f086); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf19af922); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x03fb0d5e); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 5400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000001); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffcffef); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe0ffe0); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00050056); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00b000d1); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0071ff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe53fd8c); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfddfff99); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x024104a3); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x054a034d); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xff01fa1e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf717f7ed); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfcf50461); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0ad50cf4); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0921008d); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf6e7f0bd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf13ff891); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x03920d3b); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 5500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010002); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffffff3); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffdeffd1); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffe5002f); + cx25840_write4(client, DIF_BPF_COEFF89, 0x009c00ed); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00cb0000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfebafd94); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd61feb0); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014d0422); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x05970464); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0074fb41); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf759f721); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfb7502de); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0a000d21); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0a2201d4); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf7d9f104); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf0edf804); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x03280d19); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 5600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0003fffa); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe3ffc9); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffc90002); + cx25840_write4(client, DIF_BPF_COEFF89, 0x007500ef); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x010e007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff3dfdcf); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd16fddd); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x00440365); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x059b0548); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x01e3fc90); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf7dff691); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfa0f014d); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x09020d23); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0b0a0318); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf8d7f15a); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf0a5f779); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x02bd0cf6); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 5700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00060001); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffecffc9); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffb4ffd4); + cx25840_write4(client, DIF_BPF_COEFF89, 0x004000d5); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x013600f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xffd3fe39); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd04fd31); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff360277); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x055605ef); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x033efdfe); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf8a5f642); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf8cbffb6); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x07e10cfb); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0bd50456); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf9dff1be); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf067f6f2); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x02520cd2); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 5800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00080009); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff8ffd2); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffaaffac); + cx25840_write4(client, DIF_BPF_COEFF89, 0x000200a3); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x013c014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x006dfec9); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd2bfcb7); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe350165); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x04cb0651); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0477ff7e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf9a5f635); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf7b1fe20); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x069f0ca8); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0c81058b); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfaf0f231); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf033f66d); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x01e60cae); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 5900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0009000e); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0005ffe1); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffacff90); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffc5005f); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x01210184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x00fcff72); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd8afc77); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd51003f); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x04020669); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x05830103); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfad7f66b); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf6c8fc93); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x05430c2b); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0d0d06b5); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfc08f2b2); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf00af5ec); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x017b0c89); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 6000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000001); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00070012); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0012fff5); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffbaff82); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff8e000f); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00e80198); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01750028); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfe18fc75); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc99ff15); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x03050636); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0656027f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfc32f6e2); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf614fb17); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x03d20b87); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0d7707d2); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfd26f341); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xefeaf56f); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x010f0c64); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 6100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0000); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00050012); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001c000b); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffd1ff84); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff66ffbe); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00960184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01cd00da); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfeccfcb2); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc17fdf9); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x01e005bc); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x06e703e4); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfdabf798); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf599f9b3); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x02510abd); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0dbf08df); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfe48f3dc); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xefd5f4f6); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x00a20c3e); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 6200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0002000f); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0021001f); + cx25840_write4(client, DIF_BPF_COEFF67, 0xfff0ff97); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff50ff74); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0034014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01fa0179); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xff97fd2a); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfbd3fcfa); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x00a304fe); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x07310525); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xff37f886); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf55cf86e); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x00c709d0); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0de209db); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xff6df484); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xefcbf481); + cx25840_write4(client, DIF_BPF_COEFF3435, 0x00360c18); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 6300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffe000a); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0021002f); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0010ffb8); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff50ff3b); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffcc00f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01fa01fa); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0069fdd4); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfbd3fc26); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xff5d0407); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x07310638); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x00c9f9a8); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf55cf74e); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xff3908c3); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0de20ac3); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0093f537); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xefcbf410); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xffca0bf2); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 6400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffb0003); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001c0037); + cx25840_write4(client, DIF_BPF_COEFF67, 0x002fffe2); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff66ff17); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff6a007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01cd0251); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0134fea5); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc17fb8b); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfe2002e0); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x06e70713); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x0255faf5); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf599f658); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfdaf0799); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0dbf0b96); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x01b8f5f5); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xefd5f3a3); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xff5e0bca); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 6500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff9fffb); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00120037); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00460010); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff8eff0f); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff180000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01750276); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x01e8ff8d); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc99fb31); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfcfb0198); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x065607ad); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x03cefc64); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf614f592); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfc2e0656); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0d770c52); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x02daf6bd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xefeaf33b); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfef10ba3); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 6600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff7fff5); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0005002f); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0054003c); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffc5ff22); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfedfff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x00fc0267); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0276007e); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd51fb1c); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfbfe003e); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x05830802); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x0529fdec); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf6c8f4fe); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfabd04ff); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0d0d0cf6); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x03f8f78f); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf00af2d7); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfe850b7b); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 6700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000ffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8fff0); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff80020); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00560060); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0002ff4e); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfec4ff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x006d0225); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02d50166); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe35fb4e); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfb35fee1); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0477080e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x065bff82); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf7b1f4a0); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf9610397); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0c810d80); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0510f869); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf033f278); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfe1a0b52); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 6800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010000); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffaffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffec000c); + cx25840_write4(client, DIF_BPF_COEFF67, 0x004c0078); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0040ff8e); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfecafeb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xffd301b6); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02fc0235); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff36fbc5); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfaaafd90); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x033e07d2); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x075b011b); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf8cbf47a); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf81f0224); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0bd50def); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0621f94b); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf067f21e); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfdae0b29); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 6900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010001); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffdffef); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe3fff6); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0037007f); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0075ffdc); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfef2fe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff3d0122); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02ea02dd); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0044fc79); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa65fc5d); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x01e3074e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x082102ad); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfa0ff48c); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf6fe00a9); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0b0a0e43); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0729fa33); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf0a5f1c9); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfd430b00); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 7000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0001fff3); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffdeffe2); + cx25840_write4(client, DIF_BPF_COEFF67, 0x001b0076); + cx25840_write4(client, DIF_BPF_COEFF89, 0x009c002d); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff35fe68); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfeba0076); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x029f0352); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014dfd60); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa69fb53); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x00740688); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08a7042d); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfb75f4d6); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf600ff2d); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0a220e7a); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0827fb22); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf0edf17a); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfcd80ad6); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 7100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0004fff9); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe0ffd2); + cx25840_write4(client, DIF_BPF_COEFF67, 0xfffb005e); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00b0007a); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff8ffe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe53ffc1); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0221038c); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0241fe6e); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfab6fa80); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xff010587); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08e90590); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfcf5f556); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf52bfdb3); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x09210e95); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0919fc15); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf13ff12f); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfc6e0aab); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 7200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00070000); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe6ffc9); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffdb0039); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00af00b8); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfff4feb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe13ff10); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x01790388); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0311ff92); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfb48f9ed); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfd980453); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08e306cd); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfe88f60a); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf482fc40); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x08080e93); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x09fdfd0c); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf19af0ea); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfc050a81); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 7300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00080008); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff0ffc9); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffc1000d); + cx25840_write4(client, DIF_BPF_COEFF89, 0x009800e2); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x005bff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe00fe74); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x00b50345); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03b000bc); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfc18f9a1); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfc4802f9); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x089807dc); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0022f6f0); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf407fada); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x06da0e74); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0ad3fe06); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf1fef0ab); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfb9c0a55); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 7400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000001); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0008000e); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfffdffd0); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffafffdf); + cx25840_write4(client, DIF_BPF_COEFF89, 0x006e00f2); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00b8ff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe1bfdf8); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xffe302c8); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x041301dc); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfd1af99e); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfb1e0183); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x080908b5); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x01bcf801); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf3bdf985); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x059a0e38); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0b99ff03); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf26cf071); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfb330a2a); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 7500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0000); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00070011); + cx25840_write4(client, DIF_BPF_COEFF45, 0x000affdf); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffa9ffb5); + cx25840_write4(client, DIF_BPF_COEFF89, 0x003700e6); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x01010000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe62fda8); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xff140219); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x043502e1); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfe42f9e6); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfa270000); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x073a0953); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x034cf939); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf3a4f845); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x044c0de1); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0c4f0000); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf2e2f03c); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfacc09fe); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 7600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffffffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00040012); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0016fff3); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffafff95); + cx25840_write4(client, DIF_BPF_COEFF89, 0xfff900c0); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0130007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfecefd89); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfe560146); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x041303bc); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xff81fa76); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf96cfe7d); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x063209b1); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x04c9fa93); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf3bdf71e); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x02f30d6e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0cf200fd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf361f00e); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfa6509d1); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 7700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00010010); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001e0008); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffc1ff84); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffbc0084); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x013e00f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff56fd9f); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfdb8005c); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03b00460); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x00c7fb45); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf8f4fd07); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x04fa09ce); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x062afc07); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf407f614); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x01920ce0); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0d8301fa); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf3e8efe5); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xfa0009a4); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 7800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffd000b); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0022001d); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffdbff82); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff870039); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x012a014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xffedfde7); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd47ff6b); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x031104c6); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0202fc4c); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf8c6fbad); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x039909a7); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0767fd8e); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf482f52b); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x002d0c39); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0e0002f4); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf477efc2); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf99b0977); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 7900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffa0004); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0020002d); + cx25840_write4(client, DIF_BPF_COEFF67, 0xfffbff91); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff61ffe8); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00f70184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0086fe5c); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd0bfe85); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x024104e5); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0323fd7d); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf8e2fa79); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x021d093f); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0879ff22); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf52bf465); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfec70b79); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0e6803eb); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf50defa5); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf937094a); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 8000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8fffd); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00190036); + cx25840_write4(client, DIF_BPF_COEFF67, 0x001bffaf); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff4fff99); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00aa0198); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0112fef3); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd09fdb9); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014d04be); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x041bfecc); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf947f978); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x00900897); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x095a00b9); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf600f3c5); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfd650aa3); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0ebc04de); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf5aaef8e); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf8d5091c); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 8100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000ffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff7fff6); + cx25840_write4(client, DIF_BPF_COEFF45, 0x000e0038); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0037ffd7); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff52ff56); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x004b0184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0186ffa1); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd40fd16); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x00440452); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x04de0029); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf9f2f8b2); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfefe07b5); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a05024d); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf6fef34d); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfc0a09b8); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0efa05cd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf64eef7d); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf87308ed); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 8200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010000); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8fff0); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00000031); + cx25840_write4(client, DIF_BPF_COEFF67, 0x004c0005); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff6aff27); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffe4014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01d70057); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfdacfca6); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff3603a7); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x05610184); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfadbf82e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfd74069f); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a7503d6); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf81ff2ff); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfab808b9); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0f2306b5); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf6f9ef72); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf81308bf); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 8300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010001); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffbffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff30022); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00560032); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff95ff10); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff8000f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01fe0106); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfe46fc71); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe3502c7); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x059e02ce); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfbf9f7f2); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfbff055b); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0aa9054c); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf961f2db); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf97507aa); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0f350797); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf7a9ef6d); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf7b40890); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 8400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010002); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffeffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe8000f); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00540058); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffcdff14); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff29007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01f6019e); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xff01fc7c); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd5101bf); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x059203f6); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfd41f7fe); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfaa903f3); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a9e06a9); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfabdf2e2); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf842068b); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0f320871); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf85eef6e); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf7560860); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 8500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0002fff2); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe1fff9); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00460073); + cx25840_write4(client, DIF_BPF_COEFF89, 0x000bff34); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfee90000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01c10215); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xffd0fcc5); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc99009d); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x053d04f1); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfea5f853); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf97d0270); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a5607e4); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfc2ef314); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf723055f); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0f180943); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf919ef75); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf6fa0830); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 8600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0005fff8); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffdeffe4); + cx25840_write4(client, DIF_BPF_COEFF67, 0x002f007f); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0048ff6b); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfec7ff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0163025f); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x00a2fd47); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc17ff73); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x04a405b2); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0017f8ed); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf88500dc); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x09d208f9); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfdaff370); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf61c0429); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0ee80a0b); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xf9d8ef82); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf6a00800); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 8700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0007ffff); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe1ffd4); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0010007a); + cx25840_write4(client, DIF_BPF_COEFF89, 0x007cffb2); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfec6ff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x00e60277); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0168fdf9); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfbd3fe50); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x03ce0631); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0188f9c8); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf7c7ff43); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x091509e3); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xff39f3f6); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf52d02ea); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0ea30ac9); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfa9bef95); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf64607d0); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 8800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00090007); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe9ffca); + cx25840_write4(client, DIF_BPF_COEFF67, 0xfff00065); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00a10003); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfee6feb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0053025b); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0213fed0); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfbd3fd46); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x02c70668); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x02eafadb); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf74bfdae); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x08230a9c); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x00c7f4a3); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf45b01a6); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0e480b7c); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfb61efae); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf5ef079f); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 8900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0000); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0008000d); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff5ffc8); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffd10043); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00b20053); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff24fe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xffb9020c); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0295ffbb); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc17fc64); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x019b0654); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x042dfc1c); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf714fc2a); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x07020b21); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0251f575); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf3a7005e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0dd80c24); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfc2aefcd); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf599076e); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 9000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffffffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00060011); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0002ffcf); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffba0018); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00ad009a); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff79fe68); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff260192); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02e500ab); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc99fbb6); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x005b05f7); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0545fd81); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf723fabf); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x05b80b70); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x03d2f669); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf313ff15); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0d550cbf); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfcf6eff2); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf544073d); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 9100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00030012); + cx25840_write4(client, DIF_BPF_COEFF45, 0x000fffdd); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffacffea); + cx25840_write4(client, DIF_BPF_COEFF89, 0x009300cf); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffdcfe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfea600f7); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02fd0190); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd51fb46); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xff150554); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0627fefd); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf778f978); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x044d0b87); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0543f77d); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf2a0fdcf); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0cbe0d4e); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfdc4f01d); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf4f2070b); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 9200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00000010); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001afff0); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffaaffbf); + cx25840_write4(client, DIF_BPF_COEFF89, 0x006700ed); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0043feb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe460047); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02db0258); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe35fb1b); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfddc0473); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x06c90082); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf811f85e); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x02c90b66); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x069ff8ad); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf250fc8d); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0c140dcf); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfe93f04d); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf4a106d9); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 9300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffc000c); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00200006); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffb4ff9c); + cx25840_write4(client, DIF_BPF_COEFF89, 0x002f00ef); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00a4ff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe0dff92); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x028102f7); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff36fb37); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfcbf035e); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x07260202); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf8e8f778); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x01340b0d); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x07e1f9f4); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf223fb51); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0b590e42); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xff64f083); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf45206a7); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 9400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff90005); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0022001a); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffc9ff86); + cx25840_write4(client, DIF_BPF_COEFF89, 0xfff000d7); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00f2ff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe01fee5); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x01f60362); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0044fb99); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfbcc0222); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x07380370); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf9f7f6cc); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xff990a7e); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0902fb50); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf21afa1f); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0a8d0ea6); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0034f0bf); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf4050675); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 9500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8fffe); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001e002b); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffe5ff81); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffb400a5); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x01280000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe24fe50); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x01460390); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014dfc3a); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfb1000ce); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x070104bf); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfb37f65f); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfe0009bc); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0a00fcbb); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf235f8f8); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x09b20efc); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0105f101); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf3ba0642); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 9600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0001ffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8fff7); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00150036); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0005ff8c); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff810061); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x013d007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe71fddf); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x007c0380); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0241fd13); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa94ff70); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x068005e2); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfc9bf633); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfc7308ca); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0ad5fe30); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf274f7e0); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x08c90f43); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x01d4f147); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf371060f); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 9700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010001); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff9fff1); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00090038); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0025ffa7); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff5e0012); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x013200f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfee3fd9b); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xffaa0331); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0311fe15); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa60fe18); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x05bd06d1); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfe1bf64a); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfafa07ae); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0b7effab); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf2d5f6d7); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x07d30f7a); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x02a3f194); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf32905dc); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 9800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010002); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffcffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfffb0032); + cx25840_write4(client, DIF_BPF_COEFF67, 0x003fffcd); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff4effc1); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0106014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff6efd8a); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfedd02aa); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03b0ff34); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa74fcd7); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x04bf0781); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xffaaf6a3); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf99e066b); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0bf90128); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf359f5e1); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x06d20fa2); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0370f1e5); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf2e405a8); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 9900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0xffffffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffef0024); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0051fffa); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff54ff77); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00be0184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0006fdad); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfe2701f3); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0413005e); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfad1fbba); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x039007ee); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x013bf73d); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf868050a); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0c4302a1); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf3fdf4fe); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x05c70fba); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x043bf23c); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf2a10575); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 10000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0003fff1); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe50011); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00570027); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff70ff3c); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00620198); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x009efe01); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd95011a); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x04350183); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfb71fad0); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x023c0812); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x02c3f811); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf75e0390); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0c5c0411); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf4c1f432); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x04b30fc1); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0503f297); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf2610541); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 10100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0006fff7); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffdffffc); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00510050); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff9dff18); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfffc0184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0128fe80); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd32002e); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x04130292); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfc4dfa21); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x00d107ee); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x0435f91c); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf6850205); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0c430573); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf5a1f37d); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x03990fba); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x05c7f2f8); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf222050d); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 10200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0008fffe); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffdfffe7); + cx25840_write4(client, DIF_BPF_COEFF67, 0x003f006e); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffd6ff0f); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff96014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0197ff1f); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd05ff3e); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03b0037c); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfd59f9b7); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xff5d0781); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x0585fa56); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf5e4006f); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0bf906c4); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf69df2e0); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x02790fa2); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0688f35d); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf1e604d8); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 10300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0001); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00090005); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe4ffd6); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0025007e); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0014ff20); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff3c00f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01e1ffd0); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd12fe5c); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03110433); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfe88f996); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfdf106d1); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x06aafbb7); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf57efed8); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0b7e07ff); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf7b0f25e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x01560f7a); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0745f3c7); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf1ac04a4); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 10400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffffffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0008000c); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffedffcb); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0005007d); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0050ff4c); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfef6007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01ff0086); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd58fd97); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x024104ad); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xffcaf9c0); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfc9905e2); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x079afd35); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf555fd46); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0ad50920); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf8d9f1f6); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x00310f43); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x07fdf435); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf174046f); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 10500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xfffffffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00050011); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfffaffc8); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffe5006b); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0082ff8c); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfecc0000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01f00130); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfdd2fcfc); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014d04e3); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x010efa32); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfb6404bf); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x084efec5); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf569fbc2); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0a000a23); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfa15f1ab); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xff0b0efc); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x08b0f4a7); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf13f043a); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 10600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00020012); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0007ffcd); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffc9004c); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00a4ffd9); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfec3ff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01b401c1); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfe76fc97); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x004404d2); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0245fae8); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfa5f0370); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08c1005f); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf5bcfa52); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x09020b04); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfb60f17b); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfde70ea6); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x095df51e); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf10c0405); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 10700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xffff0011); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0014ffdb); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffb40023); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00b2002a); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfedbff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0150022d); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xff38fc6f); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff36047b); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x035efbda); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf9940202); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08ee01f5); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf649f8fe); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x07e10bc2); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfcb6f169); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfcc60e42); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0a04f599); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf0db03d0); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 10800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffb000d); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001dffed); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffaafff5); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00aa0077); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff13feb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x00ce026b); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x000afc85); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe3503e3); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x044cfcfb); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf90c0082); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08d5037f); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf710f7cc); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x069f0c59); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfe16f173); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfbaa0dcf); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0aa5f617); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf0ad039b); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 10900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff90006); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00210003); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffacffc8); + cx25840_write4(client, DIF_BPF_COEFF89, 0x008e00b6); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff63fe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x003a0275); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x00dafcda); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd510313); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0501fe40); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf8cbfefd); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x087604f0); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf80af6c2); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x05430cc8); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xff7af19a); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfa940d4e); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0b3ff699); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf0810365); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 11000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0001ffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8ffff); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00210018); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffbaffa3); + cx25840_write4(client, DIF_BPF_COEFF89, 0x006000e1); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffc4fe68); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xffa0024b); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x019afd66); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc990216); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0575ff99); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf8d4fd81); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x07d40640); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf932f5e6); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x03d20d0d); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x00dff1de); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf9860cbf); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0bd1f71e); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf058032f); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 11100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010000); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8fff8); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001b0029); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffd1ff8a); + cx25840_write4(client, DIF_BPF_COEFF89, 0x002600f2); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x002cfe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff0f01f0); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x023bfe20); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc1700fa); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x05a200f7); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf927fc1c); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x06f40765); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfa82f53b); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x02510d27); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0243f23d); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf8810c24); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0c5cf7a7); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf03102fa); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 11200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010002); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffafff2); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00110035); + cx25840_write4(client, DIF_BPF_COEFF67, 0xfff0ff81); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffe700e7); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x008ffeb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe94016d); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02b0fefb); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfbd3ffd1); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x05850249); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf9c1fadb); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x05de0858); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfbf2f4c4); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x00c70d17); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x03a0f2b8); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf7870b7c); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0cdff833); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf00d02c4); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 11300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffdffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00040038); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0010ff88); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffac00c2); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00e2ff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe3900cb); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02f1ffe9); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfbd3feaa); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x05210381); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfa9cf9c8); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x04990912); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfd7af484); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xff390cdb); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x04f4f34d); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf69a0ac9); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0d5af8c1); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xefec028e); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 11400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0000ffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff60033); + cx25840_write4(client, DIF_BPF_COEFF67, 0x002fff9f); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff7b0087); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x011eff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe080018); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02f900d8); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc17fd96); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x04790490); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfbadf8ed); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x032f098e); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xff10f47d); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfdaf0c75); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x063cf3fc); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf5ba0a0b); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0dccf952); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xefcd0258); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 11500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0004fff1); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffea0026); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0046ffc3); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff5a003c); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x013b0000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe04ff63); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02c801b8); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc99fca6); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0397056a); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfcecf853); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x01ad09c9); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x00acf4ad); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfc2e0be7); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0773f4c2); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf4e90943); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0e35f9e6); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xefb10221); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 11600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0007fff6); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe20014); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0054ffee); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff4effeb); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0137007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe2efebb); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0260027a); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd51fbe6); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x02870605); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfe4af7fe); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x001d09c1); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0243f515); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfabd0b32); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0897f59e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf4280871); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0e95fa7c); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef9701eb); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 11700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0001); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0008fffd); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffdeffff); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0056001d); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff57ff9c); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x011300f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe82fe2e); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x01ca0310); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe35fb62); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0155065a); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xffbaf7f2); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfe8c0977); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x03cef5b2); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf9610a58); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x09a5f68f); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf3790797); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0eebfb14); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef8001b5); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 11800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0000); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00080004); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe0ffe9); + cx25840_write4(client, DIF_BPF_COEFF67, 0x004c0047); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff75ff58); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00d1014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfef9fdc8); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0111036f); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff36fb21); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x00120665); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x012df82e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfd0708ec); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0542f682); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf81f095c); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0a9af792); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf2db06b5); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0f38fbad); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef6c017e); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 11900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffffffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0007000b); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe7ffd8); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00370068); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffa4ff28); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00790184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff87fd91); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x00430392); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0044fb26); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfece0626); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0294f8b2); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfb990825); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0698f77f); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf6fe0842); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0b73f8a7); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf25105cd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0f7bfc48); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef5a0148); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 12000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00050010); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff2ffcc); + cx25840_write4(client, DIF_BPF_COEFF67, 0x001b007b); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffdfff10); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00140198); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0020fd8e); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xff710375); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014dfb73); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfd9a059f); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x03e0f978); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfa4e0726); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x07c8f8a7); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf600070c); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0c2ff9c9); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf1db04de); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0fb4fce5); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef4b0111); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 12100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00010012); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffffffc8); + cx25840_write4(client, DIF_BPF_COEFF67, 0xfffb007e); + cx25840_write4(client, DIF_BPF_COEFF89, 0x001dff14); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffad0184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x00b7fdbe); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfea9031b); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0241fc01); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfc8504d6); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0504fa79); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf93005f6); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x08caf9f2); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf52b05c0); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0ccbfaf9); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf17903eb); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0fe3fd83); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef3f00db); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 12200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffe0011); + cx25840_write4(client, DIF_BPF_COEFF45, 0x000cffcc); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffdb0071); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0058ff32); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff4f014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x013cfe1f); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfdfb028a); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0311fcc9); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfb9d03d6); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x05f4fbad); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf848049d); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0999fb5b); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf4820461); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0d46fc32); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf12d02f4); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x1007fe21); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef3600a4); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 12300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffa000e); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0017ffd9); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffc10055); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0088ff68); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff0400f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01a6fea7); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd7501cc); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03b0fdc0); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfaef02a8); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x06a7fd07); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf79d0326); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a31fcda); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf40702f3); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0d9ffd72); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf0f601fa); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x1021fec0); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef2f006d); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 12400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0001ffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff80007); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001fffeb); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffaf002d); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00a8ffb0); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfed3007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01e9ff4c); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd2000ee); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0413fed8); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa82015c); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0715fe7d); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf7340198); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a8dfe69); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf3bd017c); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0dd5feb8); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf0d500fd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x1031ff60); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef2b0037); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 12500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010000); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff70000); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00220000); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffa90000); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00b30000); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfec20000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x02000000); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd030000); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x04350000); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa5e0000); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x073b0000); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf7110000); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0aac0000); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf3a40000); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0de70000); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf0c90000); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x10360000); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef290000); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 12600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010001); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff8fff9); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001f0015); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffafffd3); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00a80050); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfed3ff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01e900b4); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd20ff12); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x04130128); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa82fea4); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x07150183); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf734fe68); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a8d0197); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf3bdfe84); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0dd50148); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf0d5ff03); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x103100a0); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef2bffc9); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 12700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffafff2); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00170027); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffc1ffab); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00880098); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff04ff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01a60159); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd75fe34); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03b00240); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfaeffd58); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x06a702f9); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf79dfcda); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0a310326); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf407fd0d); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0d9f028e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf0f6fe06); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x10210140); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef2fff93); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 12800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffeffef); + cx25840_write4(client, DIF_BPF_COEFF45, 0x000c0034); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffdbff8f); + cx25840_write4(client, DIF_BPF_COEFF89, 0x005800ce); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff4ffeb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x013c01e1); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfdfbfd76); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03110337); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfb9dfc2a); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x05f40453); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf848fb63); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x099904a5); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf482fb9f); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0d4603ce); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf12dfd0c); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x100701df); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef36ff5c); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 12900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0001ffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffff0038); + cx25840_write4(client, DIF_BPF_COEFF67, 0xfffbff82); + cx25840_write4(client, DIF_BPF_COEFF89, 0x001d00ec); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffadfe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x00b70242); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfea9fce5); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x024103ff); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfc85fb2a); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x05040587); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf930fa0a); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x08ca060e); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf52bfa40); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0ccb0507); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf179fc15); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0fe3027d); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef3fff25); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 13000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0005fff0); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff20034); + cx25840_write4(client, DIF_BPF_COEFF67, 0x001bff85); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffdf00f0); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0014fe68); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x00200272); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xff71fc8b); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014d048d); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfd9afa61); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x03e00688); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfa4ef8da); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x07c80759); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf600f8f4); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0c2f0637); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf1dbfb22); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0fb4031b); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef4bfeef); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 13100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0001); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0007fff5); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe70028); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0037ff98); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffa400d8); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0079fe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff87026f); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0043fc6e); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x004404da); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfecef9da); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0294074e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfb99f7db); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x06980881); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf6fef7be); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0b730759); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf251fa33); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0f7b03b8); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef5afeb8); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 13200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0000); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0008fffc); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe00017); + cx25840_write4(client, DIF_BPF_COEFF67, 0x004cffb9); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff7500a8); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00d1feb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfef90238); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0111fc91); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff3604df); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0012f99b); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x012d07d2); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfd07f714); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0542097e); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf81ff6a4); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x0a9a086e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf2dbf94b); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0f380453); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef6cfe82); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 13300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffffffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00080003); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffde0001); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0056ffe3); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff570064); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0113ff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe8201d2); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x01cafcf0); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe35049e); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0155f9a6); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xffba080e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfe8cf689); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x03ce0a4e); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xf961f5a8); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x09a50971); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf379f869); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0eeb04ec); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef80fe4b); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 13400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0007000a); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe2ffec); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00540012); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff4e0015); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0137ff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe2e0145); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0260fd86); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd51041a); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0287f9fb); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfe4a0802); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x001df63f); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x02430aeb); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfabdf4ce); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x08970a62); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf428f78f); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0e950584); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xef97fe15); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 13500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0004000f); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffeaffda); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0046003d); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff5affc4); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x013b0000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe04009d); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02c8fe48); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc99035a); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0397fa96); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfcec07ad); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x01adf637); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x00ac0b53); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfc2ef419); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x07730b3e); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf4e9f6bd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0e35061a); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xefb1fddf); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 13600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00000012); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfff6ffcd); + cx25840_write4(client, DIF_BPF_COEFF67, 0x002f0061); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff7bff79); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x011e007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe08ffe8); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02f9ff28); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc17026a); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0479fb70); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfbad0713); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x032ff672); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xff100b83); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xfdaff38b); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x063c0c04); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf5baf5f5); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0dcc06ae); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xefcdfda8); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 13700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffd0012); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0004ffc8); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00100078); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffacff3e); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00e200f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe39ff35); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02f10017); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfbd30156); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0521fc7f); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfa9c0638); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x0499f6ee); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfd7a0b7c); + cx25840_write4(client, DIF_BPF_COEFF2627, 0xff39f325); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x04f40cb3); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf69af537); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0d5a073f); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xefecfd72); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 13800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0001fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffa000e); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0011ffcb); + cx25840_write4(client, DIF_BPF_COEFF67, 0xfff0007f); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffe7ff19); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x008f014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe94fe93); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02b00105); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfbd3002f); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x0585fdb7); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf9c10525); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x05def7a8); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfbf20b3c); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x00c7f2e9); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x03a00d48); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf787f484); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0cdf07cd); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf00dfd3c); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 13900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010000); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff80008); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001bffd7); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffd10076); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0026ff0e); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x002c0184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff0ffe10); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x023b01e0); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc17ff06); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x05a2ff09); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf92703e4); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x06f4f89b); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfa820ac5); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0251f2d9); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x02430dc3); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf881f3dc); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0c5c0859); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf031fd06); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 14000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010001); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff80001); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0021ffe8); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffba005d); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0060ff1f); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffc40198); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xffa0fdb5); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x019a029a); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc99fdea); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x05750067); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf8d4027f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x07d4f9c0); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf9320a1a); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x03d2f2f3); + cx25840_write4(client, DIF_BPF_COEFF2829, 0x00df0e22); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xf986f341); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0bd108e2); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf058fcd1); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 14100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff9fffa); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0021fffd); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffac0038); + cx25840_write4(client, DIF_BPF_COEFF89, 0x008eff4a); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff630184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x003afd8b); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x00da0326); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd51fced); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x050101c0); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf8cb0103); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x0876fb10); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf80a093e); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0543f338); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xff7a0e66); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfa94f2b2); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0b3f0967); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf081fc9b); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 14200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffbfff3); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001d0013); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffaa000b); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00aaff89); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff13014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x00cefd95); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x000a037b); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe35fc1d); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x044c0305); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf90cff7e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08d5fc81); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf7100834); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x069ff3a7); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfe160e8d); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfbaaf231); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0aa509e9); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf0adfc65); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 14300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0xffffffef); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00140025); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffb4ffdd); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00b2ffd6); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfedb00f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x0150fdd3); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xff380391); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff36fb85); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x035e0426); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xf994fdfe); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08eefe0b); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf6490702); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x07e1f43e); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfcb60e97); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfcc6f1be); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x0a040a67); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf0dbfc30); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 14400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0002ffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00070033); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffc9ffb4); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00a40027); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfec3007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01b4fe3f); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfe760369); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0044fb2e); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x02450518); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfa5ffc90); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x08c1ffa1); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf5bc05ae); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0902f4fc); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfb600e85); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xfde7f15a); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x095d0ae2); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf10cfbfb); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 14500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0005ffef); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfffa0038); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffe5ff95); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00820074); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfecc0000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01f0fed0); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfdd20304); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014dfb1d); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x010e05ce); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfb64fb41); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x084e013b); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf569043e); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0a00f5dd); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xfa150e55); + cx25840_write4(client, DIF_BPF_COEFF3031, 0xff0bf104); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x08b00b59); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf13ffbc6); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 14600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0001); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0008fff4); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffed0035); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0005ff83); + cx25840_write4(client, DIF_BPF_COEFF89, 0x005000b4); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfef6ff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01ffff7a); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd580269); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0241fb53); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xffca0640); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfc99fa1e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x079a02cb); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf55502ba); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0ad5f6e0); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf8d90e0a); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0031f0bd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x07fd0bcb); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf174fb91); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 14700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffffffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0009fffb); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe4002a); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0025ff82); + cx25840_write4(client, DIF_BPF_COEFF89, 0x001400e0); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff3cff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01e10030); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd1201a4); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0311fbcd); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfe88066a); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xfdf1f92f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x06aa0449); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf57e0128); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0b7ef801); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf7b00da2); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0156f086); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x07450c39); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf1acfb5c); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 14800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00080002); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffdf0019); + cx25840_write4(client, DIF_BPF_COEFF67, 0x003fff92); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffd600f1); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff96feb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x019700e1); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd0500c2); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03b0fc84); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfd590649); + cx25840_write4(client, DIF_BPF_COEFF2021, 0xff5df87f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x058505aa); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf5e4ff91); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0bf9f93c); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf69d0d20); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0279f05e); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x06880ca3); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf1e6fb28); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 14900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0x00060009); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffdf0004); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0051ffb0); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff9d00e8); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xfffcfe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x01280180); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd32ffd2); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0413fd6e); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfc4d05df); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x00d1f812); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x043506e4); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf685fdfb); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0c43fa8d); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf5a10c83); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0399f046); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x05c70d08); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf222faf3); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 15000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0003000f); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffe5ffef); + cx25840_write4(client, DIF_BPF_COEFF67, 0x0057ffd9); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff7000c4); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0062fe68); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x009e01ff); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfd95fee6); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0435fe7d); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfb710530); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x023cf7ee); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x02c307ef); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf75efc70); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0c5cfbef); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf4c10bce); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x04b3f03f); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x05030d69); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf261fabf); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 15100000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0000fffd); + cx25840_write4(client, DIF_BPF_COEFF23, 0xffff0012); + cx25840_write4(client, DIF_BPF_COEFF45, 0xffefffdc); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00510006); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff540089); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00befe7c); + cx25840_write4(client, DIF_BPF_COEFF1213, 0x00060253); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfe27fe0d); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x0413ffa2); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfad10446); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0390f812); + cx25840_write4(client, DIF_BPF_COEFF2223, 0x013b08c3); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf868faf6); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0c43fd5f); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf3fd0b02); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x05c7f046); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x043b0dc4); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf2a1fa8b); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 15200000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0001fffe); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffc0012); + cx25840_write4(client, DIF_BPF_COEFF45, 0xfffbffce); + cx25840_write4(client, DIF_BPF_COEFF67, 0x003f0033); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff4e003f); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0106feb6); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff6e0276); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xfeddfd56); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x03b000cc); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa740329); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x04bff87f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xffaa095d); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xf99ef995); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0bf9fed8); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf3590a1f); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x06d2f05e); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x03700e1b); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf2e4fa58); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 15300000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x0001ffff); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff9000f); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0009ffc8); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00250059); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff5effee); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0132ff10); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfee30265); + cx25840_write4(client, DIF_BPF_COEFF1415, 0xffaafccf); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x031101eb); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa6001e8); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x05bdf92f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfe1b09b6); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfafaf852); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0b7e0055); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf2d50929); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x07d3f086); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x02a30e6c); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf329fa24); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 15400000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00010001); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff80009); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0015ffca); + cx25840_write4(client, DIF_BPF_COEFF67, 0x00050074); + cx25840_write4(client, DIF_BPF_COEFF89, 0xff81ff9f); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x013dff82); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe710221); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x007cfc80); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x024102ed); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfa940090); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0680fa1e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfc9b09cd); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfc73f736); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0ad501d0); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf2740820); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x08c9f0bd); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x01d40eb9); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf371f9f1); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 15500000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000002); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff80002); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001effd5); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffe5007f); + cx25840_write4(client, DIF_BPF_COEFF89, 0xffb4ff5b); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x01280000); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe2401b0); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0146fc70); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x014d03c6); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfb10ff32); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0701fb41); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xfb3709a1); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xfe00f644); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x0a000345); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf2350708); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x09b2f104); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x01050eff); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf3baf9be); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 15600000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfff9fffb); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0022ffe6); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffc9007a); + cx25840_write4(client, DIF_BPF_COEFF89, 0xfff0ff29); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00f2007e); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe01011b); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x01f6fc9e); + cx25840_write4(client, DIF_BPF_COEFF1617, 0x00440467); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfbccfdde); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0738fc90); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf9f70934); + cx25840_write4(client, DIF_BPF_COEFF2425, 0xff99f582); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x090204b0); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf21a05e1); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0a8df15a); + cx25840_write4(client, DIF_BPF_COEFF3233, 0x00340f41); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf405f98b); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 15700000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0xfffcfff4); + cx25840_write4(client, DIF_BPF_COEFF45, 0x0020fffa); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffb40064); + cx25840_write4(client, DIF_BPF_COEFF89, 0x002fff11); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x00a400f0); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe0d006e); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x0281fd09); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xff3604c9); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfcbffca2); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0726fdfe); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf8e80888); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x0134f4f3); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x07e1060c); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf22304af); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0b59f1be); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xff640f7d); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf452f959); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 15800000: + cx25840_write4(client, DIF_BPF_COEFF01, 0x00000003); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0000fff0); + cx25840_write4(client, DIF_BPF_COEFF45, 0x001a0010); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffaa0041); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0067ff13); + cx25840_write4(client, DIF_BPF_COEFF1011, 0x0043014a); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfe46ffb9); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02dbfda8); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfe3504e5); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xfddcfb8d); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x06c9ff7e); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf81107a2); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x02c9f49a); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x069f0753); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf2500373); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0c14f231); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfe930fb3); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf4a1f927); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 15900000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0002); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0003ffee); + cx25840_write4(client, DIF_BPF_COEFF45, 0x000f0023); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffac0016); + cx25840_write4(client, DIF_BPF_COEFF89, 0x0093ff31); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xffdc0184); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xfea6ff09); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02fdfe70); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfd5104ba); + cx25840_write4(client, DIF_BPF_COEFF1819, 0xff15faac); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x06270103); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf7780688); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x044df479); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x05430883); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf2a00231); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0cbef2b2); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfdc40fe3); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf4f2f8f5); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + + case 16000000: + cx25840_write4(client, DIF_BPF_COEFF01, 0xffff0001); + cx25840_write4(client, DIF_BPF_COEFF23, 0x0006ffef); + cx25840_write4(client, DIF_BPF_COEFF45, 0x00020031); + cx25840_write4(client, DIF_BPF_COEFF67, 0xffbaffe8); + cx25840_write4(client, DIF_BPF_COEFF89, 0x00adff66); + cx25840_write4(client, DIF_BPF_COEFF1011, 0xff790198); + cx25840_write4(client, DIF_BPF_COEFF1213, 0xff26fe6e); + cx25840_write4(client, DIF_BPF_COEFF1415, 0x02e5ff55); + cx25840_write4(client, DIF_BPF_COEFF1617, 0xfc99044a); + cx25840_write4(client, DIF_BPF_COEFF1819, 0x005bfa09); + cx25840_write4(client, DIF_BPF_COEFF2021, 0x0545027f); + cx25840_write4(client, DIF_BPF_COEFF2223, 0xf7230541); + cx25840_write4(client, DIF_BPF_COEFF2425, 0x05b8f490); + cx25840_write4(client, DIF_BPF_COEFF2627, 0x03d20997); + cx25840_write4(client, DIF_BPF_COEFF2829, 0xf31300eb); + cx25840_write4(client, DIF_BPF_COEFF3031, 0x0d55f341); + cx25840_write4(client, DIF_BPF_COEFF3233, 0xfcf6100e); + cx25840_write4(client, DIF_BPF_COEFF3435, 0xf544f8c3); + cx25840_write4(client, DIF_BPF_COEFF36, 0x110d0000); + break; + } +} + +static void cx23888_std_setup(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + v4l2_std_id std = state->std; + u32 ifHz; + + cx25840_write4(client, 0x478, 0x6628021F); + cx25840_write4(client, 0x400, 0x0); + cx25840_write4(client, 0x4b4, 0x20524030); + cx25840_write4(client, 0x47c, 0x010a8263); + + if (std & V4L2_STD_525_60) { + v4l_dbg(1, cx25840_debug, client, "%s() Selecting NTSC", + __func__); + + /* Horiz / vert timing */ + cx25840_write4(client, 0x428, 0x1e1e601a); + cx25840_write4(client, 0x424, 0x5b2d007a); + + /* DIF NTSC */ + cx25840_write4(client, 0x304, 0x6503bc0c); + cx25840_write4(client, 0x308, 0xbd038c85); + cx25840_write4(client, 0x30c, 0x1db4640a); + cx25840_write4(client, 0x310, 0x00008800); + cx25840_write4(client, 0x314, 0x44400400); + cx25840_write4(client, 0x32c, 0x0c800800); + cx25840_write4(client, 0x330, 0x27000100); + cx25840_write4(client, 0x334, 0x1f296e1f); + cx25840_write4(client, 0x338, 0x009f50c1); + cx25840_write4(client, 0x340, 0x1befbf06); + cx25840_write4(client, 0x344, 0x000035e8); + + /* DIF I/F */ + ifHz = 5400000; + + } else { + v4l_dbg(1, cx25840_debug, client, "%s() Selecting PAL-BG", + __func__); + + /* Horiz / vert timing */ + cx25840_write4(client, 0x428, 0x28244024); + cx25840_write4(client, 0x424, 0x5d2d0084); + + /* DIF */ + cx25840_write4(client, 0x304, 0x6503bc0c); + cx25840_write4(client, 0x308, 0xbd038c85); + cx25840_write4(client, 0x30c, 0x1db4640a); + cx25840_write4(client, 0x310, 0x00008800); + cx25840_write4(client, 0x314, 0x44400600); + cx25840_write4(client, 0x32c, 0x0c800800); + cx25840_write4(client, 0x330, 0x27000100); + cx25840_write4(client, 0x334, 0x213530ec); + cx25840_write4(client, 0x338, 0x00a65ba8); + cx25840_write4(client, 0x340, 0x1befbf06); + cx25840_write4(client, 0x344, 0x000035e8); + + /* DIF I/F */ + ifHz = 6000000; + } + + cx23885_dif_setup(client, ifHz); + + /* Explicitly ensure the inputs are reconfigured after + * a standard change. + */ + set_input(client, state->vid_input, state->aud_input); +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops cx25840_ctrl_ops = { + .s_ctrl = cx25840_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops cx25840_core_ops = { + .log_status = cx25840_log_status, + .reset = cx25840_reset, + /* calling the (optional) init op will turn on the generic mode */ + .init = cx25840_init, + .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, +#endif + .interrupt_service_routine = cx25840_irq_handler, +}; + +static const struct v4l2_subdev_tuner_ops cx25840_tuner_ops = { + .s_frequency = cx25840_s_frequency, + .s_radio = cx25840_s_radio, + .g_tuner = cx25840_g_tuner, + .s_tuner = cx25840_s_tuner, +}; + +static const struct v4l2_subdev_audio_ops cx25840_audio_ops = { + .s_clock_freq = cx25840_s_clock_freq, + .s_routing = cx25840_s_audio_routing, + .s_stream = cx25840_s_audio_stream, +}; + +static const struct v4l2_subdev_video_ops cx25840_video_ops = { + .g_std = cx25840_g_std, + .s_std = cx25840_s_std, + .querystd = cx25840_querystd, + .s_routing = cx25840_s_video_routing, + .s_stream = cx25840_s_stream, + .g_input_status = cx25840_g_input_status, +}; + +static const struct v4l2_subdev_vbi_ops cx25840_vbi_ops = { + .decode_vbi_line = cx25840_decode_vbi_line, + .s_raw_fmt = cx25840_s_raw_fmt, + .s_sliced_fmt = cx25840_s_sliced_fmt, + .g_sliced_fmt = cx25840_g_sliced_fmt, +}; + +static const struct v4l2_subdev_pad_ops cx25840_pad_ops = { + .set_fmt = cx25840_set_fmt, +}; + +static const struct v4l2_subdev_ops cx25840_ops = { + .core = &cx25840_core_ops, + .tuner = &cx25840_tuner_ops, + .audio = &cx25840_audio_ops, + .video = &cx25840_video_ops, + .vbi = &cx25840_vbi_ops, + .pad = &cx25840_pad_ops, + .ir = &cx25840_ir_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static u32 get_cx2388x_ident(struct i2c_client *client) +{ + u32 ret; + + /* Come out of digital power down */ + cx25840_write(client, 0x000, 0); + + /* + * Detecting whether the part is cx23885/7/8 is more + * difficult than it needs to be. No ID register. Instead we + * probe certain registers indicated in the datasheets to look + * for specific defaults that differ between the silicon designs. + */ + + /* It's either 885/7 if the IR Tx Clk Divider register exists */ + if (cx25840_read4(client, 0x204) & 0xffff) { + /* + * CX23885 returns bogus repetitive byte values for the DIF, + * which doesn't exist for it. (Ex. 8a8a8a8a or 31313131) + */ + ret = cx25840_read4(client, 0x300); + if (((ret & 0xffff0000) >> 16) == (ret & 0xffff)) { + /* No DIF */ + ret = CX23885_AV; + } else { + /* + * CX23887 has a broken DIF, but the registers + * appear valid (but unused), good enough to detect. + */ + ret = CX23887_AV; + } + } else if (cx25840_read4(client, 0x300) & 0x0fffffff) { + /* DIF PLL Freq Word reg exists; chip must be a CX23888 */ + ret = CX23888_AV; + } else { + v4l_err(client, "Unable to detect h/w, assuming cx23887\n"); + ret = CX23887_AV; + } + + /* Back into digital power down */ + cx25840_write(client, 0x000, 2); + return ret; +} + +static int cx25840_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct cx25840_state *state; + struct v4l2_subdev *sd; + int default_volume; + u32 id; + u16 device_id; +#if defined(CONFIG_MEDIA_CONTROLLER) + int ret; +#endif + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_dbg(1, cx25840_debug, client, + "detecting cx25840 client on address 0x%x\n", + client->addr << 1); + + device_id = cx25840_read(client, 0x101) << 8; + device_id |= cx25840_read(client, 0x100); + v4l_dbg(1, cx25840_debug, client, "device_id = 0x%04x\n", device_id); + + /* + * The high byte of the device ID should be + * 0x83 for the cx2583x and 0x84 for the cx2584x + */ + if ((device_id & 0xff00) == 0x8300) { + id = CX25836 + ((device_id >> 4) & 0xf) - 6; + } else if ((device_id & 0xff00) == 0x8400) { + id = CX25840 + ((device_id >> 4) & 0xf); + } else if (device_id == 0x0000) { + id = get_cx2388x_ident(client); + } else if ((device_id & 0xfff0) == 0x5A30) { + /* The CX23100 (0x5A3C = 23100) doesn't have an A/V decoder */ + id = CX2310X_AV; + } else if ((device_id & 0xff) == (device_id >> 8)) { + v4l_err(client, + "likely a confused/unresponsive cx2388[578] A/V decoder found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + v4l_err(client, + "A method to reset it from the cx25840 driver software is not known at this time\n"); + return -ENODEV; + } else { + v4l_dbg(1, cx25840_debug, client, "cx25840 not found\n"); + return -ENODEV; + } + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &cx25840_ops); +#if defined(CONFIG_MEDIA_CONTROLLER) + /* + * TODO: add media controller support for analog video inputs like + * composite, svideo, etc. + * A real input pad for this analog demod would be like: + * ___________ + * TUNER --------> | | + * | | + * SVIDEO .......> | cx25840 | + * | | + * COMPOSITE1 ...> |_________| + * + * However, at least for now, there's no much gain on modelling + * those extra inputs. So, let's add it only when needed. + */ + state->pads[CX25840_PAD_INPUT].flags = MEDIA_PAD_FL_SINK; + state->pads[CX25840_PAD_INPUT].sig_type = PAD_SIGNAL_ANALOG; + state->pads[CX25840_PAD_VID_OUT].flags = MEDIA_PAD_FL_SOURCE; + state->pads[CX25840_PAD_VID_OUT].sig_type = PAD_SIGNAL_DV; + sd->entity.function = MEDIA_ENT_F_ATV_DECODER; + + ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(state->pads), + state->pads); + if (ret < 0) { + v4l_info(client, "failed to initialize media entity!\n"); + return ret; + } +#endif + + switch (id) { + case CX23885_AV: + v4l_info(client, "cx23885 A/V decoder found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + break; + case CX23887_AV: + v4l_info(client, "cx23887 A/V decoder found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + break; + case CX23888_AV: + v4l_info(client, "cx23888 A/V decoder found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + break; + case CX2310X_AV: + v4l_info(client, "cx%d A/V decoder found @ 0x%x (%s)\n", + device_id, client->addr << 1, client->adapter->name); + break; + case CX25840: + case CX25841: + case CX25842: + case CX25843: + /* + * Note: revision '(device_id & 0x0f) == 2' was never built. + * The marking skips from 0x1 == 22 to 0x3 == 23. + */ + v4l_info(client, "cx25%3x-2%x found @ 0x%x (%s)\n", + (device_id & 0xfff0) >> 4, + (device_id & 0x0f) < 3 ? (device_id & 0x0f) + 1 + : (device_id & 0x0f), + client->addr << 1, client->adapter->name); + break; + case CX25836: + case CX25837: + default: + v4l_info(client, "cx25%3x-%x found @ 0x%x (%s)\n", + (device_id & 0xfff0) >> 4, device_id & 0x0f, + client->addr << 1, client->adapter->name); + break; + } + + state->c = client; + state->vid_input = CX25840_COMPOSITE7; + state->aud_input = CX25840_AUDIO8; + state->audclk_freq = 48000; + state->audmode = V4L2_TUNER_MODE_LANG1; + state->vbi_line_offset = 8; + state->id = id; + state->rev = device_id; + state->vbi_regs_offset = id == CX23888_AV ? 0x500 - 0x424 : 0; + state->std = V4L2_STD_NTSC_M; + v4l2_ctrl_handler_init(&state->hdl, 9); + v4l2_ctrl_new_std(&state->hdl, &cx25840_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&state->hdl, &cx25840_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(&state->hdl, &cx25840_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(&state->hdl, &cx25840_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + if (!is_cx2583x(state)) { + default_volume = cx25840_read(client, 0x8d4); + /* + * Enforce the legacy PVR-350/MSP3400 to PVR-150/CX25843 volume + * scale mapping limits to avoid -ERANGE errors when + * initializing the volume control + */ + if (default_volume > 228) { + /* Bottom out at -96 dB, v4l2 vol range 0x2e00-0x2fff */ + default_volume = 228; + cx25840_write(client, 0x8d4, 228); + } else if (default_volume < 20) { + /* Top out at + 8 dB, v4l2 vol range 0xfe00-0xffff */ + default_volume = 20; + cx25840_write(client, 0x8d4, 20); + } + default_volume = (((228 - default_volume) >> 1) + 23) << 9; + + state->volume = v4l2_ctrl_new_std(&state->hdl, + &cx25840_audio_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, + 0, 65535, 65535 / 100, + default_volume); + state->mute = v4l2_ctrl_new_std(&state->hdl, + &cx25840_audio_ctrl_ops, + V4L2_CID_AUDIO_MUTE, + 0, 1, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &cx25840_audio_ctrl_ops, + V4L2_CID_AUDIO_BALANCE, + 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(&state->hdl, &cx25840_audio_ctrl_ops, + V4L2_CID_AUDIO_BASS, + 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(&state->hdl, &cx25840_audio_ctrl_ops, + V4L2_CID_AUDIO_TREBLE, + 0, 65535, 65535 / 100, 32768); + } + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + if (!is_cx2583x(state)) + v4l2_ctrl_cluster(2, &state->volume); + v4l2_ctrl_handler_setup(&state->hdl); + + if (client->dev.platform_data) { + struct cx25840_platform_data *pdata = client->dev.platform_data; + + state->pvr150_workaround = pdata->pvr150_workaround; + } + + cx25840_ir_probe(sd); + return 0; +} + +static int cx25840_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct cx25840_state *state = to_state(sd); + + cx25840_ir_remove(sd); + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); + return 0; +} + +static const struct i2c_device_id cx25840_id[] = { + { "cx25840", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cx25840_id); + +static struct i2c_driver cx25840_driver = { + .driver = { + .name = "cx25840", + }, + .probe = cx25840_probe, + .remove = cx25840_remove, + .id_table = cx25840_id, +}; + +module_i2c_driver(cx25840_driver); diff --git a/drivers/media/i2c/cx25840/cx25840-core.h b/drivers/media/i2c/cx25840/cx25840-core.h new file mode 100644 index 000000000..8b89e9068 --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-core.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* cx25840 internal API header + * + * Copyright (C) 2003-2004 Chris Kennedy + */ + +#ifndef _CX25840_CORE_H_ +#define _CX25840_CORE_H_ + +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <linux/i2c.h> + +struct cx25840_ir_state; + +enum cx25840_model { + CX23885_AV, + CX23887_AV, + CX23888_AV, + CX2310X_AV, + CX25840, + CX25841, + CX25842, + CX25843, + CX25836, + CX25837, +}; + +enum cx25840_media_pads { + CX25840_PAD_INPUT, + CX25840_PAD_VID_OUT, + + CX25840_NUM_PADS +}; + +/** + * struct cx25840_state - a device instance private data + * @c: i2c_client struct representing this device + * @sd: our V4L2 sub-device + * @hdl: our V4L2 control handler + * @volume: audio volume V4L2 control (non-cx2583x devices only) + * @mute: audio mute V4L2 control (non-cx2583x devices only) + * @pvr150_workaround: whether we enable workaround for Hauppauge PVR150 + * hardware bug (audio dropping out) + * @generic_mode: whether we disable ivtv-specific hacks + * this mode gets turned on when the bridge driver calls + * cx25840 subdevice init core op + * @radio: set if we are currently in the radio mode, otherwise + * the current mode is non-radio (that is, video) + * @std: currently set video standard + * @vid_input: currently set video input + * @vid_config: currently set video output configuration + * only used in the generic mode + * @aud_input: currently set audio input + * @audclk_freq: currently set audio sample rate + * @audmode: currently set audio mode (when in non-radio mode) + * @vbi_line_offset: vbi line number offset + * @id: exact device model + * @rev: raw device id read from the chip + * @is_initialized: whether we have already loaded firmware into the chip + * and initialized it + * @vbi_regs_offset: offset of vbi regs + * @fw_wait: wait queue to wake an initialization function up when + * firmware loading (on a separate workqueue) finishes + * @fw_work: a work that actually loads the firmware on a separate + * workqueue + * @ir_state: a pointer to chip IR controller private data + * @pads: array of supported chip pads (currently only a stub) + */ +struct cx25840_state { + struct i2c_client *c; + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct { + /* volume cluster */ + struct v4l2_ctrl *volume; + struct v4l2_ctrl *mute; + }; + int pvr150_workaround; + bool generic_mode; + int radio; + v4l2_std_id std; + enum cx25840_video_input vid_input; + u32 vid_config; + enum cx25840_audio_input aud_input; + u32 audclk_freq; + int audmode; + int vbi_line_offset; + enum cx25840_model id; + u32 rev; + int is_initialized; + unsigned int vbi_regs_offset; + wait_queue_head_t fw_wait; + struct work_struct fw_work; + struct cx25840_ir_state *ir_state; +#if defined(CONFIG_MEDIA_CONTROLLER) + struct media_pad pads[CX25840_NUM_PADS]; +#endif +}; + +static inline struct cx25840_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct cx25840_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct cx25840_state, hdl)->sd; +} + +static inline bool is_cx2583x(struct cx25840_state *state) +{ + return state->id == CX25836 || + state->id == CX25837; +} + +static inline bool is_cx2584x(struct cx25840_state *state) +{ + return state->id == CX25840 || + state->id == CX25841 || + state->id == CX25842 || + state->id == CX25843; +} + +static inline bool is_cx231xx(struct cx25840_state *state) +{ + return state->id == CX2310X_AV; +} + +static inline bool is_cx2388x(struct cx25840_state *state) +{ + return state->id == CX23885_AV || + state->id == CX23887_AV || + state->id == CX23888_AV; +} + +static inline bool is_cx23885(struct cx25840_state *state) +{ + return state->id == CX23885_AV; +} + +static inline bool is_cx23887(struct cx25840_state *state) +{ + return state->id == CX23887_AV; +} + +static inline bool is_cx23888(struct cx25840_state *state) +{ + return state->id == CX23888_AV; +} + +/* ----------------------------------------------------------------------- */ +/* cx25850-core.c */ +int cx25840_write(struct i2c_client *client, u16 addr, u8 value); +int cx25840_write4(struct i2c_client *client, u16 addr, u32 value); +u8 cx25840_read(struct i2c_client *client, u16 addr); +u32 cx25840_read4(struct i2c_client *client, u16 addr); +int cx25840_and_or(struct i2c_client *client, u16 addr, unsigned int mask, + u8 value); +int cx25840_and_or4(struct i2c_client *client, u16 addr, u32 and_mask, + u32 or_value); +void cx25840_std_setup(struct i2c_client *client); + +/* ----------------------------------------------------------------------- */ +/* cx25850-firmware.c */ +int cx25840_loadfw(struct i2c_client *client); + +/* ----------------------------------------------------------------------- */ +/* cx25850-audio.c */ +void cx25840_audio_set_path(struct i2c_client *client); +int cx25840_s_clock_freq(struct v4l2_subdev *sd, u32 freq); + +extern const struct v4l2_ctrl_ops cx25840_audio_ctrl_ops; + +/* ----------------------------------------------------------------------- */ +/* cx25850-vbi.c */ +int cx25840_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt); +int cx25840_s_sliced_fmt(struct v4l2_subdev *sd, + struct v4l2_sliced_vbi_format *fmt); +int cx25840_g_sliced_fmt(struct v4l2_subdev *sd, + struct v4l2_sliced_vbi_format *fmt); +int cx25840_decode_vbi_line(struct v4l2_subdev *sd, + struct v4l2_decode_vbi_line *vbi); + +/* ----------------------------------------------------------------------- */ +/* cx25850-ir.c */ +extern const struct v4l2_subdev_ir_ops cx25840_ir_ops; +int cx25840_ir_log_status(struct v4l2_subdev *sd); +int cx25840_ir_irq_handler(struct v4l2_subdev *sd, u32 status, bool *handled); +int cx25840_ir_probe(struct v4l2_subdev *sd); +int cx25840_ir_remove(struct v4l2_subdev *sd); + +#endif diff --git a/drivers/media/i2c/cx25840/cx25840-firmware.c b/drivers/media/i2c/cx25840/cx25840-firmware.c new file mode 100644 index 000000000..02df45ccf --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-firmware.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* cx25840 firmware functions + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/firmware.h> +#include <media/v4l2-common.h> +#include <media/drv-intf/cx25840.h> + +#include "cx25840-core.h" + +/* + * Mike Isely <isely@pobox.com> - The FWSEND parameter controls the + * size of the firmware chunks sent down the I2C bus to the chip. + * Previously this had been set to 1024 but unfortunately some I2C + * implementations can't transfer data in such big gulps. + * Specifically, the pvrusb2 driver has a hard limit of around 60 + * bytes, due to the encapsulation there of I2C traffic into USB + * messages. So we have to significantly reduce this parameter. + */ +#define FWSEND 48 + +#define FWDEV(x) &((x)->dev) + +static char *firmware = ""; + +module_param(firmware, charp, 0444); + +MODULE_PARM_DESC(firmware, "Firmware image to load"); + +static void start_fw_load(struct i2c_client *client) +{ + /* DL_ADDR_LB=0 DL_ADDR_HB=0 */ + cx25840_write(client, 0x800, 0x00); + cx25840_write(client, 0x801, 0x00); + // DL_MAP=3 DL_AUTO_INC=0 DL_ENABLE=1 + cx25840_write(client, 0x803, 0x0b); + /* AUTO_INC_DIS=1 */ + cx25840_write(client, 0x000, 0x20); +} + +static void end_fw_load(struct i2c_client *client) +{ + /* AUTO_INC_DIS=0 */ + cx25840_write(client, 0x000, 0x00); + /* DL_ENABLE=0 */ + cx25840_write(client, 0x803, 0x03); +} + +#define CX2388x_FIRMWARE "v4l-cx23885-avcore-01.fw" +#define CX231xx_FIRMWARE "v4l-cx231xx-avcore-01.fw" +#define CX25840_FIRMWARE "v4l-cx25840.fw" + +static const char *get_fw_name(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (firmware[0]) + return firmware; + if (is_cx2388x(state)) + return CX2388x_FIRMWARE; + if (is_cx231xx(state)) + return CX231xx_FIRMWARE; + return CX25840_FIRMWARE; +} + +static int check_fw_load(struct i2c_client *client, int size) +{ + /* DL_ADDR_HB DL_ADDR_LB */ + int s = cx25840_read(client, 0x801) << 8; + s |= cx25840_read(client, 0x800); + + if (size != s) { + v4l_err(client, "firmware %s load failed\n", + get_fw_name(client)); + return -EINVAL; + } + + v4l_info(client, "loaded %s firmware (%d bytes)\n", + get_fw_name(client), size); + return 0; +} + +static int fw_write(struct i2c_client *client, const u8 *data, int size) +{ + if (i2c_master_send(client, data, size) < size) { + v4l_err(client, "firmware load i2c failure\n"); + return -ENOSYS; + } + + return 0; +} + +int cx25840_loadfw(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + const struct firmware *fw = NULL; + u8 buffer[FWSEND]; + const u8 *ptr; + const char *fwname = get_fw_name(client); + int size, retval; + int max_buf_size = FWSEND; + u32 gpio_oe = 0, gpio_da = 0; + + if (is_cx2388x(state)) { + /* Preserve the GPIO OE and output bits */ + gpio_oe = cx25840_read(client, 0x160); + gpio_da = cx25840_read(client, 0x164); + } + + /* cx231xx cannot accept more than 16 bytes at a time */ + if (is_cx231xx(state) && max_buf_size > 16) + max_buf_size = 16; + + if (request_firmware(&fw, fwname, FWDEV(client)) != 0) { + v4l_err(client, "unable to open firmware %s\n", fwname); + return -EINVAL; + } + + start_fw_load(client); + + buffer[0] = 0x08; + buffer[1] = 0x02; + + size = fw->size; + ptr = fw->data; + while (size > 0) { + int len = min(max_buf_size - 2, size); + + memcpy(buffer + 2, ptr, len); + + retval = fw_write(client, buffer, len + 2); + + if (retval < 0) { + release_firmware(fw); + return retval; + } + + size -= len; + ptr += len; + } + + end_fw_load(client); + + size = fw->size; + release_firmware(fw); + + if (is_cx2388x(state)) { + /* Restore GPIO configuration after f/w load */ + cx25840_write(client, 0x160, gpio_oe); + cx25840_write(client, 0x164, gpio_da); + } + + return check_fw_load(client, size); +} + +MODULE_FIRMWARE(CX2388x_FIRMWARE); +MODULE_FIRMWARE(CX231xx_FIRMWARE); +MODULE_FIRMWARE(CX25840_FIRMWARE); + diff --git a/drivers/media/i2c/cx25840/cx25840-ir.c b/drivers/media/i2c/cx25840/cx25840-ir.c new file mode 100644 index 000000000..2cf3e6a1f --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-ir.c @@ -0,0 +1,1259 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX2584x Audio/Video decoder chip and related cores + * + * Integrated Consumer Infrared Controller + * + * Copyright (C) 2010 Andy Walls <awalls@md.metrocast.net> + */ + +#include <linux/slab.h> +#include <linux/kfifo.h> +#include <linux/module.h> +#include <media/drv-intf/cx25840.h> +#include <media/rc-core.h> + +#include "cx25840-core.h" + +static unsigned int ir_debug; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug, "enable integrated IR debug messages"); + +#define CX25840_IR_REG_BASE 0x200 + +#define CX25840_IR_CNTRL_REG 0x200 +#define CNTRL_WIN_3_3 0x00000000 +#define CNTRL_WIN_4_3 0x00000001 +#define CNTRL_WIN_3_4 0x00000002 +#define CNTRL_WIN_4_4 0x00000003 +#define CNTRL_WIN 0x00000003 +#define CNTRL_EDG_NONE 0x00000000 +#define CNTRL_EDG_FALL 0x00000004 +#define CNTRL_EDG_RISE 0x00000008 +#define CNTRL_EDG_BOTH 0x0000000C +#define CNTRL_EDG 0x0000000C +#define CNTRL_DMD 0x00000010 +#define CNTRL_MOD 0x00000020 +#define CNTRL_RFE 0x00000040 +#define CNTRL_TFE 0x00000080 +#define CNTRL_RXE 0x00000100 +#define CNTRL_TXE 0x00000200 +#define CNTRL_RIC 0x00000400 +#define CNTRL_TIC 0x00000800 +#define CNTRL_CPL 0x00001000 +#define CNTRL_LBM 0x00002000 +#define CNTRL_R 0x00004000 + +#define CX25840_IR_TXCLK_REG 0x204 +#define TXCLK_TCD 0x0000FFFF + +#define CX25840_IR_RXCLK_REG 0x208 +#define RXCLK_RCD 0x0000FFFF + +#define CX25840_IR_CDUTY_REG 0x20C +#define CDUTY_CDC 0x0000000F + +#define CX25840_IR_STATS_REG 0x210 +#define STATS_RTO 0x00000001 +#define STATS_ROR 0x00000002 +#define STATS_RBY 0x00000004 +#define STATS_TBY 0x00000008 +#define STATS_RSR 0x00000010 +#define STATS_TSR 0x00000020 + +#define CX25840_IR_IRQEN_REG 0x214 +#define IRQEN_RTE 0x00000001 +#define IRQEN_ROE 0x00000002 +#define IRQEN_RSE 0x00000010 +#define IRQEN_TSE 0x00000020 +#define IRQEN_MSK 0x00000033 + +#define CX25840_IR_FILTR_REG 0x218 +#define FILTR_LPF 0x0000FFFF + +#define CX25840_IR_FIFO_REG 0x23C +#define FIFO_RXTX 0x0000FFFF +#define FIFO_RXTX_LVL 0x00010000 +#define FIFO_RXTX_RTO 0x0001FFFF +#define FIFO_RX_NDV 0x00020000 +#define FIFO_RX_DEPTH 8 +#define FIFO_TX_DEPTH 8 + +#define CX25840_VIDCLK_FREQ 108000000 /* 108 MHz, BT.656 */ +#define CX25840_IR_REFCLK_FREQ (CX25840_VIDCLK_FREQ / 2) + +/* + * We use this union internally for convenience, but callers to tx_write + * and rx_read will be expecting records of type struct ir_raw_event. + * Always ensure the size of this union is dictated by struct ir_raw_event. + */ +union cx25840_ir_fifo_rec { + u32 hw_fifo_data; + struct ir_raw_event ir_core_data; +}; + +#define CX25840_IR_RX_KFIFO_SIZE (256 * sizeof(union cx25840_ir_fifo_rec)) +#define CX25840_IR_TX_KFIFO_SIZE (256 * sizeof(union cx25840_ir_fifo_rec)) + +struct cx25840_ir_state { + struct i2c_client *c; + + struct v4l2_subdev_ir_parameters rx_params; + struct mutex rx_params_lock; /* protects Rx parameter settings cache */ + atomic_t rxclk_divider; + atomic_t rx_invert; + + struct kfifo rx_kfifo; + spinlock_t rx_kfifo_lock; /* protect Rx data kfifo */ + + struct v4l2_subdev_ir_parameters tx_params; + struct mutex tx_params_lock; /* protects Tx parameter settings cache */ + atomic_t txclk_divider; +}; + +static inline struct cx25840_ir_state *to_ir_state(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + return state ? state->ir_state : NULL; +} + + +/* + * Rx and Tx Clock Divider register computations + * + * Note the largest clock divider value of 0xffff corresponds to: + * (0xffff + 1) * 1000 / 108/2 MHz = 1,213,629.629... ns + * which fits in 21 bits, so we'll use unsigned int for time arguments. + */ +static inline u16 count_to_clock_divider(unsigned int d) +{ + if (d > RXCLK_RCD + 1) + d = RXCLK_RCD; + else if (d < 2) + d = 1; + else + d--; + return (u16) d; +} + +static inline u16 ns_to_clock_divider(unsigned int ns) +{ + return count_to_clock_divider( + DIV_ROUND_CLOSEST(CX25840_IR_REFCLK_FREQ / 1000000 * ns, 1000)); +} + +static inline unsigned int clock_divider_to_ns(unsigned int divider) +{ + /* Period of the Rx or Tx clock in ns */ + return DIV_ROUND_CLOSEST((divider + 1) * 1000, + CX25840_IR_REFCLK_FREQ / 1000000); +} + +static inline u16 carrier_freq_to_clock_divider(unsigned int freq) +{ + return count_to_clock_divider( + DIV_ROUND_CLOSEST(CX25840_IR_REFCLK_FREQ, freq * 16)); +} + +static inline unsigned int clock_divider_to_carrier_freq(unsigned int divider) +{ + return DIV_ROUND_CLOSEST(CX25840_IR_REFCLK_FREQ, (divider + 1) * 16); +} + +static inline u16 freq_to_clock_divider(unsigned int freq, + unsigned int rollovers) +{ + return count_to_clock_divider( + DIV_ROUND_CLOSEST(CX25840_IR_REFCLK_FREQ, freq * rollovers)); +} + +static inline unsigned int clock_divider_to_freq(unsigned int divider, + unsigned int rollovers) +{ + return DIV_ROUND_CLOSEST(CX25840_IR_REFCLK_FREQ, + (divider + 1) * rollovers); +} + +/* + * Low Pass Filter register calculations + * + * Note the largest count value of 0xffff corresponds to: + * 0xffff * 1000 / 108/2 MHz = 1,213,611.11... ns + * which fits in 21 bits, so we'll use unsigned int for time arguments. + */ +static inline u16 count_to_lpf_count(unsigned int d) +{ + if (d > FILTR_LPF) + d = FILTR_LPF; + else if (d < 4) + d = 0; + return (u16) d; +} + +static inline u16 ns_to_lpf_count(unsigned int ns) +{ + return count_to_lpf_count( + DIV_ROUND_CLOSEST(CX25840_IR_REFCLK_FREQ / 1000000 * ns, 1000)); +} + +static inline unsigned int lpf_count_to_ns(unsigned int count) +{ + /* Duration of the Low Pass Filter rejection window in ns */ + return DIV_ROUND_CLOSEST(count * 1000, + CX25840_IR_REFCLK_FREQ / 1000000); +} + +static inline unsigned int lpf_count_to_us(unsigned int count) +{ + /* Duration of the Low Pass Filter rejection window in us */ + return DIV_ROUND_CLOSEST(count, CX25840_IR_REFCLK_FREQ / 1000000); +} + +/* + * FIFO register pulse width count computations + */ +static u32 clock_divider_to_resolution(u16 divider) +{ + /* + * Resolution is the duration of 1 tick of the readable portion of + * of the pulse width counter as read from the FIFO. The two lsb's are + * not readable, hence the << 2. This function returns ns. + */ + return DIV_ROUND_CLOSEST((1 << 2) * ((u32) divider + 1) * 1000, + CX25840_IR_REFCLK_FREQ / 1000000); +} + +static u64 pulse_width_count_to_ns(u16 count, u16 divider) +{ + u64 n; + u32 rem; + + /* + * The 2 lsb's of the pulse width timer count are not readable, hence + * the (count << 2) | 0x3 + */ + n = (((u64) count << 2) | 0x3) * (divider + 1) * 1000; /* millicycles */ + rem = do_div(n, CX25840_IR_REFCLK_FREQ / 1000000); /* / MHz => ns */ + if (rem >= CX25840_IR_REFCLK_FREQ / 1000000 / 2) + n++; + return n; +} + +#if 0 +/* Keep as we will need this for Transmit functionality */ +static u16 ns_to_pulse_width_count(u32 ns, u16 divider) +{ + u64 n; + u32 d; + u32 rem; + + /* + * The 2 lsb's of the pulse width timer count are not accessible, hence + * the (1 << 2) + */ + n = ((u64) ns) * CX25840_IR_REFCLK_FREQ / 1000000; /* millicycles */ + d = (1 << 2) * ((u32) divider + 1) * 1000; /* millicycles/count */ + rem = do_div(n, d); + if (rem >= d / 2) + n++; + + if (n > FIFO_RXTX) + n = FIFO_RXTX; + else if (n == 0) + n = 1; + return (u16) n; +} + +#endif +static unsigned int pulse_width_count_to_us(u16 count, u16 divider) +{ + u64 n; + u32 rem; + + /* + * The 2 lsb's of the pulse width timer count are not readable, hence + * the (count << 2) | 0x3 + */ + n = (((u64) count << 2) | 0x3) * (divider + 1); /* cycles */ + rem = do_div(n, CX25840_IR_REFCLK_FREQ / 1000000); /* / MHz => us */ + if (rem >= CX25840_IR_REFCLK_FREQ / 1000000 / 2) + n++; + return (unsigned int) n; +} + +/* + * Pulse Clocks computations: Combined Pulse Width Count & Rx Clock Counts + * + * The total pulse clock count is an 18 bit pulse width timer count as the most + * significant part and (up to) 16 bit clock divider count as a modulus. + * When the Rx clock divider ticks down to 0, it increments the 18 bit pulse + * width timer count's least significant bit. + */ +static u64 ns_to_pulse_clocks(u32 ns) +{ + u64 clocks; + u32 rem; + clocks = CX25840_IR_REFCLK_FREQ / 1000000 * (u64) ns; /* millicycles */ + rem = do_div(clocks, 1000); /* /1000 = cycles */ + if (rem >= 1000 / 2) + clocks++; + return clocks; +} + +static u16 pulse_clocks_to_clock_divider(u64 count) +{ + do_div(count, (FIFO_RXTX << 2) | 0x3); + + /* net result needs to be rounded down and decremented by 1 */ + if (count > RXCLK_RCD + 1) + count = RXCLK_RCD; + else if (count < 2) + count = 1; + else + count--; + return (u16) count; +} + +/* + * IR Control Register helpers + */ +enum tx_fifo_watermark { + TX_FIFO_HALF_EMPTY = 0, + TX_FIFO_EMPTY = CNTRL_TIC, +}; + +enum rx_fifo_watermark { + RX_FIFO_HALF_FULL = 0, + RX_FIFO_NOT_EMPTY = CNTRL_RIC, +}; + +static inline void control_tx_irq_watermark(struct i2c_client *c, + enum tx_fifo_watermark level) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_TIC, level); +} + +static inline void control_rx_irq_watermark(struct i2c_client *c, + enum rx_fifo_watermark level) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_RIC, level); +} + +static inline void control_tx_enable(struct i2c_client *c, bool enable) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~(CNTRL_TXE | CNTRL_TFE), + enable ? (CNTRL_TXE | CNTRL_TFE) : 0); +} + +static inline void control_rx_enable(struct i2c_client *c, bool enable) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~(CNTRL_RXE | CNTRL_RFE), + enable ? (CNTRL_RXE | CNTRL_RFE) : 0); +} + +static inline void control_tx_modulation_enable(struct i2c_client *c, + bool enable) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_MOD, + enable ? CNTRL_MOD : 0); +} + +static inline void control_rx_demodulation_enable(struct i2c_client *c, + bool enable) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_DMD, + enable ? CNTRL_DMD : 0); +} + +static inline void control_rx_s_edge_detection(struct i2c_client *c, + u32 edge_types) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_EDG_BOTH, + edge_types & CNTRL_EDG_BOTH); +} + +static void control_rx_s_carrier_window(struct i2c_client *c, + unsigned int carrier, + unsigned int *carrier_range_low, + unsigned int *carrier_range_high) +{ + u32 v; + unsigned int c16 = carrier * 16; + + if (*carrier_range_low < DIV_ROUND_CLOSEST(c16, 16 + 3)) { + v = CNTRL_WIN_3_4; + *carrier_range_low = DIV_ROUND_CLOSEST(c16, 16 + 4); + } else { + v = CNTRL_WIN_3_3; + *carrier_range_low = DIV_ROUND_CLOSEST(c16, 16 + 3); + } + + if (*carrier_range_high > DIV_ROUND_CLOSEST(c16, 16 - 3)) { + v |= CNTRL_WIN_4_3; + *carrier_range_high = DIV_ROUND_CLOSEST(c16, 16 - 4); + } else { + v |= CNTRL_WIN_3_3; + *carrier_range_high = DIV_ROUND_CLOSEST(c16, 16 - 3); + } + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_WIN, v); +} + +static inline void control_tx_polarity_invert(struct i2c_client *c, + bool invert) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_CPL, + invert ? CNTRL_CPL : 0); +} + +/* + * IR Rx & Tx Clock Register helpers + */ +static unsigned int txclk_tx_s_carrier(struct i2c_client *c, + unsigned int freq, + u16 *divider) +{ + *divider = carrier_freq_to_clock_divider(freq); + cx25840_write4(c, CX25840_IR_TXCLK_REG, *divider); + return clock_divider_to_carrier_freq(*divider); +} + +static unsigned int rxclk_rx_s_carrier(struct i2c_client *c, + unsigned int freq, + u16 *divider) +{ + *divider = carrier_freq_to_clock_divider(freq); + cx25840_write4(c, CX25840_IR_RXCLK_REG, *divider); + return clock_divider_to_carrier_freq(*divider); +} + +static u32 txclk_tx_s_max_pulse_width(struct i2c_client *c, u32 ns, + u16 *divider) +{ + u64 pulse_clocks; + + if (ns > IR_MAX_DURATION) + ns = IR_MAX_DURATION; + pulse_clocks = ns_to_pulse_clocks(ns); + *divider = pulse_clocks_to_clock_divider(pulse_clocks); + cx25840_write4(c, CX25840_IR_TXCLK_REG, *divider); + return (u32) pulse_width_count_to_ns(FIFO_RXTX, *divider); +} + +static u32 rxclk_rx_s_max_pulse_width(struct i2c_client *c, u32 ns, + u16 *divider) +{ + u64 pulse_clocks; + + if (ns > IR_MAX_DURATION) + ns = IR_MAX_DURATION; + pulse_clocks = ns_to_pulse_clocks(ns); + *divider = pulse_clocks_to_clock_divider(pulse_clocks); + cx25840_write4(c, CX25840_IR_RXCLK_REG, *divider); + return (u32) pulse_width_count_to_ns(FIFO_RXTX, *divider); +} + +/* + * IR Tx Carrier Duty Cycle register helpers + */ +static unsigned int cduty_tx_s_duty_cycle(struct i2c_client *c, + unsigned int duty_cycle) +{ + u32 n; + n = DIV_ROUND_CLOSEST(duty_cycle * 100, 625); /* 16ths of 100% */ + if (n != 0) + n--; + if (n > 15) + n = 15; + cx25840_write4(c, CX25840_IR_CDUTY_REG, n); + return DIV_ROUND_CLOSEST((n + 1) * 100, 16); +} + +/* + * IR Filter Register helpers + */ +static u32 filter_rx_s_min_width(struct i2c_client *c, u32 min_width_ns) +{ + u32 count = ns_to_lpf_count(min_width_ns); + cx25840_write4(c, CX25840_IR_FILTR_REG, count); + return lpf_count_to_ns(count); +} + +/* + * IR IRQ Enable Register helpers + */ +static inline void irqenable_rx(struct v4l2_subdev *sd, u32 mask) +{ + struct cx25840_state *state = to_state(sd); + + if (is_cx23885(state) || is_cx23887(state)) + mask ^= IRQEN_MSK; + mask &= (IRQEN_RTE | IRQEN_ROE | IRQEN_RSE); + cx25840_and_or4(state->c, CX25840_IR_IRQEN_REG, + ~(IRQEN_RTE | IRQEN_ROE | IRQEN_RSE), mask); +} + +static inline void irqenable_tx(struct v4l2_subdev *sd, u32 mask) +{ + struct cx25840_state *state = to_state(sd); + + if (is_cx23885(state) || is_cx23887(state)) + mask ^= IRQEN_MSK; + mask &= IRQEN_TSE; + cx25840_and_or4(state->c, CX25840_IR_IRQEN_REG, ~IRQEN_TSE, mask); +} + +/* + * V4L2 Subdevice IR Ops + */ +int cx25840_ir_irq_handler(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct cx25840_state *state = to_state(sd); + struct cx25840_ir_state *ir_state = to_ir_state(sd); + struct i2c_client *c = NULL; + unsigned long flags; + + union cx25840_ir_fifo_rec rx_data[FIFO_RX_DEPTH]; + unsigned int i, j, k; + u32 events, v; + int tsr, rsr, rto, ror, tse, rse, rte, roe, kror; + u32 cntrl, irqen, stats; + + *handled = false; + if (ir_state == NULL) + return -ENODEV; + + c = ir_state->c; + + /* Only support the IR controller for the CX2388[57] AV Core for now */ + if (!(is_cx23885(state) || is_cx23887(state))) + return -ENODEV; + + cntrl = cx25840_read4(c, CX25840_IR_CNTRL_REG); + irqen = cx25840_read4(c, CX25840_IR_IRQEN_REG); + if (is_cx23885(state) || is_cx23887(state)) + irqen ^= IRQEN_MSK; + stats = cx25840_read4(c, CX25840_IR_STATS_REG); + + tsr = stats & STATS_TSR; /* Tx FIFO Service Request */ + rsr = stats & STATS_RSR; /* Rx FIFO Service Request */ + rto = stats & STATS_RTO; /* Rx Pulse Width Timer Time Out */ + ror = stats & STATS_ROR; /* Rx FIFO Over Run */ + + tse = irqen & IRQEN_TSE; /* Tx FIFO Service Request IRQ Enable */ + rse = irqen & IRQEN_RSE; /* Rx FIFO Service Request IRQ Enable */ + rte = irqen & IRQEN_RTE; /* Rx Pulse Width Timer Time Out IRQ Enable */ + roe = irqen & IRQEN_ROE; /* Rx FIFO Over Run IRQ Enable */ + + v4l2_dbg(2, ir_debug, sd, "IR IRQ Status: %s %s %s %s %s %s\n", + tsr ? "tsr" : " ", rsr ? "rsr" : " ", + rto ? "rto" : " ", ror ? "ror" : " ", + stats & STATS_TBY ? "tby" : " ", + stats & STATS_RBY ? "rby" : " "); + + v4l2_dbg(2, ir_debug, sd, "IR IRQ Enables: %s %s %s %s\n", + tse ? "tse" : " ", rse ? "rse" : " ", + rte ? "rte" : " ", roe ? "roe" : " "); + + /* + * Transmitter interrupt service + */ + if (tse && tsr) { + /* + * TODO: + * Check the watermark threshold setting + * Pull FIFO_TX_DEPTH or FIFO_TX_DEPTH/2 entries from tx_kfifo + * Push the data to the hardware FIFO. + * If there was nothing more to send in the tx_kfifo, disable + * the TSR IRQ and notify the v4l2_device. + * If there was something in the tx_kfifo, check the tx_kfifo + * level and notify the v4l2_device, if it is low. + */ + /* For now, inhibit TSR interrupt until Tx is implemented */ + irqenable_tx(sd, 0); + events = V4L2_SUBDEV_IR_TX_FIFO_SERVICE_REQ; + v4l2_subdev_notify(sd, V4L2_SUBDEV_IR_TX_NOTIFY, &events); + *handled = true; + } + + /* + * Receiver interrupt service + */ + kror = 0; + if ((rse && rsr) || (rte && rto)) { + /* + * Receive data on RSR to clear the STATS_RSR. + * Receive data on RTO, since we may not have yet hit the RSR + * watermark when we receive the RTO. + */ + for (i = 0, v = FIFO_RX_NDV; + (v & FIFO_RX_NDV) && !kror; i = 0) { + for (j = 0; + (v & FIFO_RX_NDV) && j < FIFO_RX_DEPTH; j++) { + v = cx25840_read4(c, CX25840_IR_FIFO_REG); + rx_data[i].hw_fifo_data = v & ~FIFO_RX_NDV; + i++; + } + if (i == 0) + break; + j = i * sizeof(union cx25840_ir_fifo_rec); + k = kfifo_in_locked(&ir_state->rx_kfifo, + (unsigned char *) rx_data, j, + &ir_state->rx_kfifo_lock); + if (k != j) + kror++; /* rx_kfifo over run */ + } + *handled = true; + } + + events = 0; + v = 0; + if (kror) { + events |= V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN; + v4l2_err(sd, "IR receiver software FIFO overrun\n"); + } + if (roe && ror) { + /* + * The RX FIFO Enable (CNTRL_RFE) must be toggled to clear + * the Rx FIFO Over Run status (STATS_ROR) + */ + v |= CNTRL_RFE; + events |= V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN; + v4l2_err(sd, "IR receiver hardware FIFO overrun\n"); + } + if (rte && rto) { + /* + * The IR Receiver Enable (CNTRL_RXE) must be toggled to clear + * the Rx Pulse Width Timer Time Out (STATS_RTO) + */ + v |= CNTRL_RXE; + events |= V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED; + } + if (v) { + /* Clear STATS_ROR & STATS_RTO as needed by resetting hardware */ + cx25840_write4(c, CX25840_IR_CNTRL_REG, cntrl & ~v); + cx25840_write4(c, CX25840_IR_CNTRL_REG, cntrl); + *handled = true; + } + spin_lock_irqsave(&ir_state->rx_kfifo_lock, flags); + if (kfifo_len(&ir_state->rx_kfifo) >= CX25840_IR_RX_KFIFO_SIZE / 2) + events |= V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ; + spin_unlock_irqrestore(&ir_state->rx_kfifo_lock, flags); + + if (events) + v4l2_subdev_notify(sd, V4L2_SUBDEV_IR_RX_NOTIFY, &events); + return 0; +} + +/* Receiver */ +static int cx25840_ir_rx_read(struct v4l2_subdev *sd, u8 *buf, size_t count, + ssize_t *num) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + bool invert; + u16 divider; + unsigned int i, n; + union cx25840_ir_fifo_rec *p; + unsigned u, v, w; + + if (ir_state == NULL) + return -ENODEV; + + invert = (bool) atomic_read(&ir_state->rx_invert); + divider = (u16) atomic_read(&ir_state->rxclk_divider); + + n = count / sizeof(union cx25840_ir_fifo_rec) + * sizeof(union cx25840_ir_fifo_rec); + if (n == 0) { + *num = 0; + return 0; + } + + n = kfifo_out_locked(&ir_state->rx_kfifo, buf, n, + &ir_state->rx_kfifo_lock); + + n /= sizeof(union cx25840_ir_fifo_rec); + *num = n * sizeof(union cx25840_ir_fifo_rec); + + for (p = (union cx25840_ir_fifo_rec *) buf, i = 0; i < n; p++, i++) { + + if ((p->hw_fifo_data & FIFO_RXTX_RTO) == FIFO_RXTX_RTO) { + /* Assume RTO was because of no IR light input */ + u = 0; + w = 1; + } else { + u = (p->hw_fifo_data & FIFO_RXTX_LVL) ? 1 : 0; + if (invert) + u = u ? 0 : 1; + w = 0; + } + + v = (unsigned) pulse_width_count_to_ns( + (u16)(p->hw_fifo_data & FIFO_RXTX), divider) / 1000; + if (v > IR_MAX_DURATION) + v = IR_MAX_DURATION; + + p->ir_core_data = (struct ir_raw_event) + { .pulse = u, .duration = v, .timeout = w }; + + v4l2_dbg(2, ir_debug, sd, "rx read: %10u ns %s %s\n", + v, u ? "mark" : "space", w ? "(timed out)" : ""); + if (w) + v4l2_dbg(2, ir_debug, sd, "rx read: end of rx\n"); + } + return 0; +} + +static int cx25840_ir_rx_g_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + + if (ir_state == NULL) + return -ENODEV; + + mutex_lock(&ir_state->rx_params_lock); + memcpy(p, &ir_state->rx_params, + sizeof(struct v4l2_subdev_ir_parameters)); + mutex_unlock(&ir_state->rx_params_lock); + return 0; +} + +static int cx25840_ir_rx_shutdown(struct v4l2_subdev *sd) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + struct i2c_client *c; + + if (ir_state == NULL) + return -ENODEV; + + c = ir_state->c; + mutex_lock(&ir_state->rx_params_lock); + + /* Disable or slow down all IR Rx circuits and counters */ + irqenable_rx(sd, 0); + control_rx_enable(c, false); + control_rx_demodulation_enable(c, false); + control_rx_s_edge_detection(c, CNTRL_EDG_NONE); + filter_rx_s_min_width(c, 0); + cx25840_write4(c, CX25840_IR_RXCLK_REG, RXCLK_RCD); + + ir_state->rx_params.shutdown = true; + + mutex_unlock(&ir_state->rx_params_lock); + return 0; +} + +static int cx25840_ir_rx_s_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + struct i2c_client *c; + struct v4l2_subdev_ir_parameters *o; + u16 rxclk_divider; + + if (ir_state == NULL) + return -ENODEV; + + if (p->shutdown) + return cx25840_ir_rx_shutdown(sd); + + if (p->mode != V4L2_SUBDEV_IR_MODE_PULSE_WIDTH) + return -ENOSYS; + + c = ir_state->c; + o = &ir_state->rx_params; + + mutex_lock(&ir_state->rx_params_lock); + + o->shutdown = p->shutdown; + + p->mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; + o->mode = p->mode; + + p->bytes_per_data_element = sizeof(union cx25840_ir_fifo_rec); + o->bytes_per_data_element = p->bytes_per_data_element; + + /* Before we tweak the hardware, we have to disable the receiver */ + irqenable_rx(sd, 0); + control_rx_enable(c, false); + + control_rx_demodulation_enable(c, p->modulation); + o->modulation = p->modulation; + + if (p->modulation) { + p->carrier_freq = rxclk_rx_s_carrier(c, p->carrier_freq, + &rxclk_divider); + + o->carrier_freq = p->carrier_freq; + + p->duty_cycle = 50; + o->duty_cycle = p->duty_cycle; + + control_rx_s_carrier_window(c, p->carrier_freq, + &p->carrier_range_lower, + &p->carrier_range_upper); + o->carrier_range_lower = p->carrier_range_lower; + o->carrier_range_upper = p->carrier_range_upper; + + p->max_pulse_width = + (u32) pulse_width_count_to_ns(FIFO_RXTX, rxclk_divider); + } else { + p->max_pulse_width = + rxclk_rx_s_max_pulse_width(c, p->max_pulse_width, + &rxclk_divider); + } + o->max_pulse_width = p->max_pulse_width; + atomic_set(&ir_state->rxclk_divider, rxclk_divider); + + p->noise_filter_min_width = + filter_rx_s_min_width(c, p->noise_filter_min_width); + o->noise_filter_min_width = p->noise_filter_min_width; + + p->resolution = clock_divider_to_resolution(rxclk_divider); + o->resolution = p->resolution; + + /* FIXME - make this dependent on resolution for better performance */ + control_rx_irq_watermark(c, RX_FIFO_HALF_FULL); + + control_rx_s_edge_detection(c, CNTRL_EDG_BOTH); + + o->invert_level = p->invert_level; + atomic_set(&ir_state->rx_invert, p->invert_level); + + o->interrupt_enable = p->interrupt_enable; + o->enable = p->enable; + if (p->enable) { + unsigned long flags; + + spin_lock_irqsave(&ir_state->rx_kfifo_lock, flags); + kfifo_reset(&ir_state->rx_kfifo); + spin_unlock_irqrestore(&ir_state->rx_kfifo_lock, flags); + if (p->interrupt_enable) + irqenable_rx(sd, IRQEN_RSE | IRQEN_RTE | IRQEN_ROE); + control_rx_enable(c, p->enable); + } + + mutex_unlock(&ir_state->rx_params_lock); + return 0; +} + +/* Transmitter */ +static int cx25840_ir_tx_write(struct v4l2_subdev *sd, u8 *buf, size_t count, + ssize_t *num) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + + if (ir_state == NULL) + return -ENODEV; + +#if 0 + /* + * FIXME - the code below is an incomplete and untested sketch of what + * may need to be done. The critical part is to get 4 (or 8) pulses + * from the tx_kfifo, or converted from ns to the proper units from the + * input, and push them off to the hardware Tx FIFO right away, if the + * HW TX fifo needs service. The rest can be pushed to the tx_kfifo in + * a less critical timeframe. Also watch out for overruning the + * tx_kfifo - don't let it happen and let the caller know not all his + * pulses were written. + */ + u32 *ns_pulse = (u32 *) buf; + unsigned int n; + u32 fifo_pulse[FIFO_TX_DEPTH]; + u32 mark; + + /* Compute how much we can fit in the tx kfifo */ + n = CX25840_IR_TX_KFIFO_SIZE - kfifo_len(ir_state->tx_kfifo); + n = min(n, (unsigned int) count); + n /= sizeof(u32); + + /* FIXME - turn on Tx Fifo service interrupt + * check hardware fifo level, and other stuff + */ + for (i = 0; i < n; ) { + for (j = 0; j < FIFO_TX_DEPTH / 2 && i < n; j++) { + mark = ns_pulse[i] & LEVEL_MASK; + fifo_pulse[j] = ns_to_pulse_width_count( + ns_pulse[i] & + ~LEVEL_MASK, + ir_state->txclk_divider); + if (mark) + fifo_pulse[j] &= FIFO_RXTX_LVL; + i++; + } + kfifo_put(ir_state->tx_kfifo, (u8 *) fifo_pulse, + j * sizeof(u32)); + } + *num = n * sizeof(u32); +#else + /* For now enable the Tx FIFO Service interrupt & pretend we did work */ + irqenable_tx(sd, IRQEN_TSE); + *num = count; +#endif + return 0; +} + +static int cx25840_ir_tx_g_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + + if (ir_state == NULL) + return -ENODEV; + + mutex_lock(&ir_state->tx_params_lock); + memcpy(p, &ir_state->tx_params, + sizeof(struct v4l2_subdev_ir_parameters)); + mutex_unlock(&ir_state->tx_params_lock); + return 0; +} + +static int cx25840_ir_tx_shutdown(struct v4l2_subdev *sd) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + struct i2c_client *c; + + if (ir_state == NULL) + return -ENODEV; + + c = ir_state->c; + mutex_lock(&ir_state->tx_params_lock); + + /* Disable or slow down all IR Tx circuits and counters */ + irqenable_tx(sd, 0); + control_tx_enable(c, false); + control_tx_modulation_enable(c, false); + cx25840_write4(c, CX25840_IR_TXCLK_REG, TXCLK_TCD); + + ir_state->tx_params.shutdown = true; + + mutex_unlock(&ir_state->tx_params_lock); + return 0; +} + +static int cx25840_ir_tx_s_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + struct i2c_client *c; + struct v4l2_subdev_ir_parameters *o; + u16 txclk_divider; + + if (ir_state == NULL) + return -ENODEV; + + if (p->shutdown) + return cx25840_ir_tx_shutdown(sd); + + if (p->mode != V4L2_SUBDEV_IR_MODE_PULSE_WIDTH) + return -ENOSYS; + + c = ir_state->c; + o = &ir_state->tx_params; + mutex_lock(&ir_state->tx_params_lock); + + o->shutdown = p->shutdown; + + p->mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; + o->mode = p->mode; + + p->bytes_per_data_element = sizeof(union cx25840_ir_fifo_rec); + o->bytes_per_data_element = p->bytes_per_data_element; + + /* Before we tweak the hardware, we have to disable the transmitter */ + irqenable_tx(sd, 0); + control_tx_enable(c, false); + + control_tx_modulation_enable(c, p->modulation); + o->modulation = p->modulation; + + if (p->modulation) { + p->carrier_freq = txclk_tx_s_carrier(c, p->carrier_freq, + &txclk_divider); + o->carrier_freq = p->carrier_freq; + + p->duty_cycle = cduty_tx_s_duty_cycle(c, p->duty_cycle); + o->duty_cycle = p->duty_cycle; + + p->max_pulse_width = + (u32) pulse_width_count_to_ns(FIFO_RXTX, txclk_divider); + } else { + p->max_pulse_width = + txclk_tx_s_max_pulse_width(c, p->max_pulse_width, + &txclk_divider); + } + o->max_pulse_width = p->max_pulse_width; + atomic_set(&ir_state->txclk_divider, txclk_divider); + + p->resolution = clock_divider_to_resolution(txclk_divider); + o->resolution = p->resolution; + + /* FIXME - make this dependent on resolution for better performance */ + control_tx_irq_watermark(c, TX_FIFO_HALF_EMPTY); + + control_tx_polarity_invert(c, p->invert_carrier_sense); + o->invert_carrier_sense = p->invert_carrier_sense; + + /* + * FIXME: we don't have hardware help for IO pin level inversion + * here like we have on the CX23888. + * Act on this with some mix of logical inversion of data levels, + * carrier polarity, and carrier duty cycle. + */ + o->invert_level = p->invert_level; + + o->interrupt_enable = p->interrupt_enable; + o->enable = p->enable; + if (p->enable) { + /* reset tx_fifo here */ + if (p->interrupt_enable) + irqenable_tx(sd, IRQEN_TSE); + control_tx_enable(c, p->enable); + } + + mutex_unlock(&ir_state->tx_params_lock); + return 0; +} + + +/* + * V4L2 Subdevice Core Ops support + */ +int cx25840_ir_log_status(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *c = state->c; + char *s; + int i, j; + u32 cntrl, txclk, rxclk, cduty, stats, irqen, filtr; + + /* The CX23888 chip doesn't have an IR controller on the A/V core */ + if (is_cx23888(state)) + return 0; + + cntrl = cx25840_read4(c, CX25840_IR_CNTRL_REG); + txclk = cx25840_read4(c, CX25840_IR_TXCLK_REG) & TXCLK_TCD; + rxclk = cx25840_read4(c, CX25840_IR_RXCLK_REG) & RXCLK_RCD; + cduty = cx25840_read4(c, CX25840_IR_CDUTY_REG) & CDUTY_CDC; + stats = cx25840_read4(c, CX25840_IR_STATS_REG); + irqen = cx25840_read4(c, CX25840_IR_IRQEN_REG); + if (is_cx23885(state) || is_cx23887(state)) + irqen ^= IRQEN_MSK; + filtr = cx25840_read4(c, CX25840_IR_FILTR_REG) & FILTR_LPF; + + v4l2_info(sd, "IR Receiver:\n"); + v4l2_info(sd, "\tEnabled: %s\n", + cntrl & CNTRL_RXE ? "yes" : "no"); + v4l2_info(sd, "\tDemodulation from a carrier: %s\n", + cntrl & CNTRL_DMD ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO: %s\n", + cntrl & CNTRL_RFE ? "enabled" : "disabled"); + switch (cntrl & CNTRL_EDG) { + case CNTRL_EDG_NONE: + s = "disabled"; + break; + case CNTRL_EDG_FALL: + s = "falling edge"; + break; + case CNTRL_EDG_RISE: + s = "rising edge"; + break; + case CNTRL_EDG_BOTH: + s = "rising & falling edges"; + break; + default: + s = "??? edge"; + break; + } + v4l2_info(sd, "\tPulse timers' start/stop trigger: %s\n", s); + v4l2_info(sd, "\tFIFO data on pulse timer overflow: %s\n", + cntrl & CNTRL_R ? "not loaded" : "overflow marker"); + v4l2_info(sd, "\tFIFO interrupt watermark: %s\n", + cntrl & CNTRL_RIC ? "not empty" : "half full or greater"); + v4l2_info(sd, "\tLoopback mode: %s\n", + cntrl & CNTRL_LBM ? "loopback active" : "normal receive"); + if (cntrl & CNTRL_DMD) { + v4l2_info(sd, "\tExpected carrier (16 clocks): %u Hz\n", + clock_divider_to_carrier_freq(rxclk)); + switch (cntrl & CNTRL_WIN) { + case CNTRL_WIN_3_3: + i = 3; + j = 3; + break; + case CNTRL_WIN_4_3: + i = 4; + j = 3; + break; + case CNTRL_WIN_3_4: + i = 3; + j = 4; + break; + case CNTRL_WIN_4_4: + i = 4; + j = 4; + break; + default: + i = 0; + j = 0; + break; + } + v4l2_info(sd, "\tNext carrier edge window: 16 clocks -%1d/+%1d, %u to %u Hz\n", + i, j, + clock_divider_to_freq(rxclk, 16 + j), + clock_divider_to_freq(rxclk, 16 - i)); + } + v4l2_info(sd, "\tMax measurable pulse width: %u us, %llu ns\n", + pulse_width_count_to_us(FIFO_RXTX, rxclk), + pulse_width_count_to_ns(FIFO_RXTX, rxclk)); + v4l2_info(sd, "\tLow pass filter: %s\n", + filtr ? "enabled" : "disabled"); + if (filtr) + v4l2_info(sd, "\tMin acceptable pulse width (LPF): %u us, %u ns\n", + lpf_count_to_us(filtr), + lpf_count_to_ns(filtr)); + v4l2_info(sd, "\tPulse width timer timed-out: %s\n", + stats & STATS_RTO ? "yes" : "no"); + v4l2_info(sd, "\tPulse width timer time-out intr: %s\n", + irqen & IRQEN_RTE ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO overrun: %s\n", + stats & STATS_ROR ? "yes" : "no"); + v4l2_info(sd, "\tFIFO overrun interrupt: %s\n", + irqen & IRQEN_ROE ? "enabled" : "disabled"); + v4l2_info(sd, "\tBusy: %s\n", + stats & STATS_RBY ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service requested: %s\n", + stats & STATS_RSR ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service request interrupt: %s\n", + irqen & IRQEN_RSE ? "enabled" : "disabled"); + + v4l2_info(sd, "IR Transmitter:\n"); + v4l2_info(sd, "\tEnabled: %s\n", + cntrl & CNTRL_TXE ? "yes" : "no"); + v4l2_info(sd, "\tModulation onto a carrier: %s\n", + cntrl & CNTRL_MOD ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO: %s\n", + 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, "\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)); + v4l2_info(sd, "\tCarrier duty cycle: %2u/16\n", + cduty + 1); + } + v4l2_info(sd, "\tMax pulse width: %u us, %llu ns\n", + pulse_width_count_to_us(FIFO_RXTX, txclk), + pulse_width_count_to_ns(FIFO_RXTX, txclk)); + v4l2_info(sd, "\tBusy: %s\n", + stats & STATS_TBY ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service requested: %s\n", + stats & STATS_TSR ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service request interrupt: %s\n", + irqen & IRQEN_TSE ? "enabled" : "disabled"); + + return 0; +} + + +const struct v4l2_subdev_ir_ops cx25840_ir_ops = { + .rx_read = cx25840_ir_rx_read, + .rx_g_parameters = cx25840_ir_rx_g_parameters, + .rx_s_parameters = cx25840_ir_rx_s_parameters, + + .tx_write = cx25840_ir_tx_write, + .tx_g_parameters = cx25840_ir_tx_g_parameters, + .tx_s_parameters = cx25840_ir_tx_s_parameters, +}; + + +static const struct v4l2_subdev_ir_parameters default_rx_params = { + .bytes_per_data_element = sizeof(union cx25840_ir_fifo_rec), + .mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH, + + .enable = false, + .interrupt_enable = false, + .shutdown = true, + + .modulation = true, + .carrier_freq = 36000, /* 36 kHz - RC-5, and RC-6 carrier */ + + /* RC-5: 666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */ + /* RC-6: 333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */ + .noise_filter_min_width = 333333, /* ns */ + .carrier_range_lower = 35000, + .carrier_range_upper = 37000, + .invert_level = false, +}; + +static const struct v4l2_subdev_ir_parameters default_tx_params = { + .bytes_per_data_element = sizeof(union cx25840_ir_fifo_rec), + .mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH, + + .enable = false, + .interrupt_enable = false, + .shutdown = true, + + .modulation = true, + .carrier_freq = 36000, /* 36 kHz - RC-5 carrier */ + .duty_cycle = 25, /* 25 % - RC-5 carrier */ + .invert_level = false, + .invert_carrier_sense = false, +}; + +int cx25840_ir_probe(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + struct cx25840_ir_state *ir_state; + struct v4l2_subdev_ir_parameters default_params; + + /* Only init the IR controller for the CX2388[57] AV Core for now */ + if (!(is_cx23885(state) || is_cx23887(state))) + return 0; + + ir_state = devm_kzalloc(&state->c->dev, sizeof(*ir_state), GFP_KERNEL); + if (ir_state == NULL) + return -ENOMEM; + + spin_lock_init(&ir_state->rx_kfifo_lock); + if (kfifo_alloc(&ir_state->rx_kfifo, + CX25840_IR_RX_KFIFO_SIZE, GFP_KERNEL)) + return -ENOMEM; + + ir_state->c = state->c; + state->ir_state = ir_state; + + /* Ensure no interrupts arrive yet */ + if (is_cx23885(state) || is_cx23887(state)) + cx25840_write4(ir_state->c, CX25840_IR_IRQEN_REG, IRQEN_MSK); + else + cx25840_write4(ir_state->c, CX25840_IR_IRQEN_REG, 0); + + mutex_init(&ir_state->rx_params_lock); + default_params = default_rx_params; + v4l2_subdev_call(sd, ir, rx_s_parameters, &default_params); + + mutex_init(&ir_state->tx_params_lock); + default_params = default_tx_params; + v4l2_subdev_call(sd, ir, tx_s_parameters, &default_params); + + return 0; +} + +int cx25840_ir_remove(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + struct cx25840_ir_state *ir_state = to_ir_state(sd); + + if (ir_state == NULL) + return -ENODEV; + + cx25840_ir_rx_shutdown(sd); + cx25840_ir_tx_shutdown(sd); + + kfifo_free(&ir_state->rx_kfifo); + state->ir_state = NULL; + return 0; +} diff --git a/drivers/media/i2c/cx25840/cx25840-vbi.c b/drivers/media/i2c/cx25840/cx25840-vbi.c new file mode 100644 index 000000000..a066d5f0f --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-vbi.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* cx25840 VBI functions + */ + + +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <media/v4l2-common.h> +#include <media/drv-intf/cx25840.h> + +#include "cx25840-core.h" + +static int odd_parity(u8 c) +{ + c ^= (c >> 4); + c ^= (c >> 2); + c ^= (c >> 1); + + return c & 1; +} + +static int decode_vps(u8 * dst, u8 * p) +{ + static const u8 biphase_tbl[] = { + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87, + 0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3, + 0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85, + 0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86, + 0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2, + 0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84, + 0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + }; + + u8 c, err = 0; + int i; + + for (i = 0; i < 2 * 13; i += 2) { + err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]]; + c = (biphase_tbl[p[i + 1]] & 0xf) | + ((biphase_tbl[p[i]] & 0xf) << 4); + dst[i / 2] = c; + } + + return err & 0xf0; +} + +int cx25840_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + static const u16 lcr2vbi[] = { + 0, V4L2_SLICED_TELETEXT_B, 0, /* 1 */ + 0, V4L2_SLICED_WSS_625, 0, /* 4 */ + V4L2_SLICED_CAPTION_525, /* 6 */ + 0, 0, V4L2_SLICED_VPS, 0, 0, /* 9 */ + 0, 0, 0, 0 + }; + int is_pal = !(state->std & V4L2_STD_525_60); + int i; + + memset(svbi->service_lines, 0, sizeof(svbi->service_lines)); + svbi->service_set = 0; + /* we're done if raw VBI is active */ + /* TODO: this will have to be changed for generic_mode VBI */ + if ((cx25840_read(client, 0x404) & 0x10) == 0) + return 0; + + if (is_pal) { + for (i = 7; i <= 23; i++) { + u8 v = cx25840_read(client, + state->vbi_regs_offset + 0x424 + i - 7); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= svbi->service_lines[0][i] | + svbi->service_lines[1][i]; + } + } else { + for (i = 10; i <= 21; i++) { + u8 v = cx25840_read(client, + state->vbi_regs_offset + 0x424 + i - 10); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= svbi->service_lines[0][i] | + svbi->service_lines[1][i]; + } + } + return 0; +} + +int cx25840_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + int is_pal = !(state->std & V4L2_STD_525_60); + int vbi_offset = is_pal ? 1 : 0; + + /* Setup standard */ + cx25840_std_setup(client); + + /* VBI Offset */ + if (is_cx23888(state)) + cx25840_write(client, 0x54f, vbi_offset); + else + cx25840_write(client, 0x47f, vbi_offset); + /* TODO: this will have to be changed for generic_mode VBI */ + cx25840_write(client, 0x404, 0x2e); + return 0; +} + +int cx25840_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + int is_pal = !(state->std & V4L2_STD_525_60); + int vbi_offset = is_pal ? 1 : 0; + int i, x; + u8 lcr[24]; + + for (x = 0; x <= 23; x++) + lcr[x] = 0x00; + + /* Setup standard */ + cx25840_std_setup(client); + + /* Sliced VBI */ + /* TODO: this will have to be changed for generic_mode VBI */ + cx25840_write(client, 0x404, 0x32); /* Ancillary data */ + cx25840_write(client, 0x406, 0x13); + if (is_cx23888(state)) + cx25840_write(client, 0x54f, vbi_offset); + else + cx25840_write(client, 0x47f, vbi_offset); + + if (is_pal) { + for (i = 0; i <= 6; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } else { + for (i = 0; i <= 9; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + + for (i = 22; i <= 23; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } + + for (i = 7; i <= 23; i++) { + for (x = 0; x <= 1; x++) { + switch (svbi->service_lines[1-x][i]) { + case V4L2_SLICED_TELETEXT_B: + lcr[i] |= 1 << (4 * x); + break; + case V4L2_SLICED_WSS_625: + lcr[i] |= 4 << (4 * x); + break; + case V4L2_SLICED_CAPTION_525: + lcr[i] |= 6 << (4 * x); + break; + case V4L2_SLICED_VPS: + lcr[i] |= 9 << (4 * x); + break; + } + } + } + + if (is_pal) { + for (x = 1, i = state->vbi_regs_offset + 0x424; + i <= state->vbi_regs_offset + 0x434; i++, x++) + cx25840_write(client, i, lcr[6 + x]); + } else { + for (x = 1, i = state->vbi_regs_offset + 0x424; + i <= state->vbi_regs_offset + 0x430; i++, x++) + cx25840_write(client, i, lcr[9 + x]); + for (i = state->vbi_regs_offset + 0x431; + i <= state->vbi_regs_offset + 0x434; i++) + cx25840_write(client, i, 0); + } + + cx25840_write(client, state->vbi_regs_offset + 0x43c, 0x16); + /* TODO: this will have to be changed for generic_mode VBI */ + if (is_cx23888(state)) + cx25840_write(client, 0x428, is_pal ? 0x2a : 0x22); + else + cx25840_write(client, 0x474, is_pal ? 0x2a : 0x22); + return 0; +} + +int cx25840_decode_vbi_line(struct v4l2_subdev *sd, struct v4l2_decode_vbi_line *vbi) +{ + struct cx25840_state *state = to_state(sd); + u8 *p = vbi->p; + int id1, id2, l, err = 0; + + if (p[0] || p[1] != 0xff || p[2] != 0xff || + (p[3] != 0x55 && p[3] != 0x91)) { + vbi->line = vbi->type = 0; + return 0; + } + + p += 4; + id1 = p[-1]; + id2 = p[0] & 0xf; + l = p[2] & 0x3f; + l += state->vbi_line_offset; + p += 4; + + switch (id2) { + case 1: + id2 = V4L2_SLICED_TELETEXT_B; + break; + case 4: + id2 = V4L2_SLICED_WSS_625; + break; + case 6: + id2 = V4L2_SLICED_CAPTION_525; + err = !odd_parity(p[0]) || !odd_parity(p[1]); + break; + case 9: + id2 = V4L2_SLICED_VPS; + if (decode_vps(p, p) != 0) + err = 1; + break; + default: + id2 = 0; + err = 1; + break; + } + + vbi->type = err ? 0 : id2; + vbi->line = err ? 0 : l; + vbi->is_second_field = err ? 0 : (id1 == 0x55); + vbi->p = p; + return 0; +} diff --git a/drivers/media/i2c/dw9714.c b/drivers/media/i2c/dw9714.c new file mode 100644 index 000000000..3f0b082f8 --- /dev/null +++ b/drivers/media/i2c/dw9714.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2015--2017 Intel Corporation. + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#define DW9714_NAME "dw9714" +#define DW9714_MAX_FOCUS_POS 1023 +/* + * This sets the minimum granularity for the focus positions. + * A value of 1 gives maximum accuracy for a desired focus position + */ +#define DW9714_FOCUS_STEPS 1 +/* + * This acts as the minimum granularity of lens movement. + * Keep this value power of 2, so the control steps can be + * uniformly adjusted for gradual lens movement, with desired + * number of control steps. + */ +#define DW9714_CTRL_STEPS 16 +#define DW9714_CTRL_DELAY_US 1000 +/* + * S[3:2] = 0x00, codes per step for "Linear Slope Control" + * S[1:0] = 0x00, step period + */ +#define DW9714_DEFAULT_S 0x0 +#define DW9714_VAL(data, s) ((data) << 4 | (s)) + +/* dw9714 device structure */ +struct dw9714_device { + struct v4l2_ctrl_handler ctrls_vcm; + struct v4l2_subdev sd; + u16 current_val; +}; + +static inline struct dw9714_device *to_dw9714_vcm(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct dw9714_device, ctrls_vcm); +} + +static inline struct dw9714_device *sd_to_dw9714_vcm(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct dw9714_device, sd); +} + +static int dw9714_i2c_write(struct i2c_client *client, u16 data) +{ + int ret; + __be16 val = cpu_to_be16(data); + + ret = i2c_master_send(client, (const char *)&val, sizeof(val)); + if (ret != sizeof(val)) { + dev_err(&client->dev, "I2C write fail\n"); + return -EIO; + } + return 0; +} + +static int dw9714_t_focus_vcm(struct dw9714_device *dw9714_dev, u16 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9714_dev->sd); + + dw9714_dev->current_val = val; + + return dw9714_i2c_write(client, DW9714_VAL(val, DW9714_DEFAULT_S)); +} + +static int dw9714_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9714_device *dev_vcm = to_dw9714_vcm(ctrl); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) + return dw9714_t_focus_vcm(dev_vcm, ctrl->val); + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops dw9714_vcm_ctrl_ops = { + .s_ctrl = dw9714_set_ctrl, +}; + +static int dw9714_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + int rval; + + rval = pm_runtime_get_sync(sd->dev); + if (rval < 0) { + pm_runtime_put_noidle(sd->dev); + return rval; + } + + return 0; +} + +static int dw9714_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9714_int_ops = { + .open = dw9714_open, + .close = dw9714_close, +}; + +static const struct v4l2_subdev_ops dw9714_ops = { }; + +static void dw9714_subdev_cleanup(struct dw9714_device *dw9714_dev) +{ + v4l2_async_unregister_subdev(&dw9714_dev->sd); + v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm); + media_entity_cleanup(&dw9714_dev->sd.entity); +} + +static int dw9714_init_controls(struct dw9714_device *dev_vcm) +{ + struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm; + const struct v4l2_ctrl_ops *ops = &dw9714_vcm_ctrl_ops; + + v4l2_ctrl_handler_init(hdl, 1); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, + 0, DW9714_MAX_FOCUS_POS, DW9714_FOCUS_STEPS, 0); + + if (hdl->error) + dev_err(dev_vcm->sd.dev, "%s fail error: 0x%x\n", + __func__, hdl->error); + dev_vcm->sd.ctrl_handler = hdl; + return hdl->error; +} + +static int dw9714_probe(struct i2c_client *client) +{ + struct dw9714_device *dw9714_dev; + int rval; + + dw9714_dev = devm_kzalloc(&client->dev, sizeof(*dw9714_dev), + GFP_KERNEL); + if (dw9714_dev == NULL) + return -ENOMEM; + + v4l2_i2c_subdev_init(&dw9714_dev->sd, client, &dw9714_ops); + dw9714_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9714_dev->sd.internal_ops = &dw9714_int_ops; + + rval = dw9714_init_controls(dw9714_dev); + if (rval) + goto err_cleanup; + + rval = media_entity_pads_init(&dw9714_dev->sd.entity, 0, NULL); + if (rval < 0) + goto err_cleanup; + + dw9714_dev->sd.entity.function = MEDIA_ENT_F_LENS; + + rval = v4l2_async_register_subdev(&dw9714_dev->sd); + if (rval < 0) + goto err_cleanup; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_cleanup: + v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm); + media_entity_cleanup(&dw9714_dev->sd.entity); + + return rval; +} + +static int dw9714_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + + pm_runtime_disable(&client->dev); + dw9714_subdev_cleanup(dw9714_dev); + + return 0; +} + +/* + * This function sets the vcm position, so it consumes least current + * The lens position is gradually moved in units of DW9714_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9714_vcm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + int ret, val; + + for (val = dw9714_dev->current_val & ~(DW9714_CTRL_STEPS - 1); + val >= 0; val -= DW9714_CTRL_STEPS) { + ret = dw9714_i2c_write(client, + DW9714_VAL(val, DW9714_DEFAULT_S)); + if (ret) + dev_err_once(dev, "%s I2C failure: %d", __func__, ret); + usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10); + } + return 0; +} + +/* + * This function sets the vcm position to the value set by the user + * through v4l2_ctrl_ops s_ctrl handler + * The lens position is gradually moved in units of DW9714_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9714_vcm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + int ret, val; + + for (val = dw9714_dev->current_val % DW9714_CTRL_STEPS; + val < dw9714_dev->current_val + DW9714_CTRL_STEPS - 1; + val += DW9714_CTRL_STEPS) { + ret = dw9714_i2c_write(client, + DW9714_VAL(val, DW9714_DEFAULT_S)); + if (ret) + dev_err_ratelimited(dev, "%s I2C failure: %d", + __func__, ret); + usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10); + } + + return 0; +} + +static const struct i2c_device_id dw9714_id_table[] = { + { DW9714_NAME, 0 }, + { { 0 } } +}; +MODULE_DEVICE_TABLE(i2c, dw9714_id_table); + +static const struct of_device_id dw9714_of_table[] = { + { .compatible = "dongwoon,dw9714" }, + { { 0 } } +}; +MODULE_DEVICE_TABLE(of, dw9714_of_table); + +static const struct dev_pm_ops dw9714_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume) + SET_RUNTIME_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume, NULL) +}; + +static struct i2c_driver dw9714_i2c_driver = { + .driver = { + .name = DW9714_NAME, + .pm = &dw9714_pm_ops, + .of_match_table = dw9714_of_table, + }, + .probe_new = dw9714_probe, + .remove = dw9714_remove, + .id_table = dw9714_id_table, +}; + +module_i2c_driver(dw9714_i2c_driver); + +MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); +MODULE_AUTHOR("Jian Xu Zheng"); +MODULE_AUTHOR("Yuning Pu <yuning.pu@intel.com>"); +MODULE_AUTHOR("Jouni Ukkonen <jouni.ukkonen@intel.com>"); +MODULE_AUTHOR("Tommi Franttila <tommi.franttila@intel.com>"); +MODULE_DESCRIPTION("DW9714 VCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/dw9768.c b/drivers/media/i2c/dw9768.c new file mode 100644 index 000000000..45cdd924b --- /dev/null +++ b/drivers/media/i2c/dw9768.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020 MediaTek Inc. + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define DW9768_NAME "dw9768" +#define DW9768_MAX_FOCUS_POS (1024 - 1) +/* + * This sets the minimum granularity for the focus positions. + * A value of 1 gives maximum accuracy for a desired focus position + */ +#define DW9768_FOCUS_STEPS 1 + +/* + * Ring control and Power control register + * Bit[1] RING_EN + * 0: Direct mode + * 1: AAC mode (ringing control mode) + * Bit[0] PD + * 0: Normal operation mode + * 1: Power down mode + * DW9768 requires waiting time of Topr after PD reset takes place. + */ +#define DW9768_RING_PD_CONTROL_REG 0x02 +#define DW9768_PD_MODE_OFF 0x00 +#define DW9768_PD_MODE_EN BIT(0) +#define DW9768_AAC_MODE_EN BIT(1) + +/* + * DW9768 separates two registers to control the VCM position. + * One for MSB value, another is LSB value. + * DAC_MSB: D[9:8] (ADD: 0x03) + * DAC_LSB: D[7:0] (ADD: 0x04) + * D[9:0] DAC data input: positive output current = D[9:0] / 1023 * 100[mA] + */ +#define DW9768_MSB_ADDR 0x03 +#define DW9768_LSB_ADDR 0x04 +#define DW9768_STATUS_ADDR 0x05 + +/* + * AAC mode control & prescale register + * Bit[7:5] Namely AC[2:0], decide the VCM mode and operation time. + * 001 AAC2 0.48 x Tvib + * 010 AAC3 0.70 x Tvib + * 011 AAC4 0.75 x Tvib + * 101 AAC8 1.13 x Tvib + * Bit[2:0] Namely PRESC[2:0], set the internal clock dividing rate as follow. + * 000 2 + * 001 1 + * 010 1/2 + * 011 1/4 + * 100 8 + * 101 4 + */ +#define DW9768_AAC_PRESC_REG 0x06 +#define DW9768_AAC_MODE_SEL_MASK GENMASK(7, 5) +#define DW9768_CLOCK_PRE_SCALE_SEL_MASK GENMASK(2, 0) + +/* + * VCM period of vibration register + * Bit[5:0] Defined as VCM rising periodic time (Tvib) together with PRESC[2:0] + * Tvib = (6.3ms + AACT[5:0] * 0.1ms) * Dividing Rate + * Dividing Rate is the internal clock dividing rate that is defined at + * PRESCALE register (ADD: 0x06) + */ +#define DW9768_AAC_TIME_REG 0x07 + +/* + * DW9768 requires waiting time (delay time) of t_OPR after power-up, + * or in the case of PD reset taking place. + */ +#define DW9768_T_OPR_US 1000 +#define DW9768_TVIB_MS_BASE10 (64 - 1) +#define DW9768_AAC_MODE_DEFAULT 2 +#define DW9768_AAC_TIME_DEFAULT 0x20 +#define DW9768_CLOCK_PRE_SCALE_DEFAULT 1 + +/* + * This acts as the minimum granularity of lens movement. + * Keep this value power of 2, so the control steps can be + * uniformly adjusted for gradual lens movement, with desired + * number of control steps. + */ +#define DW9768_MOVE_STEPS 16 + +static const char * const dw9768_supply_names[] = { + "vin", /* Digital I/O power */ + "vdd", /* Digital core power */ +}; + +/* dw9768 device structure */ +struct dw9768 { + struct regulator_bulk_data supplies[ARRAY_SIZE(dw9768_supply_names)]; + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *focus; + struct v4l2_subdev sd; + + u32 aac_mode; + u32 aac_timing; + u32 clock_presc; + u32 move_delay_us; +}; + +static inline struct dw9768 *sd_to_dw9768(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct dw9768, sd); +} + +struct regval_list { + u8 reg_num; + u8 value; +}; + +struct dw9768_aac_mode_ot_multi { + u32 aac_mode_enum; + u32 ot_multi_base100; +}; + +struct dw9768_clk_presc_dividing_rate { + u32 clk_presc_enum; + u32 dividing_rate_base100; +}; + +static const struct dw9768_aac_mode_ot_multi aac_mode_ot_multi[] = { + {1, 48}, + {2, 70}, + {3, 75}, + {5, 113}, +}; + +static const struct dw9768_clk_presc_dividing_rate presc_dividing_rate[] = { + {0, 200}, + {1, 100}, + {2, 50}, + {3, 25}, + {4, 800}, + {5, 400}, +}; + +static u32 dw9768_find_ot_multi(u32 aac_mode_param) +{ + u32 cur_ot_multi_base100 = 70; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(aac_mode_ot_multi); i++) { + if (aac_mode_ot_multi[i].aac_mode_enum == aac_mode_param) { + cur_ot_multi_base100 = + aac_mode_ot_multi[i].ot_multi_base100; + } + } + + return cur_ot_multi_base100; +} + +static u32 dw9768_find_dividing_rate(u32 presc_param) +{ + u32 cur_clk_dividing_rate_base100 = 100; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(presc_dividing_rate); i++) { + if (presc_dividing_rate[i].clk_presc_enum == presc_param) { + cur_clk_dividing_rate_base100 = + presc_dividing_rate[i].dividing_rate_base100; + } + } + + return cur_clk_dividing_rate_base100; +} + +/* + * DW9768_AAC_PRESC_REG & DW9768_AAC_TIME_REG determine VCM operation time. + * For current VCM mode: AAC3, Operation Time would be 0.70 x Tvib. + * Tvib = (6.3ms + AACT[5:0] * 0.1MS) * Dividing Rate. + * Below is calculation of the operation delay for each step. + */ +static inline u32 dw9768_cal_move_delay(u32 aac_mode_param, u32 presc_param, + u32 aac_timing_param) +{ + u32 Tvib_us; + u32 ot_multi_base100; + u32 clk_dividing_rate_base100; + + ot_multi_base100 = dw9768_find_ot_multi(aac_mode_param); + + clk_dividing_rate_base100 = dw9768_find_dividing_rate(presc_param); + + Tvib_us = (DW9768_TVIB_MS_BASE10 + aac_timing_param) * + clk_dividing_rate_base100; + + return Tvib_us * ot_multi_base100 / 100; +} + +static int dw9768_mod_reg(struct dw9768 *dw9768, u8 reg, u8 mask, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9768->sd); + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return ret; + + val = ((unsigned char)ret & ~mask) | (val & mask); + + return i2c_smbus_write_byte_data(client, reg, val); +} + +static int dw9768_set_dac(struct dw9768 *dw9768, u16 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9768->sd); + + /* Write VCM position to registers */ + return i2c_smbus_write_word_swapped(client, DW9768_MSB_ADDR, val); +} + +static int dw9768_init(struct dw9768 *dw9768) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9768->sd); + int ret, val; + + /* Reset DW9768_RING_PD_CONTROL_REG to default status 0x00 */ + ret = i2c_smbus_write_byte_data(client, DW9768_RING_PD_CONTROL_REG, + DW9768_PD_MODE_OFF); + if (ret < 0) + return ret; + + /* + * DW9769 requires waiting delay time of t_OPR + * after PD reset takes place. + */ + usleep_range(DW9768_T_OPR_US, DW9768_T_OPR_US + 100); + + /* Set DW9768_RING_PD_CONTROL_REG to DW9768_AAC_MODE_EN(0x01) */ + ret = i2c_smbus_write_byte_data(client, DW9768_RING_PD_CONTROL_REG, + DW9768_AAC_MODE_EN); + if (ret < 0) + return ret; + + /* Set AAC mode */ + ret = dw9768_mod_reg(dw9768, DW9768_AAC_PRESC_REG, + DW9768_AAC_MODE_SEL_MASK, + dw9768->aac_mode << 5); + if (ret < 0) + return ret; + + /* Set clock presc */ + if (dw9768->clock_presc != DW9768_CLOCK_PRE_SCALE_DEFAULT) { + ret = dw9768_mod_reg(dw9768, DW9768_AAC_PRESC_REG, + DW9768_CLOCK_PRE_SCALE_SEL_MASK, + dw9768->clock_presc); + if (ret < 0) + return ret; + } + + /* Set AAC Timing */ + if (dw9768->aac_timing != DW9768_AAC_TIME_DEFAULT) { + ret = i2c_smbus_write_byte_data(client, DW9768_AAC_TIME_REG, + dw9768->aac_timing); + if (ret < 0) + return ret; + } + + for (val = dw9768->focus->val % DW9768_MOVE_STEPS; + val <= dw9768->focus->val; + val += DW9768_MOVE_STEPS) { + ret = dw9768_set_dac(dw9768, val); + if (ret) { + dev_err(&client->dev, "I2C failure: %d", ret); + return ret; + } + usleep_range(dw9768->move_delay_us, + dw9768->move_delay_us + 1000); + } + + return 0; +} + +static int dw9768_release(struct dw9768 *dw9768) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9768->sd); + int ret, val; + + val = round_down(dw9768->focus->val, DW9768_MOVE_STEPS); + for ( ; val >= 0; val -= DW9768_MOVE_STEPS) { + ret = dw9768_set_dac(dw9768, val); + if (ret) { + dev_err(&client->dev, "I2C write fail: %d", ret); + return ret; + } + usleep_range(dw9768->move_delay_us, + dw9768->move_delay_us + 1000); + } + + ret = i2c_smbus_write_byte_data(client, DW9768_RING_PD_CONTROL_REG, + DW9768_PD_MODE_EN); + if (ret < 0) + return ret; + + /* + * DW9769 requires waiting delay time of t_OPR + * after PD reset takes place. + */ + usleep_range(DW9768_T_OPR_US, DW9768_T_OPR_US + 100); + + return 0; +} + +static int dw9768_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9768 *dw9768 = sd_to_dw9768(sd); + + dw9768_release(dw9768); + regulator_bulk_disable(ARRAY_SIZE(dw9768_supply_names), + dw9768->supplies); + + return 0; +} + +static int dw9768_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9768 *dw9768 = sd_to_dw9768(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(dw9768_supply_names), + dw9768->supplies); + if (ret < 0) { + dev_err(dev, "failed to enable regulators\n"); + return ret; + } + + /* + * The datasheet refers to t_OPR that needs to be waited before sending + * I2C commands after power-up. + */ + usleep_range(DW9768_T_OPR_US, DW9768_T_OPR_US + 100); + + ret = dw9768_init(dw9768); + if (ret < 0) + goto disable_regulator; + + return 0; + +disable_regulator: + regulator_bulk_disable(ARRAY_SIZE(dw9768_supply_names), + dw9768->supplies); + + return ret; +} + +static int dw9768_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9768 *dw9768 = container_of(ctrl->handler, + struct dw9768, ctrls); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) + return dw9768_set_dac(dw9768, ctrl->val); + + return 0; +} + +static const struct v4l2_ctrl_ops dw9768_ctrl_ops = { + .s_ctrl = dw9768_set_ctrl, +}; + +static int dw9768_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + int ret; + + ret = pm_runtime_get_sync(sd->dev); + if (ret < 0) { + pm_runtime_put_noidle(sd->dev); + return ret; + } + + return 0; +} + +static int dw9768_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9768_int_ops = { + .open = dw9768_open, + .close = dw9768_close, +}; + +static const struct v4l2_subdev_ops dw9768_ops = { }; + +static int dw9768_init_controls(struct dw9768 *dw9768) +{ + struct v4l2_ctrl_handler *hdl = &dw9768->ctrls; + const struct v4l2_ctrl_ops *ops = &dw9768_ctrl_ops; + + v4l2_ctrl_handler_init(hdl, 1); + + dw9768->focus = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, 0, + DW9768_MAX_FOCUS_POS, + DW9768_FOCUS_STEPS, 0); + + if (hdl->error) + return hdl->error; + + dw9768->sd.ctrl_handler = hdl; + + return 0; +} + +static int dw9768_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct dw9768 *dw9768; + unsigned int i; + int ret; + + dw9768 = devm_kzalloc(dev, sizeof(*dw9768), GFP_KERNEL); + if (!dw9768) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&dw9768->sd, client, &dw9768_ops); + + dw9768->aac_mode = DW9768_AAC_MODE_DEFAULT; + dw9768->aac_timing = DW9768_AAC_TIME_DEFAULT; + dw9768->clock_presc = DW9768_CLOCK_PRE_SCALE_DEFAULT; + + /* Optional indication of AAC mode select */ + fwnode_property_read_u32(dev_fwnode(dev), "dongwoon,aac-mode", + &dw9768->aac_mode); + + /* Optional indication of clock pre-scale select */ + fwnode_property_read_u32(dev_fwnode(dev), "dongwoon,clock-presc", + &dw9768->clock_presc); + + /* Optional indication of AAC Timing */ + fwnode_property_read_u32(dev_fwnode(dev), "dongwoon,aac-timing", + &dw9768->aac_timing); + + dw9768->move_delay_us = dw9768_cal_move_delay(dw9768->aac_mode, + dw9768->clock_presc, + dw9768->aac_timing); + + for (i = 0; i < ARRAY_SIZE(dw9768_supply_names); i++) + dw9768->supplies[i].supply = dw9768_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(dw9768_supply_names), + dw9768->supplies); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + /* Initialize controls */ + ret = dw9768_init_controls(dw9768); + if (ret) + goto err_free_handler; + + /* Initialize subdev */ + dw9768->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9768->sd.internal_ops = &dw9768_int_ops; + + ret = media_entity_pads_init(&dw9768->sd.entity, 0, NULL); + if (ret < 0) + goto err_free_handler; + + dw9768->sd.entity.function = MEDIA_ENT_F_LENS; + + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = dw9768_runtime_resume(dev); + if (ret < 0) { + dev_err(dev, "failed to power on: %d\n", ret); + goto err_clean_entity; + } + } + + ret = v4l2_async_register_subdev(&dw9768->sd); + if (ret < 0) { + dev_err(dev, "failed to register V4L2 subdev: %d", ret); + goto err_power_off; + } + + return 0; + +err_power_off: + if (pm_runtime_enabled(dev)) + pm_runtime_disable(dev); + else + dw9768_runtime_suspend(dev); +err_clean_entity: + media_entity_cleanup(&dw9768->sd.entity); +err_free_handler: + v4l2_ctrl_handler_free(&dw9768->ctrls); + + return ret; +} + +static int dw9768_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9768 *dw9768 = sd_to_dw9768(sd); + + v4l2_async_unregister_subdev(&dw9768->sd); + v4l2_ctrl_handler_free(&dw9768->ctrls); + media_entity_cleanup(&dw9768->sd.entity); + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + dw9768_runtime_suspend(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +static const struct of_device_id dw9768_of_table[] = { + { .compatible = "dongwoon,dw9768" }, + { .compatible = "giantec,gt9769" }, + {} +}; +MODULE_DEVICE_TABLE(of, dw9768_of_table); + +static const struct dev_pm_ops dw9768_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(dw9768_runtime_suspend, dw9768_runtime_resume, NULL) +}; + +static struct i2c_driver dw9768_i2c_driver = { + .driver = { + .name = DW9768_NAME, + .pm = &dw9768_pm_ops, + .of_match_table = dw9768_of_table, + }, + .probe_new = dw9768_probe, + .remove = dw9768_remove, +}; +module_i2c_driver(dw9768_i2c_driver); + +MODULE_AUTHOR("Dongchun Zhu <dongchun.zhu@mediatek.com>"); +MODULE_DESCRIPTION("DW9768 VCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/dw9807-vcm.c b/drivers/media/i2c/dw9807-vcm.c new file mode 100644 index 000000000..438a44b76 --- /dev/null +++ b/drivers/media/i2c/dw9807-vcm.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Intel Corporation + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#define DW9807_MAX_FOCUS_POS 1023 +/* + * This sets the minimum granularity for the focus positions. + * A value of 1 gives maximum accuracy for a desired focus position. + */ +#define DW9807_FOCUS_STEPS 1 +/* + * This acts as the minimum granularity of lens movement. + * Keep this value power of 2, so the control steps can be + * uniformly adjusted for gradual lens movement, with desired + * number of control steps. + */ +#define DW9807_CTRL_STEPS 16 +#define DW9807_CTRL_DELAY_US 1000 + +#define DW9807_CTL_ADDR 0x02 +/* + * DW9807 separates two registers to control the VCM position. + * One for MSB value, another is LSB value. + */ +#define DW9807_MSB_ADDR 0x03 +#define DW9807_LSB_ADDR 0x04 +#define DW9807_STATUS_ADDR 0x05 +#define DW9807_MODE_ADDR 0x06 +#define DW9807_RESONANCE_ADDR 0x07 + +#define MAX_RETRY 10 + +struct dw9807_device { + struct v4l2_ctrl_handler ctrls_vcm; + struct v4l2_subdev sd; + u16 current_val; +}; + +static inline struct dw9807_device *sd_to_dw9807_vcm( + struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct dw9807_device, sd); +} + +static int dw9807_i2c_check(struct i2c_client *client) +{ + const char status_addr = DW9807_STATUS_ADDR; + char status_result; + int ret; + + ret = i2c_master_send(client, &status_addr, sizeof(status_addr)); + if (ret < 0) { + dev_err(&client->dev, "I2C write STATUS address fail ret = %d\n", + ret); + return ret; + } + + ret = i2c_master_recv(client, &status_result, sizeof(status_result)); + if (ret < 0) { + dev_err(&client->dev, "I2C read STATUS value fail ret = %d\n", + ret); + return ret; + } + + return status_result; +} + +static int dw9807_set_dac(struct i2c_client *client, u16 data) +{ + const char tx_data[3] = { + DW9807_MSB_ADDR, ((data >> 8) & 0x03), (data & 0xff) + }; + int val, ret; + + /* + * According to the datasheet, need to check the bus status before we + * write VCM position. This ensure that we really write the value + * into the register + */ + ret = readx_poll_timeout(dw9807_i2c_check, client, val, val <= 0, + DW9807_CTRL_DELAY_US, MAX_RETRY * DW9807_CTRL_DELAY_US); + + if (ret || val < 0) { + if (ret) { + dev_warn(&client->dev, + "Cannot do the write operation because VCM is busy\n"); + } + + return ret ? -EBUSY : val; + } + + /* Write VCM position to registers */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, + "I2C write MSB fail ret=%d\n", ret); + + return ret; + } + + return 0; +} + +static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9807_device *dev_vcm = container_of(ctrl->handler, + struct dw9807_device, ctrls_vcm); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) { + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + + dev_vcm->current_val = ctrl->val; + return dw9807_set_dac(client, ctrl->val); + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops dw9807_vcm_ctrl_ops = { + .s_ctrl = dw9807_set_ctrl, +}; + +static int dw9807_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + int rval; + + rval = pm_runtime_get_sync(sd->dev); + if (rval < 0) { + pm_runtime_put_noidle(sd->dev); + return rval; + } + + return 0; +} + +static int dw9807_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9807_int_ops = { + .open = dw9807_open, + .close = dw9807_close, +}; + +static const struct v4l2_subdev_ops dw9807_ops = { }; + +static void dw9807_subdev_cleanup(struct dw9807_device *dw9807_dev) +{ + v4l2_async_unregister_subdev(&dw9807_dev->sd); + v4l2_ctrl_handler_free(&dw9807_dev->ctrls_vcm); + media_entity_cleanup(&dw9807_dev->sd.entity); +} + +static int dw9807_init_controls(struct dw9807_device *dev_vcm) +{ + struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm; + const struct v4l2_ctrl_ops *ops = &dw9807_vcm_ctrl_ops; + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + + v4l2_ctrl_handler_init(hdl, 1); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, + 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, 0); + + dev_vcm->sd.ctrl_handler = hdl; + if (hdl->error) { + dev_err(&client->dev, "%s fail error: 0x%x\n", + __func__, hdl->error); + return hdl->error; + } + + return 0; +} + +static int dw9807_probe(struct i2c_client *client) +{ + struct dw9807_device *dw9807_dev; + int rval; + + dw9807_dev = devm_kzalloc(&client->dev, sizeof(*dw9807_dev), + GFP_KERNEL); + if (dw9807_dev == NULL) + return -ENOMEM; + + v4l2_i2c_subdev_init(&dw9807_dev->sd, client, &dw9807_ops); + dw9807_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9807_dev->sd.internal_ops = &dw9807_int_ops; + + rval = dw9807_init_controls(dw9807_dev); + if (rval) + goto err_cleanup; + + rval = media_entity_pads_init(&dw9807_dev->sd.entity, 0, NULL); + if (rval < 0) + goto err_cleanup; + + dw9807_dev->sd.entity.function = MEDIA_ENT_F_LENS; + + rval = v4l2_async_register_subdev(&dw9807_dev->sd); + if (rval < 0) + goto err_cleanup; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_cleanup: + v4l2_ctrl_handler_free(&dw9807_dev->ctrls_vcm); + media_entity_cleanup(&dw9807_dev->sd.entity); + + return rval; +} + +static int dw9807_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + + pm_runtime_disable(&client->dev); + + dw9807_subdev_cleanup(dw9807_dev); + + return 0; +} + +/* + * This function sets the vcm position, so it consumes least current + * The lens position is gradually moved in units of DW9807_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9807_vcm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 }; + int ret, val; + + for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1); + val >= 0; val -= DW9807_CTRL_STEPS) { + ret = dw9807_set_dac(client, val); + if (ret) + dev_err_once(dev, "%s I2C failure: %d", __func__, ret); + usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); + } + + /* Power down */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); + return ret; + } + + return 0; +} + +/* + * This function sets the vcm position to the value set by the user + * through v4l2_ctrl_ops s_ctrl handler + * The lens position is gradually moved in units of DW9807_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9807_vcm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 }; + int ret, val; + + /* Power on */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); + return ret; + } + + for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS; + val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1; + val += DW9807_CTRL_STEPS) { + ret = dw9807_set_dac(client, val); + if (ret) + dev_err_ratelimited(dev, "%s I2C failure: %d", + __func__, ret); + usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); + } + + return 0; +} + +static const struct of_device_id dw9807_of_table[] = { + { .compatible = "dongwoon,dw9807-vcm" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dw9807_of_table); + +static const struct dev_pm_ops dw9807_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dw9807_vcm_suspend, dw9807_vcm_resume) + SET_RUNTIME_PM_OPS(dw9807_vcm_suspend, dw9807_vcm_resume, NULL) +}; + +static struct i2c_driver dw9807_i2c_driver = { + .driver = { + .name = "dw9807", + .pm = &dw9807_pm_ops, + .of_match_table = dw9807_of_table, + }, + .probe_new = dw9807_probe, + .remove = dw9807_remove, +}; + +module_i2c_driver(dw9807_i2c_driver); + +MODULE_AUTHOR("Chiang, Alan"); +MODULE_DESCRIPTION("DW9807 VCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/et8ek8/Kconfig b/drivers/media/i2c/et8ek8/Kconfig new file mode 100644 index 000000000..afcc4ea76 --- /dev/null +++ b/drivers/media/i2c/et8ek8/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_ET8EK8 + tristate "ET8EK8 camera sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a driver for the Toshiba ET8EK8 5 MP camera sensor. + It is used for example in Nokia N900 (RX-51). diff --git a/drivers/media/i2c/et8ek8/Makefile b/drivers/media/i2c/et8ek8/Makefile new file mode 100644 index 000000000..5e06c308c --- /dev/null +++ b/drivers/media/i2c/et8ek8/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +et8ek8-objs += et8ek8_mode.o et8ek8_driver.o +obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8.o diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c new file mode 100644 index 000000000..256acf73d --- /dev/null +++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c @@ -0,0 +1,1515 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * et8ek8_driver.c + * + * Copyright (C) 2008 Nokia Corporation + * + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + * Tuukka Toivonen <tuukkat76@gmail.com> + * Pavel Machek <pavel@ucw.cz> + * + * Based on code from Toni Leinonen <toni.leinonen@offcode.fi>. + * + * This driver is based on the Micron MT9T012 camera imager driver + * (C) Texas Instruments. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/sort.h> +#include <linux/v4l2-mediabus.h> + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#include "et8ek8_reg.h" + +#define ET8EK8_NAME "et8ek8" +#define ET8EK8_PRIV_MEM_SIZE 128 +#define ET8EK8_MAX_MSG 8 + +struct et8ek8_sensor { + struct v4l2_subdev subdev; + struct media_pad pad; + struct v4l2_mbus_framefmt format; + struct gpio_desc *reset; + struct regulator *vana; + struct clk *ext_clk; + u32 xclk_freq; + + u16 version; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *pixel_rate; + struct et8ek8_reglist *current_reglist; + + u8 priv_mem[ET8EK8_PRIV_MEM_SIZE]; + + struct mutex power_lock; + int power_count; +}; + +#define to_et8ek8_sensor(sd) container_of(sd, struct et8ek8_sensor, subdev) + +enum et8ek8_versions { + ET8EK8_REV_1 = 0x0001, + ET8EK8_REV_2, +}; + +/* + * This table describes what should be written to the sensor register + * for each gain value. The gain(index in the table) is in terms of + * 0.1EV, i.e. 10 indexes in the table give 2 time more gain [0] in + * the *analog gain, [1] in the digital gain + * + * Analog gain [dB] = 20*log10(regvalue/32); 0x20..0x100 + */ +static struct et8ek8_gain { + u16 analog; + u16 digital; +} const et8ek8_gain_table[] = { + { 32, 0}, /* x1 */ + { 34, 0}, + { 37, 0}, + { 39, 0}, + { 42, 0}, + { 45, 0}, + { 49, 0}, + { 52, 0}, + { 56, 0}, + { 60, 0}, + { 64, 0}, /* x2 */ + { 69, 0}, + { 74, 0}, + { 79, 0}, + { 84, 0}, + { 91, 0}, + { 97, 0}, + {104, 0}, + {111, 0}, + {119, 0}, + {128, 0}, /* x4 */ + {137, 0}, + {147, 0}, + {158, 0}, + {169, 0}, + {181, 0}, + {194, 0}, + {208, 0}, + {223, 0}, + {239, 0}, + {256, 0}, /* x8 */ + {256, 73}, + {256, 152}, + {256, 236}, + {256, 327}, + {256, 424}, + {256, 528}, + {256, 639}, + {256, 758}, + {256, 886}, + {256, 1023}, /* x16 */ +}; + +/* Register definitions */ +#define REG_REVISION_NUMBER_L 0x1200 +#define REG_REVISION_NUMBER_H 0x1201 + +#define PRIV_MEM_START_REG 0x0008 +#define PRIV_MEM_WIN_SIZE 8 + +#define ET8EK8_I2C_DELAY 3 /* msec delay b/w accesses */ + +#define USE_CRC 1 + +/* + * Register access helpers + * + * Read a 8/16/32-bit i2c register. The value is returned in 'val'. + * Returns zero if successful, or non-zero otherwise. + */ +static int et8ek8_i2c_read_reg(struct i2c_client *client, u16 data_length, + u16 reg, u32 *val) +{ + int r; + struct i2c_msg msg; + unsigned char data[4]; + + if (!client->adapter) + return -ENODEV; + if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT) + return -EINVAL; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = data; + + /* high byte goes out first */ + data[0] = (u8) (reg >> 8); + data[1] = (u8) (reg & 0xff); + r = i2c_transfer(client->adapter, &msg, 1); + if (r < 0) + goto err; + + msg.len = data_length; + msg.flags = I2C_M_RD; + r = i2c_transfer(client->adapter, &msg, 1); + if (r < 0) + goto err; + + *val = 0; + /* high byte comes first */ + if (data_length == ET8EK8_REG_8BIT) + *val = data[0]; + else + *val = (data[1] << 8) + data[0]; + + return 0; + +err: + dev_err(&client->dev, "read from offset 0x%x error %d\n", reg, r); + + return r; +} + +static void et8ek8_i2c_create_msg(struct i2c_client *client, u16 len, u16 reg, + u32 val, struct i2c_msg *msg, + unsigned char *buf) +{ + msg->addr = client->addr; + msg->flags = 0; /* Write */ + msg->len = 2 + len; + msg->buf = buf; + + /* high byte goes out first */ + buf[0] = (u8) (reg >> 8); + buf[1] = (u8) (reg & 0xff); + + switch (len) { + case ET8EK8_REG_8BIT: + buf[2] = (u8) (val) & 0xff; + break; + case ET8EK8_REG_16BIT: + buf[2] = (u8) (val) & 0xff; + buf[3] = (u8) (val >> 8) & 0xff; + break; + default: + WARN_ONCE(1, ET8EK8_NAME ": %s: invalid message length.\n", + __func__); + } +} + +/* + * A buffered write method that puts the wanted register write + * commands in smaller number of message lists and passes the lists to + * the i2c framework + */ +static int et8ek8_i2c_buffered_write_regs(struct i2c_client *client, + const struct et8ek8_reg *wnext, + int cnt) +{ + struct i2c_msg msg[ET8EK8_MAX_MSG]; + unsigned char data[ET8EK8_MAX_MSG][6]; + int wcnt = 0; + u16 reg, data_length; + u32 val; + int rval; + + /* Create new write messages for all writes */ + while (wcnt < cnt) { + data_length = wnext->type; + reg = wnext->reg; + val = wnext->val; + wnext++; + + et8ek8_i2c_create_msg(client, data_length, reg, + val, &msg[wcnt], &data[wcnt][0]); + + /* Update write count */ + wcnt++; + + if (wcnt < ET8EK8_MAX_MSG) + continue; + + rval = i2c_transfer(client->adapter, msg, wcnt); + if (rval < 0) + return rval; + + cnt -= wcnt; + wcnt = 0; + } + + rval = i2c_transfer(client->adapter, msg, wcnt); + + return rval < 0 ? rval : 0; +} + +/* + * Write a list of registers to i2c device. + * + * The list of registers is terminated by ET8EK8_REG_TERM. + * Returns zero if successful, or non-zero otherwise. + */ +static int et8ek8_i2c_write_regs(struct i2c_client *client, + const struct et8ek8_reg *regs) +{ + int r, cnt = 0; + const struct et8ek8_reg *next; + + if (!client->adapter) + return -ENODEV; + + if (!regs) + return -EINVAL; + + /* Initialize list pointers to the start of the list */ + next = regs; + + do { + /* + * We have to go through the list to figure out how + * many regular writes we have in a row + */ + while (next->type != ET8EK8_REG_TERM && + next->type != ET8EK8_REG_DELAY) { + /* + * Here we check that the actual length fields + * are valid + */ + if (WARN(next->type != ET8EK8_REG_8BIT && + next->type != ET8EK8_REG_16BIT, + "Invalid type = %d", next->type)) { + return -EINVAL; + } + /* + * Increment count of successive writes and + * read pointer + */ + cnt++; + next++; + } + + /* Now we start writing ... */ + r = et8ek8_i2c_buffered_write_regs(client, regs, cnt); + + /* ... and then check that everything was OK */ + if (r < 0) { + dev_err(&client->dev, "i2c transfer error!\n"); + return r; + } + + /* + * If we ran into a sleep statement when going through + * the list, this is where we snooze for the required time + */ + if (next->type == ET8EK8_REG_DELAY) { + msleep(next->val); + /* + * ZZZ ... + * Update list pointers and cnt and start over ... + */ + next++; + regs = next; + cnt = 0; + } + } while (next->type != ET8EK8_REG_TERM); + + return 0; +} + +/* + * Write to a 8/16-bit register. + * Returns zero if successful, or non-zero otherwise. + */ +static int et8ek8_i2c_write_reg(struct i2c_client *client, u16 data_length, + u16 reg, u32 val) +{ + int r; + struct i2c_msg msg; + unsigned char data[6]; + + if (!client->adapter) + return -ENODEV; + if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT) + return -EINVAL; + + et8ek8_i2c_create_msg(client, data_length, reg, val, &msg, data); + + r = i2c_transfer(client->adapter, &msg, 1); + if (r < 0) { + dev_err(&client->dev, + "wrote 0x%x to offset 0x%x error %d\n", val, reg, r); + return r; + } + + return 0; +} + +static struct et8ek8_reglist *et8ek8_reglist_find_type( + struct et8ek8_meta_reglist *meta, + u16 type) +{ + struct et8ek8_reglist **next = &meta->reglist[0].ptr; + + while (*next) { + if ((*next)->type == type) + return *next; + + next++; + } + + return NULL; +} + +static int et8ek8_i2c_reglist_find_write(struct i2c_client *client, + struct et8ek8_meta_reglist *meta, + u16 type) +{ + struct et8ek8_reglist *reglist; + + reglist = et8ek8_reglist_find_type(meta, type); + if (!reglist) + return -EINVAL; + + return et8ek8_i2c_write_regs(client, reglist->regs); +} + +static struct et8ek8_reglist **et8ek8_reglist_first( + struct et8ek8_meta_reglist *meta) +{ + return &meta->reglist[0].ptr; +} + +static void et8ek8_reglist_to_mbus(const struct et8ek8_reglist *reglist, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = reglist->mode.window_width; + fmt->height = reglist->mode.window_height; + fmt->code = reglist->mode.bus_format; +} + +static struct et8ek8_reglist *et8ek8_reglist_find_mode_fmt( + struct et8ek8_meta_reglist *meta, + struct v4l2_mbus_framefmt *fmt) +{ + struct et8ek8_reglist **list = et8ek8_reglist_first(meta); + struct et8ek8_reglist *best_match = NULL; + struct et8ek8_reglist *best_other = NULL; + struct v4l2_mbus_framefmt format; + unsigned int max_dist_match = (unsigned int)-1; + unsigned int max_dist_other = (unsigned int)-1; + + /* + * Find the mode with the closest image size. The distance between + * image sizes is the size in pixels of the non-overlapping regions + * between the requested size and the frame-specified size. + * + * Store both the closest mode that matches the requested format, and + * the closest mode for all other formats. The best match is returned + * if found, otherwise the best mode with a non-matching format is + * returned. + */ + for (; *list; list++) { + unsigned int dist; + + if ((*list)->type != ET8EK8_REGLIST_MODE) + continue; + + et8ek8_reglist_to_mbus(*list, &format); + + dist = min(fmt->width, format.width) + * min(fmt->height, format.height); + dist = format.width * format.height + + fmt->width * fmt->height - 2 * dist; + + + if (fmt->code == format.code) { + if (dist < max_dist_match || !best_match) { + best_match = *list; + max_dist_match = dist; + } + } else { + if (dist < max_dist_other || !best_other) { + best_other = *list; + max_dist_other = dist; + } + } + } + + return best_match ? best_match : best_other; +} + +#define TIMEPERFRAME_AVG_FPS(t) \ + (((t).denominator + ((t).numerator >> 1)) / (t).numerator) + +static struct et8ek8_reglist *et8ek8_reglist_find_mode_ival( + struct et8ek8_meta_reglist *meta, + struct et8ek8_reglist *current_reglist, + struct v4l2_fract *timeperframe) +{ + int fps = TIMEPERFRAME_AVG_FPS(*timeperframe); + struct et8ek8_reglist **list = et8ek8_reglist_first(meta); + struct et8ek8_mode *current_mode = ¤t_reglist->mode; + + for (; *list; list++) { + struct et8ek8_mode *mode = &(*list)->mode; + + if ((*list)->type != ET8EK8_REGLIST_MODE) + continue; + + if (mode->window_width != current_mode->window_width || + mode->window_height != current_mode->window_height) + continue; + + if (TIMEPERFRAME_AVG_FPS(mode->timeperframe) == fps) + return *list; + } + + return NULL; +} + +static int et8ek8_reglist_cmp(const void *a, const void *b) +{ + const struct et8ek8_reglist **list1 = (const struct et8ek8_reglist **)a, + **list2 = (const struct et8ek8_reglist **)b; + + /* Put real modes in the beginning. */ + if ((*list1)->type == ET8EK8_REGLIST_MODE && + (*list2)->type != ET8EK8_REGLIST_MODE) + return -1; + if ((*list1)->type != ET8EK8_REGLIST_MODE && + (*list2)->type == ET8EK8_REGLIST_MODE) + return 1; + + /* Descending width. */ + if ((*list1)->mode.window_width > (*list2)->mode.window_width) + return -1; + if ((*list1)->mode.window_width < (*list2)->mode.window_width) + return 1; + + if ((*list1)->mode.window_height > (*list2)->mode.window_height) + return -1; + if ((*list1)->mode.window_height < (*list2)->mode.window_height) + return 1; + + return 0; +} + +static int et8ek8_reglist_import(struct i2c_client *client, + struct et8ek8_meta_reglist *meta) +{ + int nlists = 0, i; + + dev_info(&client->dev, "meta_reglist version %s\n", meta->version); + + while (meta->reglist[nlists].ptr) + nlists++; + + if (!nlists) + return -EINVAL; + + sort(&meta->reglist[0].ptr, nlists, sizeof(meta->reglist[0].ptr), + et8ek8_reglist_cmp, NULL); + + i = nlists; + nlists = 0; + + while (i--) { + struct et8ek8_reglist *list; + + list = meta->reglist[nlists].ptr; + + dev_dbg(&client->dev, + "%s: type %d\tw %d\th %d\tfmt %x\tival %d/%d\tptr %p\n", + __func__, + list->type, + list->mode.window_width, list->mode.window_height, + list->mode.bus_format, + list->mode.timeperframe.numerator, + list->mode.timeperframe.denominator, + (void *)meta->reglist[nlists].ptr); + + nlists++; + } + + return 0; +} + +/* Called to change the V4L2 gain control value. This function + * rounds and clamps the given value and updates the V4L2 control value. + * If power is on, also updates the sensor analog and digital gains. + * gain is in 0.1 EV (exposure value) units. + */ +static int et8ek8_set_gain(struct et8ek8_sensor *sensor, s32 gain) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + struct et8ek8_gain new; + int r; + + new = et8ek8_gain_table[gain]; + + /* FIXME: optimise I2C writes! */ + r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, + 0x124a, new.analog >> 8); + if (r) + return r; + r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, + 0x1249, new.analog & 0xff); + if (r) + return r; + + r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, + 0x124d, new.digital >> 8); + if (r) + return r; + r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, + 0x124c, new.digital & 0xff); + + return r; +} + +static int et8ek8_set_test_pattern(struct et8ek8_sensor *sensor, s32 mode) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int cbh_mode, cbv_mode, tp_mode, din_sw, r1420, rval; + + /* Values for normal mode */ + cbh_mode = 0; + cbv_mode = 0; + tp_mode = 0; + din_sw = 0x00; + r1420 = 0xF0; + + if (mode) { + /* Test pattern mode */ + if (mode < 5) { + cbh_mode = 1; + cbv_mode = 1; + tp_mode = mode + 3; + } else { + cbh_mode = 0; + cbv_mode = 0; + tp_mode = mode - 4 + 3; + } + + din_sw = 0x01; + r1420 = 0xE0; + } + + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x111B, + tp_mode << 4); + if (rval) + return rval; + + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1121, + cbh_mode << 7); + if (rval) + return rval; + + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1124, + cbv_mode << 7); + if (rval) + return rval; + + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x112C, din_sw); + if (rval) + return rval; + + return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1420, r1420); +} + +/* ----------------------------------------------------------------------------- + * V4L2 controls + */ + +static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct et8ek8_sensor *sensor = + container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_GAIN: + return et8ek8_set_gain(sensor, ctrl->val); + + case V4L2_CID_EXPOSURE: + { + struct i2c_client *client = + v4l2_get_subdevdata(&sensor->subdev); + + return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243, + ctrl->val); + } + + case V4L2_CID_TEST_PATTERN: + return et8ek8_set_test_pattern(sensor, ctrl->val); + + case V4L2_CID_PIXEL_RATE: + return 0; + + default: + return -EINVAL; + } +} + +static const struct v4l2_ctrl_ops et8ek8_ctrl_ops = { + .s_ctrl = et8ek8_set_ctrl, +}; + +static const char * const et8ek8_test_pattern_menu[] = { + "Normal", + "Vertical colorbar", + "Horizontal colorbar", + "Scale", + "Ramp", + "Small vertical colorbar", + "Small horizontal colorbar", + "Small scale", + "Small ramp", +}; + +static int et8ek8_init_controls(struct et8ek8_sensor *sensor) +{ + s32 max_rows; + + v4l2_ctrl_handler_init(&sensor->ctrl_handler, 4); + + /* V4L2_CID_GAIN */ + v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops, + V4L2_CID_GAIN, 0, ARRAY_SIZE(et8ek8_gain_table) - 1, + 1, 0); + + max_rows = sensor->current_reglist->mode.max_exp; + { + u32 min = 1, max = max_rows; + + sensor->exposure = + v4l2_ctrl_new_std(&sensor->ctrl_handler, + &et8ek8_ctrl_ops, V4L2_CID_EXPOSURE, + min, max, min, max); + } + + /* V4L2_CID_PIXEL_RATE */ + sensor->pixel_rate = + v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops, + V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1); + + /* V4L2_CID_TEST_PATTERN */ + v4l2_ctrl_new_std_menu_items(&sensor->ctrl_handler, + &et8ek8_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(et8ek8_test_pattern_menu) - 1, + 0, 0, et8ek8_test_pattern_menu); + + if (sensor->ctrl_handler.error) + return sensor->ctrl_handler.error; + + sensor->subdev.ctrl_handler = &sensor->ctrl_handler; + + return 0; +} + +static void et8ek8_update_controls(struct et8ek8_sensor *sensor) +{ + struct v4l2_ctrl *ctrl; + struct et8ek8_mode *mode = &sensor->current_reglist->mode; + + u32 min, max, pixel_rate; + static const int S = 8; + + ctrl = sensor->exposure; + + min = 1; + max = mode->max_exp; + + /* + * Calculate average pixel clock per line. Assume buffers can spread + * the data over horizontal blanking time. Rounding upwards. + * Formula taken from stock Nokia N900 kernel. + */ + pixel_rate = ((mode->pixel_clock + (1 << S) - 1) >> S) + mode->width; + pixel_rate = mode->window_width * (pixel_rate - 1) / mode->width; + + __v4l2_ctrl_modify_range(ctrl, min, max, min, max); + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, pixel_rate << S); +} + +static int et8ek8_configure(struct et8ek8_sensor *sensor) +{ + struct v4l2_subdev *subdev = &sensor->subdev; + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int rval; + + rval = et8ek8_i2c_write_regs(client, sensor->current_reglist->regs); + if (rval) + goto fail; + + /* Controls set while the power to the sensor is turned off are saved + * but not applied to the hardware. Now that we're about to start + * streaming apply all the current values to the hardware. + */ + rval = v4l2_ctrl_handler_setup(&sensor->ctrl_handler); + if (rval) + goto fail; + + return 0; + +fail: + dev_err(&client->dev, "sensor configuration failed\n"); + + return rval; +} + +static int et8ek8_stream_on(struct et8ek8_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + + return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0xb0); +} + +static int et8ek8_stream_off(struct et8ek8_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + + return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0x30); +} + +static int et8ek8_s_stream(struct v4l2_subdev *subdev, int streaming) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + int ret; + + if (!streaming) + return et8ek8_stream_off(sensor); + + ret = et8ek8_configure(sensor); + if (ret < 0) + return ret; + + return et8ek8_stream_on(sensor); +} + +/* -------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static int et8ek8_power_off(struct et8ek8_sensor *sensor) +{ + gpiod_set_value(sensor->reset, 0); + udelay(1); + + clk_disable_unprepare(sensor->ext_clk); + + return regulator_disable(sensor->vana); +} + +static int et8ek8_power_on(struct et8ek8_sensor *sensor) +{ + struct v4l2_subdev *subdev = &sensor->subdev; + struct i2c_client *client = v4l2_get_subdevdata(subdev); + unsigned int xclk_freq; + int val, rval; + + rval = regulator_enable(sensor->vana); + if (rval) { + dev_err(&client->dev, "failed to enable vana regulator\n"); + return rval; + } + + if (sensor->current_reglist) + xclk_freq = sensor->current_reglist->mode.ext_clock; + else + xclk_freq = sensor->xclk_freq; + + rval = clk_set_rate(sensor->ext_clk, xclk_freq); + if (rval < 0) { + dev_err(&client->dev, "unable to set extclk clock freq to %u\n", + xclk_freq); + goto out; + } + rval = clk_prepare_enable(sensor->ext_clk); + if (rval < 0) { + dev_err(&client->dev, "failed to enable extclk\n"); + goto out; + } + + if (rval) + goto out; + + udelay(10); /* I wish this is a good value */ + + gpiod_set_value(sensor->reset, 1); + + msleep(5000 * 1000 / xclk_freq + 1); /* Wait 5000 cycles */ + + rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist, + ET8EK8_REGLIST_POWERON); + if (rval) + goto out; + +#ifdef USE_CRC + rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, 0x1263, &val); + if (rval) + goto out; +#if USE_CRC /* TODO get crc setting from DT */ + val |= BIT(4); +#else + val &= ~BIT(4); +#endif + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1263, val); + if (rval) + goto out; +#endif + +out: + if (rval) + et8ek8_power_off(sensor); + + return rval; +} + +/* -------------------------------------------------------------------------- + * V4L2 subdev video operations + */ +#define MAX_FMTS 4 +static int et8ek8_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct et8ek8_reglist **list = + et8ek8_reglist_first(&meta_reglist); + u32 pixelformat[MAX_FMTS]; + int npixelformat = 0; + + if (code->index >= MAX_FMTS) + return -EINVAL; + + for (; *list; list++) { + struct et8ek8_mode *mode = &(*list)->mode; + int i; + + if ((*list)->type != ET8EK8_REGLIST_MODE) + continue; + + for (i = 0; i < npixelformat; i++) { + if (pixelformat[i] == mode->bus_format) + break; + } + if (i != npixelformat) + continue; + + if (code->index == npixelformat) { + code->code = mode->bus_format; + return 0; + } + + pixelformat[npixelformat] = mode->bus_format; + npixelformat++; + } + + return -EINVAL; +} + +static int et8ek8_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct et8ek8_reglist **list = + et8ek8_reglist_first(&meta_reglist); + struct v4l2_mbus_framefmt format; + int cmp_width = INT_MAX; + int cmp_height = INT_MAX; + int index = fse->index; + + for (; *list; list++) { + if ((*list)->type != ET8EK8_REGLIST_MODE) + continue; + + et8ek8_reglist_to_mbus(*list, &format); + if (fse->code != format.code) + continue; + + /* Assume that the modes are grouped by frame size. */ + if (format.width == cmp_width && format.height == cmp_height) + continue; + + cmp_width = format.width; + cmp_height = format.height; + + if (index-- == 0) { + fse->min_width = format.width; + fse->min_height = format.height; + fse->max_width = format.width; + fse->max_height = format.height; + return 0; + } + } + + return -EINVAL; +} + +static int et8ek8_enum_frame_ival(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct et8ek8_reglist **list = + et8ek8_reglist_first(&meta_reglist); + struct v4l2_mbus_framefmt format; + int index = fie->index; + + for (; *list; list++) { + struct et8ek8_mode *mode = &(*list)->mode; + + if ((*list)->type != ET8EK8_REGLIST_MODE) + continue; + + et8ek8_reglist_to_mbus(*list, &format); + if (fie->code != format.code) + continue; + + if (fie->width != format.width || fie->height != format.height) + continue; + + if (index-- == 0) { + fie->interval = mode->timeperframe; + return 0; + } + } + + return -EINVAL; +} + +static struct v4l2_mbus_framefmt * +__et8ek8_get_pad_format(struct et8ek8_sensor *sensor, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&sensor->subdev, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &sensor->format; + default: + return NULL; + } +} + +static int et8ek8_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct v4l2_mbus_framefmt *format; + + format = __et8ek8_get_pad_format(sensor, cfg, fmt->pad, fmt->which); + if (!format) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +static int et8ek8_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct v4l2_mbus_framefmt *format; + struct et8ek8_reglist *reglist; + + format = __et8ek8_get_pad_format(sensor, cfg, fmt->pad, fmt->which); + if (!format) + return -EINVAL; + + reglist = et8ek8_reglist_find_mode_fmt(&meta_reglist, &fmt->format); + et8ek8_reglist_to_mbus(reglist, &fmt->format); + *format = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + sensor->current_reglist = reglist; + et8ek8_update_controls(sensor); + } + + return 0; +} + +static int et8ek8_get_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fi) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + + memset(fi, 0, sizeof(*fi)); + fi->interval = sensor->current_reglist->mode.timeperframe; + + return 0; +} + +static int et8ek8_set_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fi) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct et8ek8_reglist *reglist; + + reglist = et8ek8_reglist_find_mode_ival(&meta_reglist, + sensor->current_reglist, + &fi->interval); + + if (!reglist) + return -EINVAL; + + if (sensor->current_reglist->mode.ext_clock != reglist->mode.ext_clock) + return -EINVAL; + + sensor->current_reglist = reglist; + et8ek8_update_controls(sensor); + + return 0; +} + +static int et8ek8_g_priv_mem(struct v4l2_subdev *subdev) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + unsigned int length = ET8EK8_PRIV_MEM_SIZE; + unsigned int offset = 0; + u8 *ptr = sensor->priv_mem; + int rval = 0; + + /* Read the EEPROM window-by-window, each window 8 bytes */ + do { + u8 buffer[PRIV_MEM_WIN_SIZE]; + struct i2c_msg msg; + int bytes, i; + int ofs; + + /* Set the current window */ + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x0001, + 0xe0 | (offset >> 3)); + if (rval < 0) + return rval; + + /* Wait for status bit */ + for (i = 0; i < 1000; ++i) { + u32 status; + + rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, + 0x0003, &status); + if (rval < 0) + return rval; + if (!(status & 0x08)) + break; + usleep_range(1000, 2000); + } + + if (i == 1000) + return -EIO; + + /* Read window, 8 bytes at once, and copy to user space */ + ofs = offset & 0x07; /* Offset within this window */ + bytes = length + ofs > 8 ? 8-ofs : length; + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = buffer; + ofs += PRIV_MEM_START_REG; + buffer[0] = (u8)(ofs >> 8); + buffer[1] = (u8)(ofs & 0xFF); + + rval = i2c_transfer(client->adapter, &msg, 1); + if (rval < 0) + return rval; + + mdelay(ET8EK8_I2C_DELAY); + msg.addr = client->addr; + msg.len = bytes; + msg.flags = I2C_M_RD; + msg.buf = buffer; + memset(buffer, 0, sizeof(buffer)); + + rval = i2c_transfer(client->adapter, &msg, 1); + if (rval < 0) + return rval; + + rval = 0; + memcpy(ptr, buffer, bytes); + + length -= bytes; + offset += bytes; + ptr += bytes; + } while (length > 0); + + return rval; +} + +static int et8ek8_dev_init(struct v4l2_subdev *subdev) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int rval, rev_l, rev_h; + + rval = et8ek8_power_on(sensor); + if (rval) { + dev_err(&client->dev, "could not power on\n"); + return rval; + } + + rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, + REG_REVISION_NUMBER_L, &rev_l); + if (!rval) + rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, + REG_REVISION_NUMBER_H, &rev_h); + if (rval) { + dev_err(&client->dev, "no et8ek8 sensor detected\n"); + goto out_poweroff; + } + + sensor->version = (rev_h << 8) + rev_l; + if (sensor->version != ET8EK8_REV_1 && sensor->version != ET8EK8_REV_2) + dev_info(&client->dev, + "unknown version 0x%x detected, continuing anyway\n", + sensor->version); + + rval = et8ek8_reglist_import(client, &meta_reglist); + if (rval) { + dev_err(&client->dev, + "invalid register list %s, import failed\n", + ET8EK8_NAME); + goto out_poweroff; + } + + sensor->current_reglist = et8ek8_reglist_find_type(&meta_reglist, + ET8EK8_REGLIST_MODE); + if (!sensor->current_reglist) { + dev_err(&client->dev, + "invalid register list %s, no mode found\n", + ET8EK8_NAME); + rval = -ENODEV; + goto out_poweroff; + } + + et8ek8_reglist_to_mbus(sensor->current_reglist, &sensor->format); + + rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist, + ET8EK8_REGLIST_POWERON); + if (rval) { + dev_err(&client->dev, + "invalid register list %s, no POWERON mode found\n", + ET8EK8_NAME); + goto out_poweroff; + } + rval = et8ek8_stream_on(sensor); /* Needed to be able to read EEPROM */ + if (rval) + goto out_poweroff; + rval = et8ek8_g_priv_mem(subdev); + if (rval) + dev_warn(&client->dev, + "can not read OTP (EEPROM) memory from sensor\n"); + rval = et8ek8_stream_off(sensor); + if (rval) + goto out_poweroff; + + rval = et8ek8_power_off(sensor); + if (rval) + goto out_poweroff; + + return 0; + +out_poweroff: + et8ek8_power_off(sensor); + + return rval; +} + +/* -------------------------------------------------------------------------- + * sysfs attributes + */ +static ssize_t +et8ek8_priv_mem_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev)); + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + +#if PAGE_SIZE < ET8EK8_PRIV_MEM_SIZE +#error PAGE_SIZE too small! +#endif + + memcpy(buf, sensor->priv_mem, ET8EK8_PRIV_MEM_SIZE); + + return ET8EK8_PRIV_MEM_SIZE; +} +static DEVICE_ATTR(priv_mem, 0444, et8ek8_priv_mem_read, NULL); + +/* -------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +static int +et8ek8_registered(struct v4l2_subdev *subdev) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int rval; + + dev_dbg(&client->dev, "registered!"); + + rval = device_create_file(&client->dev, &dev_attr_priv_mem); + if (rval) { + dev_err(&client->dev, "could not register sysfs entry\n"); + return rval; + } + + rval = et8ek8_dev_init(subdev); + if (rval) + goto err_file; + + rval = et8ek8_init_controls(sensor); + if (rval) { + dev_err(&client->dev, "controls initialization failed\n"); + goto err_file; + } + + __et8ek8_get_pad_format(sensor, NULL, 0, V4L2_SUBDEV_FORMAT_ACTIVE); + + return 0; + +err_file: + device_remove_file(&client->dev, &dev_attr_priv_mem); + + return rval; +} + +static int __et8ek8_set_power(struct et8ek8_sensor *sensor, bool on) +{ + return on ? et8ek8_power_on(sensor) : et8ek8_power_off(sensor); +} + +static int et8ek8_set_power(struct v4l2_subdev *subdev, int on) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + int ret = 0; + + mutex_lock(&sensor->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (sensor->power_count == !on) { + ret = __et8ek8_set_power(sensor, !!on); + if (ret < 0) + goto done; + } + + /* Update the power count. */ + sensor->power_count += on ? 1 : -1; + WARN_ON(sensor->power_count < 0); + +done: + mutex_unlock(&sensor->power_lock); + + return ret; +} + +static int et8ek8_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(sd); + struct v4l2_mbus_framefmt *format; + struct et8ek8_reglist *reglist; + + reglist = et8ek8_reglist_find_type(&meta_reglist, ET8EK8_REGLIST_MODE); + format = __et8ek8_get_pad_format(sensor, fh->pad, 0, + V4L2_SUBDEV_FORMAT_TRY); + et8ek8_reglist_to_mbus(reglist, format); + + return et8ek8_set_power(sd, true); +} + +static int et8ek8_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return et8ek8_set_power(sd, false); +} + +static const struct v4l2_subdev_video_ops et8ek8_video_ops = { + .s_stream = et8ek8_s_stream, + .g_frame_interval = et8ek8_get_frame_interval, + .s_frame_interval = et8ek8_set_frame_interval, +}; + +static const struct v4l2_subdev_core_ops et8ek8_core_ops = { + .s_power = et8ek8_set_power, +}; + +static const struct v4l2_subdev_pad_ops et8ek8_pad_ops = { + .enum_mbus_code = et8ek8_enum_mbus_code, + .enum_frame_size = et8ek8_enum_frame_size, + .enum_frame_interval = et8ek8_enum_frame_ival, + .get_fmt = et8ek8_get_pad_format, + .set_fmt = et8ek8_set_pad_format, +}; + +static const struct v4l2_subdev_ops et8ek8_ops = { + .core = &et8ek8_core_ops, + .video = &et8ek8_video_ops, + .pad = &et8ek8_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops et8ek8_internal_ops = { + .registered = et8ek8_registered, + .open = et8ek8_open, + .close = et8ek8_close, +}; + +/* -------------------------------------------------------------------------- + * I2C driver + */ +static int __maybe_unused et8ek8_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + + if (!sensor->power_count) + return 0; + + return __et8ek8_set_power(sensor, false); +} + +static int __maybe_unused et8ek8_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + + if (!sensor->power_count) + return 0; + + return __et8ek8_set_power(sensor, true); +} + +static int et8ek8_probe(struct i2c_client *client) +{ + struct et8ek8_sensor *sensor; + struct device *dev = &client->dev; + int ret; + + sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(sensor->reset)) { + dev_dbg(&client->dev, "could not request reset gpio\n"); + return PTR_ERR(sensor->reset); + } + + sensor->vana = devm_regulator_get(dev, "vana"); + if (IS_ERR(sensor->vana)) { + dev_err(&client->dev, "could not get regulator for vana\n"); + return PTR_ERR(sensor->vana); + } + + sensor->ext_clk = devm_clk_get(dev, NULL); + if (IS_ERR(sensor->ext_clk)) { + dev_err(&client->dev, "could not get clock\n"); + return PTR_ERR(sensor->ext_clk); + } + + ret = of_property_read_u32(dev->of_node, "clock-frequency", + &sensor->xclk_freq); + if (ret) { + dev_warn(dev, "can't get clock-frequency\n"); + return ret; + } + + mutex_init(&sensor->power_lock); + + v4l2_i2c_subdev_init(&sensor->subdev, client, &et8ek8_ops); + sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->subdev.internal_ops = &et8ek8_internal_ops; + + sensor->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad); + if (ret < 0) { + dev_err(&client->dev, "media entity init failed!\n"); + goto err_mutex; + } + + ret = v4l2_async_register_subdev_sensor_common(&sensor->subdev); + if (ret < 0) + goto err_entity; + + dev_dbg(dev, "initialized!\n"); + + return 0; + +err_entity: + media_entity_cleanup(&sensor->subdev.entity); +err_mutex: + mutex_destroy(&sensor->power_lock); + return ret; +} + +static int __exit et8ek8_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + + if (sensor->power_count) { + WARN_ON(1); + et8ek8_power_off(sensor); + sensor->power_count = 0; + } + + v4l2_device_unregister_subdev(&sensor->subdev); + device_remove_file(&client->dev, &dev_attr_priv_mem); + v4l2_ctrl_handler_free(&sensor->ctrl_handler); + v4l2_async_unregister_subdev(&sensor->subdev); + media_entity_cleanup(&sensor->subdev.entity); + mutex_destroy(&sensor->power_lock); + + return 0; +} + +static const struct of_device_id et8ek8_of_table[] = { + { .compatible = "toshiba,et8ek8" }, + { }, +}; +MODULE_DEVICE_TABLE(of, et8ek8_of_table); + +static const struct i2c_device_id et8ek8_id_table[] = { + { ET8EK8_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, et8ek8_id_table); + +static const struct dev_pm_ops et8ek8_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(et8ek8_suspend, et8ek8_resume) +}; + +static struct i2c_driver et8ek8_i2c_driver = { + .driver = { + .name = ET8EK8_NAME, + .pm = &et8ek8_pm_ops, + .of_match_table = et8ek8_of_table, + }, + .probe_new = et8ek8_probe, + .remove = __exit_p(et8ek8_remove), + .id_table = et8ek8_id_table, +}; + +module_i2c_driver(et8ek8_i2c_driver); + +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>, Pavel Machek <pavel@ucw.cz"); +MODULE_DESCRIPTION("Toshiba ET8EK8 camera sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/et8ek8/et8ek8_mode.c b/drivers/media/i2c/et8ek8/et8ek8_mode.c new file mode 100644 index 000000000..c9088eb0a --- /dev/null +++ b/drivers/media/i2c/et8ek8/et8ek8_mode.c @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * et8ek8_mode.c + * + * Copyright (C) 2008 Nokia Corporation + * + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + * Tuukka Toivonen <tuukkat76@gmail.com> + */ + +#include "et8ek8_reg.h" + +/* + * Stingray sensor mode settings for Scooby + */ + +/* Mode1_poweron_Mode2_16VGA_2592x1968_12.07fps */ +static struct et8ek8_reglist mode1_poweron_mode2_16vga_2592x1968_12_07fps = { +/* (without the +1) + * SPCK = 80 MHz + * CCP2 = 640 MHz + * VCO = 640 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 137 (3288) + * CKREF_DIV = 2 + * CKVAR_DIV = 200 + * VCO_DIV = 0 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_POWERON, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3288, + .height = 2016, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 2592, + .window_height = 1968, + .pixel_clock = 80000000, + .ext_clock = 9600000, + .timeperframe = { + .numerator = 100, + .denominator = 1207 + }, + .max_exp = 2012, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + /* Need to set firstly */ + { ET8EK8_REG_8BIT, 0x126C, 0xCC }, + /* Strobe and Data of CCP2 delay are minimized. */ + { ET8EK8_REG_8BIT, 0x1269, 0x00 }, + /* Refined value of Min H_COUNT */ + { ET8EK8_REG_8BIT, 0x1220, 0x89 }, + /* Frequency of SPCK setting (SPCK=MRCK) */ + { ET8EK8_REG_8BIT, 0x123A, 0x07 }, + { ET8EK8_REG_8BIT, 0x1241, 0x94 }, + { ET8EK8_REG_8BIT, 0x1242, 0x02 }, + { ET8EK8_REG_8BIT, 0x124B, 0x00 }, + { ET8EK8_REG_8BIT, 0x1255, 0xFF }, + { ET8EK8_REG_8BIT, 0x1256, 0x9F }, + { ET8EK8_REG_8BIT, 0x1258, 0x00 }, + /* From parallel out to serial out */ + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, + /* From w/ embedded data to w/o embedded data */ + { ET8EK8_REG_8BIT, 0x125E, 0xC0 }, + /* CCP2 out is from STOP to ACTIVE */ + { ET8EK8_REG_8BIT, 0x1263, 0x98 }, + { ET8EK8_REG_8BIT, 0x1268, 0xC6 }, + { ET8EK8_REG_8BIT, 0x1434, 0x00 }, + { ET8EK8_REG_8BIT, 0x1163, 0x44 }, + { ET8EK8_REG_8BIT, 0x1166, 0x29 }, + { ET8EK8_REG_8BIT, 0x1140, 0x02 }, + { ET8EK8_REG_8BIT, 0x1011, 0x24 }, + { ET8EK8_REG_8BIT, 0x1151, 0x80 }, + { ET8EK8_REG_8BIT, 0x1152, 0x23 }, + /* Initial setting for improvement2 of lower frequency noise */ + { ET8EK8_REG_8BIT, 0x1014, 0x05 }, + { ET8EK8_REG_8BIT, 0x1033, 0x06 }, + { ET8EK8_REG_8BIT, 0x1034, 0x79 }, + { ET8EK8_REG_8BIT, 0x1423, 0x3F }, + { ET8EK8_REG_8BIT, 0x1424, 0x3F }, + { ET8EK8_REG_8BIT, 0x1426, 0x00 }, + /* Switch of Preset-White-balance (0d:disable / 1d:enable) */ + { ET8EK8_REG_8BIT, 0x1439, 0x00 }, + /* Switch of blemish correction (0d:disable / 1d:enable) */ + { ET8EK8_REG_8BIT, 0x161F, 0x60 }, + /* Switch of auto noise correction (0d:disable / 1d:enable) */ + { ET8EK8_REG_8BIT, 0x1634, 0x00 }, + { ET8EK8_REG_8BIT, 0x1646, 0x00 }, + { ET8EK8_REG_8BIT, 0x1648, 0x00 }, + { ET8EK8_REG_8BIT, 0x113E, 0x01 }, + { ET8EK8_REG_8BIT, 0x113F, 0x22 }, + { ET8EK8_REG_8BIT, 0x1239, 0x64 }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x07 }, + { ET8EK8_REG_8BIT, 0x121B, 0x64 }, + { ET8EK8_REG_8BIT, 0x121D, 0x64 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0x89 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode1_16VGA_2592x1968_13.12fps_DPCM10-8 */ +static struct et8ek8_reglist mode1_16vga_2592x1968_13_12fps_dpcm10_8 = { +/* (without the +1) + * SPCK = 80 MHz + * CCP2 = 560 MHz + * VCO = 560 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 128 (3072) + * CKREF_DIV = 2 + * CKVAR_DIV = 175 + * VCO_DIV = 0 + * SPCK_DIV = 6 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3072, + .height = 2016, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 2592, + .window_height = 1968, + .pixel_clock = 80000000, + .ext_clock = 9600000, + .timeperframe = { + .numerator = 100, + .denominator = 1292 + }, + .max_exp = 2012, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x57 }, + { ET8EK8_REG_8BIT, 0x1238, 0x82 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x06 }, + { ET8EK8_REG_8BIT, 0x121B, 0x64 }, + { ET8EK8_REG_8BIT, 0x121D, 0x64 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0x80 }, /* <-changed to v14 7E->80 */ + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode3_4VGA_1296x984_29.99fps_DPCM10-8 */ +static struct et8ek8_reglist mode3_4vga_1296x984_29_99fps_dpcm10_8 = { +/* (without the +1) + * SPCK = 96.5333333333333 MHz + * CCP2 = 579.2 MHz + * VCO = 579.2 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 133 (3192) + * CKREF_DIV = 2 + * CKVAR_DIV = 181 + * VCO_DIV = 0 + * SPCK_DIV = 5 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3192, + .height = 1008, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 1296, + .window_height = 984, + .pixel_clock = 96533333, + .ext_clock = 9600000, + .timeperframe = { + .numerator = 100, + .denominator = 3000 + }, + .max_exp = 1004, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x5A }, + { ET8EK8_REG_8BIT, 0x1238, 0x82 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x05 }, + { ET8EK8_REG_8BIT, 0x121B, 0x63 }, + { ET8EK8_REG_8BIT, 0x1220, 0x85 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x121D, 0x63 }, + { ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode4_SVGA_864x656_29.88fps */ +static struct et8ek8_reglist mode4_svga_864x656_29_88fps = { +/* (without the +1) + * SPCK = 80 MHz + * CCP2 = 320 MHz + * VCO = 640 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 166 (3984) + * CKREF_DIV = 2 + * CKVAR_DIV = 200 + * VCO_DIV = 0 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 1 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3984, + .height = 672, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 864, + .window_height = 656, + .pixel_clock = 80000000, + .ext_clock = 9600000, + .timeperframe = { + .numerator = 100, + .denominator = 2988 + }, + .max_exp = 668, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x64 }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x71 }, + { ET8EK8_REG_8BIT, 0x123A, 0x07 }, + { ET8EK8_REG_8BIT, 0x121B, 0x62 }, + { ET8EK8_REG_8BIT, 0x121D, 0x62 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0xA6 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode5_VGA_648x492_29.93fps */ +static struct et8ek8_reglist mode5_vga_648x492_29_93fps = { +/* (without the +1) + * SPCK = 80 MHz + * CCP2 = 320 MHz + * VCO = 640 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 221 (5304) + * CKREF_DIV = 2 + * CKVAR_DIV = 200 + * VCO_DIV = 0 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 1 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 5304, + .height = 504, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 648, + .window_height = 492, + .pixel_clock = 80000000, + .ext_clock = 9600000, + .timeperframe = { + .numerator = 100, + .denominator = 2993 + }, + .max_exp = 500, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x64 }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x71 }, + { ET8EK8_REG_8BIT, 0x123A, 0x07 }, + { ET8EK8_REG_8BIT, 0x121B, 0x61 }, + { ET8EK8_REG_8BIT, 0x121D, 0x61 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0xDD }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode2_16VGA_2592x1968_3.99fps */ +static struct et8ek8_reglist mode2_16vga_2592x1968_3_99fps = { +/* (without the +1) + * SPCK = 80 MHz + * CCP2 = 640 MHz + * VCO = 640 MHz + * VCOUNT = 254 (6096) + * HCOUNT = 137 (3288) + * CKREF_DIV = 2 + * CKVAR_DIV = 200 + * VCO_DIV = 0 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3288, + .height = 6096, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 2592, + .window_height = 1968, + .pixel_clock = 80000000, + .ext_clock = 9600000, + .timeperframe = { + .numerator = 100, + .denominator = 399 + }, + .max_exp = 6092, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x64 }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x07 }, + { ET8EK8_REG_8BIT, 0x121B, 0x64 }, + { ET8EK8_REG_8BIT, 0x121D, 0x64 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0x89 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0xFE }, + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode_648x492_5fps */ +static struct et8ek8_reglist mode_648x492_5fps = { +/* (without the +1) + * SPCK = 13.3333333333333 MHz + * CCP2 = 53.3333333333333 MHz + * VCO = 640 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 221 (5304) + * CKREF_DIV = 2 + * CKVAR_DIV = 200 + * VCO_DIV = 5 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 1 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 5304, + .height = 504, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 648, + .window_height = 492, + .pixel_clock = 13333333, + .ext_clock = 9600000, + .timeperframe = { + .numerator = 100, + .denominator = 499 + }, + .max_exp = 500, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x64 }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x71 }, + { ET8EK8_REG_8BIT, 0x123A, 0x57 }, + { ET8EK8_REG_8BIT, 0x121B, 0x61 }, + { ET8EK8_REG_8BIT, 0x121D, 0x61 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0xDD }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode3_4VGA_1296x984_5fps */ +static struct et8ek8_reglist mode3_4vga_1296x984_5fps = { +/* (without the +1) + * SPCK = 49.4 MHz + * CCP2 = 395.2 MHz + * VCO = 790.4 MHz + * VCOUNT = 250 (6000) + * HCOUNT = 137 (3288) + * CKREF_DIV = 2 + * CKVAR_DIV = 247 + * VCO_DIV = 1 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3288, + .height = 3000, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 1296, + .window_height = 984, + .pixel_clock = 49400000, + .ext_clock = 9600000, + .timeperframe = { + .numerator = 100, + .denominator = 501 + }, + .max_exp = 2996, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x7B }, + { ET8EK8_REG_8BIT, 0x1238, 0x82 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x17 }, + { ET8EK8_REG_8BIT, 0x121B, 0x63 }, + { ET8EK8_REG_8BIT, 0x121D, 0x63 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0x89 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0xFA }, + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode_4VGA_1296x984_25fps_DPCM10-8 */ +static struct et8ek8_reglist mode_4vga_1296x984_25fps_dpcm10_8 = { +/* (without the +1) + * SPCK = 84.2666666666667 MHz + * CCP2 = 505.6 MHz + * VCO = 505.6 MHz + * VCOUNT = 88 (2112) + * HCOUNT = 133 (3192) + * CKREF_DIV = 2 + * CKVAR_DIV = 158 + * VCO_DIV = 0 + * SPCK_DIV = 5 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3192, + .height = 1056, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 1296, + .window_height = 984, + .pixel_clock = 84266667, + .ext_clock = 9600000, + .timeperframe = { + .numerator = 100, + .denominator = 2500 + }, + .max_exp = 1052, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x4F }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x05 }, + { ET8EK8_REG_8BIT, 0x121B, 0x63 }, + { ET8EK8_REG_8BIT, 0x1220, 0x85 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x58 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x121D, 0x63 }, + { ET8EK8_REG_8BIT, 0x125D, 0x83 }, + { ET8EK8_REG_TERM, 0, 0} + } +}; + +struct et8ek8_meta_reglist meta_reglist = { + .version = "V14 03-June-2008", + .reglist = { + { .ptr = &mode1_poweron_mode2_16vga_2592x1968_12_07fps }, + { .ptr = &mode1_16vga_2592x1968_13_12fps_dpcm10_8 }, + { .ptr = &mode3_4vga_1296x984_29_99fps_dpcm10_8 }, + { .ptr = &mode4_svga_864x656_29_88fps }, + { .ptr = &mode5_vga_648x492_29_93fps }, + { .ptr = &mode2_16vga_2592x1968_3_99fps }, + { .ptr = &mode_648x492_5fps }, + { .ptr = &mode3_4vga_1296x984_5fps }, + { .ptr = &mode_4vga_1296x984_25fps_dpcm10_8 }, + { .ptr = NULL } + } +}; diff --git a/drivers/media/i2c/et8ek8/et8ek8_reg.h b/drivers/media/i2c/et8ek8/et8ek8_reg.h new file mode 100644 index 000000000..c90e74935 --- /dev/null +++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * et8ek8_reg.h + * + * Copyright (C) 2008 Nokia Corporation + * + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + * Tuukka Toivonen <tuukkat76@gmail.com> + */ + +#ifndef ET8EK8REGS_H +#define ET8EK8REGS_H + +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev2.h> +#include <linux/v4l2-subdev.h> + +struct v4l2_mbus_framefmt; +struct v4l2_subdev_pad_mbus_code_enum; + +struct et8ek8_mode { + /* Physical sensor resolution and current image window */ + u16 sensor_width; + u16 sensor_height; + u16 sensor_window_origin_x; + u16 sensor_window_origin_y; + u16 sensor_window_width; + u16 sensor_window_height; + + /* Image data coming from sensor (after scaling) */ + u16 width; + u16 height; + u16 window_origin_x; + u16 window_origin_y; + u16 window_width; + u16 window_height; + + u32 pixel_clock; /* in Hz */ + u32 ext_clock; /* in Hz */ + struct v4l2_fract timeperframe; + u32 max_exp; /* Maximum exposure value */ + u32 bus_format; /* MEDIA_BUS_FMT_ */ + u32 sensitivity; /* 16.16 fixed point */ +}; + +#define ET8EK8_REG_8BIT 1 +#define ET8EK8_REG_16BIT 2 +#define ET8EK8_REG_DELAY 100 +#define ET8EK8_REG_TERM 0xff +struct et8ek8_reg { + u16 type; + u16 reg; /* 16-bit offset */ + u32 val; /* 8/16/32-bit value */ +}; + +/* Possible struct smia_reglist types. */ +#define ET8EK8_REGLIST_STANDBY 0 +#define ET8EK8_REGLIST_POWERON 1 +#define ET8EK8_REGLIST_RESUME 2 +#define ET8EK8_REGLIST_STREAMON 3 +#define ET8EK8_REGLIST_STREAMOFF 4 +#define ET8EK8_REGLIST_DISABLED 5 + +#define ET8EK8_REGLIST_MODE 10 + +#define ET8EK8_REGLIST_LSC_ENABLE 100 +#define ET8EK8_REGLIST_LSC_DISABLE 101 +#define ET8EK8_REGLIST_ANR_ENABLE 102 +#define ET8EK8_REGLIST_ANR_DISABLE 103 + +struct et8ek8_reglist { + u32 type; + struct et8ek8_mode mode; + struct et8ek8_reg regs[]; +}; + +#define ET8EK8_MAX_LEN 32 +struct et8ek8_meta_reglist { + char version[ET8EK8_MAX_LEN]; + union { + struct et8ek8_reglist *ptr; + } reglist[]; +}; + +extern struct et8ek8_meta_reglist meta_reglist; + +#endif /* ET8EK8REGS */ diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c new file mode 100644 index 000000000..c66cd1446 --- /dev/null +++ b/drivers/media/i2c/hi556.c @@ -0,0 +1,1200 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Intel Corporation. + +#include <asm/unaligned.h> +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define HI556_REG_VALUE_08BIT 1 +#define HI556_REG_VALUE_16BIT 2 +#define HI556_REG_VALUE_24BIT 3 + +#define HI556_LINK_FREQ_437MHZ 437000000ULL +#define HI556_MCLK 19200000 +#define HI556_DATA_LANES 2 +#define HI556_RGB_DEPTH 10 + +#define HI556_REG_CHIP_ID 0x0f16 +#define HI556_CHIP_ID 0x0556 + +#define HI556_REG_MODE_SELECT 0x0a00 +#define HI556_MODE_STANDBY 0x0000 +#define HI556_MODE_STREAMING 0x0100 + +/* vertical-timings from sensor */ +#define HI556_REG_FLL 0x0006 +#define HI556_FLL_30FPS 0x0814 +#define HI556_FLL_30FPS_MIN 0x0814 +#define HI556_FLL_MAX 0x7fff + +/* horizontal-timings from sensor */ +#define HI556_REG_LLP 0x0008 + +/* Exposure controls from sensor */ +#define HI556_REG_EXPOSURE 0x0074 +#define HI556_EXPOSURE_MIN 6 +#define HI556_EXPOSURE_MAX_MARGIN 2 +#define HI556_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define HI556_REG_ANALOG_GAIN 0x0077 +#define HI556_ANAL_GAIN_MIN 0 +#define HI556_ANAL_GAIN_MAX 240 +#define HI556_ANAL_GAIN_STEP 1 + +/* Digital gain controls from sensor */ +#define HI556_REG_MWB_GR_GAIN 0x0078 +#define HI556_REG_MWB_GB_GAIN 0x007a +#define HI556_REG_MWB_R_GAIN 0x007c +#define HI556_REG_MWB_B_GAIN 0x007e +#define HI556_DGTL_GAIN_MIN 0 +#define HI556_DGTL_GAIN_MAX 2048 +#define HI556_DGTL_GAIN_STEP 1 +#define HI556_DGTL_GAIN_DEFAULT 256 + +/* Test Pattern Control */ +#define HI556_REG_ISP 0X0a05 +#define HI556_REG_ISP_TPG_EN 0x01 +#define HI556_REG_TEST_PATTERN 0x0201 + +enum { + HI556_LINK_FREQ_437MHZ_INDEX, +}; + +struct hi556_reg { + u16 address; + u16 val; +}; + +struct hi556_reg_list { + u32 num_of_regs; + const struct hi556_reg *regs; +}; + +struct hi556_link_freq_config { + const struct hi556_reg_list reg_list; +}; + +struct hi556_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timining size */ + u32 llp; + + /* Default vertical timining size */ + u32 fll_def; + + /* Min vertical timining size */ + u32 fll_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* Sensor register settings for this resolution */ + const struct hi556_reg_list reg_list; +}; + +#define to_hi556(_sd) container_of(_sd, struct hi556, sd) + +//SENSOR_INITIALIZATION +static const struct hi556_reg mipi_data_rate_874mbps[] = { + {0x0e00, 0x0102}, + {0x0e02, 0x0102}, + {0x0e0c, 0x0100}, + {0x2000, 0x7400}, + {0x2002, 0x001c}, + {0x2004, 0x0242}, + {0x2006, 0x0942}, + {0x2008, 0x7007}, + {0x200a, 0x0fd9}, + {0x200c, 0x0259}, + {0x200e, 0x7008}, + {0x2010, 0x160e}, + {0x2012, 0x0047}, + {0x2014, 0x2118}, + {0x2016, 0x0041}, + {0x2018, 0x00d8}, + {0x201a, 0x0145}, + {0x201c, 0x0006}, + {0x201e, 0x0181}, + {0x2020, 0x13cc}, + {0x2022, 0x2057}, + {0x2024, 0x7001}, + {0x2026, 0x0fca}, + {0x2028, 0x00cb}, + {0x202a, 0x009f}, + {0x202c, 0x7002}, + {0x202e, 0x13cc}, + {0x2030, 0x019b}, + {0x2032, 0x014d}, + {0x2034, 0x2987}, + {0x2036, 0x2766}, + {0x2038, 0x0020}, + {0x203a, 0x2060}, + {0x203c, 0x0e5d}, + {0x203e, 0x181d}, + {0x2040, 0x2066}, + {0x2042, 0x20c4}, + {0x2044, 0x5000}, + {0x2046, 0x0005}, + {0x2048, 0x0000}, + {0x204a, 0x01db}, + {0x204c, 0x025a}, + {0x204e, 0x00c0}, + {0x2050, 0x0005}, + {0x2052, 0x0006}, + {0x2054, 0x0ad9}, + {0x2056, 0x0259}, + {0x2058, 0x0618}, + {0x205a, 0x0258}, + {0x205c, 0x2266}, + {0x205e, 0x20c8}, + {0x2060, 0x2060}, + {0x2062, 0x707b}, + {0x2064, 0x0fdd}, + {0x2066, 0x81b8}, + {0x2068, 0x5040}, + {0x206a, 0x0020}, + {0x206c, 0x5060}, + {0x206e, 0x3143}, + {0x2070, 0x5081}, + {0x2072, 0x025c}, + {0x2074, 0x7800}, + {0x2076, 0x7400}, + {0x2078, 0x001c}, + {0x207a, 0x0242}, + {0x207c, 0x0942}, + {0x207e, 0x0bd9}, + {0x2080, 0x0259}, + {0x2082, 0x7008}, + {0x2084, 0x160e}, + {0x2086, 0x0047}, + {0x2088, 0x2118}, + {0x208a, 0x0041}, + {0x208c, 0x00d8}, + {0x208e, 0x0145}, + {0x2090, 0x0006}, + {0x2092, 0x0181}, + {0x2094, 0x13cc}, + {0x2096, 0x2057}, + {0x2098, 0x7001}, + {0x209a, 0x0fca}, + {0x209c, 0x00cb}, + {0x209e, 0x009f}, + {0x20a0, 0x7002}, + {0x20a2, 0x13cc}, + {0x20a4, 0x019b}, + {0x20a6, 0x014d}, + {0x20a8, 0x2987}, + {0x20aa, 0x2766}, + {0x20ac, 0x0020}, + {0x20ae, 0x2060}, + {0x20b0, 0x0e5d}, + {0x20b2, 0x181d}, + {0x20b4, 0x2066}, + {0x20b6, 0x20c4}, + {0x20b8, 0x50a0}, + {0x20ba, 0x0005}, + {0x20bc, 0x0000}, + {0x20be, 0x01db}, + {0x20c0, 0x025a}, + {0x20c2, 0x00c0}, + {0x20c4, 0x0005}, + {0x20c6, 0x0006}, + {0x20c8, 0x0ad9}, + {0x20ca, 0x0259}, + {0x20cc, 0x0618}, + {0x20ce, 0x0258}, + {0x20d0, 0x2266}, + {0x20d2, 0x20c8}, + {0x20d4, 0x2060}, + {0x20d6, 0x707b}, + {0x20d8, 0x0fdd}, + {0x20da, 0x86b8}, + {0x20dc, 0x50e0}, + {0x20de, 0x0020}, + {0x20e0, 0x5100}, + {0x20e2, 0x3143}, + {0x20e4, 0x5121}, + {0x20e6, 0x7800}, + {0x20e8, 0x3140}, + {0x20ea, 0x01c4}, + {0x20ec, 0x01c1}, + {0x20ee, 0x01c0}, + {0x20f0, 0x01c4}, + {0x20f2, 0x2700}, + {0x20f4, 0x3d40}, + {0x20f6, 0x7800}, + {0x20f8, 0xffff}, + {0x27fe, 0xe000}, + {0x3000, 0x60f8}, + {0x3002, 0x187f}, + {0x3004, 0x7060}, + {0x3006, 0x0114}, + {0x3008, 0x60b0}, + {0x300a, 0x1473}, + {0x300c, 0x0013}, + {0x300e, 0x140f}, + {0x3010, 0x0040}, + {0x3012, 0x100f}, + {0x3014, 0x60f8}, + {0x3016, 0x187f}, + {0x3018, 0x7060}, + {0x301a, 0x0114}, + {0x301c, 0x60b0}, + {0x301e, 0x1473}, + {0x3020, 0x0013}, + {0x3022, 0x140f}, + {0x3024, 0x0040}, + {0x3026, 0x000f}, + + {0x0b00, 0x0000}, + {0x0b02, 0x0045}, + {0x0b04, 0xb405}, + {0x0b06, 0xc403}, + {0x0b08, 0x0081}, + {0x0b0a, 0x8252}, + {0x0b0c, 0xf814}, + {0x0b0e, 0xc618}, + {0x0b10, 0xa828}, + {0x0b12, 0x004c}, + {0x0b14, 0x4068}, + {0x0b16, 0x0000}, + {0x0f30, 0x5b15}, + {0x0f32, 0x7067}, + {0x0954, 0x0009}, + {0x0956, 0x0000}, + {0x0958, 0xbb80}, + {0x095a, 0x5140}, + {0x0c00, 0x1110}, + {0x0c02, 0x0011}, + {0x0c04, 0x0000}, + {0x0c06, 0x0200}, + {0x0c10, 0x0040}, + {0x0c12, 0x0040}, + {0x0c14, 0x0040}, + {0x0c16, 0x0040}, + {0x0a10, 0x4000}, + {0x3068, 0xf800}, + {0x306a, 0xf876}, + {0x006c, 0x0000}, + {0x005e, 0x0200}, + {0x000e, 0x0100}, + {0x0e0a, 0x0001}, + {0x004a, 0x0100}, + {0x004c, 0x0000}, + {0x004e, 0x0100}, + {0x000c, 0x0022}, + {0x0008, 0x0b00}, + {0x005a, 0x0202}, + {0x0012, 0x000e}, + {0x0018, 0x0a33}, + {0x0022, 0x0008}, + {0x0028, 0x0017}, + {0x0024, 0x0028}, + {0x002a, 0x002d}, + {0x0026, 0x0030}, + {0x002c, 0x07c9}, + {0x002e, 0x1111}, + {0x0030, 0x1111}, + {0x0032, 0x1111}, + {0x0006, 0x07bc}, + {0x0a22, 0x0000}, + {0x0a12, 0x0a20}, + {0x0a14, 0x0798}, + {0x003e, 0x0000}, + {0x0074, 0x080e}, + {0x0070, 0x0407}, + {0x0002, 0x0000}, + {0x0a02, 0x0100}, + {0x0a24, 0x0100}, + {0x0046, 0x0000}, + {0x0076, 0x0000}, + {0x0060, 0x0000}, + {0x0062, 0x0530}, + {0x0064, 0x0500}, + {0x0066, 0x0530}, + {0x0068, 0x0500}, + {0x0122, 0x0300}, + {0x015a, 0xff08}, + {0x0804, 0x0300}, + {0x0806, 0x0100}, + {0x005c, 0x0102}, + {0x0a1a, 0x0800}, +}; + +static const struct hi556_reg mode_2592x1944_regs[] = { + {0x0a00, 0x0000}, + {0x0b0a, 0x8252}, + {0x0f30, 0x5b15}, + {0x0f32, 0x7067}, + {0x004a, 0x0100}, + {0x004c, 0x0000}, + {0x004e, 0x0100}, + {0x000c, 0x0022}, + {0x0008, 0x0b00}, + {0x005a, 0x0202}, + {0x0012, 0x000e}, + {0x0018, 0x0a33}, + {0x0022, 0x0008}, + {0x0028, 0x0017}, + {0x0024, 0x0028}, + {0x002a, 0x002d}, + {0x0026, 0x0030}, + {0x002c, 0x07c9}, + {0x002e, 0x1111}, + {0x0030, 0x1111}, + {0x0032, 0x1111}, + {0x0006, 0x0814}, + {0x0a22, 0x0000}, + {0x0a12, 0x0a20}, + {0x0a14, 0x0798}, + {0x003e, 0x0000}, + {0x0074, 0x0812}, + {0x0070, 0x0409}, + {0x0804, 0x0300}, + {0x0806, 0x0100}, + {0x0a04, 0x014a}, + {0x090c, 0x0fdc}, + {0x090e, 0x002d}, + + {0x0902, 0x4319}, + {0x0914, 0xc10a}, + {0x0916, 0x071f}, + {0x0918, 0x0408}, + {0x091a, 0x0c0d}, + {0x091c, 0x0f09}, + {0x091e, 0x0a00}, + {0x0958, 0xbb80}, +}; + +static const struct hi556_reg mode_1296x972_regs[] = { + {0x0a00, 0x0000}, + {0x0b0a, 0x8259}, + {0x0f30, 0x5b15}, + {0x0f32, 0x7167}, + {0x004a, 0x0100}, + {0x004c, 0x0000}, + {0x004e, 0x0100}, + {0x000c, 0x0122}, + {0x0008, 0x0b00}, + {0x005a, 0x0404}, + {0x0012, 0x000c}, + {0x0018, 0x0a33}, + {0x0022, 0x0008}, + {0x0028, 0x0017}, + {0x0024, 0x0022}, + {0x002a, 0x002b}, + {0x0026, 0x0030}, + {0x002c, 0x07c9}, + {0x002e, 0x3311}, + {0x0030, 0x3311}, + {0x0032, 0x3311}, + {0x0006, 0x0814}, + {0x0a22, 0x0000}, + {0x0a12, 0x0510}, + {0x0a14, 0x03cc}, + {0x003e, 0x0000}, + {0x0074, 0x0812}, + {0x0070, 0x0409}, + {0x0804, 0x0308}, + {0x0806, 0x0100}, + {0x0a04, 0x016a}, + {0x090e, 0x0010}, + {0x090c, 0x09c0}, + + {0x0902, 0x4319}, + {0x0914, 0xc106}, + {0x0916, 0x040e}, + {0x0918, 0x0304}, + {0x091a, 0x0708}, + {0x091c, 0x0e06}, + {0x091e, 0x0300}, + {0x0958, 0xbb80}, +}; + +static const char * const hi556_test_pattern_menu[] = { + "Disabled", + "Solid Colour", + "100% Colour Bars", + "Fade To Grey Colour Bars", + "PN9", + "Gradient Horizontal", + "Gradient Vertical", + "Check Board", + "Slant Pattern", +}; + +static const s64 link_freq_menu_items[] = { + HI556_LINK_FREQ_437MHZ, +}; + +static const struct hi556_link_freq_config link_freq_configs[] = { + [HI556_LINK_FREQ_437MHZ_INDEX] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_874mbps), + .regs = mipi_data_rate_874mbps, + } + } +}; + +static const struct hi556_mode supported_modes[] = { + { + .width = 2592, + .height = 1944, + .fll_def = HI556_FLL_30FPS, + .fll_min = HI556_FLL_30FPS_MIN, + .llp = 0x0b00, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2592x1944_regs), + .regs = mode_2592x1944_regs, + }, + .link_freq_index = HI556_LINK_FREQ_437MHZ_INDEX, + }, + { + .width = 1296, + .height = 972, + .fll_def = HI556_FLL_30FPS, + .fll_min = HI556_FLL_30FPS_MIN, + .llp = 0x0b00, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x972_regs), + .regs = mode_1296x972_regs, + }, + .link_freq_index = HI556_LINK_FREQ_437MHZ_INDEX, + } +}; + +struct hi556 { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct hi556_mode *cur_mode; + + /* To serialize asynchronus callbacks */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static u64 to_pixel_rate(u32 f_index) +{ + u64 pixel_rate = link_freq_menu_items[f_index] * 2 * HI556_DATA_LANES; + + do_div(pixel_rate, HI556_RGB_DEPTH); + + return pixel_rate; +} + +static int hi556_read_reg(struct hi556 *hi556, u16 reg, u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi556->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = {0}; + int ret; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +static int hi556_write_reg(struct hi556 *hi556, u16 reg, u16 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi556->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << 8 * (4 - len), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int hi556_write_reg_list(struct hi556 *hi556, + const struct hi556_reg_list *r_list) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi556->sd); + unsigned int i; + int ret; + + for (i = 0; i < r_list->num_of_regs; i++) { + ret = hi556_write_reg(hi556, r_list->regs[i].address, + HI556_REG_VALUE_16BIT, + r_list->regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "failed to write reg 0x%4.4x. error = %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int hi556_update_digital_gain(struct hi556 *hi556, u32 d_gain) +{ + int ret; + + ret = hi556_write_reg(hi556, HI556_REG_MWB_GR_GAIN, + HI556_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = hi556_write_reg(hi556, HI556_REG_MWB_GB_GAIN, + HI556_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = hi556_write_reg(hi556, HI556_REG_MWB_R_GAIN, + HI556_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + return hi556_write_reg(hi556, HI556_REG_MWB_B_GAIN, + HI556_REG_VALUE_16BIT, d_gain); +} + +static int hi556_test_pattern(struct hi556 *hi556, u32 pattern) +{ + int ret; + u32 val; + + if (pattern) { + ret = hi556_read_reg(hi556, HI556_REG_ISP, + HI556_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + ret = hi556_write_reg(hi556, HI556_REG_ISP, + HI556_REG_VALUE_08BIT, + val | HI556_REG_ISP_TPG_EN); + if (ret) + return ret; + } + + return hi556_write_reg(hi556, HI556_REG_TEST_PATTERN, + HI556_REG_VALUE_08BIT, pattern); +} + +static int hi556_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct hi556 *hi556 = container_of(ctrl->handler, + struct hi556, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&hi556->sd); + s64 exposure_max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = hi556->cur_mode->height + ctrl->val - + HI556_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(hi556->exposure, + hi556->exposure->minimum, + exposure_max, hi556->exposure->step, + exposure_max); + } + + /* V4L2 controls values will be applied only when power is already up */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = hi556_write_reg(hi556, HI556_REG_ANALOG_GAIN, + HI556_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_DIGITAL_GAIN: + ret = hi556_update_digital_gain(hi556, ctrl->val); + break; + + case V4L2_CID_EXPOSURE: + ret = hi556_write_reg(hi556, HI556_REG_EXPOSURE, + HI556_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ + ret = hi556_write_reg(hi556, HI556_REG_FLL, + HI556_REG_VALUE_16BIT, + hi556->cur_mode->height + ctrl->val); + break; + + case V4L2_CID_TEST_PATTERN: + ret = hi556_test_pattern(hi556, ctrl->val); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops hi556_ctrl_ops = { + .s_ctrl = hi556_set_ctrl, +}; + +static int hi556_init_controls(struct hi556 *hi556) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max, h_blank; + int ret; + + ctrl_hdlr = &hi556->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + ctrl_hdlr->lock = &hi556->mutex; + hi556->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + if (hi556->link_freq) + hi556->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + hi556->pixel_rate = v4l2_ctrl_new_std + (ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + to_pixel_rate(HI556_LINK_FREQ_437MHZ_INDEX), + 1, + to_pixel_rate(HI556_LINK_FREQ_437MHZ_INDEX)); + hi556->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_VBLANK, + hi556->cur_mode->fll_min - + hi556->cur_mode->height, + HI556_FLL_MAX - + hi556->cur_mode->height, 1, + hi556->cur_mode->fll_def - + hi556->cur_mode->height); + + h_blank = hi556->cur_mode->llp - hi556->cur_mode->width; + + hi556->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (hi556->hblank) + hi556->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &hi556_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + HI556_ANAL_GAIN_MIN, HI556_ANAL_GAIN_MAX, + HI556_ANAL_GAIN_STEP, HI556_ANAL_GAIN_MIN); + v4l2_ctrl_new_std(ctrl_hdlr, &hi556_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + HI556_DGTL_GAIN_MIN, HI556_DGTL_GAIN_MAX, + HI556_DGTL_GAIN_STEP, HI556_DGTL_GAIN_DEFAULT); + exposure_max = hi556->cur_mode->fll_def - HI556_EXPOSURE_MAX_MARGIN; + hi556->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_EXPOSURE, + HI556_EXPOSURE_MIN, exposure_max, + HI556_EXPOSURE_STEP, + exposure_max); + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(hi556_test_pattern_menu) - 1, + 0, 0, hi556_test_pattern_menu); + if (ctrl_hdlr->error) + return ctrl_hdlr->error; + + hi556->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static void hi556_assign_pad_format(const struct hi556_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->field = V4L2_FIELD_NONE; +} + +static int hi556_start_streaming(struct hi556 *hi556) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi556->sd); + const struct hi556_reg_list *reg_list; + int link_freq_index, ret; + + link_freq_index = hi556->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = hi556_write_reg_list(hi556, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set plls"); + return ret; + } + + reg_list = &hi556->cur_mode->reg_list; + ret = hi556_write_reg_list(hi556, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set mode"); + return ret; + } + + ret = __v4l2_ctrl_handler_setup(hi556->sd.ctrl_handler); + if (ret) + return ret; + + ret = hi556_write_reg(hi556, HI556_REG_MODE_SELECT, + HI556_REG_VALUE_16BIT, HI556_MODE_STREAMING); + + if (ret) { + dev_err(&client->dev, "failed to set stream"); + return ret; + } + + return 0; +} + +static void hi556_stop_streaming(struct hi556 *hi556) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi556->sd); + + if (hi556_write_reg(hi556, HI556_REG_MODE_SELECT, + HI556_REG_VALUE_16BIT, HI556_MODE_STANDBY)) + dev_err(&client->dev, "failed to set stream"); +} + +static int hi556_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct hi556 *hi556 = to_hi556(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + if (hi556->streaming == enable) + return 0; + + mutex_lock(&hi556->mutex); + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + mutex_unlock(&hi556->mutex); + return ret; + } + + ret = hi556_start_streaming(hi556); + if (ret) { + enable = 0; + hi556_stop_streaming(hi556); + pm_runtime_put(&client->dev); + } + } else { + hi556_stop_streaming(hi556); + pm_runtime_put(&client->dev); + } + + hi556->streaming = enable; + mutex_unlock(&hi556->mutex); + + return ret; +} + +static int __maybe_unused hi556_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi556 *hi556 = to_hi556(sd); + + mutex_lock(&hi556->mutex); + if (hi556->streaming) + hi556_stop_streaming(hi556); + + mutex_unlock(&hi556->mutex); + + return 0; +} + +static int __maybe_unused hi556_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi556 *hi556 = to_hi556(sd); + int ret; + + mutex_lock(&hi556->mutex); + if (hi556->streaming) { + ret = hi556_start_streaming(hi556); + if (ret) + goto error; + } + + mutex_unlock(&hi556->mutex); + + return 0; + +error: + hi556_stop_streaming(hi556); + hi556->streaming = 0; + mutex_unlock(&hi556->mutex); + return ret; +} + +static int hi556_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct hi556 *hi556 = to_hi556(sd); + const struct hi556_mode *mode; + s32 vblank_def, h_blank; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, + height, fmt->format.width, + fmt->format.height); + + mutex_lock(&hi556->mutex); + hi556_assign_pad_format(mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; + } else { + hi556->cur_mode = mode; + __v4l2_ctrl_s_ctrl(hi556->link_freq, mode->link_freq_index); + __v4l2_ctrl_s_ctrl_int64(hi556->pixel_rate, + to_pixel_rate(mode->link_freq_index)); + + /* Update limits and set FPS to default */ + vblank_def = mode->fll_def - mode->height; + __v4l2_ctrl_modify_range(hi556->vblank, + mode->fll_min - mode->height, + HI556_FLL_MAX - mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(hi556->vblank, vblank_def); + + h_blank = hi556->cur_mode->llp - hi556->cur_mode->width; + + __v4l2_ctrl_modify_range(hi556->hblank, h_blank, h_blank, 1, + h_blank); + } + + mutex_unlock(&hi556->mutex); + + return 0; +} + +static int hi556_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct hi556 *hi556 = to_hi556(sd); + + mutex_lock(&hi556->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(&hi556->sd, cfg, + fmt->pad); + else + hi556_assign_pad_format(hi556->cur_mode, &fmt->format); + + mutex_unlock(&hi556->mutex); + + return 0; +} + +static int hi556_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int hi556_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int hi556_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct hi556 *hi556 = to_hi556(sd); + + mutex_lock(&hi556->mutex); + hi556_assign_pad_format(&supported_modes[0], + v4l2_subdev_get_try_format(sd, fh->pad, 0)); + mutex_unlock(&hi556->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops hi556_video_ops = { + .s_stream = hi556_set_stream, +}; + +static const struct v4l2_subdev_pad_ops hi556_pad_ops = { + .set_fmt = hi556_set_format, + .get_fmt = hi556_get_format, + .enum_mbus_code = hi556_enum_mbus_code, + .enum_frame_size = hi556_enum_frame_size, +}; + +static const struct v4l2_subdev_ops hi556_subdev_ops = { + .video = &hi556_video_ops, + .pad = &hi556_pad_ops, +}; + +static const struct media_entity_operations hi556_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops hi556_internal_ops = { + .open = hi556_open, +}; + +static int hi556_identify_module(struct hi556 *hi556) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi556->sd); + int ret; + u32 val; + + ret = hi556_read_reg(hi556, HI556_REG_CHIP_ID, + HI556_REG_VALUE_16BIT, &val); + if (ret) + return ret; + + if (val != HI556_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x", + HI556_CHIP_ID, val); + return -ENXIO; + } + + return 0; +} + +static int hi556_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + u32 mclk; + int ret = 0; + unsigned int i, j; + + if (!fwnode) + return -ENXIO; + + ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk); + if (ret) { + dev_err(dev, "can't get clock frequency"); + return ret; + } + + if (mclk != HI556_MCLK) { + dev_err(dev, "external clock %d is not supported", mclk); + return -EINVAL; + } + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -ENXIO; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto check_hwcfg_error; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + ret = -EINVAL; + goto check_hwcfg_error; + } + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) { + if (link_freq_menu_items[i] == + bus_cfg.link_frequencies[j]) + break; + } + + if (j == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequency %lld supported", + link_freq_menu_items[i]); + ret = -EINVAL; + goto check_hwcfg_error; + } + } + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int hi556_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi556 *hi556 = to_hi556(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(&client->dev); + mutex_destroy(&hi556->mutex); + + return 0; +} + +static int hi556_probe(struct i2c_client *client) +{ + struct hi556 *hi556; + int ret; + + ret = hi556_check_hwcfg(&client->dev); + if (ret) { + dev_err(&client->dev, "failed to check HW configuration: %d", + ret); + return ret; + } + + hi556 = devm_kzalloc(&client->dev, sizeof(*hi556), GFP_KERNEL); + if (!hi556) + return -ENOMEM; + + v4l2_i2c_subdev_init(&hi556->sd, client, &hi556_subdev_ops); + ret = hi556_identify_module(hi556); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d", ret); + return ret; + } + + mutex_init(&hi556->mutex); + hi556->cur_mode = &supported_modes[0]; + ret = hi556_init_controls(hi556); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + hi556->sd.internal_ops = &hi556_internal_ops; + hi556->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + hi556->sd.entity.ops = &hi556_subdev_entity_ops; + hi556->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + hi556->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&hi556->sd.entity, 1, &hi556->pad); + if (ret) { + dev_err(&client->dev, "failed to init entity pads: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = v4l2_async_register_subdev_sensor_common(&hi556->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %d", + ret); + goto probe_error_media_entity_cleanup; + } + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +probe_error_media_entity_cleanup: + media_entity_cleanup(&hi556->sd.entity); + +probe_error_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(hi556->sd.ctrl_handler); + mutex_destroy(&hi556->mutex); + + return ret; +} + +static const struct dev_pm_ops hi556_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(hi556_suspend, hi556_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id hi556_acpi_ids[] = { + {"INT3537"}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, hi556_acpi_ids); +#endif + +static struct i2c_driver hi556_i2c_driver = { + .driver = { + .name = "hi556", + .pm = &hi556_pm_ops, + .acpi_match_table = ACPI_PTR(hi556_acpi_ids), + }, + .probe_new = hi556_probe, + .remove = hi556_remove, +}; + +module_i2c_driver(hi556_i2c_driver); + +MODULE_AUTHOR("Shawn Tu <shawnx.tu@intel.com>"); +MODULE_DESCRIPTION("Hynix HI556 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx214.c b/drivers/media/i2c/imx214.c new file mode 100644 index 000000000..cee1a4817 --- /dev/null +++ b/drivers/media/i2c/imx214.c @@ -0,0 +1,1124 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * imx214.c - imx214 sensor driver + * + * Copyright 2018 Qtechnology A/S + * + * Ricardo Ribalda <ribalda@kernel.org> + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define IMX214_DEFAULT_CLK_FREQ 24000000 +#define IMX214_DEFAULT_LINK_FREQ 480000000 +#define IMX214_DEFAULT_PIXEL_RATE ((IMX214_DEFAULT_LINK_FREQ * 8LL) / 10) +#define IMX214_FPS 30 +#define IMX214_MBUS_CODE MEDIA_BUS_FMT_SRGGB10_1X10 + +static const char * const imx214_supply_name[] = { + "vdda", + "vddd", + "vdddo", +}; + +#define IMX214_NUM_SUPPLIES ARRAY_SIZE(imx214_supply_name) + +struct imx214 { + struct device *dev; + struct clk *xclk; + struct regmap *regmap; + + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_mbus_framefmt fmt; + struct v4l2_rect crop; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *unit_size; + + struct regulator_bulk_data supplies[IMX214_NUM_SUPPLIES]; + + struct gpio_desc *enable_gpio; + + /* + * Serialize control access, get/set format, get selection + * and start streaming. + */ + struct mutex mutex; + + bool streaming; +}; + +struct reg_8 { + u16 addr; + u8 val; +}; + +enum { + IMX214_TABLE_WAIT_MS = 0, + IMX214_TABLE_END, + IMX214_MAX_RETRIES, + IMX214_WAIT_MS +}; + +/*From imx214_mode_tbls.h*/ +static const struct reg_8 mode_4096x2304[] = { + {0x0114, 0x03}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0222, 0x01}, + {0x0340, 0x0C}, + {0x0341, 0x7A}, + {0x0342, 0x13}, + {0x0343, 0x90}, + {0x0344, 0x00}, + {0x0345, 0x38}, + {0x0346, 0x01}, + {0x0347, 0x98}, + {0x0348, 0x10}, + {0x0349, 0x37}, + {0x034A, 0x0A}, + {0x034B, 0x97}, + {0x0381, 0x01}, + {0x0383, 0x01}, + {0x0385, 0x01}, + {0x0387, 0x01}, + {0x0900, 0x00}, + {0x0901, 0x00}, + {0x0902, 0x00}, + {0x3000, 0x35}, + {0x3054, 0x01}, + {0x305C, 0x11}, + + {0x0112, 0x0A}, + {0x0113, 0x0A}, + {0x034C, 0x10}, + {0x034D, 0x00}, + {0x034E, 0x09}, + {0x034F, 0x00}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x10}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x10}, + {0x040D, 0x00}, + {0x040E, 0x09}, + {0x040F, 0x00}, + + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x03}, + {0x0306, 0x00}, + {0x0307, 0x96}, + {0x0309, 0x0A}, + {0x030B, 0x01}, + {0x0310, 0x00}, + + {0x0820, 0x12}, + {0x0821, 0xC0}, + {0x0822, 0x00}, + {0x0823, 0x00}, + + {0x3A03, 0x09}, + {0x3A04, 0x50}, + {0x3A05, 0x01}, + + {0x0B06, 0x01}, + {0x30A2, 0x00}, + + {0x30B4, 0x00}, + + {0x3A02, 0xFF}, + + {0x3011, 0x00}, + {0x3013, 0x01}, + + {0x0202, 0x0C}, + {0x0203, 0x70}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + + {0x0204, 0x00}, + {0x0205, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x0210, 0x01}, + {0x0211, 0x00}, + {0x0212, 0x01}, + {0x0213, 0x00}, + {0x0214, 0x01}, + {0x0215, 0x00}, + {0x0216, 0x00}, + {0x0217, 0x00}, + + {0x4170, 0x00}, + {0x4171, 0x10}, + {0x4176, 0x00}, + {0x4177, 0x3C}, + {0xAE20, 0x04}, + {0xAE21, 0x5C}, + + {IMX214_TABLE_WAIT_MS, 10}, + {0x0138, 0x01}, + {IMX214_TABLE_END, 0x00} +}; + +static const struct reg_8 mode_1920x1080[] = { + {0x0114, 0x03}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0222, 0x01}, + {0x0340, 0x0C}, + {0x0341, 0x7A}, + {0x0342, 0x13}, + {0x0343, 0x90}, + {0x0344, 0x04}, + {0x0345, 0x78}, + {0x0346, 0x03}, + {0x0347, 0xFC}, + {0x0348, 0x0B}, + {0x0349, 0xF7}, + {0x034A, 0x08}, + {0x034B, 0x33}, + {0x0381, 0x01}, + {0x0383, 0x01}, + {0x0385, 0x01}, + {0x0387, 0x01}, + {0x0900, 0x00}, + {0x0901, 0x00}, + {0x0902, 0x00}, + {0x3000, 0x35}, + {0x3054, 0x01}, + {0x305C, 0x11}, + + {0x0112, 0x0A}, + {0x0113, 0x0A}, + {0x034C, 0x07}, + {0x034D, 0x80}, + {0x034E, 0x04}, + {0x034F, 0x38}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x10}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x07}, + {0x040D, 0x80}, + {0x040E, 0x04}, + {0x040F, 0x38}, + + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x03}, + {0x0306, 0x00}, + {0x0307, 0x96}, + {0x0309, 0x0A}, + {0x030B, 0x01}, + {0x0310, 0x00}, + + {0x0820, 0x12}, + {0x0821, 0xC0}, + {0x0822, 0x00}, + {0x0823, 0x00}, + + {0x3A03, 0x04}, + {0x3A04, 0xF8}, + {0x3A05, 0x02}, + + {0x0B06, 0x01}, + {0x30A2, 0x00}, + + {0x30B4, 0x00}, + + {0x3A02, 0xFF}, + + {0x3011, 0x00}, + {0x3013, 0x01}, + + {0x0202, 0x0C}, + {0x0203, 0x70}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + + {0x0204, 0x00}, + {0x0205, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x0210, 0x01}, + {0x0211, 0x00}, + {0x0212, 0x01}, + {0x0213, 0x00}, + {0x0214, 0x01}, + {0x0215, 0x00}, + {0x0216, 0x00}, + {0x0217, 0x00}, + + {0x4170, 0x00}, + {0x4171, 0x10}, + {0x4176, 0x00}, + {0x4177, 0x3C}, + {0xAE20, 0x04}, + {0xAE21, 0x5C}, + + {IMX214_TABLE_WAIT_MS, 10}, + {0x0138, 0x01}, + {IMX214_TABLE_END, 0x00} +}; + +static const struct reg_8 mode_table_common[] = { + /* software reset */ + + /* software standby settings */ + {0x0100, 0x00}, + + /* ATR setting */ + {0x9300, 0x02}, + + /* external clock setting */ + {0x0136, 0x18}, + {0x0137, 0x00}, + + /* global setting */ + /* basic config */ + {0x0101, 0x00}, + {0x0105, 0x01}, + {0x0106, 0x01}, + {0x4550, 0x02}, + {0x4601, 0x00}, + {0x4642, 0x05}, + {0x6227, 0x11}, + {0x6276, 0x00}, + {0x900E, 0x06}, + {0xA802, 0x90}, + {0xA803, 0x11}, + {0xA804, 0x62}, + {0xA805, 0x77}, + {0xA806, 0xAE}, + {0xA807, 0x34}, + {0xA808, 0xAE}, + {0xA809, 0x35}, + {0xA80A, 0x62}, + {0xA80B, 0x83}, + {0xAE33, 0x00}, + + /* analog setting */ + {0x4174, 0x00}, + {0x4175, 0x11}, + {0x4612, 0x29}, + {0x461B, 0x12}, + {0x461F, 0x06}, + {0x4635, 0x07}, + {0x4637, 0x30}, + {0x463F, 0x18}, + {0x4641, 0x0D}, + {0x465B, 0x12}, + {0x465F, 0x11}, + {0x4663, 0x11}, + {0x4667, 0x0F}, + {0x466F, 0x0F}, + {0x470E, 0x09}, + {0x4909, 0xAB}, + {0x490B, 0x95}, + {0x4915, 0x5D}, + {0x4A5F, 0xFF}, + {0x4A61, 0xFF}, + {0x4A73, 0x62}, + {0x4A85, 0x00}, + {0x4A87, 0xFF}, + + /* embedded data */ + {0x5041, 0x04}, + {0x583C, 0x04}, + {0x620E, 0x04}, + {0x6EB2, 0x01}, + {0x6EB3, 0x00}, + {0x9300, 0x02}, + + /* imagequality */ + /* HDR setting */ + {0x3001, 0x07}, + {0x6D12, 0x3F}, + {0x6D13, 0xFF}, + {0x9344, 0x03}, + {0x9706, 0x10}, + {0x9707, 0x03}, + {0x9708, 0x03}, + {0x9E04, 0x01}, + {0x9E05, 0x00}, + {0x9E0C, 0x01}, + {0x9E0D, 0x02}, + {0x9E24, 0x00}, + {0x9E25, 0x8C}, + {0x9E26, 0x00}, + {0x9E27, 0x94}, + {0x9E28, 0x00}, + {0x9E29, 0x96}, + + /* CNR parameter setting */ + {0x69DB, 0x01}, + + /* Moire reduction */ + {0x6957, 0x01}, + + /* image enhancement */ + {0x6987, 0x17}, + {0x698A, 0x03}, + {0x698B, 0x03}, + + /* white balanace */ + {0x0B8E, 0x01}, + {0x0B8F, 0x00}, + {0x0B90, 0x01}, + {0x0B91, 0x00}, + {0x0B92, 0x01}, + {0x0B93, 0x00}, + {0x0B94, 0x01}, + {0x0B95, 0x00}, + + /* ATR setting */ + {0x6E50, 0x00}, + {0x6E51, 0x32}, + {0x9340, 0x00}, + {0x9341, 0x3C}, + {0x9342, 0x03}, + {0x9343, 0xFF}, + {IMX214_TABLE_END, 0x00} +}; + +/* + * Declare modes in order, from biggest + * to smallest height. + */ +static const struct imx214_mode { + u32 width; + u32 height; + const struct reg_8 *reg_table; +} imx214_modes[] = { + { + .width = 4096, + .height = 2304, + .reg_table = mode_4096x2304, + }, + { + .width = 1920, + .height = 1080, + .reg_table = mode_1920x1080, + }, +}; + +static inline struct imx214 *to_imx214(struct v4l2_subdev *sd) +{ + return container_of(sd, struct imx214, sd); +} + +static int __maybe_unused imx214_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx214 *imx214 = to_imx214(sd); + int ret; + + ret = regulator_bulk_enable(IMX214_NUM_SUPPLIES, imx214->supplies); + if (ret < 0) { + dev_err(imx214->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + usleep_range(2000, 3000); + + ret = clk_prepare_enable(imx214->xclk); + if (ret < 0) { + regulator_bulk_disable(IMX214_NUM_SUPPLIES, imx214->supplies); + dev_err(imx214->dev, "clk prepare enable failed\n"); + return ret; + } + + gpiod_set_value_cansleep(imx214->enable_gpio, 1); + usleep_range(12000, 15000); + + return 0; +} + +static int __maybe_unused imx214_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx214 *imx214 = to_imx214(sd); + + gpiod_set_value_cansleep(imx214->enable_gpio, 0); + + clk_disable_unprepare(imx214->xclk); + + regulator_bulk_disable(IMX214_NUM_SUPPLIES, imx214->supplies); + usleep_range(10, 20); + + return 0; +} + +static int imx214_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = IMX214_MBUS_CODE; + + return 0; +} + +static int imx214_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->code != IMX214_MBUS_CODE) + return -EINVAL; + + if (fse->index >= ARRAY_SIZE(imx214_modes)) + return -EINVAL; + + fse->min_width = fse->max_width = imx214_modes[fse->index].width; + fse->min_height = fse->max_height = imx214_modes[fse->index].height; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int imx214_s_register(struct v4l2_subdev *subdev, + const struct v4l2_dbg_register *reg) +{ + struct imx214 *imx214 = container_of(subdev, struct imx214, sd); + + return regmap_write(imx214->regmap, reg->reg, reg->val); +} + +static int imx214_g_register(struct v4l2_subdev *subdev, + struct v4l2_dbg_register *reg) +{ + struct imx214 *imx214 = container_of(subdev, struct imx214, sd); + unsigned int aux; + int ret; + + reg->size = 1; + ret = regmap_read(imx214->regmap, reg->reg, &aux); + reg->val = aux; + + return ret; +} +#endif + +static const struct v4l2_subdev_core_ops imx214_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = imx214_g_register, + .s_register = imx214_s_register, +#endif +}; + +static struct v4l2_mbus_framefmt * +__imx214_get_pad_format(struct imx214 *imx214, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&imx214->sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx214->fmt; + default: + return NULL; + } +} + +static int imx214_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct imx214 *imx214 = to_imx214(sd); + + mutex_lock(&imx214->mutex); + format->format = *__imx214_get_pad_format(imx214, cfg, format->pad, + format->which); + mutex_unlock(&imx214->mutex); + + return 0; +} + +static struct v4l2_rect * +__imx214_get_pad_crop(struct imx214 *imx214, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&imx214->sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx214->crop; + default: + return NULL; + } +} + +static int imx214_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct imx214 *imx214 = to_imx214(sd); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + const struct imx214_mode *mode; + + mutex_lock(&imx214->mutex); + + __crop = __imx214_get_pad_crop(imx214, cfg, format->pad, format->which); + + mode = v4l2_find_nearest_size(imx214_modes, + ARRAY_SIZE(imx214_modes), width, height, + format->format.width, + format->format.height); + + __crop->width = mode->width; + __crop->height = mode->height; + + __format = __imx214_get_pad_format(imx214, cfg, format->pad, + format->which); + __format->width = __crop->width; + __format->height = __crop->height; + __format->code = IMX214_MBUS_CODE; + __format->field = V4L2_FIELD_NONE; + __format->colorspace = V4L2_COLORSPACE_SRGB; + __format->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(__format->colorspace); + __format->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + __format->colorspace, __format->ycbcr_enc); + __format->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(__format->colorspace); + + format->format = *__format; + + mutex_unlock(&imx214->mutex); + + return 0; +} + +static int imx214_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct imx214 *imx214 = to_imx214(sd); + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + mutex_lock(&imx214->mutex); + sel->r = *__imx214_get_pad_crop(imx214, cfg, sel->pad, + sel->which); + mutex_unlock(&imx214->mutex); + return 0; +} + +static int imx214_entity_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg) +{ + struct v4l2_subdev_format fmt = { }; + + fmt.which = cfg ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.format.width = imx214_modes[0].width; + fmt.format.height = imx214_modes[0].height; + + imx214_set_format(subdev, cfg, &fmt); + + return 0; +} + +static int imx214_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx214 *imx214 = container_of(ctrl->handler, + struct imx214, ctrls); + u8 vals[2]; + int ret; + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(imx214->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + vals[1] = ctrl->val; + vals[0] = ctrl->val >> 8; + ret = regmap_bulk_write(imx214->regmap, 0x202, vals, 2); + if (ret < 0) + dev_err(imx214->dev, "Error %d\n", ret); + ret = 0; + break; + + default: + ret = -EINVAL; + } + + pm_runtime_put(imx214->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx214_ctrl_ops = { + .s_ctrl = imx214_set_ctrl, +}; + +#define MAX_CMD 4 +static int imx214_write_table(struct imx214 *imx214, + const struct reg_8 table[]) +{ + u8 vals[MAX_CMD]; + int i; + int ret; + + for (; table->addr != IMX214_TABLE_END ; table++) { + if (table->addr == IMX214_TABLE_WAIT_MS) { + usleep_range(table->val * 1000, + table->val * 1000 + 500); + continue; + } + + for (i = 0; i < MAX_CMD; i++) { + if (table[i].addr != (table[0].addr + i)) + break; + vals[i] = table[i].val; + } + + ret = regmap_bulk_write(imx214->regmap, table->addr, vals, i); + + if (ret) { + dev_err(imx214->dev, "write_table error: %d\n", ret); + return ret; + } + + table += i - 1; + } + + return 0; +} + +static int imx214_start_streaming(struct imx214 *imx214) +{ + const struct imx214_mode *mode; + int ret; + + mutex_lock(&imx214->mutex); + ret = imx214_write_table(imx214, mode_table_common); + if (ret < 0) { + dev_err(imx214->dev, "could not sent common table %d\n", ret); + goto error; + } + + mode = v4l2_find_nearest_size(imx214_modes, + ARRAY_SIZE(imx214_modes), width, height, + imx214->fmt.width, imx214->fmt.height); + ret = imx214_write_table(imx214, mode->reg_table); + if (ret < 0) { + dev_err(imx214->dev, "could not sent mode table %d\n", ret); + goto error; + } + ret = __v4l2_ctrl_handler_setup(&imx214->ctrls); + if (ret < 0) { + dev_err(imx214->dev, "could not sync v4l2 controls\n"); + goto error; + } + ret = regmap_write(imx214->regmap, 0x100, 1); + if (ret < 0) { + dev_err(imx214->dev, "could not sent start table %d\n", ret); + goto error; + } + + mutex_unlock(&imx214->mutex); + return 0; + +error: + mutex_unlock(&imx214->mutex); + return ret; +} + +static int imx214_stop_streaming(struct imx214 *imx214) +{ + int ret; + + ret = regmap_write(imx214->regmap, 0x100, 0); + if (ret < 0) + dev_err(imx214->dev, "could not sent stop table %d\n", ret); + + return ret; +} + +static int imx214_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct imx214 *imx214 = to_imx214(subdev); + int ret; + + if (imx214->streaming == enable) + return 0; + + if (enable) { + ret = pm_runtime_get_sync(imx214->dev); + if (ret < 0) { + pm_runtime_put_noidle(imx214->dev); + return ret; + } + + ret = imx214_start_streaming(imx214); + if (ret < 0) + goto err_rpm_put; + } else { + ret = imx214_stop_streaming(imx214); + if (ret < 0) + goto err_rpm_put; + pm_runtime_put(imx214->dev); + } + + imx214->streaming = enable; + return 0; + +err_rpm_put: + pm_runtime_put(imx214->dev); + return ret; +} + +static int imx214_g_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fival) +{ + fival->interval.numerator = 1; + fival->interval.denominator = IMX214_FPS; + + return 0; +} + +static int imx214_enum_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + const struct imx214_mode *mode; + + if (fie->index != 0) + return -EINVAL; + + mode = v4l2_find_nearest_size(imx214_modes, + ARRAY_SIZE(imx214_modes), width, height, + fie->width, fie->height); + + fie->code = IMX214_MBUS_CODE; + fie->width = mode->width; + fie->height = mode->height; + fie->interval.numerator = 1; + fie->interval.denominator = IMX214_FPS; + + return 0; +} + +static const struct v4l2_subdev_video_ops imx214_video_ops = { + .s_stream = imx214_s_stream, + .g_frame_interval = imx214_g_frame_interval, + .s_frame_interval = imx214_g_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops imx214_subdev_pad_ops = { + .enum_mbus_code = imx214_enum_mbus_code, + .enum_frame_size = imx214_enum_frame_size, + .enum_frame_interval = imx214_enum_frame_interval, + .get_fmt = imx214_get_format, + .set_fmt = imx214_set_format, + .get_selection = imx214_get_selection, + .init_cfg = imx214_entity_init_cfg, +}; + +static const struct v4l2_subdev_ops imx214_subdev_ops = { + .core = &imx214_core_ops, + .video = &imx214_video_ops, + .pad = &imx214_subdev_pad_ops, +}; + +static const struct regmap_config sensor_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +}; + +static int imx214_get_regulators(struct device *dev, struct imx214 *imx214) +{ + unsigned int i; + + for (i = 0; i < IMX214_NUM_SUPPLIES; i++) + imx214->supplies[i].supply = imx214_supply_name[i]; + + return devm_regulator_bulk_get(dev, IMX214_NUM_SUPPLIES, + imx214->supplies); +} + +static int imx214_parse_fwnode(struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + unsigned int i; + int ret; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); + if (ret) { + dev_err(dev, "parsing endpoint node failed\n"); + goto done; + } + + for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) + if (bus_cfg.link_frequencies[i] == IMX214_DEFAULT_LINK_FREQ) + break; + + if (i == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequencies %d not supported, Please review your DT\n", + IMX214_DEFAULT_LINK_FREQ); + ret = -EINVAL; + goto done; + } + +done: + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(endpoint); + return ret; +} + +static int __maybe_unused imx214_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx214 *imx214 = to_imx214(sd); + + if (imx214->streaming) + imx214_stop_streaming(imx214); + + return 0; +} + +static int __maybe_unused imx214_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx214 *imx214 = to_imx214(sd); + int ret; + + if (imx214->streaming) { + ret = imx214_start_streaming(imx214); + if (ret) + goto error; + } + + return 0; + +error: + imx214_stop_streaming(imx214); + imx214->streaming = 0; + return ret; +} + +static int imx214_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx214 *imx214; + static const s64 link_freq[] = { + IMX214_DEFAULT_LINK_FREQ, + }; + static const struct v4l2_area unit_size = { + .width = 1120, + .height = 1120, + }; + int ret; + + ret = imx214_parse_fwnode(dev); + if (ret) + return ret; + + imx214 = devm_kzalloc(dev, sizeof(*imx214), GFP_KERNEL); + if (!imx214) + return -ENOMEM; + + imx214->dev = dev; + + imx214->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(imx214->xclk)) { + dev_err(dev, "could not get xclk"); + return PTR_ERR(imx214->xclk); + } + + ret = clk_set_rate(imx214->xclk, IMX214_DEFAULT_CLK_FREQ); + if (ret) { + dev_err(dev, "could not set xclk frequency\n"); + return ret; + } + + ret = imx214_get_regulators(dev, imx214); + if (ret < 0) { + dev_err(dev, "cannot get regulators\n"); + return ret; + } + + imx214->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(imx214->enable_gpio)) { + dev_err(dev, "cannot get enable gpio\n"); + return PTR_ERR(imx214->enable_gpio); + } + + imx214->regmap = devm_regmap_init_i2c(client, &sensor_regmap_config); + if (IS_ERR(imx214->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(imx214->regmap); + } + + v4l2_i2c_subdev_init(&imx214->sd, client, &imx214_subdev_ops); + + /* + * Enable power initially, to avoid warnings + * from clk_disable on power_off + */ + imx214_power_on(imx214->dev); + + pm_runtime_set_active(imx214->dev); + pm_runtime_enable(imx214->dev); + pm_runtime_idle(imx214->dev); + + v4l2_ctrl_handler_init(&imx214->ctrls, 3); + + imx214->pixel_rate = v4l2_ctrl_new_std(&imx214->ctrls, NULL, + V4L2_CID_PIXEL_RATE, 0, + IMX214_DEFAULT_PIXEL_RATE, 1, + IMX214_DEFAULT_PIXEL_RATE); + imx214->link_freq = v4l2_ctrl_new_int_menu(&imx214->ctrls, NULL, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq) - 1, + 0, link_freq); + if (imx214->link_freq) + imx214->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* + * WARNING! + * Values obtained reverse engineering blobs and/or devices. + * Ranges and functionality might be wrong. + * + * Sony, please release some register set documentation for the + * device. + * + * Yours sincerely, Ricardo. + */ + imx214->exposure = v4l2_ctrl_new_std(&imx214->ctrls, &imx214_ctrl_ops, + V4L2_CID_EXPOSURE, + 0, 3184, 1, 0x0c70); + + imx214->unit_size = v4l2_ctrl_new_std_compound(&imx214->ctrls, + NULL, + V4L2_CID_UNIT_CELL_SIZE, + v4l2_ctrl_ptr_create((void *)&unit_size)); + ret = imx214->ctrls.error; + if (ret) { + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto free_ctrl; + } + + imx214->sd.ctrl_handler = &imx214->ctrls; + mutex_init(&imx214->mutex); + imx214->ctrls.lock = &imx214->mutex; + + imx214->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx214->pad.flags = MEDIA_PAD_FL_SOURCE; + imx214->sd.dev = &client->dev; + imx214->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&imx214->sd.entity, 1, &imx214->pad); + if (ret < 0) { + dev_err(dev, "could not register media entity\n"); + goto free_ctrl; + } + + imx214_entity_init_cfg(&imx214->sd, NULL); + + ret = v4l2_async_register_subdev_sensor_common(&imx214->sd); + if (ret < 0) { + dev_err(dev, "could not register v4l2 device\n"); + goto free_entity; + } + + return 0; + +free_entity: + media_entity_cleanup(&imx214->sd.entity); +free_ctrl: + mutex_destroy(&imx214->mutex); + v4l2_ctrl_handler_free(&imx214->ctrls); + pm_runtime_disable(imx214->dev); + + return ret; +} + +static int imx214_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx214 *imx214 = to_imx214(sd); + + v4l2_async_unregister_subdev(&imx214->sd); + media_entity_cleanup(&imx214->sd.entity); + v4l2_ctrl_handler_free(&imx214->ctrls); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + mutex_destroy(&imx214->mutex); + + return 0; +} + +static const struct of_device_id imx214_of_match[] = { + { .compatible = "sony,imx214" }, + { } +}; +MODULE_DEVICE_TABLE(of, imx214_of_match); + +static const struct dev_pm_ops imx214_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx214_suspend, imx214_resume) + SET_RUNTIME_PM_OPS(imx214_power_off, imx214_power_on, NULL) +}; + +static struct i2c_driver imx214_i2c_driver = { + .driver = { + .of_match_table = imx214_of_match, + .pm = &imx214_pm_ops, + .name = "imx214", + }, + .probe_new = imx214_probe, + .remove = imx214_remove, +}; + +module_i2c_driver(imx214_i2c_driver); + +MODULE_DESCRIPTION("Sony IMX214 Camera driver"); +MODULE_AUTHOR("Ricardo Ribalda <ribalda@kernel.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c new file mode 100644 index 000000000..b975636d9 --- /dev/null +++ b/drivers/media/i2c/imx219.c @@ -0,0 +1,1544 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX219 cameras. + * Copyright (C) 2019, Raspberry Pi (Trading) Ltd + * + * Based on Sony imx258 camera driver + * Copyright (C) 2018 Intel Corporation + * + * DT / fwnode changes, and regulator / GPIO control taken from imx214 driver + * Copyright 2018 Qtechnology A/S + * + * Flip handling taken from the Sony IMX319 driver. + * Copyright (C) 2018 Intel Corporation + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mediabus.h> +#include <asm/unaligned.h> + +#define IMX219_REG_VALUE_08BIT 1 +#define IMX219_REG_VALUE_16BIT 2 + +#define IMX219_REG_MODE_SELECT 0x0100 +#define IMX219_MODE_STANDBY 0x00 +#define IMX219_MODE_STREAMING 0x01 + +/* Chip ID */ +#define IMX219_REG_CHIP_ID 0x0000 +#define IMX219_CHIP_ID 0x0219 + +/* External clock frequency is 24.0M */ +#define IMX219_XCLK_FREQ 24000000 + +/* Pixel rate is fixed at 182.4M for all the modes */ +#define IMX219_PIXEL_RATE 182400000 + +#define IMX219_DEFAULT_LINK_FREQ 456000000 + +/* V_TIMING internal */ +#define IMX219_REG_VTS 0x0160 +#define IMX219_VTS_15FPS 0x0dc6 +#define IMX219_VTS_30FPS_1080P 0x06e3 +#define IMX219_VTS_30FPS_BINNED 0x06e3 +#define IMX219_VTS_30FPS_640x480 0x06e3 +#define IMX219_VTS_MAX 0xffff + +#define IMX219_VBLANK_MIN 4 + +/*Frame Length Line*/ +#define IMX219_FLL_MIN 0x08a6 +#define IMX219_FLL_MAX 0xffff +#define IMX219_FLL_STEP 1 +#define IMX219_FLL_DEFAULT 0x0c98 + +/* HBLANK control - read only */ +#define IMX219_PPL_DEFAULT 3448 + +/* Exposure control */ +#define IMX219_REG_EXPOSURE 0x015a +#define IMX219_EXPOSURE_MIN 4 +#define IMX219_EXPOSURE_STEP 1 +#define IMX219_EXPOSURE_DEFAULT 0x640 +#define IMX219_EXPOSURE_MAX 65535 + +/* Analog gain control */ +#define IMX219_REG_ANALOG_GAIN 0x0157 +#define IMX219_ANA_GAIN_MIN 0 +#define IMX219_ANA_GAIN_MAX 232 +#define IMX219_ANA_GAIN_STEP 1 +#define IMX219_ANA_GAIN_DEFAULT 0x0 + +/* Digital gain control */ +#define IMX219_REG_DIGITAL_GAIN 0x0158 +#define IMX219_DGTL_GAIN_MIN 0x0100 +#define IMX219_DGTL_GAIN_MAX 0x0fff +#define IMX219_DGTL_GAIN_DEFAULT 0x0100 +#define IMX219_DGTL_GAIN_STEP 1 + +#define IMX219_REG_ORIENTATION 0x0172 + +/* Binning Mode */ +#define IMX219_REG_BINNING_MODE 0x0174 +#define IMX219_BINNING_NONE 0x0000 +#define IMX219_BINNING_2X2 0x0101 +#define IMX219_BINNING_2X2_ANALOG 0x0303 + +/* Test Pattern Control */ +#define IMX219_REG_TEST_PATTERN 0x0600 +#define IMX219_TEST_PATTERN_DISABLE 0 +#define IMX219_TEST_PATTERN_SOLID_COLOR 1 +#define IMX219_TEST_PATTERN_COLOR_BARS 2 +#define IMX219_TEST_PATTERN_GREY_COLOR 3 +#define IMX219_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define IMX219_REG_TESTP_RED 0x0602 +#define IMX219_REG_TESTP_GREENR 0x0604 +#define IMX219_REG_TESTP_BLUE 0x0606 +#define IMX219_REG_TESTP_GREENB 0x0608 +#define IMX219_TESTP_COLOUR_MIN 0 +#define IMX219_TESTP_COLOUR_MAX 0x03ff +#define IMX219_TESTP_COLOUR_STEP 1 +#define IMX219_TESTP_RED_DEFAULT IMX219_TESTP_COLOUR_MAX +#define IMX219_TESTP_GREENR_DEFAULT 0 +#define IMX219_TESTP_BLUE_DEFAULT 0 +#define IMX219_TESTP_GREENB_DEFAULT 0 + +/* IMX219 native and active pixel array size. */ +#define IMX219_NATIVE_WIDTH 3296U +#define IMX219_NATIVE_HEIGHT 2480U +#define IMX219_PIXEL_ARRAY_LEFT 8U +#define IMX219_PIXEL_ARRAY_TOP 8U +#define IMX219_PIXEL_ARRAY_WIDTH 3280U +#define IMX219_PIXEL_ARRAY_HEIGHT 2464U + +struct imx219_reg { + u16 address; + u8 val; +}; + +struct imx219_reg_list { + unsigned int num_of_regs; + const struct imx219_reg *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx219_mode { + /* Frame width */ + unsigned int width; + /* Frame height */ + unsigned int height; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* V-timing */ + unsigned int vts_def; + + /* Default register values */ + struct imx219_reg_list reg_list; + + /* 2x2 binning is used */ + bool binning; +}; + +static const struct imx219_reg imx219_common_regs[] = { + {0x0100, 0x00}, /* Mode Select */ + + /* To Access Addresses 3000-5fff, send the following commands */ + {0x30eb, 0x0c}, + {0x30eb, 0x05}, + {0x300a, 0xff}, + {0x300b, 0xff}, + {0x30eb, 0x05}, + {0x30eb, 0x09}, + + /* PLL Clock Table */ + {0x0301, 0x05}, /* VTPXCK_DIV */ + {0x0303, 0x01}, /* VTSYSCK_DIV */ + {0x0304, 0x03}, /* PREPLLCK_VT_DIV 0x03 = AUTO set */ + {0x0305, 0x03}, /* PREPLLCK_OP_DIV 0x03 = AUTO set */ + {0x0306, 0x00}, /* PLL_VT_MPY */ + {0x0307, 0x39}, + {0x030b, 0x01}, /* OP_SYS_CLK_DIV */ + {0x030c, 0x00}, /* PLL_OP_MPY */ + {0x030d, 0x72}, + + /* Undocumented registers */ + {0x455e, 0x00}, + {0x471e, 0x4b}, + {0x4767, 0x0f}, + {0x4750, 0x14}, + {0x4540, 0x00}, + {0x47b4, 0x14}, + {0x4713, 0x30}, + {0x478b, 0x10}, + {0x478f, 0x10}, + {0x4793, 0x10}, + {0x4797, 0x0e}, + {0x479b, 0x0e}, + + /* Frame Bank Register Group "A" */ + {0x0162, 0x0d}, /* Line_Length_A */ + {0x0163, 0x78}, + {0x0170, 0x01}, /* X_ODD_INC_A */ + {0x0171, 0x01}, /* Y_ODD_INC_A */ + + /* Output setup registers */ + {0x0114, 0x01}, /* CSI 2-Lane Mode */ + {0x0128, 0x00}, /* DPHY Auto Mode */ + {0x012a, 0x18}, /* EXCK_Freq */ + {0x012b, 0x00}, +}; + +/* + * Register sets lifted off the i2C interface from the Raspberry Pi firmware + * driver. + * 3280x2464 = mode 2, 1920x1080 = mode 1, 1640x1232 = mode 4, 640x480 = mode 7. + */ +static const struct imx219_reg mode_3280x2464_regs[] = { + {0x0164, 0x00}, + {0x0165, 0x00}, + {0x0166, 0x0c}, + {0x0167, 0xcf}, + {0x0168, 0x00}, + {0x0169, 0x00}, + {0x016a, 0x09}, + {0x016b, 0x9f}, + {0x016c, 0x0c}, + {0x016d, 0xd0}, + {0x016e, 0x09}, + {0x016f, 0xa0}, + {0x0624, 0x0c}, + {0x0625, 0xd0}, + {0x0626, 0x09}, + {0x0627, 0xa0}, +}; + +static const struct imx219_reg mode_1920_1080_regs[] = { + {0x0164, 0x02}, + {0x0165, 0xa8}, + {0x0166, 0x0a}, + {0x0167, 0x27}, + {0x0168, 0x02}, + {0x0169, 0xb4}, + {0x016a, 0x06}, + {0x016b, 0xeb}, + {0x016c, 0x07}, + {0x016d, 0x80}, + {0x016e, 0x04}, + {0x016f, 0x38}, + {0x0624, 0x07}, + {0x0625, 0x80}, + {0x0626, 0x04}, + {0x0627, 0x38}, +}; + +static const struct imx219_reg mode_1640_1232_regs[] = { + {0x0164, 0x00}, + {0x0165, 0x00}, + {0x0166, 0x0c}, + {0x0167, 0xcf}, + {0x0168, 0x00}, + {0x0169, 0x00}, + {0x016a, 0x09}, + {0x016b, 0x9f}, + {0x016c, 0x06}, + {0x016d, 0x68}, + {0x016e, 0x04}, + {0x016f, 0xd0}, + {0x0624, 0x06}, + {0x0625, 0x68}, + {0x0626, 0x04}, + {0x0627, 0xd0}, +}; + +static const struct imx219_reg mode_640_480_regs[] = { + {0x0164, 0x03}, + {0x0165, 0xe8}, + {0x0166, 0x08}, + {0x0167, 0xe7}, + {0x0168, 0x02}, + {0x0169, 0xf0}, + {0x016a, 0x06}, + {0x016b, 0xaf}, + {0x016c, 0x02}, + {0x016d, 0x80}, + {0x016e, 0x01}, + {0x016f, 0xe0}, + {0x0624, 0x06}, + {0x0625, 0x68}, + {0x0626, 0x04}, + {0x0627, 0xd0}, +}; + +static const struct imx219_reg raw8_framefmt_regs[] = { + {0x018c, 0x08}, + {0x018d, 0x08}, + {0x0309, 0x08}, +}; + +static const struct imx219_reg raw10_framefmt_regs[] = { + {0x018c, 0x0a}, + {0x018d, 0x0a}, + {0x0309, 0x0a}, +}; + +static const char * const imx219_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9" +}; + +static const int imx219_test_pattern_val[] = { + IMX219_TEST_PATTERN_DISABLE, + IMX219_TEST_PATTERN_COLOR_BARS, + IMX219_TEST_PATTERN_SOLID_COLOR, + IMX219_TEST_PATTERN_GREY_COLOR, + IMX219_TEST_PATTERN_PN9, +}; + +/* regulator supplies */ +static const char * const imx219_supply_name[] = { + /* Supplies can be enabled in any order */ + "VANA", /* Analog (2.8V) supply */ + "VDIG", /* Digital Core (1.8V) supply */ + "VDDL", /* IF (1.2V) supply */ +}; + +#define IMX219_NUM_SUPPLIES ARRAY_SIZE(imx219_supply_name) + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, + + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SBGGR8_1X8, +}; + +/* + * Initialisation delay between XCLR low->high and the moment when the sensor + * can start capture (i.e. can leave software stanby) must be not less than: + * t4 + max(t5, t6 + <time to initialize the sensor register over I2C>) + * where + * t4 is fixed, and is max 200uS, + * t5 is fixed, and is 6000uS, + * t6 depends on the sensor external clock, and is max 32000 clock periods. + * As per sensor datasheet, the external clock must be from 6MHz to 27MHz. + * So for any acceptable external clock t6 is always within the range of + * 1185 to 5333 uS, and is always less than t5. + * For this reason this is always safe to wait (t4 + t5) = 6200 uS, then + * initialize the sensor over I2C, and then exit the software standby. + * + * This start-up time can be optimized a bit more, if we start the writes + * over I2C after (t4+t6), but before (t4+t5) expires. But then sensor + * initialization over I2C may complete before (t4+t5) expires, and we must + * ensure that capture is not started before (t4+t5). + * + * This delay doesn't account for the power supply startup time. If needed, + * this should be taken care of via the regulator framework. E.g. in the + * case of DT for regulator-fixed one should define the startup-delay-us + * property. + */ +#define IMX219_XCLR_MIN_DELAY_US 6200 +#define IMX219_XCLR_DELAY_RANGE_US 1000 + +/* Mode configs */ +static const struct imx219_mode supported_modes[] = { + { + /* 8MPix 15fps mode */ + .width = 3280, + .height = 2464, + .crop = { + .left = IMX219_PIXEL_ARRAY_LEFT, + .top = IMX219_PIXEL_ARRAY_TOP, + .width = 3280, + .height = 2464 + }, + .vts_def = IMX219_VTS_15FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs), + .regs = mode_3280x2464_regs, + }, + .binning = false, + }, + { + /* 1080P 30fps cropped */ + .width = 1920, + .height = 1080, + .crop = { + .left = 688, + .top = 700, + .width = 1920, + .height = 1080 + }, + .vts_def = IMX219_VTS_30FPS_1080P, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920_1080_regs), + .regs = mode_1920_1080_regs, + }, + .binning = false, + }, + { + /* 2x2 binned 30fps mode */ + .width = 1640, + .height = 1232, + .crop = { + .left = IMX219_PIXEL_ARRAY_LEFT, + .top = IMX219_PIXEL_ARRAY_TOP, + .width = 3280, + .height = 2464 + }, + .vts_def = IMX219_VTS_30FPS_BINNED, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640_1232_regs), + .regs = mode_1640_1232_regs, + }, + .binning = true, + }, + { + /* 640x480 30fps mode */ + .width = 640, + .height = 480, + .crop = { + .left = 1008, + .top = 760, + .width = 1280, + .height = 960 + }, + .vts_def = IMX219_VTS_30FPS_640x480, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_640_480_regs), + .regs = mode_640_480_regs, + }, + .binning = true, + }, +}; + +struct imx219 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_mbus_framefmt fmt; + + struct clk *xclk; /* system clock to IMX219 */ + u32 xclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX219_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + + /* Current mode */ + const struct imx219_mode *mode; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static inline struct imx219 *to_imx219(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx219, sd); +} + +/* Read registers up to 2 at a time */ +static int imx219_read_reg(struct imx219 *imx219, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 2 at a time */ +static int imx219_write_reg(struct imx219 *imx219, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx219_write_regs(struct imx219 *imx219, + const struct imx219_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = imx219_write_reg(imx219, regs[i].address, 1, regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 imx219_get_format_code(struct imx219 *imx219, u32 code) +{ + unsigned int i; + + lockdep_assert_held(&imx219->mutex); + + for (i = 0; i < ARRAY_SIZE(codes); i++) + if (codes[i] == code) + break; + + if (i >= ARRAY_SIZE(codes)) + i = 0; + + i = (i & ~3) | (imx219->vflip->val ? 2 : 0) | + (imx219->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx219_set_default_format(struct imx219 *imx219) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = &imx219->fmt; + fmt->code = MEDIA_BUS_FMT_SRGGB10_1X10; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + fmt->width = supported_modes[0].width; + fmt->height = supported_modes[0].height; + fmt->field = V4L2_FIELD_NONE; +} + +static int imx219_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct imx219 *imx219 = to_imx219(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + struct v4l2_rect *try_crop; + + mutex_lock(&imx219->mutex); + + /* Initialize try_fmt */ + try_fmt->width = supported_modes[0].width; + try_fmt->height = supported_modes[0].height; + try_fmt->code = imx219_get_format_code(imx219, + MEDIA_BUS_FMT_SRGGB10_1X10); + try_fmt->field = V4L2_FIELD_NONE; + + /* Initialize try_crop rectangle. */ + try_crop = v4l2_subdev_get_try_crop(sd, fh->pad, 0); + try_crop->top = IMX219_PIXEL_ARRAY_TOP; + try_crop->left = IMX219_PIXEL_ARRAY_LEFT; + try_crop->width = IMX219_PIXEL_ARRAY_WIDTH; + try_crop->height = IMX219_PIXEL_ARRAY_HEIGHT; + + mutex_unlock(&imx219->mutex); + + return 0; +} + +static int imx219_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx219 *imx219 = + container_of(ctrl->handler, struct imx219, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); + int ret; + + if (ctrl->id == V4L2_CID_VBLANK) { + int exposure_max, exposure_def; + + /* Update max exposure while meeting expected vblanking */ + exposure_max = imx219->mode->height + ctrl->val - 4; + exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? + exposure_max : IMX219_EXPOSURE_DEFAULT; + __v4l2_ctrl_modify_range(imx219->exposure, + imx219->exposure->minimum, + exposure_max, imx219->exposure->step, + exposure_def); + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = imx219_write_reg(imx219, IMX219_REG_ANALOG_GAIN, + IMX219_REG_VALUE_08BIT, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx219_write_reg(imx219, IMX219_REG_EXPOSURE, + IMX219_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx219_write_reg(imx219, IMX219_REG_DIGITAL_GAIN, + IMX219_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx219_write_reg(imx219, IMX219_REG_TEST_PATTERN, + IMX219_REG_VALUE_16BIT, + imx219_test_pattern_val[ctrl->val]); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx219_write_reg(imx219, IMX219_REG_ORIENTATION, 1, + imx219->hflip->val | + imx219->vflip->val << 1); + break; + case V4L2_CID_VBLANK: + ret = imx219_write_reg(imx219, IMX219_REG_VTS, + IMX219_REG_VALUE_16BIT, + imx219->mode->height + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_RED: + ret = imx219_write_reg(imx219, IMX219_REG_TESTP_RED, + IMX219_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + ret = imx219_write_reg(imx219, IMX219_REG_TESTP_GREENR, + IMX219_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + ret = imx219_write_reg(imx219, IMX219_REG_TESTP_BLUE, + IMX219_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + ret = imx219_write_reg(imx219, IMX219_REG_TESTP_GREENB, + IMX219_REG_VALUE_16BIT, ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx219_ctrl_ops = { + .s_ctrl = imx219_set_ctrl, +}; + +static int imx219_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx219 *imx219 = to_imx219(sd); + + if (code->index >= (ARRAY_SIZE(codes) / 4)) + return -EINVAL; + + code->code = imx219_get_format_code(imx219, codes[code->index * 4]); + + return 0; +} + +static int imx219_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx219 *imx219 = to_imx219(sd); + + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != imx219_get_format_code(imx219, fse->code)) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void imx219_reset_colorspace(struct v4l2_mbus_framefmt *fmt) +{ + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); +} + +static void imx219_update_pad_format(struct imx219 *imx219, + const struct imx219_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + imx219_reset_colorspace(&fmt->format); +} + +static int __imx219_get_pad_format(struct imx219 *imx219, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(&imx219->sd, cfg, fmt->pad); + /* update the code which could change due to vflip or hflip: */ + try_fmt->code = imx219_get_format_code(imx219, try_fmt->code); + fmt->format = *try_fmt; + } else { + imx219_update_pad_format(imx219, imx219->mode, fmt); + fmt->format.code = imx219_get_format_code(imx219, + imx219->fmt.code); + } + + return 0; +} + +static int imx219_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx219 *imx219 = to_imx219(sd); + int ret; + + mutex_lock(&imx219->mutex); + ret = __imx219_get_pad_format(imx219, cfg, fmt); + mutex_unlock(&imx219->mutex); + + return ret; +} + +static int imx219_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx219 *imx219 = to_imx219(sd); + const struct imx219_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + int exposure_max, exposure_def, hblank; + unsigned int i; + + mutex_lock(&imx219->mutex); + + for (i = 0; i < ARRAY_SIZE(codes); i++) + if (codes[i] == fmt->format.code) + break; + if (i >= ARRAY_SIZE(codes)) + i = 0; + + /* Bayer order varies with flips */ + fmt->format.code = imx219_get_format_code(imx219, codes[i]); + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + imx219_update_pad_format(imx219, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *framefmt = fmt->format; + } else if (imx219->mode != mode || + imx219->fmt.code != fmt->format.code) { + imx219->fmt = fmt->format; + imx219->mode = mode; + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range(imx219->vblank, IMX219_VBLANK_MIN, + IMX219_VTS_MAX - mode->height, 1, + mode->vts_def - mode->height); + __v4l2_ctrl_s_ctrl(imx219->vblank, + mode->vts_def - mode->height); + /* Update max exposure while meeting expected vblanking */ + exposure_max = mode->vts_def - 4; + exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? + exposure_max : IMX219_EXPOSURE_DEFAULT; + __v4l2_ctrl_modify_range(imx219->exposure, + imx219->exposure->minimum, + exposure_max, imx219->exposure->step, + exposure_def); + /* + * Currently PPL is fixed to IMX219_PPL_DEFAULT, so hblank + * depends on mode->width only, and is not changeble in any + * way other than changing the mode. + */ + hblank = IMX219_PPL_DEFAULT - mode->width; + __v4l2_ctrl_modify_range(imx219->hblank, hblank, hblank, 1, + hblank); + } + + mutex_unlock(&imx219->mutex); + + return 0; +} + +static int imx219_set_framefmt(struct imx219 *imx219) +{ + switch (imx219->fmt.code) { + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + return imx219_write_regs(imx219, raw8_framefmt_regs, + ARRAY_SIZE(raw8_framefmt_regs)); + + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + return imx219_write_regs(imx219, raw10_framefmt_regs, + ARRAY_SIZE(raw10_framefmt_regs)); + } + + return -EINVAL; +} + +static int imx219_set_binning(struct imx219 *imx219) +{ + if (!imx219->mode->binning) { + return imx219_write_reg(imx219, IMX219_REG_BINNING_MODE, + IMX219_REG_VALUE_16BIT, + IMX219_BINNING_NONE); + } + + switch (imx219->fmt.code) { + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + return imx219_write_reg(imx219, IMX219_REG_BINNING_MODE, + IMX219_REG_VALUE_16BIT, + IMX219_BINNING_2X2_ANALOG); + + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + return imx219_write_reg(imx219, IMX219_REG_BINNING_MODE, + IMX219_REG_VALUE_16BIT, + IMX219_BINNING_2X2); + } + + return -EINVAL; +} + +static const struct v4l2_rect * +__imx219_get_pad_crop(struct imx219 *imx219, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&imx219->sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx219->mode->crop; + } + + return NULL; +} + +static int imx219_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct imx219 *imx219 = to_imx219(sd); + + mutex_lock(&imx219->mutex); + sel->r = *__imx219_get_pad_crop(imx219, cfg, sel->pad, + sel->which); + mutex_unlock(&imx219->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = IMX219_NATIVE_WIDTH; + sel->r.height = IMX219_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = IMX219_PIXEL_ARRAY_TOP; + sel->r.left = IMX219_PIXEL_ARRAY_LEFT; + sel->r.width = IMX219_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX219_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +static int imx219_start_streaming(struct imx219 *imx219) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); + const struct imx219_reg_list *reg_list; + int ret; + + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + return ret; + } + + /* Send all registers that are common to all modes */ + ret = imx219_write_regs(imx219, imx219_common_regs, ARRAY_SIZE(imx219_common_regs)); + if (ret) { + dev_err(&client->dev, "%s failed to send mfg header\n", __func__); + goto err_rpm_put; + } + + /* Apply default values of current mode */ + reg_list = &imx219->mode->reg_list; + ret = imx219_write_regs(imx219, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + goto err_rpm_put; + } + + ret = imx219_set_framefmt(imx219); + if (ret) { + dev_err(&client->dev, "%s failed to set frame format: %d\n", + __func__, ret); + goto err_rpm_put; + } + + ret = imx219_set_binning(imx219); + if (ret) { + dev_err(&client->dev, "%s failed to set binning: %d\n", + __func__, ret); + goto err_rpm_put; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx219->sd.ctrl_handler); + if (ret) + goto err_rpm_put; + + /* set stream on register */ + ret = imx219_write_reg(imx219, IMX219_REG_MODE_SELECT, + IMX219_REG_VALUE_08BIT, IMX219_MODE_STREAMING); + if (ret) + goto err_rpm_put; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx219->vflip, true); + __v4l2_ctrl_grab(imx219->hflip, true); + + return 0; + +err_rpm_put: + pm_runtime_put(&client->dev); + return ret; +} + +static void imx219_stop_streaming(struct imx219 *imx219) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); + int ret; + + /* set stream off register */ + ret = imx219_write_reg(imx219, IMX219_REG_MODE_SELECT, + IMX219_REG_VALUE_08BIT, IMX219_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); + + __v4l2_ctrl_grab(imx219->vflip, false); + __v4l2_ctrl_grab(imx219->hflip, false); + + pm_runtime_put(&client->dev); +} + +static int imx219_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx219 *imx219 = to_imx219(sd); + int ret = 0; + + mutex_lock(&imx219->mutex); + if (imx219->streaming == enable) { + mutex_unlock(&imx219->mutex); + return 0; + } + + if (enable) { + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx219_start_streaming(imx219); + if (ret) + goto err_unlock; + } else { + imx219_stop_streaming(imx219); + } + + imx219->streaming = enable; + + mutex_unlock(&imx219->mutex); + + return ret; + +err_unlock: + mutex_unlock(&imx219->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int imx219_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx219 *imx219 = to_imx219(sd); + int ret; + + ret = regulator_bulk_enable(IMX219_NUM_SUPPLIES, + imx219->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(imx219->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + gpiod_set_value_cansleep(imx219->reset_gpio, 1); + usleep_range(IMX219_XCLR_MIN_DELAY_US, + IMX219_XCLR_MIN_DELAY_US + IMX219_XCLR_DELAY_RANGE_US); + + return 0; + +reg_off: + regulator_bulk_disable(IMX219_NUM_SUPPLIES, imx219->supplies); + + return ret; +} + +static int imx219_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx219 *imx219 = to_imx219(sd); + + gpiod_set_value_cansleep(imx219->reset_gpio, 0); + regulator_bulk_disable(IMX219_NUM_SUPPLIES, imx219->supplies); + clk_disable_unprepare(imx219->xclk); + + return 0; +} + +static int __maybe_unused imx219_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx219 *imx219 = to_imx219(sd); + + if (imx219->streaming) + imx219_stop_streaming(imx219); + + return 0; +} + +static int __maybe_unused imx219_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx219 *imx219 = to_imx219(sd); + int ret; + + if (imx219->streaming) { + ret = imx219_start_streaming(imx219); + if (ret) + goto error; + } + + return 0; + +error: + imx219_stop_streaming(imx219); + imx219->streaming = false; + + return ret; +} + +static int imx219_get_regulators(struct imx219 *imx219) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); + unsigned int i; + + for (i = 0; i < IMX219_NUM_SUPPLIES; i++) + imx219->supplies[i].supply = imx219_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + IMX219_NUM_SUPPLIES, + imx219->supplies); +} + +/* Verify chip ID */ +static int imx219_identify_module(struct imx219 *imx219) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); + int ret; + u32 val; + + ret = imx219_read_reg(imx219, IMX219_REG_CHIP_ID, + IMX219_REG_VALUE_16BIT, &val); + if (ret) { + dev_err(&client->dev, "failed to read chip id %x\n", + IMX219_CHIP_ID); + return ret; + } + + if (val != IMX219_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + IMX219_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_core_ops imx219_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx219_video_ops = { + .s_stream = imx219_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx219_pad_ops = { + .enum_mbus_code = imx219_enum_mbus_code, + .get_fmt = imx219_get_pad_format, + .set_fmt = imx219_set_pad_format, + .get_selection = imx219_get_selection, + .enum_frame_size = imx219_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx219_subdev_ops = { + .core = &imx219_core_ops, + .video = &imx219_video_ops, + .pad = &imx219_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx219_internal_ops = { + .open = imx219_open, +}; + +/* Initialize control handlers */ +static int imx219_init_controls(struct imx219 *imx219) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); + struct v4l2_ctrl_handler *ctrl_hdlr; + unsigned int height = imx219->mode->height; + struct v4l2_fwnode_device_properties props; + int exposure_max, exposure_def, hblank; + int i, ret; + + ctrl_hdlr = &imx219->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 11); + if (ret) + return ret; + + mutex_init(&imx219->mutex); + ctrl_hdlr->lock = &imx219->mutex; + + /* By default, PIXEL_RATE is read only */ + imx219->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, + V4L2_CID_PIXEL_RATE, + IMX219_PIXEL_RATE, + IMX219_PIXEL_RATE, 1, + IMX219_PIXEL_RATE); + + /* Initial vblank/hblank/exposure parameters based on current mode */ + imx219->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, + V4L2_CID_VBLANK, IMX219_VBLANK_MIN, + IMX219_VTS_MAX - height, 1, + imx219->mode->vts_def - height); + hblank = IMX219_PPL_DEFAULT - imx219->mode->width; + imx219->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, + 1, hblank); + if (imx219->hblank) + imx219->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + exposure_max = imx219->mode->vts_def - 4; + exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? + exposure_max : IMX219_EXPOSURE_DEFAULT; + imx219->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX219_EXPOSURE_MIN, exposure_max, + IMX219_EXPOSURE_STEP, + exposure_def); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX219_ANA_GAIN_MIN, IMX219_ANA_GAIN_MAX, + IMX219_ANA_GAIN_STEP, IMX219_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX219_DGTL_GAIN_MIN, IMX219_DGTL_GAIN_MAX, + IMX219_DGTL_GAIN_STEP, IMX219_DGTL_GAIN_DEFAULT); + + imx219->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx219->hflip) + imx219->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx219->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx219->vflip) + imx219->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx219_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx219_test_pattern_menu) - 1, + 0, 0, imx219_test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 + * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 + * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + IMX219_TESTP_COLOUR_MIN, + IMX219_TESTP_COLOUR_MAX, + IMX219_TESTP_COLOUR_STEP, + IMX219_TESTP_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx219_ctrl_ops, + &props); + if (ret) + goto error; + + imx219->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx219->mutex); + + return ret; +} + +static void imx219_free_controls(struct imx219 *imx219) +{ + v4l2_ctrl_handler_free(imx219->sd.ctrl_handler); + mutex_destroy(&imx219->mutex); +} + +static int imx219_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + goto error_out; + } + + /* Check the link frequency set in device tree */ + if (!ep_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + goto error_out; + } + + if (ep_cfg.nr_of_link_frequencies != 1 || + ep_cfg.link_frequencies[0] != IMX219_DEFAULT_LINK_FREQ) { + dev_err(dev, "Link frequency not supported: %lld\n", + ep_cfg.link_frequencies[0]); + goto error_out; + } + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int imx219_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx219 *imx219; + int ret; + + imx219 = devm_kzalloc(&client->dev, sizeof(*imx219), GFP_KERNEL); + if (!imx219) + return -ENOMEM; + + v4l2_i2c_subdev_init(&imx219->sd, client, &imx219_subdev_ops); + + /* Check the hardware configuration in device tree */ + if (imx219_check_hwcfg(dev)) + return -EINVAL; + + /* Get system clock (xclk) */ + imx219->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(imx219->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(imx219->xclk); + } + + imx219->xclk_freq = clk_get_rate(imx219->xclk); + if (imx219->xclk_freq != IMX219_XCLK_FREQ) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + imx219->xclk_freq); + return -EINVAL; + } + + ret = imx219_get_regulators(imx219); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + /* Request optional enable pin */ + imx219->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + /* + * The sensor must be powered for imx219_identify_module() + * to be able to read the CHIP_ID register + */ + ret = imx219_power_on(dev); + if (ret) + return ret; + + ret = imx219_identify_module(imx219); + if (ret) + goto error_power_off; + + /* Set default mode to max resolution */ + imx219->mode = &supported_modes[0]; + + /* sensor doesn't enter LP-11 state upon power up until and unless + * streaming is started, so upon power up switch the modes to: + * streaming -> standby + */ + ret = imx219_write_reg(imx219, IMX219_REG_MODE_SELECT, + IMX219_REG_VALUE_08BIT, IMX219_MODE_STREAMING); + if (ret < 0) + goto error_power_off; + usleep_range(100, 110); + + /* put sensor back to standby mode */ + ret = imx219_write_reg(imx219, IMX219_REG_MODE_SELECT, + IMX219_REG_VALUE_08BIT, IMX219_MODE_STANDBY); + if (ret < 0) + goto error_power_off; + usleep_range(100, 110); + + ret = imx219_init_controls(imx219); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + imx219->sd.internal_ops = &imx219_internal_ops; + imx219->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx219->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + imx219->pad.flags = MEDIA_PAD_FL_SOURCE; + + /* Initialize default format */ + imx219_set_default_format(imx219); + + ret = media_entity_pads_init(&imx219->sd.entity, 1, &imx219->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor_common(&imx219->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto error_media_entity; + } + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&imx219->sd.entity); + +error_handler_free: + imx219_free_controls(imx219); + +error_power_off: + imx219_power_off(dev); + + return ret; +} + +static int imx219_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx219 *imx219 = to_imx219(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx219_free_controls(imx219); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx219_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +static const struct of_device_id imx219_dt_ids[] = { + { .compatible = "sony,imx219" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx219_dt_ids); + +static const struct dev_pm_ops imx219_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx219_suspend, imx219_resume) + SET_RUNTIME_PM_OPS(imx219_power_off, imx219_power_on, NULL) +}; + +static struct i2c_driver imx219_i2c_driver = { + .driver = { + .name = "imx219", + .of_match_table = imx219_dt_ids, + .pm = &imx219_pm_ops, + }, + .probe_new = imx219_probe, + .remove = imx219_remove, +}; + +module_i2c_driver(imx219_i2c_driver); + +MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com"); +MODULE_DESCRIPTION("Sony IMX219 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx258.c b/drivers/media/i2c/imx258.c new file mode 100644 index 000000000..e6104ee97 --- /dev/null +++ b/drivers/media/i2c/imx258.c @@ -0,0 +1,1310 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Intel Corporation + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <asm/unaligned.h> + +#define IMX258_REG_VALUE_08BIT 1 +#define IMX258_REG_VALUE_16BIT 2 + +#define IMX258_REG_MODE_SELECT 0x0100 +#define IMX258_MODE_STANDBY 0x00 +#define IMX258_MODE_STREAMING 0x01 + +/* Chip ID */ +#define IMX258_REG_CHIP_ID 0x0016 +#define IMX258_CHIP_ID 0x0258 + +/* V_TIMING internal */ +#define IMX258_VTS_30FPS 0x0c50 +#define IMX258_VTS_30FPS_2K 0x0638 +#define IMX258_VTS_30FPS_VGA 0x034c +#define IMX258_VTS_MAX 0xffff + +/*Frame Length Line*/ +#define IMX258_FLL_MIN 0x08a6 +#define IMX258_FLL_MAX 0xffff +#define IMX258_FLL_STEP 1 +#define IMX258_FLL_DEFAULT 0x0c98 + +/* HBLANK control - read only */ +#define IMX258_PPL_DEFAULT 5352 + +/* Exposure control */ +#define IMX258_REG_EXPOSURE 0x0202 +#define IMX258_EXPOSURE_MIN 4 +#define IMX258_EXPOSURE_STEP 1 +#define IMX258_EXPOSURE_DEFAULT 0x640 +#define IMX258_EXPOSURE_MAX 65535 + +/* Analog gain control */ +#define IMX258_REG_ANALOG_GAIN 0x0204 +#define IMX258_ANA_GAIN_MIN 0 +#define IMX258_ANA_GAIN_MAX 480 +#define IMX258_ANA_GAIN_STEP 1 +#define IMX258_ANA_GAIN_DEFAULT 0x0 + +/* Digital gain control */ +#define IMX258_REG_GR_DIGITAL_GAIN 0x020e +#define IMX258_REG_R_DIGITAL_GAIN 0x0210 +#define IMX258_REG_B_DIGITAL_GAIN 0x0212 +#define IMX258_REG_GB_DIGITAL_GAIN 0x0214 +#define IMX258_DGTL_GAIN_MIN 0 +#define IMX258_DGTL_GAIN_MAX 4096 /* Max = 0xFFF */ +#define IMX258_DGTL_GAIN_DEFAULT 1024 +#define IMX258_DGTL_GAIN_STEP 1 + +/* Test Pattern Control */ +#define IMX258_REG_TEST_PATTERN 0x0600 + +/* Orientation */ +#define REG_MIRROR_FLIP_CONTROL 0x0101 +#define REG_CONFIG_MIRROR_FLIP 0x03 +#define REG_CONFIG_FLIP_TEST_PATTERN 0x02 + +struct imx258_reg { + u16 address; + u8 val; +}; + +struct imx258_reg_list { + u32 num_of_regs; + const struct imx258_reg *regs; +}; + +/* Link frequency config */ +struct imx258_link_freq_config { + u32 pixels_per_line; + + /* PLL registers for this link frequency */ + struct imx258_reg_list reg_list; +}; + +/* Mode : resolution and related config&values */ +struct imx258_mode { + /* Frame width */ + u32 width; + /* Frame height */ + u32 height; + + /* V-timing */ + u32 vts_def; + u32 vts_min; + + /* Index of Link frequency config to be used */ + u32 link_freq_index; + /* Default register values */ + struct imx258_reg_list reg_list; +}; + +/* 4208x3118 needs 1267Mbps/lane, 4 lanes */ +static const struct imx258_reg mipi_data_rate_1267mbps[] = { + { 0x0301, 0x05 }, + { 0x0303, 0x02 }, + { 0x0305, 0x03 }, + { 0x0306, 0x00 }, + { 0x0307, 0xC6 }, + { 0x0309, 0x0A }, + { 0x030B, 0x01 }, + { 0x030D, 0x02 }, + { 0x030E, 0x00 }, + { 0x030F, 0xD8 }, + { 0x0310, 0x00 }, + { 0x0820, 0x13 }, + { 0x0821, 0x4C }, + { 0x0822, 0xCC }, + { 0x0823, 0xCC }, +}; + +static const struct imx258_reg mipi_data_rate_640mbps[] = { + { 0x0301, 0x05 }, + { 0x0303, 0x02 }, + { 0x0305, 0x03 }, + { 0x0306, 0x00 }, + { 0x0307, 0x64 }, + { 0x0309, 0x0A }, + { 0x030B, 0x01 }, + { 0x030D, 0x02 }, + { 0x030E, 0x00 }, + { 0x030F, 0xD8 }, + { 0x0310, 0x00 }, + { 0x0820, 0x0A }, + { 0x0821, 0x00 }, + { 0x0822, 0x00 }, + { 0x0823, 0x00 }, +}; + +static const struct imx258_reg mode_4208x3118_regs[] = { + { 0x0136, 0x13 }, + { 0x0137, 0x33 }, + { 0x3051, 0x00 }, + { 0x3052, 0x00 }, + { 0x4E21, 0x14 }, + { 0x6B11, 0xCF }, + { 0x7FF0, 0x08 }, + { 0x7FF1, 0x0F }, + { 0x7FF2, 0x08 }, + { 0x7FF3, 0x1B }, + { 0x7FF4, 0x23 }, + { 0x7FF5, 0x60 }, + { 0x7FF6, 0x00 }, + { 0x7FF7, 0x01 }, + { 0x7FF8, 0x00 }, + { 0x7FF9, 0x78 }, + { 0x7FFA, 0x00 }, + { 0x7FFB, 0x00 }, + { 0x7FFC, 0x00 }, + { 0x7FFD, 0x00 }, + { 0x7FFE, 0x00 }, + { 0x7FFF, 0x03 }, + { 0x7F76, 0x03 }, + { 0x7F77, 0xFE }, + { 0x7FA8, 0x03 }, + { 0x7FA9, 0xFE }, + { 0x7B24, 0x81 }, + { 0x7B25, 0x00 }, + { 0x6564, 0x07 }, + { 0x6B0D, 0x41 }, + { 0x653D, 0x04 }, + { 0x6B05, 0x8C }, + { 0x6B06, 0xF9 }, + { 0x6B08, 0x65 }, + { 0x6B09, 0xFC }, + { 0x6B0A, 0xCF }, + { 0x6B0B, 0xD2 }, + { 0x6700, 0x0E }, + { 0x6707, 0x0E }, + { 0x9104, 0x00 }, + { 0x4648, 0x7F }, + { 0x7420, 0x00 }, + { 0x7421, 0x1C }, + { 0x7422, 0x00 }, + { 0x7423, 0xD7 }, + { 0x5F04, 0x00 }, + { 0x5F05, 0xED }, + { 0x0112, 0x0A }, + { 0x0113, 0x0A }, + { 0x0114, 0x03 }, + { 0x0342, 0x14 }, + { 0x0343, 0xE8 }, + { 0x0340, 0x0C }, + { 0x0341, 0x50 }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x00 }, + { 0x0347, 0x00 }, + { 0x0348, 0x10 }, + { 0x0349, 0x6F }, + { 0x034A, 0x0C }, + { 0x034B, 0x2E }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0401, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x10 }, + { 0x0408, 0x00 }, + { 0x0409, 0x00 }, + { 0x040A, 0x00 }, + { 0x040B, 0x00 }, + { 0x040C, 0x10 }, + { 0x040D, 0x70 }, + { 0x040E, 0x0C }, + { 0x040F, 0x30 }, + { 0x3038, 0x00 }, + { 0x303A, 0x00 }, + { 0x303B, 0x10 }, + { 0x300D, 0x00 }, + { 0x034C, 0x10 }, + { 0x034D, 0x70 }, + { 0x034E, 0x0C }, + { 0x034F, 0x30 }, + { 0x0350, 0x01 }, + { 0x0202, 0x0C }, + { 0x0203, 0x46 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x020E, 0x01 }, + { 0x020F, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x7BCD, 0x00 }, + { 0x94DC, 0x20 }, + { 0x94DD, 0x20 }, + { 0x94DE, 0x20 }, + { 0x95DC, 0x20 }, + { 0x95DD, 0x20 }, + { 0x95DE, 0x20 }, + { 0x7FB0, 0x00 }, + { 0x9010, 0x3E }, + { 0x9419, 0x50 }, + { 0x941B, 0x50 }, + { 0x9519, 0x50 }, + { 0x951B, 0x50 }, + { 0x3030, 0x00 }, + { 0x3032, 0x00 }, + { 0x0220, 0x00 }, +}; + +static const struct imx258_reg mode_2104_1560_regs[] = { + { 0x0136, 0x13 }, + { 0x0137, 0x33 }, + { 0x3051, 0x00 }, + { 0x3052, 0x00 }, + { 0x4E21, 0x14 }, + { 0x6B11, 0xCF }, + { 0x7FF0, 0x08 }, + { 0x7FF1, 0x0F }, + { 0x7FF2, 0x08 }, + { 0x7FF3, 0x1B }, + { 0x7FF4, 0x23 }, + { 0x7FF5, 0x60 }, + { 0x7FF6, 0x00 }, + { 0x7FF7, 0x01 }, + { 0x7FF8, 0x00 }, + { 0x7FF9, 0x78 }, + { 0x7FFA, 0x00 }, + { 0x7FFB, 0x00 }, + { 0x7FFC, 0x00 }, + { 0x7FFD, 0x00 }, + { 0x7FFE, 0x00 }, + { 0x7FFF, 0x03 }, + { 0x7F76, 0x03 }, + { 0x7F77, 0xFE }, + { 0x7FA8, 0x03 }, + { 0x7FA9, 0xFE }, + { 0x7B24, 0x81 }, + { 0x7B25, 0x00 }, + { 0x6564, 0x07 }, + { 0x6B0D, 0x41 }, + { 0x653D, 0x04 }, + { 0x6B05, 0x8C }, + { 0x6B06, 0xF9 }, + { 0x6B08, 0x65 }, + { 0x6B09, 0xFC }, + { 0x6B0A, 0xCF }, + { 0x6B0B, 0xD2 }, + { 0x6700, 0x0E }, + { 0x6707, 0x0E }, + { 0x9104, 0x00 }, + { 0x4648, 0x7F }, + { 0x7420, 0x00 }, + { 0x7421, 0x1C }, + { 0x7422, 0x00 }, + { 0x7423, 0xD7 }, + { 0x5F04, 0x00 }, + { 0x5F05, 0xED }, + { 0x0112, 0x0A }, + { 0x0113, 0x0A }, + { 0x0114, 0x03 }, + { 0x0342, 0x14 }, + { 0x0343, 0xE8 }, + { 0x0340, 0x06 }, + { 0x0341, 0x38 }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x00 }, + { 0x0347, 0x00 }, + { 0x0348, 0x10 }, + { 0x0349, 0x6F }, + { 0x034A, 0x0C }, + { 0x034B, 0x2E }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x12 }, + { 0x0401, 0x01 }, + { 0x0404, 0x00 }, + { 0x0405, 0x20 }, + { 0x0408, 0x00 }, + { 0x0409, 0x02 }, + { 0x040A, 0x00 }, + { 0x040B, 0x00 }, + { 0x040C, 0x10 }, + { 0x040D, 0x6A }, + { 0x040E, 0x06 }, + { 0x040F, 0x18 }, + { 0x3038, 0x00 }, + { 0x303A, 0x00 }, + { 0x303B, 0x10 }, + { 0x300D, 0x00 }, + { 0x034C, 0x08 }, + { 0x034D, 0x38 }, + { 0x034E, 0x06 }, + { 0x034F, 0x18 }, + { 0x0350, 0x01 }, + { 0x0202, 0x06 }, + { 0x0203, 0x2E }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x020E, 0x01 }, + { 0x020F, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x7BCD, 0x01 }, + { 0x94DC, 0x20 }, + { 0x94DD, 0x20 }, + { 0x94DE, 0x20 }, + { 0x95DC, 0x20 }, + { 0x95DD, 0x20 }, + { 0x95DE, 0x20 }, + { 0x7FB0, 0x00 }, + { 0x9010, 0x3E }, + { 0x9419, 0x50 }, + { 0x941B, 0x50 }, + { 0x9519, 0x50 }, + { 0x951B, 0x50 }, + { 0x3030, 0x00 }, + { 0x3032, 0x00 }, + { 0x0220, 0x00 }, +}; + +static const struct imx258_reg mode_1048_780_regs[] = { + { 0x0136, 0x13 }, + { 0x0137, 0x33 }, + { 0x3051, 0x00 }, + { 0x3052, 0x00 }, + { 0x4E21, 0x14 }, + { 0x6B11, 0xCF }, + { 0x7FF0, 0x08 }, + { 0x7FF1, 0x0F }, + { 0x7FF2, 0x08 }, + { 0x7FF3, 0x1B }, + { 0x7FF4, 0x23 }, + { 0x7FF5, 0x60 }, + { 0x7FF6, 0x00 }, + { 0x7FF7, 0x01 }, + { 0x7FF8, 0x00 }, + { 0x7FF9, 0x78 }, + { 0x7FFA, 0x00 }, + { 0x7FFB, 0x00 }, + { 0x7FFC, 0x00 }, + { 0x7FFD, 0x00 }, + { 0x7FFE, 0x00 }, + { 0x7FFF, 0x03 }, + { 0x7F76, 0x03 }, + { 0x7F77, 0xFE }, + { 0x7FA8, 0x03 }, + { 0x7FA9, 0xFE }, + { 0x7B24, 0x81 }, + { 0x7B25, 0x00 }, + { 0x6564, 0x07 }, + { 0x6B0D, 0x41 }, + { 0x653D, 0x04 }, + { 0x6B05, 0x8C }, + { 0x6B06, 0xF9 }, + { 0x6B08, 0x65 }, + { 0x6B09, 0xFC }, + { 0x6B0A, 0xCF }, + { 0x6B0B, 0xD2 }, + { 0x6700, 0x0E }, + { 0x6707, 0x0E }, + { 0x9104, 0x00 }, + { 0x4648, 0x7F }, + { 0x7420, 0x00 }, + { 0x7421, 0x1C }, + { 0x7422, 0x00 }, + { 0x7423, 0xD7 }, + { 0x5F04, 0x00 }, + { 0x5F05, 0xED }, + { 0x0112, 0x0A }, + { 0x0113, 0x0A }, + { 0x0114, 0x03 }, + { 0x0342, 0x14 }, + { 0x0343, 0xE8 }, + { 0x0340, 0x03 }, + { 0x0341, 0x4C }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x00 }, + { 0x0347, 0x00 }, + { 0x0348, 0x10 }, + { 0x0349, 0x6F }, + { 0x034A, 0x0C }, + { 0x034B, 0x2E }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x14 }, + { 0x0401, 0x01 }, + { 0x0404, 0x00 }, + { 0x0405, 0x40 }, + { 0x0408, 0x00 }, + { 0x0409, 0x06 }, + { 0x040A, 0x00 }, + { 0x040B, 0x00 }, + { 0x040C, 0x10 }, + { 0x040D, 0x64 }, + { 0x040E, 0x03 }, + { 0x040F, 0x0C }, + { 0x3038, 0x00 }, + { 0x303A, 0x00 }, + { 0x303B, 0x10 }, + { 0x300D, 0x00 }, + { 0x034C, 0x04 }, + { 0x034D, 0x18 }, + { 0x034E, 0x03 }, + { 0x034F, 0x0C }, + { 0x0350, 0x01 }, + { 0x0202, 0x03 }, + { 0x0203, 0x42 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x020E, 0x01 }, + { 0x020F, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x7BCD, 0x00 }, + { 0x94DC, 0x20 }, + { 0x94DD, 0x20 }, + { 0x94DE, 0x20 }, + { 0x95DC, 0x20 }, + { 0x95DD, 0x20 }, + { 0x95DE, 0x20 }, + { 0x7FB0, 0x00 }, + { 0x9010, 0x3E }, + { 0x9419, 0x50 }, + { 0x941B, 0x50 }, + { 0x9519, 0x50 }, + { 0x951B, 0x50 }, + { 0x3030, 0x00 }, + { 0x3032, 0x00 }, + { 0x0220, 0x00 }, +}; + +static const char * const imx258_test_pattern_menu[] = { + "Disabled", + "Solid Colour", + "Eight Vertical Colour Bars", + "Colour Bars With Fade to Grey", + "Pseudorandom Sequence (PN9)", +}; + +/* Configurations for supported link frequencies */ +#define IMX258_LINK_FREQ_634MHZ 633600000ULL +#define IMX258_LINK_FREQ_320MHZ 320000000ULL + +enum { + IMX258_LINK_FREQ_1267MBPS, + IMX258_LINK_FREQ_640MBPS, +}; + +/* + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample + * data rate => double data rate; number of lanes => 4; bits per pixel => 10 + */ +static u64 link_freq_to_pixel_rate(u64 f) +{ + f *= 2 * 4; + do_div(f, 10); + + return f; +} + +/* Menu items for LINK_FREQ V4L2 control */ +static const s64 link_freq_menu_items[] = { + IMX258_LINK_FREQ_634MHZ, + IMX258_LINK_FREQ_320MHZ, +}; + +/* Link frequency configs */ +static const struct imx258_link_freq_config link_freq_configs[] = { + [IMX258_LINK_FREQ_1267MBPS] = { + .pixels_per_line = IMX258_PPL_DEFAULT, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_1267mbps), + .regs = mipi_data_rate_1267mbps, + } + }, + [IMX258_LINK_FREQ_640MBPS] = { + .pixels_per_line = IMX258_PPL_DEFAULT, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_640mbps), + .regs = mipi_data_rate_640mbps, + } + }, +}; + +/* Mode configs */ +static const struct imx258_mode supported_modes[] = { + { + .width = 4208, + .height = 3118, + .vts_def = IMX258_VTS_30FPS, + .vts_min = IMX258_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4208x3118_regs), + .regs = mode_4208x3118_regs, + }, + .link_freq_index = IMX258_LINK_FREQ_1267MBPS, + }, + { + .width = 2104, + .height = 1560, + .vts_def = IMX258_VTS_30FPS_2K, + .vts_min = IMX258_VTS_30FPS_2K, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2104_1560_regs), + .regs = mode_2104_1560_regs, + }, + .link_freq_index = IMX258_LINK_FREQ_640MBPS, + }, + { + .width = 1048, + .height = 780, + .vts_def = IMX258_VTS_30FPS_VGA, + .vts_min = IMX258_VTS_30FPS_VGA, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1048_780_regs), + .regs = mode_1048_780_regs, + }, + .link_freq_index = IMX258_LINK_FREQ_640MBPS, + }, +}; + +struct imx258 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct imx258_mode *cur_mode; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static inline struct imx258 *to_imx258(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx258, sd); +} + +/* Read registers up to 2 at a time */ +static int imx258_read_reg(struct imx258 *imx258, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx258->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 2 at a time */ +static int imx258_write_reg(struct imx258 *imx258, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx258->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx258_write_regs(struct imx258 *imx258, + const struct imx258_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx258->sd); + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = imx258_write_reg(imx258, regs[i].address, 1, + regs[i].val); + if (ret) { + dev_err_ratelimited( + &client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Open sub-device */ +static int imx258_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + + /* Initialize try_fmt */ + try_fmt->width = supported_modes[0].width; + try_fmt->height = supported_modes[0].height; + try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + + return 0; +} + +static int imx258_update_digital_gain(struct imx258 *imx258, u32 len, u32 val) +{ + int ret; + + ret = imx258_write_reg(imx258, IMX258_REG_GR_DIGITAL_GAIN, + IMX258_REG_VALUE_16BIT, + val); + if (ret) + return ret; + ret = imx258_write_reg(imx258, IMX258_REG_GB_DIGITAL_GAIN, + IMX258_REG_VALUE_16BIT, + val); + if (ret) + return ret; + ret = imx258_write_reg(imx258, IMX258_REG_R_DIGITAL_GAIN, + IMX258_REG_VALUE_16BIT, + val); + if (ret) + return ret; + ret = imx258_write_reg(imx258, IMX258_REG_B_DIGITAL_GAIN, + IMX258_REG_VALUE_16BIT, + val); + if (ret) + return ret; + return 0; +} + +static int imx258_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx258 *imx258 = + container_of(ctrl->handler, struct imx258, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx258->sd); + int ret = 0; + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = imx258_write_reg(imx258, IMX258_REG_ANALOG_GAIN, + IMX258_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx258_write_reg(imx258, IMX258_REG_EXPOSURE, + IMX258_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx258_update_digital_gain(imx258, IMX258_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx258_write_reg(imx258, IMX258_REG_TEST_PATTERN, + IMX258_REG_VALUE_16BIT, + ctrl->val); + ret = imx258_write_reg(imx258, REG_MIRROR_FLIP_CONTROL, + IMX258_REG_VALUE_08BIT, + !ctrl->val ? REG_CONFIG_MIRROR_FLIP : + REG_CONFIG_FLIP_TEST_PATTERN); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx258_ctrl_ops = { + .s_ctrl = imx258_set_ctrl, +}; + +static int imx258_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Only one bayer order(GRBG) is supported */ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int imx258_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void imx258_update_pad_format(const struct imx258_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int __imx258_get_pad_format(struct imx258 *imx258, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(&imx258->sd, cfg, + fmt->pad); + else + imx258_update_pad_format(imx258->cur_mode, fmt); + + return 0; +} + +static int imx258_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx258 *imx258 = to_imx258(sd); + int ret; + + mutex_lock(&imx258->mutex); + ret = __imx258_get_pad_format(imx258, cfg, fmt); + mutex_unlock(&imx258->mutex); + + return ret; +} + +static int imx258_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx258 *imx258 = to_imx258(sd); + const struct imx258_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + s32 vblank_def; + s32 vblank_min; + s64 h_blank; + s64 pixel_rate; + s64 link_freq; + + mutex_lock(&imx258->mutex); + + /* Only one raw bayer(GBRG) order is supported */ + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, height, + fmt->format.width, fmt->format.height); + imx258_update_pad_format(mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *framefmt = fmt->format; + } else { + imx258->cur_mode = mode; + __v4l2_ctrl_s_ctrl(imx258->link_freq, mode->link_freq_index); + + link_freq = link_freq_menu_items[mode->link_freq_index]; + pixel_rate = link_freq_to_pixel_rate(link_freq); + __v4l2_ctrl_s_ctrl_int64(imx258->pixel_rate, pixel_rate); + /* Update limits and set FPS to default */ + vblank_def = imx258->cur_mode->vts_def - + imx258->cur_mode->height; + vblank_min = imx258->cur_mode->vts_min - + imx258->cur_mode->height; + __v4l2_ctrl_modify_range( + imx258->vblank, vblank_min, + IMX258_VTS_MAX - imx258->cur_mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(imx258->vblank, vblank_def); + h_blank = + link_freq_configs[mode->link_freq_index].pixels_per_line + - imx258->cur_mode->width; + __v4l2_ctrl_modify_range(imx258->hblank, h_blank, + h_blank, 1, h_blank); + } + + mutex_unlock(&imx258->mutex); + + return 0; +} + +/* Start streaming */ +static int imx258_start_streaming(struct imx258 *imx258) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx258->sd); + const struct imx258_reg_list *reg_list; + int ret, link_freq_index; + + /* Setup PLL */ + link_freq_index = imx258->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = imx258_write_regs(imx258, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set plls\n", __func__); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &imx258->cur_mode->reg_list; + ret = imx258_write_regs(imx258, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Set Orientation be 180 degree */ + ret = imx258_write_reg(imx258, REG_MIRROR_FLIP_CONTROL, + IMX258_REG_VALUE_08BIT, REG_CONFIG_MIRROR_FLIP); + if (ret) { + dev_err(&client->dev, "%s failed to set orientation\n", + __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx258->sd.ctrl_handler); + if (ret) + return ret; + + /* set stream on register */ + return imx258_write_reg(imx258, IMX258_REG_MODE_SELECT, + IMX258_REG_VALUE_08BIT, + IMX258_MODE_STREAMING); +} + +/* Stop streaming */ +static int imx258_stop_streaming(struct imx258 *imx258) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx258->sd); + int ret; + + /* set stream off register */ + ret = imx258_write_reg(imx258, IMX258_REG_MODE_SELECT, + IMX258_REG_VALUE_08BIT, IMX258_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); + + /* + * Return success even if it was an error, as there is nothing the + * caller can do about it. + */ + return 0; +} + +static int imx258_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx258 *imx258 = to_imx258(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&imx258->mutex); + if (imx258->streaming == enable) { + mutex_unlock(&imx258->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx258_start_streaming(imx258); + if (ret) + goto err_rpm_put; + } else { + imx258_stop_streaming(imx258); + pm_runtime_put(&client->dev); + } + + imx258->streaming = enable; + mutex_unlock(&imx258->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&imx258->mutex); + + return ret; +} + +static int __maybe_unused imx258_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx258 *imx258 = to_imx258(sd); + + if (imx258->streaming) + imx258_stop_streaming(imx258); + + return 0; +} + +static int __maybe_unused imx258_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx258 *imx258 = to_imx258(sd); + int ret; + + if (imx258->streaming) { + ret = imx258_start_streaming(imx258); + if (ret) + goto error; + } + + return 0; + +error: + imx258_stop_streaming(imx258); + imx258->streaming = 0; + return ret; +} + +/* Verify chip ID */ +static int imx258_identify_module(struct imx258 *imx258) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx258->sd); + int ret; + u32 val; + + ret = imx258_read_reg(imx258, IMX258_REG_CHIP_ID, + IMX258_REG_VALUE_16BIT, &val); + if (ret) { + dev_err(&client->dev, "failed to read chip id %x\n", + IMX258_CHIP_ID); + return ret; + } + + if (val != IMX258_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + IMX258_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops imx258_video_ops = { + .s_stream = imx258_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx258_pad_ops = { + .enum_mbus_code = imx258_enum_mbus_code, + .get_fmt = imx258_get_pad_format, + .set_fmt = imx258_set_pad_format, + .enum_frame_size = imx258_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx258_subdev_ops = { + .video = &imx258_video_ops, + .pad = &imx258_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx258_internal_ops = { + .open = imx258_open, +}; + +/* Initialize control handlers */ +static int imx258_init_controls(struct imx258 *imx258) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx258->sd); + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 vblank_def; + s64 vblank_min; + s64 pixel_rate_min; + s64 pixel_rate_max; + int ret; + + ctrl_hdlr = &imx258->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + mutex_init(&imx258->mutex); + ctrl_hdlr->lock = &imx258->mutex; + imx258->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, + &imx258_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, + link_freq_menu_items); + + if (imx258->link_freq) + imx258->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate_max = link_freq_to_pixel_rate(link_freq_menu_items[0]); + pixel_rate_min = link_freq_to_pixel_rate(link_freq_menu_items[1]); + /* By default, PIXEL_RATE is read only */ + imx258->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx258_ctrl_ops, + V4L2_CID_PIXEL_RATE, + pixel_rate_min, pixel_rate_max, + 1, pixel_rate_max); + + + vblank_def = imx258->cur_mode->vts_def - imx258->cur_mode->height; + vblank_min = imx258->cur_mode->vts_min - imx258->cur_mode->height; + imx258->vblank = v4l2_ctrl_new_std( + ctrl_hdlr, &imx258_ctrl_ops, V4L2_CID_VBLANK, + vblank_min, + IMX258_VTS_MAX - imx258->cur_mode->height, 1, + vblank_def); + + if (imx258->vblank) + imx258->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx258->hblank = v4l2_ctrl_new_std( + ctrl_hdlr, &imx258_ctrl_ops, V4L2_CID_HBLANK, + IMX258_PPL_DEFAULT - imx258->cur_mode->width, + IMX258_PPL_DEFAULT - imx258->cur_mode->width, + 1, + IMX258_PPL_DEFAULT - imx258->cur_mode->width); + + if (imx258->hblank) + imx258->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx258->exposure = v4l2_ctrl_new_std( + ctrl_hdlr, &imx258_ctrl_ops, + V4L2_CID_EXPOSURE, IMX258_EXPOSURE_MIN, + IMX258_EXPOSURE_MAX, IMX258_EXPOSURE_STEP, + IMX258_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx258_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX258_ANA_GAIN_MIN, IMX258_ANA_GAIN_MAX, + IMX258_ANA_GAIN_STEP, IMX258_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx258_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX258_DGTL_GAIN_MIN, IMX258_DGTL_GAIN_MAX, + IMX258_DGTL_GAIN_STEP, + IMX258_DGTL_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx258_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx258_test_pattern_menu) - 1, + 0, 0, imx258_test_pattern_menu); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + imx258->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx258->mutex); + + return ret; +} + +static void imx258_free_controls(struct imx258 *imx258) +{ + v4l2_ctrl_handler_free(imx258->sd.ctrl_handler); + mutex_destroy(&imx258->mutex); +} + +static int imx258_probe(struct i2c_client *client) +{ + struct imx258 *imx258; + int ret; + u32 val = 0; + + device_property_read_u32(&client->dev, "clock-frequency", &val); + if (val != 19200000) + return -EINVAL; + + /* + * Check that the device is mounted upside down. The driver only + * supports a single pixel order right now. + */ + ret = device_property_read_u32(&client->dev, "rotation", &val); + if (ret || val != 180) + return -EINVAL; + + imx258 = devm_kzalloc(&client->dev, sizeof(*imx258), GFP_KERNEL); + if (!imx258) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx258->sd, client, &imx258_subdev_ops); + + /* Check module identity */ + ret = imx258_identify_module(imx258); + if (ret) + return ret; + + /* Set default mode to max resolution */ + imx258->cur_mode = &supported_modes[0]; + + ret = imx258_init_controls(imx258); + if (ret) + return ret; + + /* Initialize subdev */ + imx258->sd.internal_ops = &imx258_internal_ops; + imx258->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx258->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + imx258->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx258->sd.entity, 1, &imx258->pad); + if (ret) + goto error_handler_free; + + ret = v4l2_async_register_subdev_sensor_common(&imx258->sd); + if (ret < 0) + goto error_media_entity; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&imx258->sd.entity); + +error_handler_free: + imx258_free_controls(imx258); + + return ret; +} + +static int imx258_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx258 *imx258 = to_imx258(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx258_free_controls(imx258); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +static const struct dev_pm_ops imx258_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx258_suspend, imx258_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id imx258_acpi_ids[] = { + { "SONY258A" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(acpi, imx258_acpi_ids); +#endif + +static struct i2c_driver imx258_i2c_driver = { + .driver = { + .name = "imx258", + .pm = &imx258_pm_ops, + .acpi_match_table = ACPI_PTR(imx258_acpi_ids), + }, + .probe_new = imx258_probe, + .remove = imx258_remove, +}; + +module_i2c_driver(imx258_i2c_driver); + +MODULE_AUTHOR("Yeh, Andy <andy.yeh@intel.com>"); +MODULE_AUTHOR("Chiang, Alan"); +MODULE_AUTHOR("Chen, Jason <jasonx.z.chen@intel.com>"); +MODULE_DESCRIPTION("Sony IMX258 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx274.c b/drivers/media/i2c/imx274.c new file mode 100644 index 000000000..e6aa9f32b --- /dev/null +++ b/drivers/media/i2c/imx274.c @@ -0,0 +1,1997 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * imx274.c - IMX274 CMOS Image Sensor driver + * + * Copyright (C) 2017, Leopard Imaging, Inc. + * + * Leon Luo <leonl@leopardimaging.com> + * Edwin Zou <edwinz@leopardimaging.com> + * Luca Ceresoli <luca@lucaceresoli.net> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/v4l2-mediabus.h> +#include <linux/videodev2.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +/* + * See "SHR, SVR Setting" in datasheet + */ +#define IMX274_DEFAULT_FRAME_LENGTH (4550) +#define IMX274_MAX_FRAME_LENGTH (0x000fffff) + +/* + * See "Frame Rate Adjustment" in datasheet + */ +#define IMX274_PIXCLK_CONST1 (72000000) +#define IMX274_PIXCLK_CONST2 (1000000) + +/* + * The input gain is shifted by IMX274_GAIN_SHIFT to get + * decimal number. The real gain is + * (float)input_gain_value / (1 << IMX274_GAIN_SHIFT) + */ +#define IMX274_GAIN_SHIFT (8) +#define IMX274_GAIN_SHIFT_MASK ((1 << IMX274_GAIN_SHIFT) - 1) + +/* + * See "Analog Gain" and "Digital Gain" in datasheet + * min gain is 1X + * max gain is calculated based on IMX274_GAIN_REG_MAX + */ +#define IMX274_GAIN_REG_MAX (1957) +#define IMX274_MIN_GAIN (0x01 << IMX274_GAIN_SHIFT) +#define IMX274_MAX_ANALOG_GAIN ((2048 << IMX274_GAIN_SHIFT)\ + / (2048 - IMX274_GAIN_REG_MAX)) +#define IMX274_MAX_DIGITAL_GAIN (8) +#define IMX274_DEF_GAIN (20 << IMX274_GAIN_SHIFT) +#define IMX274_GAIN_CONST (2048) /* for gain formula */ + +/* + * 1 line time in us = (HMAX / 72), minimal is 4 lines + */ +#define IMX274_MIN_EXPOSURE_TIME (4 * 260 / 72) + +#define IMX274_DEFAULT_BINNING IMX274_BINNING_OFF +#define IMX274_MAX_WIDTH (3840) +#define IMX274_MAX_HEIGHT (2160) +#define IMX274_MAX_FRAME_RATE (120) +#define IMX274_MIN_FRAME_RATE (5) +#define IMX274_DEF_FRAME_RATE (60) + +/* + * register SHR is limited to (SVR value + 1) x VMAX value - 4 + */ +#define IMX274_SHR_LIMIT_CONST (4) + +/* + * Min and max sensor reset delay (microseconds) + */ +#define IMX274_RESET_DELAY1 (2000) +#define IMX274_RESET_DELAY2 (2200) + +/* + * shift and mask constants + */ +#define IMX274_SHIFT_8_BITS (8) +#define IMX274_SHIFT_16_BITS (16) +#define IMX274_MASK_LSB_2_BITS (0x03) +#define IMX274_MASK_LSB_3_BITS (0x07) +#define IMX274_MASK_LSB_4_BITS (0x0f) +#define IMX274_MASK_LSB_8_BITS (0x00ff) + +#define DRIVER_NAME "IMX274" + +/* + * IMX274 register definitions + */ +#define IMX274_SHR_REG_MSB 0x300D /* SHR */ +#define IMX274_SHR_REG_LSB 0x300C /* SHR */ +#define IMX274_SVR_REG_MSB 0x300F /* SVR */ +#define IMX274_SVR_REG_LSB 0x300E /* SVR */ +#define IMX274_HTRIM_EN_REG 0x3037 +#define IMX274_HTRIM_START_REG_LSB 0x3038 +#define IMX274_HTRIM_START_REG_MSB 0x3039 +#define IMX274_HTRIM_END_REG_LSB 0x303A +#define IMX274_HTRIM_END_REG_MSB 0x303B +#define IMX274_VWIDCUTEN_REG 0x30DD +#define IMX274_VWIDCUT_REG_LSB 0x30DE +#define IMX274_VWIDCUT_REG_MSB 0x30DF +#define IMX274_VWINPOS_REG_LSB 0x30E0 +#define IMX274_VWINPOS_REG_MSB 0x30E1 +#define IMX274_WRITE_VSIZE_REG_LSB 0x3130 +#define IMX274_WRITE_VSIZE_REG_MSB 0x3131 +#define IMX274_Y_OUT_SIZE_REG_LSB 0x3132 +#define IMX274_Y_OUT_SIZE_REG_MSB 0x3133 +#define IMX274_VMAX_REG_1 0x30FA /* VMAX, MSB */ +#define IMX274_VMAX_REG_2 0x30F9 /* VMAX */ +#define IMX274_VMAX_REG_3 0x30F8 /* VMAX, LSB */ +#define IMX274_HMAX_REG_MSB 0x30F7 /* HMAX */ +#define IMX274_HMAX_REG_LSB 0x30F6 /* HMAX */ +#define IMX274_ANALOG_GAIN_ADDR_LSB 0x300A /* ANALOG GAIN LSB */ +#define IMX274_ANALOG_GAIN_ADDR_MSB 0x300B /* ANALOG GAIN MSB */ +#define IMX274_DIGITAL_GAIN_REG 0x3012 /* Digital Gain */ +#define IMX274_VFLIP_REG 0x301A /* VERTICAL FLIP */ +#define IMX274_TEST_PATTERN_REG 0x303D /* TEST PATTERN */ +#define IMX274_STANDBY_REG 0x3000 /* STANDBY */ + +#define IMX274_TABLE_WAIT_MS 0 +#define IMX274_TABLE_END 1 + +/* + * imx274 I2C operation related structure + */ +struct reg_8 { + u16 addr; + u8 val; +}; + +static const struct regmap_config imx274_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +}; + +enum imx274_binning { + IMX274_BINNING_OFF, + IMX274_BINNING_2_1, + IMX274_BINNING_3_1, +}; + +/* + * Parameters for each imx274 readout mode. + * + * These are the values to configure the sensor in one of the + * implemented modes. + * + * @init_regs: registers to initialize the mode + * @bin_ratio: downscale factor (e.g. 3 for 3:1 binning) + * @min_frame_len: Minimum frame length for each mode (see "Frame Rate + * Adjustment (CSI-2)" in the datasheet) + * @min_SHR: Minimum SHR register value (see "Shutter Setting (CSI-2)" in the + * datasheet) + * @max_fps: Maximum frames per second + * @nocpiop: Number of clocks per internal offset period (see "Integration Time + * in Each Readout Drive Mode (CSI-2)" in the datasheet) + */ +struct imx274_mode { + const struct reg_8 *init_regs; + unsigned int bin_ratio; + int min_frame_len; + int min_SHR; + int max_fps; + int nocpiop; +}; + +/* + * imx274 test pattern related structure + */ +enum { + TEST_PATTERN_DISABLED = 0, + TEST_PATTERN_ALL_000H, + TEST_PATTERN_ALL_FFFH, + TEST_PATTERN_ALL_555H, + TEST_PATTERN_ALL_AAAH, + TEST_PATTERN_VSP_5AH, /* VERTICAL STRIPE PATTERN 555H/AAAH */ + TEST_PATTERN_VSP_A5H, /* VERTICAL STRIPE PATTERN AAAH/555H */ + TEST_PATTERN_VSP_05H, /* VERTICAL STRIPE PATTERN 000H/555H */ + TEST_PATTERN_VSP_50H, /* VERTICAL STRIPE PATTERN 555H/000H */ + TEST_PATTERN_VSP_0FH, /* VERTICAL STRIPE PATTERN 000H/FFFH */ + TEST_PATTERN_VSP_F0H, /* VERTICAL STRIPE PATTERN FFFH/000H */ + TEST_PATTERN_H_COLOR_BARS, + TEST_PATTERN_V_COLOR_BARS, +}; + +static const char * const tp_qmenu[] = { + "Disabled", + "All 000h Pattern", + "All FFFh Pattern", + "All 555h Pattern", + "All AAAh Pattern", + "Vertical Stripe (555h / AAAh)", + "Vertical Stripe (AAAh / 555h)", + "Vertical Stripe (000h / 555h)", + "Vertical Stripe (555h / 000h)", + "Vertical Stripe (000h / FFFh)", + "Vertical Stripe (FFFh / 000h)", + "Vertical Color Bars", + "Horizontal Color Bars", +}; + +/* + * All-pixel scan mode (10-bit) + * imx274 mode1(refer to datasheet) register configuration with + * 3840x2160 resolution, raw10 data and mipi four lane output + */ +static const struct reg_8 imx274_mode1_3840x2160_raw10[] = { + {0x3004, 0x01}, + {0x3005, 0x01}, + {0x3006, 0x00}, + {0x3007, 0xa2}, + + {0x3018, 0xA2}, /* output XVS, HVS */ + + {0x306B, 0x05}, + {0x30E2, 0x01}, + + {0x30EE, 0x01}, + {0x3342, 0x0A}, + {0x3343, 0x00}, + {0x3344, 0x16}, + {0x3345, 0x00}, + {0x33A6, 0x01}, + {0x3528, 0x0E}, + {0x3554, 0x1F}, + {0x3555, 0x01}, + {0x3556, 0x01}, + {0x3557, 0x01}, + {0x3558, 0x01}, + {0x3559, 0x00}, + {0x355A, 0x00}, + {0x35BA, 0x0E}, + {0x366A, 0x1B}, + {0x366B, 0x1A}, + {0x366C, 0x19}, + {0x366D, 0x17}, + {0x3A41, 0x08}, + + {IMX274_TABLE_END, 0x00} +}; + +/* + * Horizontal/vertical 2/2-line binning + * (Horizontal and vertical weightedbinning, 10-bit) + * imx274 mode3(refer to datasheet) register configuration with + * 1920x1080 resolution, raw10 data and mipi four lane output + */ +static const struct reg_8 imx274_mode3_1920x1080_raw10[] = { + {0x3004, 0x02}, + {0x3005, 0x21}, + {0x3006, 0x00}, + {0x3007, 0xb1}, + + {0x3018, 0xA2}, /* output XVS, HVS */ + + {0x306B, 0x05}, + {0x30E2, 0x02}, + + {0x30EE, 0x01}, + {0x3342, 0x0A}, + {0x3343, 0x00}, + {0x3344, 0x1A}, + {0x3345, 0x00}, + {0x33A6, 0x01}, + {0x3528, 0x0E}, + {0x3554, 0x00}, + {0x3555, 0x01}, + {0x3556, 0x01}, + {0x3557, 0x01}, + {0x3558, 0x01}, + {0x3559, 0x00}, + {0x355A, 0x00}, + {0x35BA, 0x0E}, + {0x366A, 0x1B}, + {0x366B, 0x1A}, + {0x366C, 0x19}, + {0x366D, 0x17}, + {0x3A41, 0x08}, + + {IMX274_TABLE_END, 0x00} +}; + +/* + * Vertical 2/3 subsampling binning horizontal 3 binning + * imx274 mode5(refer to datasheet) register configuration with + * 1280x720 resolution, raw10 data and mipi four lane output + */ +static const struct reg_8 imx274_mode5_1280x720_raw10[] = { + {0x3004, 0x03}, + {0x3005, 0x31}, + {0x3006, 0x00}, + {0x3007, 0xa9}, + + {0x3018, 0xA2}, /* output XVS, HVS */ + + {0x306B, 0x05}, + {0x30E2, 0x03}, + + {0x30EE, 0x01}, + {0x3342, 0x0A}, + {0x3343, 0x00}, + {0x3344, 0x1B}, + {0x3345, 0x00}, + {0x33A6, 0x01}, + {0x3528, 0x0E}, + {0x3554, 0x00}, + {0x3555, 0x01}, + {0x3556, 0x01}, + {0x3557, 0x01}, + {0x3558, 0x01}, + {0x3559, 0x00}, + {0x355A, 0x00}, + {0x35BA, 0x0E}, + {0x366A, 0x1B}, + {0x366B, 0x19}, + {0x366C, 0x17}, + {0x366D, 0x17}, + {0x3A41, 0x04}, + + {IMX274_TABLE_END, 0x00} +}; + +/* + * imx274 first step register configuration for + * starting stream + */ +static const struct reg_8 imx274_start_1[] = { + {IMX274_STANDBY_REG, 0x12}, + + /* PLRD: clock settings */ + {0x3120, 0xF0}, + {0x3121, 0x00}, + {0x3122, 0x02}, + {0x3129, 0x9C}, + {0x312A, 0x02}, + {0x312D, 0x02}, + + {0x310B, 0x00}, + + /* PLSTMG */ + {0x304C, 0x00}, /* PLSTMG01 */ + {0x304D, 0x03}, + {0x331C, 0x1A}, + {0x331D, 0x00}, + {0x3502, 0x02}, + {0x3529, 0x0E}, + {0x352A, 0x0E}, + {0x352B, 0x0E}, + {0x3538, 0x0E}, + {0x3539, 0x0E}, + {0x3553, 0x00}, + {0x357D, 0x05}, + {0x357F, 0x05}, + {0x3581, 0x04}, + {0x3583, 0x76}, + {0x3587, 0x01}, + {0x35BB, 0x0E}, + {0x35BC, 0x0E}, + {0x35BD, 0x0E}, + {0x35BE, 0x0E}, + {0x35BF, 0x0E}, + {0x366E, 0x00}, + {0x366F, 0x00}, + {0x3670, 0x00}, + {0x3671, 0x00}, + + /* PSMIPI */ + {0x3304, 0x32}, /* PSMIPI1 */ + {0x3305, 0x00}, + {0x3306, 0x32}, + {0x3307, 0x00}, + {0x3590, 0x32}, + {0x3591, 0x00}, + {0x3686, 0x32}, + {0x3687, 0x00}, + + {IMX274_TABLE_END, 0x00} +}; + +/* + * imx274 second step register configuration for + * starting stream + */ +static const struct reg_8 imx274_start_2[] = { + {IMX274_STANDBY_REG, 0x00}, + {0x303E, 0x02}, /* SYS_MODE = 2 */ + {IMX274_TABLE_END, 0x00} +}; + +/* + * imx274 third step register configuration for + * starting stream + */ +static const struct reg_8 imx274_start_3[] = { + {0x30F4, 0x00}, + {0x3018, 0xA2}, /* XHS VHS OUTPUT */ + {IMX274_TABLE_END, 0x00} +}; + +/* + * imx274 register configuration for stopping stream + */ +static const struct reg_8 imx274_stop[] = { + {IMX274_STANDBY_REG, 0x01}, + {IMX274_TABLE_END, 0x00} +}; + +/* + * imx274 disable test pattern register configuration + */ +static const struct reg_8 imx274_tp_disabled[] = { + {0x303C, 0x00}, + {0x377F, 0x00}, + {0x3781, 0x00}, + {0x370B, 0x00}, + {IMX274_TABLE_END, 0x00} +}; + +/* + * imx274 test pattern register configuration + * reg 0x303D defines the test pattern modes + */ +static const struct reg_8 imx274_tp_regs[] = { + {0x303C, 0x11}, + {0x370E, 0x01}, + {0x377F, 0x01}, + {0x3781, 0x01}, + {0x370B, 0x11}, + {IMX274_TABLE_END, 0x00} +}; + +/* nocpiop happens to be the same number for the implemented modes */ +static const struct imx274_mode imx274_modes[] = { + { + /* mode 1, 4K */ + .bin_ratio = 1, + .init_regs = imx274_mode1_3840x2160_raw10, + .min_frame_len = 4550, + .min_SHR = 12, + .max_fps = 60, + .nocpiop = 112, + }, + { + /* mode 3, 1080p */ + .bin_ratio = 2, + .init_regs = imx274_mode3_1920x1080_raw10, + .min_frame_len = 2310, + .min_SHR = 8, + .max_fps = 120, + .nocpiop = 112, + }, + { + /* mode 5, 720p */ + .bin_ratio = 3, + .init_regs = imx274_mode5_1280x720_raw10, + .min_frame_len = 2310, + .min_SHR = 8, + .max_fps = 120, + .nocpiop = 112, + }, +}; + +/* + * struct imx274_ctrls - imx274 ctrl structure + * @handler: V4L2 ctrl handler structure + * @exposure: Pointer to expsure ctrl structure + * @gain: Pointer to gain ctrl structure + * @vflip: Pointer to vflip ctrl structure + * @test_pattern: Pointer to test pattern ctrl structure + */ +struct imx274_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *test_pattern; +}; + +/* + * struct stim274 - imx274 device structure + * @sd: V4L2 subdevice structure + * @pad: Media pad structure + * @client: Pointer to I2C client + * @ctrls: imx274 control structure + * @crop: rect to be captured + * @compose: compose rect, i.e. output resolution + * @format: V4L2 media bus frame format structure + * (width and height are in sync with the compose rect) + * @frame_rate: V4L2 frame rate structure + * @regmap: Pointer to regmap structure + * @reset_gpio: Pointer to reset gpio + * @lock: Mutex structure + * @mode: Parameters for the selected readout mode + */ +struct stimx274 { + struct v4l2_subdev sd; + struct media_pad pad; + struct i2c_client *client; + struct imx274_ctrls ctrls; + struct v4l2_rect crop; + struct v4l2_mbus_framefmt format; + struct v4l2_fract frame_interval; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + struct mutex lock; /* mutex lock for operations */ + const struct imx274_mode *mode; +}; + +#define IMX274_ROUND(dim, step, flags) \ + ((flags) & V4L2_SEL_FLAG_GE \ + ? roundup((dim), (step)) \ + : ((flags) & V4L2_SEL_FLAG_LE \ + ? rounddown((dim), (step)) \ + : rounddown((dim) + (step) / 2, (step)))) + +/* + * Function declaration + */ +static int imx274_set_gain(struct stimx274 *priv, struct v4l2_ctrl *ctrl); +static int imx274_set_exposure(struct stimx274 *priv, int val); +static int imx274_set_vflip(struct stimx274 *priv, int val); +static int imx274_set_test_pattern(struct stimx274 *priv, int val); +static int imx274_set_frame_interval(struct stimx274 *priv, + struct v4l2_fract frame_interval); + +static inline void msleep_range(unsigned int delay_base) +{ + usleep_range(delay_base * 1000, delay_base * 1000 + 500); +} + +/* + * v4l2_ctrl and v4l2_subdev related operations + */ +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, + struct stimx274, ctrls.handler)->sd; +} + +static inline struct stimx274 *to_imx274(struct v4l2_subdev *sd) +{ + return container_of(sd, struct stimx274, sd); +} + +/* + * Writing a register table + * + * @priv: Pointer to device + * @table: Table containing register values (with optional delays) + * + * This is used to write register table into sensor's reg map. + * + * Return: 0 on success, errors otherwise + */ +static int imx274_write_table(struct stimx274 *priv, const struct reg_8 table[]) +{ + struct regmap *regmap = priv->regmap; + int err = 0; + const struct reg_8 *next; + u8 val; + + int range_start = -1; + int range_count = 0; + u8 range_vals[16]; + int max_range_vals = ARRAY_SIZE(range_vals); + + for (next = table;; next++) { + if ((next->addr != range_start + range_count) || + (next->addr == IMX274_TABLE_END) || + (next->addr == IMX274_TABLE_WAIT_MS) || + (range_count == max_range_vals)) { + if (range_count == 1) + err = regmap_write(regmap, + range_start, range_vals[0]); + else if (range_count > 1) + err = regmap_bulk_write(regmap, range_start, + &range_vals[0], + range_count); + else + err = 0; + + if (err) + return err; + + range_start = -1; + range_count = 0; + + /* Handle special address values */ + if (next->addr == IMX274_TABLE_END) + break; + + if (next->addr == IMX274_TABLE_WAIT_MS) { + msleep_range(next->val); + continue; + } + } + + val = next->val; + + if (range_start == -1) + range_start = next->addr; + + range_vals[range_count++] = val; + } + return 0; +} + +static inline int imx274_write_reg(struct stimx274 *priv, u16 addr, u8 val) +{ + int err; + + err = regmap_write(priv->regmap, addr, val); + if (err) + dev_err(&priv->client->dev, + "%s : i2c write failed, %x = %x\n", __func__, + addr, val); + else + dev_dbg(&priv->client->dev, + "%s : addr 0x%x, val=0x%x\n", __func__, + addr, val); + return err; +} + +/** + * Read a multibyte register. + * + * Uses a bulk read where possible. + * + * @priv: Pointer to device structure + * @addr: Address of the LSB register. Other registers must be + * consecutive, least-to-most significant. + * @val: Pointer to store the register value (cpu endianness) + * @nbytes: Number of bytes to read (range: [1..3]). + * Other bytes are zet to 0. + * + * Return: 0 on success, errors otherwise + */ +static int imx274_read_mbreg(struct stimx274 *priv, u16 addr, u32 *val, + size_t nbytes) +{ + __le32 val_le = 0; + int err; + + err = regmap_bulk_read(priv->regmap, addr, &val_le, nbytes); + if (err) { + dev_err(&priv->client->dev, + "%s : i2c bulk read failed, %x (%zu bytes)\n", + __func__, addr, nbytes); + } else { + *val = le32_to_cpu(val_le); + dev_dbg(&priv->client->dev, + "%s : addr 0x%x, val=0x%x (%zu bytes)\n", + __func__, addr, *val, nbytes); + } + + return err; +} + +/** + * Write a multibyte register. + * + * Uses a bulk write where possible. + * + * @priv: Pointer to device structure + * @addr: Address of the LSB register. Other registers must be + * consecutive, least-to-most significant. + * @val: Value to be written to the register (cpu endianness) + * @nbytes: Number of bytes to write (range: [1..3]) + */ +static int imx274_write_mbreg(struct stimx274 *priv, u16 addr, u32 val, + size_t nbytes) +{ + __le32 val_le = cpu_to_le32(val); + int err; + + err = regmap_bulk_write(priv->regmap, addr, &val_le, nbytes); + if (err) + dev_err(&priv->client->dev, + "%s : i2c bulk write failed, %x = %x (%zu bytes)\n", + __func__, addr, val, nbytes); + else + dev_dbg(&priv->client->dev, + "%s : addr 0x%x, val=0x%x (%zu bytes)\n", + __func__, addr, val, nbytes); + return err; +} + +/* + * Set mode registers to start stream. + * @priv: Pointer to device structure + * + * Return: 0 on success, errors otherwise + */ +static int imx274_mode_regs(struct stimx274 *priv) +{ + int err = 0; + + err = imx274_write_table(priv, imx274_start_1); + if (err) + return err; + + err = imx274_write_table(priv, priv->mode->init_regs); + + return err; +} + +/* + * imx274_start_stream - Function for starting stream per mode index + * @priv: Pointer to device structure + * + * Return: 0 on success, errors otherwise + */ +static int imx274_start_stream(struct stimx274 *priv) +{ + int err = 0; + + /* + * Refer to "Standby Cancel Sequence when using CSI-2" in + * imx274 datasheet, it should wait 10ms or more here. + * give it 1 extra ms for margin + */ + msleep_range(11); + err = imx274_write_table(priv, imx274_start_2); + if (err) + return err; + + /* + * Refer to "Standby Cancel Sequence when using CSI-2" in + * imx274 datasheet, it should wait 7ms or more here. + * give it 1 extra ms for margin + */ + msleep_range(8); + err = imx274_write_table(priv, imx274_start_3); + if (err) + return err; + + return 0; +} + +/* + * imx274_reset - Function called to reset the sensor + * @priv: Pointer to device structure + * @rst: Input value for determining the sensor's end state after reset + * + * Set the senor in reset and then + * if rst = 0, keep it in reset; + * if rst = 1, bring it out of reset. + * + */ +static void imx274_reset(struct stimx274 *priv, int rst) +{ + gpiod_set_value_cansleep(priv->reset_gpio, 0); + usleep_range(IMX274_RESET_DELAY1, IMX274_RESET_DELAY2); + gpiod_set_value_cansleep(priv->reset_gpio, !!rst); + usleep_range(IMX274_RESET_DELAY1, IMX274_RESET_DELAY2); +} + +/** + * imx274_s_ctrl - This is used to set the imx274 V4L2 controls + * @ctrl: V4L2 control to be set + * + * This function is used to set the V4L2 controls for the imx274 sensor. + * + * Return: 0 on success, errors otherwise + */ +static int imx274_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct stimx274 *imx274 = to_imx274(sd); + int ret = -EINVAL; + + dev_dbg(&imx274->client->dev, + "%s : s_ctrl: %s, value: %d\n", __func__, + ctrl->name, ctrl->val); + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + dev_dbg(&imx274->client->dev, + "%s : set V4L2_CID_EXPOSURE\n", __func__); + ret = imx274_set_exposure(imx274, ctrl->val); + break; + + case V4L2_CID_GAIN: + dev_dbg(&imx274->client->dev, + "%s : set V4L2_CID_GAIN\n", __func__); + ret = imx274_set_gain(imx274, ctrl); + break; + + case V4L2_CID_VFLIP: + dev_dbg(&imx274->client->dev, + "%s : set V4L2_CID_VFLIP\n", __func__); + ret = imx274_set_vflip(imx274, ctrl->val); + break; + + case V4L2_CID_TEST_PATTERN: + dev_dbg(&imx274->client->dev, + "%s : set V4L2_CID_TEST_PATTERN\n", __func__); + ret = imx274_set_test_pattern(imx274, ctrl->val); + break; + } + + return ret; +} + +static int imx274_binning_goodness(struct stimx274 *imx274, + int w, int ask_w, + int h, int ask_h, u32 flags) +{ + struct device *dev = &imx274->client->dev; + const int goodness = 100000; + int val = 0; + + if (flags & V4L2_SEL_FLAG_GE) { + if (w < ask_w) + val -= goodness; + if (h < ask_h) + val -= goodness; + } + + if (flags & V4L2_SEL_FLAG_LE) { + if (w > ask_w) + val -= goodness; + if (h > ask_h) + val -= goodness; + } + + val -= abs(w - ask_w); + val -= abs(h - ask_h); + + dev_dbg(dev, "%s: ask %dx%d, size %dx%d, goodness %d\n", + __func__, ask_w, ask_h, w, h, val); + + return val; +} + +/** + * Helper function to change binning and set both compose and format. + * + * We have two entry points to change binning: set_fmt and + * set_selection(COMPOSE). Both have to compute the new output size + * and set it in both the compose rect and the frame format size. We + * also need to do the same things after setting cropping to restore + * 1:1 binning. + * + * This function contains the common code for these three cases, it + * has many arguments in order to accommodate the needs of all of + * them. + * + * Must be called with imx274->lock locked. + * + * @imx274: The device object + * @cfg: The pad config we are editing for TRY requests + * @which: V4L2_SUBDEV_FORMAT_ACTIVE or V4L2_SUBDEV_FORMAT_TRY from the caller + * @width: Input-output parameter: set to the desired width before + * the call, contains the chosen value after returning successfully + * @height: Input-output parameter for height (see @width) + * @flags: Selection flags from struct v4l2_subdev_selection, or 0 if not + * available (when called from set_fmt) + */ +static int __imx274_change_compose(struct stimx274 *imx274, + struct v4l2_subdev_pad_config *cfg, + u32 which, + u32 *width, + u32 *height, + u32 flags) +{ + struct device *dev = &imx274->client->dev; + const struct v4l2_rect *cur_crop; + struct v4l2_mbus_framefmt *tgt_fmt; + unsigned int i; + const struct imx274_mode *best_mode = &imx274_modes[0]; + int best_goodness = INT_MIN; + + if (which == V4L2_SUBDEV_FORMAT_TRY) { + cur_crop = &cfg->try_crop; + tgt_fmt = &cfg->try_fmt; + } else { + cur_crop = &imx274->crop; + tgt_fmt = &imx274->format; + } + + for (i = 0; i < ARRAY_SIZE(imx274_modes); i++) { + unsigned int ratio = imx274_modes[i].bin_ratio; + + int goodness = imx274_binning_goodness( + imx274, + cur_crop->width / ratio, *width, + cur_crop->height / ratio, *height, + flags); + + if (goodness >= best_goodness) { + best_goodness = goodness; + best_mode = &imx274_modes[i]; + } + } + + *width = cur_crop->width / best_mode->bin_ratio; + *height = cur_crop->height / best_mode->bin_ratio; + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) + imx274->mode = best_mode; + + dev_dbg(dev, "%s: selected %u:1 binning\n", + __func__, best_mode->bin_ratio); + + tgt_fmt->width = *width; + tgt_fmt->height = *height; + tgt_fmt->field = V4L2_FIELD_NONE; + + return 0; +} + +/** + * imx274_get_fmt - Get the pad format + * @sd: Pointer to V4L2 Sub device structure + * @cfg: Pointer to sub device pad information structure + * @fmt: Pointer to pad level media bus format + * + * This function is used to get the pad format information. + * + * Return: 0 on success + */ +static int imx274_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct stimx274 *imx274 = to_imx274(sd); + + mutex_lock(&imx274->lock); + fmt->format = imx274->format; + mutex_unlock(&imx274->lock); + return 0; +} + +/** + * imx274_set_fmt - This is used to set the pad format + * @sd: Pointer to V4L2 Sub device structure + * @cfg: Pointer to sub device pad information structure + * @format: Pointer to pad level media bus format + * + * This function is used to set the pad format. + * + * Return: 0 on success + */ +static int imx274_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + struct stimx274 *imx274 = to_imx274(sd); + int err = 0; + + mutex_lock(&imx274->lock); + + err = __imx274_change_compose(imx274, cfg, format->which, + &fmt->width, &fmt->height, 0); + + if (err) + goto out; + + /* + * __imx274_change_compose already set width and height in the + * applicable format, but we need to keep all other format + * values, so do a full copy here + */ + fmt->field = V4L2_FIELD_NONE; + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + cfg->try_fmt = *fmt; + else + imx274->format = *fmt; + +out: + mutex_unlock(&imx274->lock); + + return err; +} + +static int imx274_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct stimx274 *imx274 = to_imx274(sd); + const struct v4l2_rect *src_crop; + const struct v4l2_mbus_framefmt *src_fmt; + int ret = 0; + + if (sel->pad != 0) + return -EINVAL; + + if (sel->target == V4L2_SEL_TGT_CROP_BOUNDS) { + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX274_MAX_WIDTH; + sel->r.height = IMX274_MAX_HEIGHT; + return 0; + } + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + src_crop = &cfg->try_crop; + src_fmt = &cfg->try_fmt; + } else { + src_crop = &imx274->crop; + src_fmt = &imx274->format; + } + + mutex_lock(&imx274->lock); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *src_crop; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = src_crop->width; + sel->r.height = src_crop->height; + break; + case V4L2_SEL_TGT_COMPOSE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = src_fmt->width; + sel->r.height = src_fmt->height; + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&imx274->lock); + + return ret; +} + +static int imx274_set_selection_crop(struct stimx274 *imx274, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_rect *tgt_crop; + struct v4l2_rect new_crop; + bool size_changed; + + /* + * h_step could be 12 or 24 depending on the binning. But we + * won't know the binning until we choose the mode later in + * __imx274_change_compose(). Thus let's be safe and use the + * most conservative value in all cases. + */ + const u32 h_step = 24; + + new_crop.width = min_t(u32, + IMX274_ROUND(sel->r.width, h_step, sel->flags), + IMX274_MAX_WIDTH); + + /* Constraint: HTRIMMING_END - HTRIMMING_START >= 144 */ + if (new_crop.width < 144) + new_crop.width = 144; + + new_crop.left = min_t(u32, + IMX274_ROUND(sel->r.left, h_step, 0), + IMX274_MAX_WIDTH - new_crop.width); + + new_crop.height = min_t(u32, + IMX274_ROUND(sel->r.height, 2, sel->flags), + IMX274_MAX_HEIGHT); + + new_crop.top = min_t(u32, IMX274_ROUND(sel->r.top, 2, 0), + IMX274_MAX_HEIGHT - new_crop.height); + + sel->r = new_crop; + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) + tgt_crop = &cfg->try_crop; + else + tgt_crop = &imx274->crop; + + mutex_lock(&imx274->lock); + + size_changed = (new_crop.width != tgt_crop->width || + new_crop.height != tgt_crop->height); + + /* __imx274_change_compose needs the new size in *tgt_crop */ + *tgt_crop = new_crop; + + /* if crop size changed then reset the output image size */ + if (size_changed) + __imx274_change_compose(imx274, cfg, sel->which, + &new_crop.width, &new_crop.height, + sel->flags); + + mutex_unlock(&imx274->lock); + + return 0; +} + +static int imx274_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct stimx274 *imx274 = to_imx274(sd); + + if (sel->pad != 0) + return -EINVAL; + + if (sel->target == V4L2_SEL_TGT_CROP) + return imx274_set_selection_crop(imx274, cfg, sel); + + if (sel->target == V4L2_SEL_TGT_COMPOSE) { + int err; + + mutex_lock(&imx274->lock); + err = __imx274_change_compose(imx274, cfg, sel->which, + &sel->r.width, &sel->r.height, + sel->flags); + mutex_unlock(&imx274->lock); + + /* + * __imx274_change_compose already set width and + * height in set->r, we still need to set top-left + */ + if (!err) { + sel->r.top = 0; + sel->r.left = 0; + } + + return err; + } + + return -EINVAL; +} + +static int imx274_apply_trimming(struct stimx274 *imx274) +{ + u32 h_start; + u32 h_end; + u32 hmax; + u32 v_cut; + s32 v_pos; + u32 write_v_size; + u32 y_out_size; + int err; + + h_start = imx274->crop.left + 12; + h_end = h_start + imx274->crop.width; + + /* Use the minimum allowed value of HMAX */ + /* Note: except in mode 1, (width / 16 + 23) is always < hmax_min */ + /* Note: 260 is the minimum HMAX in all implemented modes */ + hmax = max_t(u32, 260, (imx274->crop.width) / 16 + 23); + + /* invert v_pos if VFLIP */ + v_pos = imx274->ctrls.vflip->cur.val ? + (-imx274->crop.top / 2) : (imx274->crop.top / 2); + v_cut = (IMX274_MAX_HEIGHT - imx274->crop.height) / 2; + write_v_size = imx274->crop.height + 22; + y_out_size = imx274->crop.height + 14; + + err = imx274_write_mbreg(imx274, IMX274_HMAX_REG_LSB, hmax, 2); + if (!err) + err = imx274_write_mbreg(imx274, IMX274_HTRIM_EN_REG, 1, 1); + if (!err) + err = imx274_write_mbreg(imx274, IMX274_HTRIM_START_REG_LSB, + h_start, 2); + if (!err) + err = imx274_write_mbreg(imx274, IMX274_HTRIM_END_REG_LSB, + h_end, 2); + if (!err) + err = imx274_write_mbreg(imx274, IMX274_VWIDCUTEN_REG, 1, 1); + if (!err) + err = imx274_write_mbreg(imx274, IMX274_VWIDCUT_REG_LSB, + v_cut, 2); + if (!err) + err = imx274_write_mbreg(imx274, IMX274_VWINPOS_REG_LSB, + v_pos, 2); + if (!err) + err = imx274_write_mbreg(imx274, IMX274_WRITE_VSIZE_REG_LSB, + write_v_size, 2); + if (!err) + err = imx274_write_mbreg(imx274, IMX274_Y_OUT_SIZE_REG_LSB, + y_out_size, 2); + + return err; +} + +/** + * imx274_g_frame_interval - Get the frame interval + * @sd: Pointer to V4L2 Sub device structure + * @fi: Pointer to V4l2 Sub device frame interval structure + * + * This function is used to get the frame interval. + * + * Return: 0 on success + */ +static int imx274_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct stimx274 *imx274 = to_imx274(sd); + + fi->interval = imx274->frame_interval; + dev_dbg(&imx274->client->dev, "%s frame rate = %d / %d\n", + __func__, imx274->frame_interval.numerator, + imx274->frame_interval.denominator); + + return 0; +} + +/** + * imx274_s_frame_interval - Set the frame interval + * @sd: Pointer to V4L2 Sub device structure + * @fi: Pointer to V4l2 Sub device frame interval structure + * + * This function is used to set the frame intervavl. + * + * Return: 0 on success + */ +static int imx274_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct stimx274 *imx274 = to_imx274(sd); + struct v4l2_ctrl *ctrl = imx274->ctrls.exposure; + int min, max, def; + int ret; + + mutex_lock(&imx274->lock); + ret = imx274_set_frame_interval(imx274, fi->interval); + + if (!ret) { + fi->interval = imx274->frame_interval; + + /* + * exposure time range is decided by frame interval + * need to update it after frame interval changes + */ + min = IMX274_MIN_EXPOSURE_TIME; + max = fi->interval.numerator * 1000000 + / fi->interval.denominator; + def = max; + if (__v4l2_ctrl_modify_range(ctrl, min, max, 1, def)) { + dev_err(&imx274->client->dev, + "Exposure ctrl range update failed\n"); + goto unlock; + } + + /* update exposure time accordingly */ + imx274_set_exposure(imx274, ctrl->val); + + dev_dbg(&imx274->client->dev, "set frame interval to %uus\n", + fi->interval.numerator * 1000000 + / fi->interval.denominator); + } + +unlock: + mutex_unlock(&imx274->lock); + + return ret; +} + +/** + * imx274_load_default - load default control values + * @priv: Pointer to device structure + * + * Return: 0 on success, errors otherwise + */ +static int imx274_load_default(struct stimx274 *priv) +{ + int ret; + + /* load default control values */ + priv->frame_interval.numerator = 1; + priv->frame_interval.denominator = IMX274_DEF_FRAME_RATE; + priv->ctrls.exposure->val = 1000000 / IMX274_DEF_FRAME_RATE; + priv->ctrls.gain->val = IMX274_DEF_GAIN; + priv->ctrls.vflip->val = 0; + priv->ctrls.test_pattern->val = TEST_PATTERN_DISABLED; + + /* update frame rate */ + ret = imx274_set_frame_interval(priv, + priv->frame_interval); + if (ret) + return ret; + + /* update exposure time */ + ret = v4l2_ctrl_s_ctrl(priv->ctrls.exposure, priv->ctrls.exposure->val); + if (ret) + return ret; + + /* update gain */ + ret = v4l2_ctrl_s_ctrl(priv->ctrls.gain, priv->ctrls.gain->val); + if (ret) + return ret; + + /* update vflip */ + ret = v4l2_ctrl_s_ctrl(priv->ctrls.vflip, priv->ctrls.vflip->val); + if (ret) + return ret; + + return 0; +} + +/** + * imx274_s_stream - It is used to start/stop the streaming. + * @sd: V4L2 Sub device + * @on: Flag (True / False) + * + * This function controls the start or stop of streaming for the + * imx274 sensor. + * + * Return: 0 on success, errors otherwise + */ +static int imx274_s_stream(struct v4l2_subdev *sd, int on) +{ + struct stimx274 *imx274 = to_imx274(sd); + int ret = 0; + + dev_dbg(&imx274->client->dev, "%s : %s, mode index = %td\n", __func__, + on ? "Stream Start" : "Stream Stop", + imx274->mode - &imx274_modes[0]); + + mutex_lock(&imx274->lock); + + if (on) { + /* load mode registers */ + ret = imx274_mode_regs(imx274); + if (ret) + goto fail; + + ret = imx274_apply_trimming(imx274); + if (ret) + goto fail; + + /* + * update frame rate & expsoure. if the last mode is different, + * HMAX could be changed. As the result, frame rate & exposure + * are changed. + * gain is not affected. + */ + ret = imx274_set_frame_interval(imx274, + imx274->frame_interval); + if (ret) + goto fail; + + /* update exposure time */ + ret = __v4l2_ctrl_s_ctrl(imx274->ctrls.exposure, + imx274->ctrls.exposure->val); + if (ret) + goto fail; + + /* start stream */ + ret = imx274_start_stream(imx274); + if (ret) + goto fail; + } else { + /* stop stream */ + ret = imx274_write_table(imx274, imx274_stop); + if (ret) + goto fail; + } + + mutex_unlock(&imx274->lock); + dev_dbg(&imx274->client->dev, "%s : Done\n", __func__); + return 0; + +fail: + mutex_unlock(&imx274->lock); + dev_err(&imx274->client->dev, "s_stream failed\n"); + return ret; +} + +/* + * imx274_get_frame_length - Function for obtaining current frame length + * @priv: Pointer to device structure + * @val: Pointer to obainted value + * + * frame_length = vmax x (svr + 1), in unit of hmax. + * + * Return: 0 on success + */ +static int imx274_get_frame_length(struct stimx274 *priv, u32 *val) +{ + int err; + u32 svr; + u32 vmax; + + err = imx274_read_mbreg(priv, IMX274_SVR_REG_LSB, &svr, 2); + if (err) + goto fail; + + err = imx274_read_mbreg(priv, IMX274_VMAX_REG_3, &vmax, 3); + if (err) + goto fail; + + *val = vmax * (svr + 1); + + return 0; + +fail: + dev_err(&priv->client->dev, "%s error = %d\n", __func__, err); + return err; +} + +static int imx274_clamp_coarse_time(struct stimx274 *priv, u32 *val, + u32 *frame_length) +{ + int err; + + err = imx274_get_frame_length(priv, frame_length); + if (err) + return err; + + if (*frame_length < priv->mode->min_frame_len) + *frame_length = priv->mode->min_frame_len; + + *val = *frame_length - *val; /* convert to raw shr */ + if (*val > *frame_length - IMX274_SHR_LIMIT_CONST) + *val = *frame_length - IMX274_SHR_LIMIT_CONST; + else if (*val < priv->mode->min_SHR) + *val = priv->mode->min_SHR; + + return 0; +} + +/* + * imx274_set_digital gain - Function called when setting digital gain + * @priv: Pointer to device structure + * @dgain: Value of digital gain. + * + * Digital gain has only 4 steps: 1x, 2x, 4x, and 8x + * + * Return: 0 on success + */ +static int imx274_set_digital_gain(struct stimx274 *priv, u32 dgain) +{ + u8 reg_val; + + reg_val = ffs(dgain); + + if (reg_val) + reg_val--; + + reg_val = clamp(reg_val, (u8)0, (u8)3); + + return imx274_write_reg(priv, IMX274_DIGITAL_GAIN_REG, + reg_val & IMX274_MASK_LSB_4_BITS); +} + +/* + * imx274_set_gain - Function called when setting gain + * @priv: Pointer to device structure + * @val: Value of gain. the real value = val << IMX274_GAIN_SHIFT; + * @ctrl: v4l2 control pointer + * + * Set the gain based on input value. + * The caller should hold the mutex lock imx274->lock if necessary + * + * Return: 0 on success + */ +static int imx274_set_gain(struct stimx274 *priv, struct v4l2_ctrl *ctrl) +{ + int err; + u32 gain, analog_gain, digital_gain, gain_reg; + + gain = (u32)(ctrl->val); + + dev_dbg(&priv->client->dev, + "%s : input gain = %d.%d\n", __func__, + gain >> IMX274_GAIN_SHIFT, + ((gain & IMX274_GAIN_SHIFT_MASK) * 100) >> IMX274_GAIN_SHIFT); + + if (gain > IMX274_MAX_DIGITAL_GAIN * IMX274_MAX_ANALOG_GAIN) + gain = IMX274_MAX_DIGITAL_GAIN * IMX274_MAX_ANALOG_GAIN; + else if (gain < IMX274_MIN_GAIN) + gain = IMX274_MIN_GAIN; + + if (gain <= IMX274_MAX_ANALOG_GAIN) + digital_gain = 1; + else if (gain <= IMX274_MAX_ANALOG_GAIN * 2) + digital_gain = 2; + else if (gain <= IMX274_MAX_ANALOG_GAIN * 4) + digital_gain = 4; + else + digital_gain = IMX274_MAX_DIGITAL_GAIN; + + analog_gain = gain / digital_gain; + + dev_dbg(&priv->client->dev, + "%s : digital gain = %d, analog gain = %d.%d\n", + __func__, digital_gain, analog_gain >> IMX274_GAIN_SHIFT, + ((analog_gain & IMX274_GAIN_SHIFT_MASK) * 100) + >> IMX274_GAIN_SHIFT); + + err = imx274_set_digital_gain(priv, digital_gain); + if (err) + goto fail; + + /* convert to register value, refer to imx274 datasheet */ + gain_reg = (u32)IMX274_GAIN_CONST - + (IMX274_GAIN_CONST << IMX274_GAIN_SHIFT) / analog_gain; + if (gain_reg > IMX274_GAIN_REG_MAX) + gain_reg = IMX274_GAIN_REG_MAX; + + err = imx274_write_mbreg(priv, IMX274_ANALOG_GAIN_ADDR_LSB, gain_reg, + 2); + if (err) + goto fail; + + if (IMX274_GAIN_CONST - gain_reg == 0) { + err = -EINVAL; + goto fail; + } + + /* convert register value back to gain value */ + ctrl->val = (IMX274_GAIN_CONST << IMX274_GAIN_SHIFT) + / (IMX274_GAIN_CONST - gain_reg) * digital_gain; + + dev_dbg(&priv->client->dev, + "%s : GAIN control success, gain_reg = %d, new gain = %d\n", + __func__, gain_reg, ctrl->val); + + return 0; + +fail: + dev_err(&priv->client->dev, "%s error = %d\n", __func__, err); + return err; +} + +/* + * imx274_set_coarse_time - Function called when setting SHR value + * @priv: Pointer to device structure + * @val: Value for exposure time in number of line_length, or [HMAX] + * + * Set SHR value based on input value. + * + * Return: 0 on success + */ +static int imx274_set_coarse_time(struct stimx274 *priv, u32 *val) +{ + int err; + u32 coarse_time, frame_length; + + coarse_time = *val; + + /* convert exposure_time to appropriate SHR value */ + err = imx274_clamp_coarse_time(priv, &coarse_time, &frame_length); + if (err) + goto fail; + + err = imx274_write_mbreg(priv, IMX274_SHR_REG_LSB, coarse_time, 2); + if (err) + goto fail; + + *val = frame_length - coarse_time; + return 0; + +fail: + dev_err(&priv->client->dev, "%s error = %d\n", __func__, err); + return err; +} + +/* + * imx274_set_exposure - Function called when setting exposure time + * @priv: Pointer to device structure + * @val: Variable for exposure time, in the unit of micro-second + * + * Set exposure time based on input value. + * The caller should hold the mutex lock imx274->lock if necessary + * + * Return: 0 on success + */ +static int imx274_set_exposure(struct stimx274 *priv, int val) +{ + int err; + u32 hmax; + u32 coarse_time; /* exposure time in unit of line (HMAX)*/ + + dev_dbg(&priv->client->dev, + "%s : EXPOSURE control input = %d\n", __func__, val); + + /* step 1: convert input exposure_time (val) into number of 1[HMAX] */ + + err = imx274_read_mbreg(priv, IMX274_HMAX_REG_LSB, &hmax, 2); + if (err) + goto fail; + + if (hmax == 0) { + err = -EINVAL; + goto fail; + } + + coarse_time = (IMX274_PIXCLK_CONST1 / IMX274_PIXCLK_CONST2 * val + - priv->mode->nocpiop) / hmax; + + /* step 2: convert exposure_time into SHR value */ + + /* set SHR */ + err = imx274_set_coarse_time(priv, &coarse_time); + if (err) + goto fail; + + priv->ctrls.exposure->val = + (coarse_time * hmax + priv->mode->nocpiop) + / (IMX274_PIXCLK_CONST1 / IMX274_PIXCLK_CONST2); + + dev_dbg(&priv->client->dev, + "%s : EXPOSURE control success\n", __func__); + return 0; + +fail: + dev_err(&priv->client->dev, "%s error = %d\n", __func__, err); + + return err; +} + +/* + * imx274_set_vflip - Function called when setting vertical flip + * @priv: Pointer to device structure + * @val: Value for vflip setting + * + * Set vertical flip based on input value. + * val = 0: normal, no vertical flip + * val = 1: vertical flip enabled + * The caller should hold the mutex lock imx274->lock if necessary + * + * Return: 0 on success + */ +static int imx274_set_vflip(struct stimx274 *priv, int val) +{ + int err; + + err = imx274_write_reg(priv, IMX274_VFLIP_REG, val); + if (err) { + dev_err(&priv->client->dev, "VFLIP control error\n"); + return err; + } + + dev_dbg(&priv->client->dev, + "%s : VFLIP control success\n", __func__); + + return 0; +} + +/* + * imx274_set_test_pattern - Function called when setting test pattern + * @priv: Pointer to device structure + * @val: Variable for test pattern + * + * Set to different test patterns based on input value. + * + * Return: 0 on success + */ +static int imx274_set_test_pattern(struct stimx274 *priv, int val) +{ + int err = 0; + + if (val == TEST_PATTERN_DISABLED) { + err = imx274_write_table(priv, imx274_tp_disabled); + } else if (val <= TEST_PATTERN_V_COLOR_BARS) { + err = imx274_write_reg(priv, IMX274_TEST_PATTERN_REG, val - 1); + if (!err) + err = imx274_write_table(priv, imx274_tp_regs); + } else { + err = -EINVAL; + } + + if (!err) + dev_dbg(&priv->client->dev, + "%s : TEST PATTERN control success\n", __func__); + else + dev_err(&priv->client->dev, "%s error = %d\n", __func__, err); + + return err; +} + +/* + * imx274_set_frame_length - Function called when setting frame length + * @priv: Pointer to device structure + * @val: Variable for frame length (= VMAX, i.e. vertical drive period length) + * + * Set frame length based on input value. + * + * Return: 0 on success + */ +static int imx274_set_frame_length(struct stimx274 *priv, u32 val) +{ + int err; + u32 frame_length; + + dev_dbg(&priv->client->dev, "%s : input length = %d\n", + __func__, val); + + frame_length = (u32)val; + + err = imx274_write_mbreg(priv, IMX274_VMAX_REG_3, frame_length, 3); + if (err) + goto fail; + + return 0; + +fail: + dev_err(&priv->client->dev, "%s error = %d\n", __func__, err); + return err; +} + +/* + * imx274_set_frame_interval - Function called when setting frame interval + * @priv: Pointer to device structure + * @frame_interval: Variable for frame interval + * + * Change frame interval by updating VMAX value + * The caller should hold the mutex lock imx274->lock if necessary + * + * Return: 0 on success + */ +static int imx274_set_frame_interval(struct stimx274 *priv, + struct v4l2_fract frame_interval) +{ + int err; + u32 frame_length, req_frame_rate; + u32 svr; + u32 hmax; + + dev_dbg(&priv->client->dev, "%s: input frame interval = %d / %d", + __func__, frame_interval.numerator, + frame_interval.denominator); + + if (frame_interval.numerator == 0 || frame_interval.denominator == 0) { + frame_interval.denominator = IMX274_DEF_FRAME_RATE; + frame_interval.numerator = 1; + } + + req_frame_rate = (u32)(frame_interval.denominator + / frame_interval.numerator); + + /* boundary check */ + if (req_frame_rate > priv->mode->max_fps) { + frame_interval.numerator = 1; + frame_interval.denominator = priv->mode->max_fps; + } else if (req_frame_rate < IMX274_MIN_FRAME_RATE) { + frame_interval.numerator = 1; + frame_interval.denominator = IMX274_MIN_FRAME_RATE; + } + + /* + * VMAX = 1/frame_rate x 72M / (SVR+1) / HMAX + * frame_length (i.e. VMAX) = (frame_interval) x 72M /(SVR+1) / HMAX + */ + + err = imx274_read_mbreg(priv, IMX274_SVR_REG_LSB, &svr, 2); + if (err) + goto fail; + + dev_dbg(&priv->client->dev, + "%s : register SVR = %d\n", __func__, svr); + + err = imx274_read_mbreg(priv, IMX274_HMAX_REG_LSB, &hmax, 2); + if (err) + goto fail; + + dev_dbg(&priv->client->dev, + "%s : register HMAX = %d\n", __func__, hmax); + + if (hmax == 0 || frame_interval.denominator == 0) { + err = -EINVAL; + goto fail; + } + + frame_length = IMX274_PIXCLK_CONST1 / (svr + 1) / hmax + * frame_interval.numerator + / frame_interval.denominator; + + err = imx274_set_frame_length(priv, frame_length); + if (err) + goto fail; + + priv->frame_interval = frame_interval; + return 0; + +fail: + dev_err(&priv->client->dev, "%s error = %d\n", __func__, err); + return err; +} + +static const struct v4l2_subdev_pad_ops imx274_pad_ops = { + .get_fmt = imx274_get_fmt, + .set_fmt = imx274_set_fmt, + .get_selection = imx274_get_selection, + .set_selection = imx274_set_selection, +}; + +static const struct v4l2_subdev_video_ops imx274_video_ops = { + .g_frame_interval = imx274_g_frame_interval, + .s_frame_interval = imx274_s_frame_interval, + .s_stream = imx274_s_stream, +}; + +static const struct v4l2_subdev_ops imx274_subdev_ops = { + .pad = &imx274_pad_ops, + .video = &imx274_video_ops, +}; + +static const struct v4l2_ctrl_ops imx274_ctrl_ops = { + .s_ctrl = imx274_s_ctrl, +}; + +static const struct of_device_id imx274_of_id_table[] = { + { .compatible = "sony,imx274" }, + { } +}; +MODULE_DEVICE_TABLE(of, imx274_of_id_table); + +static const struct i2c_device_id imx274_id[] = { + { "IMX274", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, imx274_id); + +static int imx274_probe(struct i2c_client *client) +{ + struct v4l2_subdev *sd; + struct stimx274 *imx274; + int ret; + + /* initialize imx274 */ + imx274 = devm_kzalloc(&client->dev, sizeof(*imx274), GFP_KERNEL); + if (!imx274) + return -ENOMEM; + + mutex_init(&imx274->lock); + + /* initialize format */ + imx274->mode = &imx274_modes[IMX274_DEFAULT_BINNING]; + imx274->crop.width = IMX274_MAX_WIDTH; + imx274->crop.height = IMX274_MAX_HEIGHT; + imx274->format.width = imx274->crop.width / imx274->mode->bin_ratio; + imx274->format.height = imx274->crop.height / imx274->mode->bin_ratio; + imx274->format.field = V4L2_FIELD_NONE; + imx274->format.code = MEDIA_BUS_FMT_SRGGB10_1X10; + imx274->format.colorspace = V4L2_COLORSPACE_SRGB; + imx274->frame_interval.numerator = 1; + imx274->frame_interval.denominator = IMX274_DEF_FRAME_RATE; + + /* initialize regmap */ + imx274->regmap = devm_regmap_init_i2c(client, &imx274_regmap_config); + if (IS_ERR(imx274->regmap)) { + dev_err(&client->dev, + "regmap init failed: %ld\n", PTR_ERR(imx274->regmap)); + ret = -ENODEV; + goto err_regmap; + } + + /* initialize subdevice */ + imx274->client = client; + sd = &imx274->sd; + v4l2_i2c_subdev_init(sd, client, &imx274_subdev_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + + /* initialize subdev media pad */ + imx274->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &imx274->pad); + if (ret < 0) { + dev_err(&client->dev, + "%s : media entity init Failed %d\n", __func__, ret); + goto err_regmap; + } + + /* initialize sensor reset gpio */ + imx274->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(imx274->reset_gpio)) { + if (PTR_ERR(imx274->reset_gpio) != -EPROBE_DEFER) + dev_err(&client->dev, "Reset GPIO not setup in DT"); + ret = PTR_ERR(imx274->reset_gpio); + goto err_me; + } + + /* pull sensor out of reset */ + imx274_reset(imx274, 1); + + /* initialize controls */ + ret = v4l2_ctrl_handler_init(&imx274->ctrls.handler, 4); + if (ret < 0) { + dev_err(&client->dev, + "%s : ctrl handler init Failed\n", __func__); + goto err_me; + } + + imx274->ctrls.handler.lock = &imx274->lock; + + /* add new controls */ + imx274->ctrls.test_pattern = v4l2_ctrl_new_std_menu_items( + &imx274->ctrls.handler, &imx274_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(tp_qmenu) - 1, 0, 0, tp_qmenu); + + imx274->ctrls.gain = v4l2_ctrl_new_std( + &imx274->ctrls.handler, + &imx274_ctrl_ops, + V4L2_CID_GAIN, IMX274_MIN_GAIN, + IMX274_MAX_DIGITAL_GAIN * IMX274_MAX_ANALOG_GAIN, 1, + IMX274_DEF_GAIN); + + imx274->ctrls.exposure = v4l2_ctrl_new_std( + &imx274->ctrls.handler, + &imx274_ctrl_ops, + V4L2_CID_EXPOSURE, IMX274_MIN_EXPOSURE_TIME, + 1000000 / IMX274_DEF_FRAME_RATE, 1, + IMX274_MIN_EXPOSURE_TIME); + + imx274->ctrls.vflip = v4l2_ctrl_new_std( + &imx274->ctrls.handler, + &imx274_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + imx274->sd.ctrl_handler = &imx274->ctrls.handler; + if (imx274->ctrls.handler.error) { + ret = imx274->ctrls.handler.error; + goto err_ctrls; + } + + /* setup default controls */ + ret = v4l2_ctrl_handler_setup(&imx274->ctrls.handler); + if (ret) { + dev_err(&client->dev, + "Error %d setup default controls\n", ret); + goto err_ctrls; + } + + /* load default control values */ + ret = imx274_load_default(imx274); + if (ret) { + dev_err(&client->dev, + "%s : imx274_load_default failed %d\n", + __func__, ret); + goto err_ctrls; + } + + /* register subdevice */ + ret = v4l2_async_register_subdev(sd); + if (ret < 0) { + dev_err(&client->dev, + "%s : v4l2_async_register_subdev failed %d\n", + __func__, ret); + goto err_ctrls; + } + + dev_info(&client->dev, "imx274 : imx274 probe success !\n"); + return 0; + +err_ctrls: + v4l2_ctrl_handler_free(&imx274->ctrls.handler); +err_me: + media_entity_cleanup(&sd->entity); +err_regmap: + mutex_destroy(&imx274->lock); + return ret; +} + +static int imx274_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct stimx274 *imx274 = to_imx274(sd); + + /* stop stream */ + imx274_write_table(imx274, imx274_stop); + + v4l2_async_unregister_subdev(sd); + v4l2_ctrl_handler_free(&imx274->ctrls.handler); + media_entity_cleanup(&sd->entity); + mutex_destroy(&imx274->lock); + return 0; +} + +static struct i2c_driver imx274_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = imx274_of_id_table, + }, + .probe_new = imx274_probe, + .remove = imx274_remove, + .id_table = imx274_id, +}; + +module_i2c_driver(imx274_i2c_driver); + +MODULE_AUTHOR("Leon Luo <leonl@leopardimaging.com>"); +MODULE_DESCRIPTION("IMX274 CMOS Image Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c new file mode 100644 index 000000000..adcddf320 --- /dev/null +++ b/drivers/media/i2c/imx290.c @@ -0,0 +1,1166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sony IMX290 CMOS Image Sensor Driver + * + * Copyright (C) 2019 FRAMOS GmbH. + * + * Copyright (C) 2019 Linaro Ltd. + * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define IMX290_STANDBY 0x3000 +#define IMX290_REGHOLD 0x3001 +#define IMX290_XMSTA 0x3002 +#define IMX290_FR_FDG_SEL 0x3009 +#define IMX290_BLKLEVEL_LOW 0x300a +#define IMX290_BLKLEVEL_HIGH 0x300b +#define IMX290_GAIN 0x3014 +#define IMX290_HMAX_LOW 0x301c +#define IMX290_HMAX_HIGH 0x301d +#define IMX290_PGCTRL 0x308c +#define IMX290_PHY_LANE_NUM 0x3407 +#define IMX290_CSI_LANE_MODE 0x3443 + +#define IMX290_PGCTRL_REGEN BIT(0) +#define IMX290_PGCTRL_THRU BIT(1) +#define IMX290_PGCTRL_MODE(n) ((n) << 4) + +static const char * const imx290_supply_name[] = { + "vdda", + "vddd", + "vdddo", +}; + +#define IMX290_NUM_SUPPLIES ARRAY_SIZE(imx290_supply_name) + +struct imx290_regval { + u16 reg; + u8 val; +}; + +struct imx290_mode { + u32 width; + u32 height; + u32 hmax; + u8 link_freq_index; + + const struct imx290_regval *data; + u32 data_size; +}; + +struct imx290 { + struct device *dev; + struct clk *xclk; + struct regmap *regmap; + u8 nlanes; + u8 bpp; + + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_mbus_framefmt current_format; + const struct imx290_mode *current_mode; + + struct regulator_bulk_data supplies[IMX290_NUM_SUPPLIES]; + struct gpio_desc *rst_gpio; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + + struct mutex lock; +}; + +struct imx290_pixfmt { + u32 code; + u8 bpp; +}; + +static const struct imx290_pixfmt imx290_formats[] = { + { MEDIA_BUS_FMT_SRGGB10_1X10, 10 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12 }, +}; + +static const struct regmap_config imx290_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +}; + +static const char * const imx290_test_pattern_menu[] = { + "Disabled", + "Sequence Pattern 1", + "Horizontal Color-bar Chart", + "Vertical Color-bar Chart", + "Sequence Pattern 2", + "Gradation Pattern 1", + "Gradation Pattern 2", + "000/555h Toggle Pattern", +}; + +static const struct imx290_regval imx290_global_init_settings[] = { + { 0x3007, 0x00 }, + { 0x3018, 0x65 }, + { 0x3019, 0x04 }, + { 0x301a, 0x00 }, + { 0x3444, 0x20 }, + { 0x3445, 0x25 }, + { 0x303a, 0x0c }, + { 0x3040, 0x00 }, + { 0x3041, 0x00 }, + { 0x303c, 0x00 }, + { 0x303d, 0x00 }, + { 0x3042, 0x9c }, + { 0x3043, 0x07 }, + { 0x303e, 0x49 }, + { 0x303f, 0x04 }, + { 0x304b, 0x0a }, + { 0x300f, 0x00 }, + { 0x3010, 0x21 }, + { 0x3012, 0x64 }, + { 0x3016, 0x09 }, + { 0x3070, 0x02 }, + { 0x3071, 0x11 }, + { 0x309b, 0x10 }, + { 0x309c, 0x22 }, + { 0x30a2, 0x02 }, + { 0x30a6, 0x20 }, + { 0x30a8, 0x20 }, + { 0x30aa, 0x20 }, + { 0x30ac, 0x20 }, + { 0x30b0, 0x43 }, + { 0x3119, 0x9e }, + { 0x311c, 0x1e }, + { 0x311e, 0x08 }, + { 0x3128, 0x05 }, + { 0x313d, 0x83 }, + { 0x3150, 0x03 }, + { 0x317e, 0x00 }, + { 0x32b8, 0x50 }, + { 0x32b9, 0x10 }, + { 0x32ba, 0x00 }, + { 0x32bb, 0x04 }, + { 0x32c8, 0x50 }, + { 0x32c9, 0x10 }, + { 0x32ca, 0x00 }, + { 0x32cb, 0x04 }, + { 0x332c, 0xd3 }, + { 0x332d, 0x10 }, + { 0x332e, 0x0d }, + { 0x3358, 0x06 }, + { 0x3359, 0xe1 }, + { 0x335a, 0x11 }, + { 0x3360, 0x1e }, + { 0x3361, 0x61 }, + { 0x3362, 0x10 }, + { 0x33b0, 0x50 }, + { 0x33b2, 0x1a }, + { 0x33b3, 0x04 }, +}; + +static const struct imx290_regval imx290_1080p_settings[] = { + /* mode settings */ + { 0x3007, 0x00 }, + { 0x303a, 0x0c }, + { 0x3414, 0x0a }, + { 0x3472, 0x80 }, + { 0x3473, 0x07 }, + { 0x3418, 0x38 }, + { 0x3419, 0x04 }, + { 0x3012, 0x64 }, + { 0x3013, 0x00 }, + { 0x305c, 0x18 }, + { 0x305d, 0x03 }, + { 0x305e, 0x20 }, + { 0x305f, 0x01 }, + { 0x315e, 0x1a }, + { 0x3164, 0x1a }, + { 0x3480, 0x49 }, + /* data rate settings */ + { 0x3405, 0x10 }, + { 0x3446, 0x57 }, + { 0x3447, 0x00 }, + { 0x3448, 0x37 }, + { 0x3449, 0x00 }, + { 0x344a, 0x1f }, + { 0x344b, 0x00 }, + { 0x344c, 0x1f }, + { 0x344d, 0x00 }, + { 0x344e, 0x1f }, + { 0x344f, 0x00 }, + { 0x3450, 0x77 }, + { 0x3451, 0x00 }, + { 0x3452, 0x1f }, + { 0x3453, 0x00 }, + { 0x3454, 0x17 }, + { 0x3455, 0x00 }, +}; + +static const struct imx290_regval imx290_720p_settings[] = { + /* mode settings */ + { 0x3007, 0x10 }, + { 0x303a, 0x06 }, + { 0x3414, 0x04 }, + { 0x3472, 0x00 }, + { 0x3473, 0x05 }, + { 0x3418, 0xd0 }, + { 0x3419, 0x02 }, + { 0x3012, 0x64 }, + { 0x3013, 0x00 }, + { 0x305c, 0x20 }, + { 0x305d, 0x00 }, + { 0x305e, 0x20 }, + { 0x305f, 0x01 }, + { 0x315e, 0x1a }, + { 0x3164, 0x1a }, + { 0x3480, 0x49 }, + /* data rate settings */ + { 0x3405, 0x10 }, + { 0x3446, 0x4f }, + { 0x3447, 0x00 }, + { 0x3448, 0x2f }, + { 0x3449, 0x00 }, + { 0x344a, 0x17 }, + { 0x344b, 0x00 }, + { 0x344c, 0x17 }, + { 0x344d, 0x00 }, + { 0x344e, 0x17 }, + { 0x344f, 0x00 }, + { 0x3450, 0x57 }, + { 0x3451, 0x00 }, + { 0x3452, 0x17 }, + { 0x3453, 0x00 }, + { 0x3454, 0x17 }, + { 0x3455, 0x00 }, +}; + +static const struct imx290_regval imx290_10bit_settings[] = { + { 0x3005, 0x00}, + { 0x3046, 0x00}, + { 0x3129, 0x1d}, + { 0x317c, 0x12}, + { 0x31ec, 0x37}, + { 0x3441, 0x0a}, + { 0x3442, 0x0a}, + { 0x300a, 0x3c}, + { 0x300b, 0x00}, +}; + +static const struct imx290_regval imx290_12bit_settings[] = { + { 0x3005, 0x01 }, + { 0x3046, 0x01 }, + { 0x3129, 0x00 }, + { 0x317c, 0x00 }, + { 0x31ec, 0x0e }, + { 0x3441, 0x0c }, + { 0x3442, 0x0c }, + { 0x300a, 0xf0 }, + { 0x300b, 0x00 }, +}; + +/* supported link frequencies */ +#define FREQ_INDEX_1080P 0 +#define FREQ_INDEX_720P 1 +static const s64 imx290_link_freq_2lanes[] = { + [FREQ_INDEX_1080P] = 445500000, + [FREQ_INDEX_720P] = 297000000, +}; +static const s64 imx290_link_freq_4lanes[] = { + [FREQ_INDEX_1080P] = 222750000, + [FREQ_INDEX_720P] = 148500000, +}; + +/* + * In this function and in the similar ones below We rely on imx290_probe() + * to ensure that nlanes is either 2 or 4. + */ +static inline const s64 *imx290_link_freqs_ptr(const struct imx290 *imx290) +{ + if (imx290->nlanes == 2) + return imx290_link_freq_2lanes; + else + return imx290_link_freq_4lanes; +} + +static inline int imx290_link_freqs_num(const struct imx290 *imx290) +{ + if (imx290->nlanes == 2) + return ARRAY_SIZE(imx290_link_freq_2lanes); + else + return ARRAY_SIZE(imx290_link_freq_4lanes); +} + +/* Mode configs */ +static const struct imx290_mode imx290_modes_2lanes[] = { + { + .width = 1920, + .height = 1080, + .hmax = 0x1130, + .link_freq_index = FREQ_INDEX_1080P, + .data = imx290_1080p_settings, + .data_size = ARRAY_SIZE(imx290_1080p_settings), + }, + { + .width = 1280, + .height = 720, + .hmax = 0x19c8, + .link_freq_index = FREQ_INDEX_720P, + .data = imx290_720p_settings, + .data_size = ARRAY_SIZE(imx290_720p_settings), + }, +}; + +static const struct imx290_mode imx290_modes_4lanes[] = { + { + .width = 1920, + .height = 1080, + .hmax = 0x0898, + .link_freq_index = FREQ_INDEX_1080P, + .data = imx290_1080p_settings, + .data_size = ARRAY_SIZE(imx290_1080p_settings), + }, + { + .width = 1280, + .height = 720, + .hmax = 0x0ce4, + .link_freq_index = FREQ_INDEX_720P, + .data = imx290_720p_settings, + .data_size = ARRAY_SIZE(imx290_720p_settings), + }, +}; + +static inline const struct imx290_mode *imx290_modes_ptr(const struct imx290 *imx290) +{ + if (imx290->nlanes == 2) + return imx290_modes_2lanes; + else + return imx290_modes_4lanes; +} + +static inline int imx290_modes_num(const struct imx290 *imx290) +{ + if (imx290->nlanes == 2) + return ARRAY_SIZE(imx290_modes_2lanes); + else + return ARRAY_SIZE(imx290_modes_4lanes); +} + +static inline struct imx290 *to_imx290(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx290, sd); +} + +static inline int imx290_read_reg(struct imx290 *imx290, u16 addr, u8 *value) +{ + unsigned int regval; + int ret; + + ret = regmap_read(imx290->regmap, addr, ®val); + if (ret) { + dev_err(imx290->dev, "I2C read failed for addr: %x\n", addr); + return ret; + } + + *value = regval & 0xff; + + return 0; +} + +static int imx290_write_reg(struct imx290 *imx290, u16 addr, u8 value) +{ + int ret; + + ret = regmap_write(imx290->regmap, addr, value); + if (ret) { + dev_err(imx290->dev, "I2C write failed for addr: %x\n", addr); + return ret; + } + + return ret; +} + +static int imx290_set_register_array(struct imx290 *imx290, + const struct imx290_regval *settings, + unsigned int num_settings) +{ + unsigned int i; + int ret; + + for (i = 0; i < num_settings; ++i, ++settings) { + ret = imx290_write_reg(imx290, settings->reg, settings->val); + if (ret < 0) + return ret; + } + + /* Provide 10ms settle time */ + usleep_range(10000, 11000); + + return 0; +} + +static int imx290_write_buffered_reg(struct imx290 *imx290, u16 address_low, + u8 nr_regs, u32 value) +{ + unsigned int i; + int ret; + + ret = imx290_write_reg(imx290, IMX290_REGHOLD, 0x01); + if (ret) { + dev_err(imx290->dev, "Error setting hold register\n"); + return ret; + } + + for (i = 0; i < nr_regs; i++) { + ret = imx290_write_reg(imx290, address_low + i, + (u8)(value >> (i * 8))); + if (ret) { + dev_err(imx290->dev, "Error writing buffered registers\n"); + return ret; + } + } + + ret = imx290_write_reg(imx290, IMX290_REGHOLD, 0x00); + if (ret) { + dev_err(imx290->dev, "Error setting hold register\n"); + return ret; + } + + return ret; +} + +static int imx290_set_gain(struct imx290 *imx290, u32 value) +{ + int ret; + + ret = imx290_write_buffered_reg(imx290, IMX290_GAIN, 1, value); + if (ret) + dev_err(imx290->dev, "Unable to write gain\n"); + + return ret; +} + +/* Stop streaming */ +static int imx290_stop_streaming(struct imx290 *imx290) +{ + int ret; + + ret = imx290_write_reg(imx290, IMX290_STANDBY, 0x01); + if (ret < 0) + return ret; + + msleep(30); + + return imx290_write_reg(imx290, IMX290_XMSTA, 0x01); +} + +static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx290 *imx290 = container_of(ctrl->handler, + struct imx290, ctrls); + int ret = 0; + + /* V4L2 controls values will be applied only when power is already up */ + if (!pm_runtime_get_if_in_use(imx290->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + ret = imx290_set_gain(imx290, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + if (ctrl->val) { + imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, 0x00); + imx290_write_reg(imx290, IMX290_BLKLEVEL_HIGH, 0x00); + usleep_range(10000, 11000); + imx290_write_reg(imx290, IMX290_PGCTRL, + (u8)(IMX290_PGCTRL_REGEN | + IMX290_PGCTRL_THRU | + IMX290_PGCTRL_MODE(ctrl->val))); + } else { + imx290_write_reg(imx290, IMX290_PGCTRL, 0x00); + usleep_range(10000, 11000); + if (imx290->bpp == 10) + imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, + 0x3c); + else /* 12 bits per pixel */ + imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, + 0xf0); + imx290_write_reg(imx290, IMX290_BLKLEVEL_HIGH, 0x00); + } + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(imx290->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx290_ctrl_ops = { + .s_ctrl = imx290_set_ctrl, +}; + +static int imx290_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(imx290_formats)) + return -EINVAL; + + code->code = imx290_formats[code->index].code; + + return 0; +} + +static int imx290_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + const struct imx290 *imx290 = to_imx290(sd); + const struct imx290_mode *imx290_modes = imx290_modes_ptr(imx290); + + if ((fse->code != imx290_formats[0].code) && + (fse->code != imx290_formats[1].code)) + return -EINVAL; + + if (fse->index >= imx290_modes_num(imx290)) + return -EINVAL; + + fse->min_width = imx290_modes[fse->index].width; + fse->max_width = imx290_modes[fse->index].width; + fse->min_height = imx290_modes[fse->index].height; + fse->max_height = imx290_modes[fse->index].height; + + return 0; +} + +static int imx290_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx290 *imx290 = to_imx290(sd); + struct v4l2_mbus_framefmt *framefmt; + + mutex_lock(&imx290->lock); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + framefmt = v4l2_subdev_get_try_format(&imx290->sd, cfg, + fmt->pad); + else + framefmt = &imx290->current_format; + + fmt->format = *framefmt; + + mutex_unlock(&imx290->lock); + + return 0; +} + +static inline u8 imx290_get_link_freq_index(struct imx290 *imx290) +{ + return imx290->current_mode->link_freq_index; +} + +static s64 imx290_get_link_freq(struct imx290 *imx290) +{ + u8 index = imx290_get_link_freq_index(imx290); + + return *(imx290_link_freqs_ptr(imx290) + index); +} + +static u64 imx290_calc_pixel_rate(struct imx290 *imx290) +{ + s64 link_freq = imx290_get_link_freq(imx290); + u8 nlanes = imx290->nlanes; + u64 pixel_rate; + + /* pixel rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ + pixel_rate = link_freq * 2 * nlanes; + do_div(pixel_rate, imx290->bpp); + return pixel_rate; +} + +static int imx290_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx290 *imx290 = to_imx290(sd); + const struct imx290_mode *mode; + struct v4l2_mbus_framefmt *format; + unsigned int i; + + mutex_lock(&imx290->lock); + + mode = v4l2_find_nearest_size(imx290_modes_ptr(imx290), + imx290_modes_num(imx290), width, height, + fmt->format.width, fmt->format.height); + + fmt->format.width = mode->width; + fmt->format.height = mode->height; + + for (i = 0; i < ARRAY_SIZE(imx290_formats); i++) + if (imx290_formats[i].code == fmt->format.code) + break; + + if (i >= ARRAY_SIZE(imx290_formats)) + i = 0; + + fmt->format.code = imx290_formats[i].code; + fmt->format.field = V4L2_FIELD_NONE; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + format = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + } else { + format = &imx290->current_format; + imx290->current_mode = mode; + imx290->bpp = imx290_formats[i].bpp; + + if (imx290->link_freq) + __v4l2_ctrl_s_ctrl(imx290->link_freq, + imx290_get_link_freq_index(imx290)); + if (imx290->pixel_rate) + __v4l2_ctrl_s_ctrl_int64(imx290->pixel_rate, + imx290_calc_pixel_rate(imx290)); + } + + *format = fmt->format; + + mutex_unlock(&imx290->lock); + + return 0; +} + +static int imx290_entity_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg) +{ + struct v4l2_subdev_format fmt = { 0 }; + + fmt.which = cfg ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.format.width = 1920; + fmt.format.height = 1080; + + imx290_set_fmt(subdev, cfg, &fmt); + + return 0; +} + +static int imx290_write_current_format(struct imx290 *imx290) +{ + int ret; + + switch (imx290->current_format.code) { + case MEDIA_BUS_FMT_SRGGB10_1X10: + ret = imx290_set_register_array(imx290, imx290_10bit_settings, + ARRAY_SIZE( + imx290_10bit_settings)); + if (ret < 0) { + dev_err(imx290->dev, "Could not set format registers\n"); + return ret; + } + break; + case MEDIA_BUS_FMT_SRGGB12_1X12: + ret = imx290_set_register_array(imx290, imx290_12bit_settings, + ARRAY_SIZE( + imx290_12bit_settings)); + if (ret < 0) { + dev_err(imx290->dev, "Could not set format registers\n"); + return ret; + } + break; + default: + dev_err(imx290->dev, "Unknown pixel format\n"); + return -EINVAL; + } + + return 0; +} + +static int imx290_set_hmax(struct imx290 *imx290, u32 val) +{ + int ret; + + ret = imx290_write_reg(imx290, IMX290_HMAX_LOW, (val & 0xff)); + if (ret) { + dev_err(imx290->dev, "Error setting HMAX register\n"); + return ret; + } + + ret = imx290_write_reg(imx290, IMX290_HMAX_HIGH, ((val >> 8) & 0xff)); + if (ret) { + dev_err(imx290->dev, "Error setting HMAX register\n"); + return ret; + } + + return 0; +} + +/* Start streaming */ +static int imx290_start_streaming(struct imx290 *imx290) +{ + int ret; + + /* Set init register settings */ + ret = imx290_set_register_array(imx290, imx290_global_init_settings, + ARRAY_SIZE( + imx290_global_init_settings)); + if (ret < 0) { + dev_err(imx290->dev, "Could not set init registers\n"); + return ret; + } + + /* Apply the register values related to current frame format */ + ret = imx290_write_current_format(imx290); + if (ret < 0) { + dev_err(imx290->dev, "Could not set frame format\n"); + return ret; + } + + /* Apply default values of current mode */ + ret = imx290_set_register_array(imx290, imx290->current_mode->data, + imx290->current_mode->data_size); + if (ret < 0) { + dev_err(imx290->dev, "Could not set current mode\n"); + return ret; + } + ret = imx290_set_hmax(imx290, imx290->current_mode->hmax); + if (ret < 0) + return ret; + + /* Apply customized values from user */ + ret = v4l2_ctrl_handler_setup(imx290->sd.ctrl_handler); + if (ret) { + dev_err(imx290->dev, "Could not sync v4l2 controls\n"); + return ret; + } + + ret = imx290_write_reg(imx290, IMX290_STANDBY, 0x00); + if (ret < 0) + return ret; + + msleep(30); + + /* Start streaming */ + return imx290_write_reg(imx290, IMX290_XMSTA, 0x00); +} + +static int imx290_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx290 *imx290 = to_imx290(sd); + int ret = 0; + + if (enable) { + ret = pm_runtime_get_sync(imx290->dev); + if (ret < 0) { + pm_runtime_put_noidle(imx290->dev); + goto unlock_and_return; + } + + ret = imx290_start_streaming(imx290); + if (ret) { + dev_err(imx290->dev, "Start stream failed\n"); + pm_runtime_put(imx290->dev); + goto unlock_and_return; + } + } else { + imx290_stop_streaming(imx290); + pm_runtime_put(imx290->dev); + } + +unlock_and_return: + + return ret; +} + +static int imx290_get_regulators(struct device *dev, struct imx290 *imx290) +{ + unsigned int i; + + for (i = 0; i < IMX290_NUM_SUPPLIES; i++) + imx290->supplies[i].supply = imx290_supply_name[i]; + + return devm_regulator_bulk_get(dev, IMX290_NUM_SUPPLIES, + imx290->supplies); +} + +static int imx290_set_data_lanes(struct imx290 *imx290) +{ + int ret = 0, laneval, frsel; + + switch (imx290->nlanes) { + case 2: + laneval = 0x01; + frsel = 0x02; + break; + case 4: + laneval = 0x03; + frsel = 0x01; + break; + default: + /* + * We should never hit this since the data lane count is + * validated in probe itself + */ + dev_err(imx290->dev, "Lane configuration not supported\n"); + ret = -EINVAL; + goto exit; + } + + ret = imx290_write_reg(imx290, IMX290_PHY_LANE_NUM, laneval); + if (ret) { + dev_err(imx290->dev, "Error setting Physical Lane number register\n"); + goto exit; + } + + ret = imx290_write_reg(imx290, IMX290_CSI_LANE_MODE, laneval); + if (ret) { + dev_err(imx290->dev, "Error setting CSI Lane mode register\n"); + goto exit; + } + + ret = imx290_write_reg(imx290, IMX290_FR_FDG_SEL, frsel); + if (ret) + dev_err(imx290->dev, "Error setting FR/FDG SEL register\n"); + +exit: + return ret; +} + +static int imx290_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx290 *imx290 = to_imx290(sd); + int ret; + + ret = clk_prepare_enable(imx290->xclk); + if (ret) { + dev_err(imx290->dev, "Failed to enable clock\n"); + return ret; + } + + ret = regulator_bulk_enable(IMX290_NUM_SUPPLIES, imx290->supplies); + if (ret) { + dev_err(imx290->dev, "Failed to enable regulators\n"); + clk_disable_unprepare(imx290->xclk); + return ret; + } + + usleep_range(1, 2); + gpiod_set_value_cansleep(imx290->rst_gpio, 0); + usleep_range(30000, 31000); + + /* Set data lane count */ + imx290_set_data_lanes(imx290); + + return 0; +} + +static int imx290_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx290 *imx290 = to_imx290(sd); + + clk_disable_unprepare(imx290->xclk); + gpiod_set_value_cansleep(imx290->rst_gpio, 1); + regulator_bulk_disable(IMX290_NUM_SUPPLIES, imx290->supplies); + + return 0; +} + +static const struct dev_pm_ops imx290_pm_ops = { + SET_RUNTIME_PM_OPS(imx290_power_off, imx290_power_on, NULL) +}; + +static const struct v4l2_subdev_video_ops imx290_video_ops = { + .s_stream = imx290_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx290_pad_ops = { + .init_cfg = imx290_entity_init_cfg, + .enum_mbus_code = imx290_enum_mbus_code, + .enum_frame_size = imx290_enum_frame_size, + .get_fmt = imx290_get_fmt, + .set_fmt = imx290_set_fmt, +}; + +static const struct v4l2_subdev_ops imx290_subdev_ops = { + .video = &imx290_video_ops, + .pad = &imx290_pad_ops, +}; + +static const struct media_entity_operations imx290_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * Returns 0 if all link frequencies used by the driver for the given number + * of MIPI data lanes are mentioned in the device tree, or the value of the + * first missing frequency otherwise. + */ +static s64 imx290_check_link_freqs(const struct imx290 *imx290, + const struct v4l2_fwnode_endpoint *ep) +{ + int i, j; + const s64 *freqs = imx290_link_freqs_ptr(imx290); + int freqs_count = imx290_link_freqs_num(imx290); + + for (i = 0; i < freqs_count; i++) { + for (j = 0; j < ep->nr_of_link_frequencies; j++) + if (freqs[i] == ep->link_frequencies[j]) + break; + if (j == ep->nr_of_link_frequencies) + return freqs[i]; + } + return 0; +} + +static int imx290_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct fwnode_handle *endpoint; + /* Only CSI2 is supported for now: */ + struct v4l2_fwnode_endpoint ep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct imx290 *imx290; + u32 xclk_freq; + s64 fq; + int ret; + + imx290 = devm_kzalloc(dev, sizeof(*imx290), GFP_KERNEL); + if (!imx290) + return -ENOMEM; + + imx290->dev = dev; + imx290->regmap = devm_regmap_init_i2c(client, &imx290_regmap_config); + if (IS_ERR(imx290->regmap)) { + dev_err(dev, "Unable to initialize I2C\n"); + return -ENODEV; + } + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "Endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep); + fwnode_handle_put(endpoint); + if (ret == -ENXIO) { + dev_err(dev, "Unsupported bus type, should be CSI2\n"); + goto free_err; + } else if (ret) { + dev_err(dev, "Parsing endpoint node failed\n"); + goto free_err; + } + + /* Get number of data lanes */ + imx290->nlanes = ep.bus.mipi_csi2.num_data_lanes; + if (imx290->nlanes != 2 && imx290->nlanes != 4) { + dev_err(dev, "Invalid data lanes: %d\n", imx290->nlanes); + ret = -EINVAL; + goto free_err; + } + + dev_dbg(dev, "Using %u data lanes\n", imx290->nlanes); + + if (!ep.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + ret = -EINVAL; + goto free_err; + } + + /* Check that link frequences for all the modes are in device tree */ + fq = imx290_check_link_freqs(imx290, &ep); + if (fq) { + dev_err(dev, "Link frequency of %lld is not supported\n", fq); + ret = -EINVAL; + goto free_err; + } + + /* get system clock (xclk) */ + imx290->xclk = devm_clk_get(dev, "xclk"); + if (IS_ERR(imx290->xclk)) { + dev_err(dev, "Could not get xclk"); + ret = PTR_ERR(imx290->xclk); + goto free_err; + } + + ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &xclk_freq); + if (ret) { + dev_err(dev, "Could not get xclk frequency\n"); + goto free_err; + } + + /* external clock must be 37.125 MHz */ + if (xclk_freq != 37125000) { + dev_err(dev, "External clock frequency %u is not supported\n", + xclk_freq); + ret = -EINVAL; + goto free_err; + } + + ret = clk_set_rate(imx290->xclk, xclk_freq); + if (ret) { + dev_err(dev, "Could not set xclk frequency\n"); + goto free_err; + } + + ret = imx290_get_regulators(dev, imx290); + if (ret < 0) { + dev_err(dev, "Cannot get regulators\n"); + goto free_err; + } + + imx290->rst_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(imx290->rst_gpio)) { + dev_err(dev, "Cannot get reset gpio\n"); + ret = PTR_ERR(imx290->rst_gpio); + goto free_err; + } + + mutex_init(&imx290->lock); + + /* + * Initialize the frame format. In particular, imx290->current_mode + * and imx290->bpp are set to defaults: imx290_calc_pixel_rate() call + * below relies on these fields. + */ + imx290_entity_init_cfg(&imx290->sd, NULL); + + v4l2_ctrl_handler_init(&imx290->ctrls, 4); + + v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_GAIN, 0, 72, 1, 0); + + imx290->link_freq = + v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_LINK_FREQ, + imx290_link_freqs_num(imx290) - 1, 0, + imx290_link_freqs_ptr(imx290)); + if (imx290->link_freq) + imx290->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx290->pixel_rate = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 1, INT_MAX, 1, + imx290_calc_pixel_rate(imx290)); + + v4l2_ctrl_new_std_menu_items(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx290_test_pattern_menu) - 1, + 0, 0, imx290_test_pattern_menu); + + imx290->sd.ctrl_handler = &imx290->ctrls; + + if (imx290->ctrls.error) { + dev_err(dev, "Control initialization error %d\n", + imx290->ctrls.error); + ret = imx290->ctrls.error; + goto free_ctrl; + } + + v4l2_i2c_subdev_init(&imx290->sd, client, &imx290_subdev_ops); + imx290->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx290->sd.dev = &client->dev; + imx290->sd.entity.ops = &imx290_subdev_entity_ops; + imx290->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + imx290->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&imx290->sd.entity, 1, &imx290->pad); + if (ret < 0) { + dev_err(dev, "Could not register media entity\n"); + goto free_ctrl; + } + + ret = v4l2_async_register_subdev(&imx290->sd); + if (ret < 0) { + dev_err(dev, "Could not register v4l2 device\n"); + goto free_entity; + } + + /* Power on the device to match runtime PM state below */ + ret = imx290_power_on(dev); + if (ret < 0) { + dev_err(dev, "Could not power on the device\n"); + goto free_entity; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + v4l2_fwnode_endpoint_free(&ep); + + return 0; + +free_entity: + media_entity_cleanup(&imx290->sd.entity); +free_ctrl: + v4l2_ctrl_handler_free(&imx290->ctrls); + mutex_destroy(&imx290->lock); +free_err: + v4l2_fwnode_endpoint_free(&ep); + + return ret; +} + +static int imx290_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx290 *imx290 = to_imx290(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + + mutex_destroy(&imx290->lock); + + pm_runtime_disable(imx290->dev); + if (!pm_runtime_status_suspended(imx290->dev)) + imx290_power_off(imx290->dev); + pm_runtime_set_suspended(imx290->dev); + + return 0; +} + +static const struct of_device_id imx290_of_match[] = { + { .compatible = "sony,imx290" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx290_of_match); + +static struct i2c_driver imx290_i2c_driver = { + .probe_new = imx290_probe, + .remove = imx290_remove, + .driver = { + .name = "imx290", + .pm = &imx290_pm_ops, + .of_match_table = of_match_ptr(imx290_of_match), + }, +}; + +module_i2c_driver(imx290_i2c_driver); + +MODULE_DESCRIPTION("Sony IMX290 CMOS Image Sensor Driver"); +MODULE_AUTHOR("FRAMOS GmbH"); +MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx319.c b/drivers/media/i2c/imx319.c new file mode 100644 index 000000000..17c2e4b41 --- /dev/null +++ b/drivers/media/i2c/imx319.c @@ -0,0 +1,2560 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Intel Corporation + +#include <asm/unaligned.h> +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> + +#define IMX319_REG_MODE_SELECT 0x0100 +#define IMX319_MODE_STANDBY 0x00 +#define IMX319_MODE_STREAMING 0x01 + +/* Chip ID */ +#define IMX319_REG_CHIP_ID 0x0016 +#define IMX319_CHIP_ID 0x0319 + +/* V_TIMING internal */ +#define IMX319_REG_FLL 0x0340 +#define IMX319_FLL_MAX 0xffff + +/* Exposure control */ +#define IMX319_REG_EXPOSURE 0x0202 +#define IMX319_EXPOSURE_MIN 1 +#define IMX319_EXPOSURE_STEP 1 +#define IMX319_EXPOSURE_DEFAULT 0x04f6 + +/* + * the digital control register for all color control looks like: + * +-----------------+------------------+ + * | [7:0] | [15:8] | + * +-----------------+------------------+ + * | 0x020f | 0x020e | + * -------------------------------------- + * it is used to calculate the digital gain times value(integral + fractional) + * the [15:8] bits is the fractional part and [7:0] bits is the integral + * calculation equation is: + * gain value (unit: times) = REG[15:8] + REG[7:0]/0x100 + * Only value in 0x0100 ~ 0x0FFF range is allowed. + * Analog gain use 10 bits in the registers and allowed range is 0 ~ 960 + */ +/* Analog gain control */ +#define IMX319_REG_ANALOG_GAIN 0x0204 +#define IMX319_ANA_GAIN_MIN 0 +#define IMX319_ANA_GAIN_MAX 960 +#define IMX319_ANA_GAIN_STEP 1 +#define IMX319_ANA_GAIN_DEFAULT 0 + +/* Digital gain control */ +#define IMX319_REG_DPGA_USE_GLOBAL_GAIN 0x3ff9 +#define IMX319_REG_DIG_GAIN_GLOBAL 0x020e +#define IMX319_DGTL_GAIN_MIN 256 +#define IMX319_DGTL_GAIN_MAX 4095 +#define IMX319_DGTL_GAIN_STEP 1 +#define IMX319_DGTL_GAIN_DEFAULT 256 + +/* Test Pattern Control */ +#define IMX319_REG_TEST_PATTERN 0x0600 +#define IMX319_TEST_PATTERN_DISABLED 0 +#define IMX319_TEST_PATTERN_SOLID_COLOR 1 +#define IMX319_TEST_PATTERN_COLOR_BARS 2 +#define IMX319_TEST_PATTERN_GRAY_COLOR_BARS 3 +#define IMX319_TEST_PATTERN_PN9 4 + +/* Flip Control */ +#define IMX319_REG_ORIENTATION 0x0101 + +/* default link frequency and external clock */ +#define IMX319_LINK_FREQ_DEFAULT 482400000 +#define IMX319_EXT_CLK 19200000 +#define IMX319_LINK_FREQ_INDEX 0 + +struct imx319_reg { + u16 address; + u8 val; +}; + +struct imx319_reg_list { + u32 num_of_regs; + const struct imx319_reg *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx319_mode { + /* Frame width */ + u32 width; + /* Frame height */ + u32 height; + + /* V-timing */ + u32 fll_def; + u32 fll_min; + + /* H-timing */ + u32 llp; + + /* index of link frequency */ + u32 link_freq_index; + + /* Default register values */ + struct imx319_reg_list reg_list; +}; + +struct imx319_hwcfg { + u32 ext_clk; /* sensor external clk */ + s64 *link_freqs; /* CSI-2 link frequencies */ + unsigned int nr_of_link_freqs; +}; + +struct imx319 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + + /* Current mode */ + const struct imx319_mode *cur_mode; + + struct imx319_hwcfg *hwcfg; + s64 link_def_freq; /* CSI-2 link default frequency */ + + /* + * Mutex for serialized access: + * Protect sensor set pad format and start/stop streaming safely. + * Protect access to sensor v4l2 controls. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static const struct imx319_reg imx319_global_regs[] = { + { 0x0136, 0x13 }, + { 0x0137, 0x33 }, + { 0x3c7e, 0x05 }, + { 0x3c7f, 0x07 }, + { 0x4d39, 0x0b }, + { 0x4d41, 0x33 }, + { 0x4d43, 0x0c }, + { 0x4d49, 0x89 }, + { 0x4e05, 0x0b }, + { 0x4e0d, 0x33 }, + { 0x4e0f, 0x0c }, + { 0x4e15, 0x89 }, + { 0x4e49, 0x2a }, + { 0x4e51, 0x33 }, + { 0x4e53, 0x0c }, + { 0x4e59, 0x89 }, + { 0x5601, 0x4f }, + { 0x560b, 0x45 }, + { 0x562f, 0x0a }, + { 0x5643, 0x0a }, + { 0x5645, 0x0c }, + { 0x56ef, 0x51 }, + { 0x586f, 0x33 }, + { 0x5873, 0x89 }, + { 0x5905, 0x33 }, + { 0x5907, 0x89 }, + { 0x590d, 0x33 }, + { 0x590f, 0x89 }, + { 0x5915, 0x33 }, + { 0x5917, 0x89 }, + { 0x5969, 0x1c }, + { 0x596b, 0x72 }, + { 0x5971, 0x33 }, + { 0x5973, 0x89 }, + { 0x5975, 0x33 }, + { 0x5977, 0x89 }, + { 0x5979, 0x1c }, + { 0x597b, 0x72 }, + { 0x5985, 0x33 }, + { 0x5987, 0x89 }, + { 0x5999, 0x1c }, + { 0x599b, 0x72 }, + { 0x59a5, 0x33 }, + { 0x59a7, 0x89 }, + { 0x7485, 0x08 }, + { 0x7487, 0x0c }, + { 0x7489, 0xc7 }, + { 0x748b, 0x8b }, + { 0x9004, 0x09 }, + { 0x9200, 0x6a }, + { 0x9201, 0x22 }, + { 0x9202, 0x6a }, + { 0x9203, 0x23 }, + { 0x9204, 0x5f }, + { 0x9205, 0x23 }, + { 0x9206, 0x5f }, + { 0x9207, 0x24 }, + { 0x9208, 0x5f }, + { 0x9209, 0x26 }, + { 0x920a, 0x5f }, + { 0x920b, 0x27 }, + { 0x920c, 0x5f }, + { 0x920d, 0x29 }, + { 0x920e, 0x5f }, + { 0x920f, 0x2a }, + { 0x9210, 0x5f }, + { 0x9211, 0x2c }, + { 0xbc22, 0x1a }, + { 0xf01f, 0x04 }, + { 0xf021, 0x03 }, + { 0xf023, 0x02 }, + { 0xf03d, 0x05 }, + { 0xf03f, 0x03 }, + { 0xf041, 0x02 }, + { 0xf0af, 0x04 }, + { 0xf0b1, 0x03 }, + { 0xf0b3, 0x02 }, + { 0xf0cd, 0x05 }, + { 0xf0cf, 0x03 }, + { 0xf0d1, 0x02 }, + { 0xf13f, 0x04 }, + { 0xf141, 0x03 }, + { 0xf143, 0x02 }, + { 0xf15d, 0x05 }, + { 0xf15f, 0x03 }, + { 0xf161, 0x02 }, + { 0xf1cf, 0x04 }, + { 0xf1d1, 0x03 }, + { 0xf1d3, 0x02 }, + { 0xf1ed, 0x05 }, + { 0xf1ef, 0x03 }, + { 0xf1f1, 0x02 }, + { 0xf287, 0x04 }, + { 0xf289, 0x03 }, + { 0xf28b, 0x02 }, + { 0xf2a5, 0x05 }, + { 0xf2a7, 0x03 }, + { 0xf2a9, 0x02 }, + { 0xf2b7, 0x04 }, + { 0xf2b9, 0x03 }, + { 0xf2bb, 0x02 }, + { 0xf2d5, 0x05 }, + { 0xf2d7, 0x03 }, + { 0xf2d9, 0x02 }, +}; + +static const struct imx319_reg_list imx319_global_setting = { + .num_of_regs = ARRAY_SIZE(imx319_global_regs), + .regs = imx319_global_regs, +}; + +static const struct imx319_reg mode_3264x2448_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0f }, + { 0x0343, 0x80 }, + { 0x0340, 0x0c }, + { 0x0341, 0xaa }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x00 }, + { 0x0347, 0x00 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x09 }, + { 0x034b, 0x9f }, + { 0x0220, 0x00 }, + { 0x0221, 0x11 }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x0a }, + { 0x3140, 0x02 }, + { 0x3141, 0x00 }, + { 0x3f0d, 0x0a }, + { 0x3f14, 0x01 }, + { 0x3f3c, 0x01 }, + { 0x3f4d, 0x01 }, + { 0x3f4c, 0x01 }, + { 0x4254, 0x7f }, + { 0x0401, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x10 }, + { 0x0408, 0x00 }, + { 0x0409, 0x08 }, + { 0x040a, 0x00 }, + { 0x040b, 0x08 }, + { 0x040c, 0x0c }, + { 0x040d, 0xc0 }, + { 0x040e, 0x09 }, + { 0x040f, 0x90 }, + { 0x034c, 0x0c }, + { 0x034d, 0xc0 }, + { 0x034e, 0x09 }, + { 0x034f, 0x90 }, + { 0x3261, 0x00 }, + { 0x3264, 0x00 }, + { 0x3265, 0x10 }, + { 0x0301, 0x05 }, + { 0x0303, 0x04 }, + { 0x0305, 0x04 }, + { 0x0306, 0x01 }, + { 0x0307, 0x92 }, + { 0x0309, 0x0a }, + { 0x030b, 0x02 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0xfa }, + { 0x0310, 0x00 }, + { 0x0820, 0x0f }, + { 0x0821, 0x13 }, + { 0x0822, 0x33 }, + { 0x0823, 0x33 }, + { 0x3e20, 0x01 }, + { 0x3e37, 0x00 }, + { 0x3e3b, 0x01 }, + { 0x38a3, 0x01 }, + { 0x38a8, 0x00 }, + { 0x38a9, 0x00 }, + { 0x38aa, 0x00 }, + { 0x38ab, 0x00 }, + { 0x3234, 0x00 }, + { 0x3fc1, 0x00 }, + { 0x3235, 0x00 }, + { 0x3802, 0x00 }, + { 0x3143, 0x04 }, + { 0x360a, 0x00 }, + { 0x0b00, 0x00 }, + { 0x0106, 0x00 }, + { 0x0b05, 0x01 }, + { 0x0b06, 0x01 }, + { 0x3230, 0x00 }, + { 0x3602, 0x01 }, + { 0x3607, 0x01 }, + { 0x3c00, 0x00 }, + { 0x3c01, 0x48 }, + { 0x3c02, 0xc8 }, + { 0x3c03, 0xaa }, + { 0x3c04, 0x91 }, + { 0x3c05, 0x54 }, + { 0x3c06, 0x26 }, + { 0x3c07, 0x20 }, + { 0x3c08, 0x51 }, + { 0x3d80, 0x00 }, + { 0x3f50, 0x00 }, + { 0x3f56, 0x00 }, + { 0x3f57, 0x30 }, + { 0x3f78, 0x01 }, + { 0x3f79, 0x18 }, + { 0x3f7c, 0x00 }, + { 0x3f7d, 0x00 }, + { 0x3fba, 0x00 }, + { 0x3fbb, 0x00 }, + { 0xa081, 0x00 }, + { 0xe014, 0x00 }, + { 0x0202, 0x0a }, + { 0x0203, 0x7a }, + { 0x0224, 0x01 }, + { 0x0225, 0xf4 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x0216, 0x00 }, + { 0x0217, 0x00 }, + { 0x020e, 0x01 }, + { 0x020f, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x0218, 0x01 }, + { 0x0219, 0x00 }, + { 0x3614, 0x00 }, + { 0x3616, 0x0d }, + { 0x3617, 0x56 }, + { 0xb612, 0x20 }, + { 0xb613, 0x20 }, + { 0xb614, 0x20 }, + { 0xb615, 0x20 }, + { 0xb616, 0x0a }, + { 0xb617, 0x0a }, + { 0xb618, 0x20 }, + { 0xb619, 0x20 }, + { 0xb61a, 0x20 }, + { 0xb61b, 0x20 }, + { 0xb61c, 0x0a }, + { 0xb61d, 0x0a }, + { 0xb666, 0x30 }, + { 0xb667, 0x30 }, + { 0xb668, 0x30 }, + { 0xb669, 0x30 }, + { 0xb66a, 0x14 }, + { 0xb66b, 0x14 }, + { 0xb66c, 0x20 }, + { 0xb66d, 0x20 }, + { 0xb66e, 0x20 }, + { 0xb66f, 0x20 }, + { 0xb670, 0x10 }, + { 0xb671, 0x10 }, + { 0x3237, 0x00 }, + { 0x3900, 0x00 }, + { 0x3901, 0x00 }, + { 0x3902, 0x00 }, + { 0x3904, 0x00 }, + { 0x3905, 0x00 }, + { 0x3906, 0x00 }, + { 0x3907, 0x00 }, + { 0x3908, 0x00 }, + { 0x3909, 0x00 }, + { 0x3912, 0x00 }, + { 0x3930, 0x00 }, + { 0x3931, 0x00 }, + { 0x3933, 0x00 }, + { 0x3934, 0x00 }, + { 0x3935, 0x00 }, + { 0x3936, 0x00 }, + { 0x3937, 0x00 }, + { 0x30ac, 0x00 }, +}; + +static const struct imx319_reg mode_3280x2464_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0f }, + { 0x0343, 0x80 }, + { 0x0340, 0x0c }, + { 0x0341, 0xaa }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x00 }, + { 0x0347, 0x00 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x09 }, + { 0x034b, 0x9f }, + { 0x0220, 0x00 }, + { 0x0221, 0x11 }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x0a }, + { 0x3140, 0x02 }, + { 0x3141, 0x00 }, + { 0x3f0d, 0x0a }, + { 0x3f14, 0x01 }, + { 0x3f3c, 0x01 }, + { 0x3f4d, 0x01 }, + { 0x3f4c, 0x01 }, + { 0x4254, 0x7f }, + { 0x0401, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x10 }, + { 0x0408, 0x00 }, + { 0x0409, 0x00 }, + { 0x040a, 0x00 }, + { 0x040b, 0x00 }, + { 0x040c, 0x0c }, + { 0x040d, 0xd0 }, + { 0x040e, 0x09 }, + { 0x040f, 0xa0 }, + { 0x034c, 0x0c }, + { 0x034d, 0xd0 }, + { 0x034e, 0x09 }, + { 0x034f, 0xa0 }, + { 0x3261, 0x00 }, + { 0x3264, 0x00 }, + { 0x3265, 0x10 }, + { 0x0301, 0x05 }, + { 0x0303, 0x04 }, + { 0x0305, 0x04 }, + { 0x0306, 0x01 }, + { 0x0307, 0x92 }, + { 0x0309, 0x0a }, + { 0x030b, 0x02 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0xfa }, + { 0x0310, 0x00 }, + { 0x0820, 0x0f }, + { 0x0821, 0x13 }, + { 0x0822, 0x33 }, + { 0x0823, 0x33 }, + { 0x3e20, 0x01 }, + { 0x3e37, 0x00 }, + { 0x3e3b, 0x01 }, + { 0x38a3, 0x01 }, + { 0x38a8, 0x00 }, + { 0x38a9, 0x00 }, + { 0x38aa, 0x00 }, + { 0x38ab, 0x00 }, + { 0x3234, 0x00 }, + { 0x3fc1, 0x00 }, + { 0x3235, 0x00 }, + { 0x3802, 0x00 }, + { 0x3143, 0x04 }, + { 0x360a, 0x00 }, + { 0x0b00, 0x00 }, + { 0x0106, 0x00 }, + { 0x0b05, 0x01 }, + { 0x0b06, 0x01 }, + { 0x3230, 0x00 }, + { 0x3602, 0x01 }, + { 0x3607, 0x01 }, + { 0x3c00, 0x00 }, + { 0x3c01, 0x48 }, + { 0x3c02, 0xc8 }, + { 0x3c03, 0xaa }, + { 0x3c04, 0x91 }, + { 0x3c05, 0x54 }, + { 0x3c06, 0x26 }, + { 0x3c07, 0x20 }, + { 0x3c08, 0x51 }, + { 0x3d80, 0x00 }, + { 0x3f50, 0x00 }, + { 0x3f56, 0x00 }, + { 0x3f57, 0x30 }, + { 0x3f78, 0x01 }, + { 0x3f79, 0x18 }, + { 0x3f7c, 0x00 }, + { 0x3f7d, 0x00 }, + { 0x3fba, 0x00 }, + { 0x3fbb, 0x00 }, + { 0xa081, 0x00 }, + { 0xe014, 0x00 }, + { 0x0202, 0x0a }, + { 0x0203, 0x7a }, + { 0x0224, 0x01 }, + { 0x0225, 0xf4 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x0216, 0x00 }, + { 0x0217, 0x00 }, + { 0x020e, 0x01 }, + { 0x020f, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x0218, 0x01 }, + { 0x0219, 0x00 }, + { 0x3614, 0x00 }, + { 0x3616, 0x0d }, + { 0x3617, 0x56 }, + { 0xb612, 0x20 }, + { 0xb613, 0x20 }, + { 0xb614, 0x20 }, + { 0xb615, 0x20 }, + { 0xb616, 0x0a }, + { 0xb617, 0x0a }, + { 0xb618, 0x20 }, + { 0xb619, 0x20 }, + { 0xb61a, 0x20 }, + { 0xb61b, 0x20 }, + { 0xb61c, 0x0a }, + { 0xb61d, 0x0a }, + { 0xb666, 0x30 }, + { 0xb667, 0x30 }, + { 0xb668, 0x30 }, + { 0xb669, 0x30 }, + { 0xb66a, 0x14 }, + { 0xb66b, 0x14 }, + { 0xb66c, 0x20 }, + { 0xb66d, 0x20 }, + { 0xb66e, 0x20 }, + { 0xb66f, 0x20 }, + { 0xb670, 0x10 }, + { 0xb671, 0x10 }, + { 0x3237, 0x00 }, + { 0x3900, 0x00 }, + { 0x3901, 0x00 }, + { 0x3902, 0x00 }, + { 0x3904, 0x00 }, + { 0x3905, 0x00 }, + { 0x3906, 0x00 }, + { 0x3907, 0x00 }, + { 0x3908, 0x00 }, + { 0x3909, 0x00 }, + { 0x3912, 0x00 }, + { 0x3930, 0x00 }, + { 0x3931, 0x00 }, + { 0x3933, 0x00 }, + { 0x3934, 0x00 }, + { 0x3935, 0x00 }, + { 0x3936, 0x00 }, + { 0x3937, 0x00 }, + { 0x30ac, 0x00 }, +}; + +static const struct imx319_reg mode_1936x1096_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0f }, + { 0x0343, 0x80 }, + { 0x0340, 0x0c }, + { 0x0341, 0xaa }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x02 }, + { 0x0347, 0xac }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x06 }, + { 0x034b, 0xf3 }, + { 0x0220, 0x00 }, + { 0x0221, 0x11 }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x0a }, + { 0x3140, 0x02 }, + { 0x3141, 0x00 }, + { 0x3f0d, 0x0a }, + { 0x3f14, 0x01 }, + { 0x3f3c, 0x01 }, + { 0x3f4d, 0x01 }, + { 0x3f4c, 0x01 }, + { 0x4254, 0x7f }, + { 0x0401, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x10 }, + { 0x0408, 0x02 }, + { 0x0409, 0xa0 }, + { 0x040a, 0x00 }, + { 0x040b, 0x00 }, + { 0x040c, 0x07 }, + { 0x040d, 0x90 }, + { 0x040e, 0x04 }, + { 0x040f, 0x48 }, + { 0x034c, 0x07 }, + { 0x034d, 0x90 }, + { 0x034e, 0x04 }, + { 0x034f, 0x48 }, + { 0x3261, 0x00 }, + { 0x3264, 0x00 }, + { 0x3265, 0x10 }, + { 0x0301, 0x05 }, + { 0x0303, 0x04 }, + { 0x0305, 0x04 }, + { 0x0306, 0x01 }, + { 0x0307, 0x92 }, + { 0x0309, 0x0a }, + { 0x030b, 0x02 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0xfa }, + { 0x0310, 0x00 }, + { 0x0820, 0x0f }, + { 0x0821, 0x13 }, + { 0x0822, 0x33 }, + { 0x0823, 0x33 }, + { 0x3e20, 0x01 }, + { 0x3e37, 0x00 }, + { 0x3e3b, 0x01 }, + { 0x38a3, 0x01 }, + { 0x38a8, 0x00 }, + { 0x38a9, 0x00 }, + { 0x38aa, 0x00 }, + { 0x38ab, 0x00 }, + { 0x3234, 0x00 }, + { 0x3fc1, 0x00 }, + { 0x3235, 0x00 }, + { 0x3802, 0x00 }, + { 0x3143, 0x04 }, + { 0x360a, 0x00 }, + { 0x0b00, 0x00 }, + { 0x0106, 0x00 }, + { 0x0b05, 0x01 }, + { 0x0b06, 0x01 }, + { 0x3230, 0x00 }, + { 0x3602, 0x01 }, + { 0x3607, 0x01 }, + { 0x3c00, 0x00 }, + { 0x3c01, 0x48 }, + { 0x3c02, 0xc8 }, + { 0x3c03, 0xaa }, + { 0x3c04, 0x91 }, + { 0x3c05, 0x54 }, + { 0x3c06, 0x26 }, + { 0x3c07, 0x20 }, + { 0x3c08, 0x51 }, + { 0x3d80, 0x00 }, + { 0x3f50, 0x00 }, + { 0x3f56, 0x00 }, + { 0x3f57, 0x30 }, + { 0x3f78, 0x01 }, + { 0x3f79, 0x18 }, + { 0x3f7c, 0x00 }, + { 0x3f7d, 0x00 }, + { 0x3fba, 0x00 }, + { 0x3fbb, 0x00 }, + { 0xa081, 0x00 }, + { 0xe014, 0x00 }, + { 0x0202, 0x05 }, + { 0x0203, 0x34 }, + { 0x0224, 0x01 }, + { 0x0225, 0xf4 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x0216, 0x00 }, + { 0x0217, 0x00 }, + { 0x020e, 0x01 }, + { 0x020f, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x0218, 0x01 }, + { 0x0219, 0x00 }, + { 0x3614, 0x00 }, + { 0x3616, 0x0d }, + { 0x3617, 0x56 }, + { 0xb612, 0x20 }, + { 0xb613, 0x20 }, + { 0xb614, 0x20 }, + { 0xb615, 0x20 }, + { 0xb616, 0x0a }, + { 0xb617, 0x0a }, + { 0xb618, 0x20 }, + { 0xb619, 0x20 }, + { 0xb61a, 0x20 }, + { 0xb61b, 0x20 }, + { 0xb61c, 0x0a }, + { 0xb61d, 0x0a }, + { 0xb666, 0x30 }, + { 0xb667, 0x30 }, + { 0xb668, 0x30 }, + { 0xb669, 0x30 }, + { 0xb66a, 0x14 }, + { 0xb66b, 0x14 }, + { 0xb66c, 0x20 }, + { 0xb66d, 0x20 }, + { 0xb66e, 0x20 }, + { 0xb66f, 0x20 }, + { 0xb670, 0x10 }, + { 0xb671, 0x10 }, + { 0x3237, 0x00 }, + { 0x3900, 0x00 }, + { 0x3901, 0x00 }, + { 0x3902, 0x00 }, + { 0x3904, 0x00 }, + { 0x3905, 0x00 }, + { 0x3906, 0x00 }, + { 0x3907, 0x00 }, + { 0x3908, 0x00 }, + { 0x3909, 0x00 }, + { 0x3912, 0x00 }, + { 0x3930, 0x00 }, + { 0x3931, 0x00 }, + { 0x3933, 0x00 }, + { 0x3934, 0x00 }, + { 0x3935, 0x00 }, + { 0x3936, 0x00 }, + { 0x3937, 0x00 }, + { 0x30ac, 0x00 }, +}; + +static const struct imx319_reg mode_1920x1080_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0f }, + { 0x0343, 0x80 }, + { 0x0340, 0x0c }, + { 0x0341, 0xaa }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x02 }, + { 0x0347, 0xb4 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x06 }, + { 0x034b, 0xeb }, + { 0x0220, 0x00 }, + { 0x0221, 0x11 }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x0a }, + { 0x3140, 0x02 }, + { 0x3141, 0x00 }, + { 0x3f0d, 0x0a }, + { 0x3f14, 0x01 }, + { 0x3f3c, 0x01 }, + { 0x3f4d, 0x01 }, + { 0x3f4c, 0x01 }, + { 0x4254, 0x7f }, + { 0x0401, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x10 }, + { 0x0408, 0x02 }, + { 0x0409, 0xa8 }, + { 0x040a, 0x00 }, + { 0x040b, 0x00 }, + { 0x040c, 0x07 }, + { 0x040d, 0x80 }, + { 0x040e, 0x04 }, + { 0x040f, 0x38 }, + { 0x034c, 0x07 }, + { 0x034d, 0x80 }, + { 0x034e, 0x04 }, + { 0x034f, 0x38 }, + { 0x3261, 0x00 }, + { 0x3264, 0x00 }, + { 0x3265, 0x10 }, + { 0x0301, 0x05 }, + { 0x0303, 0x04 }, + { 0x0305, 0x04 }, + { 0x0306, 0x01 }, + { 0x0307, 0x92 }, + { 0x0309, 0x0a }, + { 0x030b, 0x02 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0xfa }, + { 0x0310, 0x00 }, + { 0x0820, 0x0f }, + { 0x0821, 0x13 }, + { 0x0822, 0x33 }, + { 0x0823, 0x33 }, + { 0x3e20, 0x01 }, + { 0x3e37, 0x00 }, + { 0x3e3b, 0x01 }, + { 0x38a3, 0x01 }, + { 0x38a8, 0x00 }, + { 0x38a9, 0x00 }, + { 0x38aa, 0x00 }, + { 0x38ab, 0x00 }, + { 0x3234, 0x00 }, + { 0x3fc1, 0x00 }, + { 0x3235, 0x00 }, + { 0x3802, 0x00 }, + { 0x3143, 0x04 }, + { 0x360a, 0x00 }, + { 0x0b00, 0x00 }, + { 0x0106, 0x00 }, + { 0x0b05, 0x01 }, + { 0x0b06, 0x01 }, + { 0x3230, 0x00 }, + { 0x3602, 0x01 }, + { 0x3607, 0x01 }, + { 0x3c00, 0x00 }, + { 0x3c01, 0x48 }, + { 0x3c02, 0xc8 }, + { 0x3c03, 0xaa }, + { 0x3c04, 0x91 }, + { 0x3c05, 0x54 }, + { 0x3c06, 0x26 }, + { 0x3c07, 0x20 }, + { 0x3c08, 0x51 }, + { 0x3d80, 0x00 }, + { 0x3f50, 0x00 }, + { 0x3f56, 0x00 }, + { 0x3f57, 0x30 }, + { 0x3f78, 0x01 }, + { 0x3f79, 0x18 }, + { 0x3f7c, 0x00 }, + { 0x3f7d, 0x00 }, + { 0x3fba, 0x00 }, + { 0x3fbb, 0x00 }, + { 0xa081, 0x00 }, + { 0xe014, 0x00 }, + { 0x0202, 0x05 }, + { 0x0203, 0x34 }, + { 0x0224, 0x01 }, + { 0x0225, 0xf4 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x0216, 0x00 }, + { 0x0217, 0x00 }, + { 0x020e, 0x01 }, + { 0x020f, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x0218, 0x01 }, + { 0x0219, 0x00 }, + { 0x3614, 0x00 }, + { 0x3616, 0x0d }, + { 0x3617, 0x56 }, + { 0xb612, 0x20 }, + { 0xb613, 0x20 }, + { 0xb614, 0x20 }, + { 0xb615, 0x20 }, + { 0xb616, 0x0a }, + { 0xb617, 0x0a }, + { 0xb618, 0x20 }, + { 0xb619, 0x20 }, + { 0xb61a, 0x20 }, + { 0xb61b, 0x20 }, + { 0xb61c, 0x0a }, + { 0xb61d, 0x0a }, + { 0xb666, 0x30 }, + { 0xb667, 0x30 }, + { 0xb668, 0x30 }, + { 0xb669, 0x30 }, + { 0xb66a, 0x14 }, + { 0xb66b, 0x14 }, + { 0xb66c, 0x20 }, + { 0xb66d, 0x20 }, + { 0xb66e, 0x20 }, + { 0xb66f, 0x20 }, + { 0xb670, 0x10 }, + { 0xb671, 0x10 }, + { 0x3237, 0x00 }, + { 0x3900, 0x00 }, + { 0x3901, 0x00 }, + { 0x3902, 0x00 }, + { 0x3904, 0x00 }, + { 0x3905, 0x00 }, + { 0x3906, 0x00 }, + { 0x3907, 0x00 }, + { 0x3908, 0x00 }, + { 0x3909, 0x00 }, + { 0x3912, 0x00 }, + { 0x3930, 0x00 }, + { 0x3931, 0x00 }, + { 0x3933, 0x00 }, + { 0x3934, 0x00 }, + { 0x3935, 0x00 }, + { 0x3936, 0x00 }, + { 0x3937, 0x00 }, + { 0x30ac, 0x00 }, +}; + +static const struct imx319_reg mode_1640x1232_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x08 }, + { 0x0343, 0x20 }, + { 0x0340, 0x18 }, + { 0x0341, 0x2a }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x00 }, + { 0x0347, 0x00 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x09 }, + { 0x034b, 0x9f }, + { 0x0220, 0x00 }, + { 0x0221, 0x11 }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x22 }, + { 0x0902, 0x0a }, + { 0x3140, 0x02 }, + { 0x3141, 0x00 }, + { 0x3f0d, 0x0a }, + { 0x3f14, 0x01 }, + { 0x3f3c, 0x02 }, + { 0x3f4d, 0x01 }, + { 0x3f4c, 0x01 }, + { 0x4254, 0x7f }, + { 0x0401, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x10 }, + { 0x0408, 0x00 }, + { 0x0409, 0x00 }, + { 0x040a, 0x00 }, + { 0x040b, 0x00 }, + { 0x040c, 0x06 }, + { 0x040d, 0x68 }, + { 0x040e, 0x04 }, + { 0x040f, 0xd0 }, + { 0x034c, 0x06 }, + { 0x034d, 0x68 }, + { 0x034e, 0x04 }, + { 0x034f, 0xd0 }, + { 0x3261, 0x00 }, + { 0x3264, 0x00 }, + { 0x3265, 0x10 }, + { 0x0301, 0x05 }, + { 0x0303, 0x04 }, + { 0x0305, 0x04 }, + { 0x0306, 0x01 }, + { 0x0307, 0x92 }, + { 0x0309, 0x0a }, + { 0x030b, 0x02 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0xfa }, + { 0x0310, 0x00 }, + { 0x0820, 0x0f }, + { 0x0821, 0x13 }, + { 0x0822, 0x33 }, + { 0x0823, 0x33 }, + { 0x3e20, 0x01 }, + { 0x3e37, 0x00 }, + { 0x3e3b, 0x01 }, + { 0x38a3, 0x01 }, + { 0x38a8, 0x00 }, + { 0x38a9, 0x00 }, + { 0x38aa, 0x00 }, + { 0x38ab, 0x00 }, + { 0x3234, 0x00 }, + { 0x3fc1, 0x00 }, + { 0x3235, 0x00 }, + { 0x3802, 0x00 }, + { 0x3143, 0x04 }, + { 0x360a, 0x00 }, + { 0x0b00, 0x00 }, + { 0x0106, 0x00 }, + { 0x0b05, 0x01 }, + { 0x0b06, 0x01 }, + { 0x3230, 0x00 }, + { 0x3602, 0x01 }, + { 0x3607, 0x01 }, + { 0x3c00, 0x00 }, + { 0x3c01, 0xba }, + { 0x3c02, 0xc8 }, + { 0x3c03, 0xaa }, + { 0x3c04, 0x91 }, + { 0x3c05, 0x54 }, + { 0x3c06, 0x26 }, + { 0x3c07, 0x20 }, + { 0x3c08, 0x51 }, + { 0x3d80, 0x00 }, + { 0x3f50, 0x00 }, + { 0x3f56, 0x00 }, + { 0x3f57, 0x30 }, + { 0x3f78, 0x00 }, + { 0x3f79, 0x34 }, + { 0x3f7c, 0x00 }, + { 0x3f7d, 0x00 }, + { 0x3fba, 0x00 }, + { 0x3fbb, 0x00 }, + { 0xa081, 0x04 }, + { 0xe014, 0x00 }, + { 0x0202, 0x04 }, + { 0x0203, 0xf6 }, + { 0x0224, 0x01 }, + { 0x0225, 0xf4 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x0216, 0x00 }, + { 0x0217, 0x00 }, + { 0x020e, 0x01 }, + { 0x020f, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x0218, 0x01 }, + { 0x0219, 0x00 }, + { 0x3614, 0x00 }, + { 0x3616, 0x0d }, + { 0x3617, 0x56 }, + { 0xb612, 0x20 }, + { 0xb613, 0x20 }, + { 0xb614, 0x20 }, + { 0xb615, 0x20 }, + { 0xb616, 0x0a }, + { 0xb617, 0x0a }, + { 0xb618, 0x20 }, + { 0xb619, 0x20 }, + { 0xb61a, 0x20 }, + { 0xb61b, 0x20 }, + { 0xb61c, 0x0a }, + { 0xb61d, 0x0a }, + { 0xb666, 0x30 }, + { 0xb667, 0x30 }, + { 0xb668, 0x30 }, + { 0xb669, 0x30 }, + { 0xb66a, 0x14 }, + { 0xb66b, 0x14 }, + { 0xb66c, 0x20 }, + { 0xb66d, 0x20 }, + { 0xb66e, 0x20 }, + { 0xb66f, 0x20 }, + { 0xb670, 0x10 }, + { 0xb671, 0x10 }, + { 0x3237, 0x00 }, + { 0x3900, 0x00 }, + { 0x3901, 0x00 }, + { 0x3902, 0x00 }, + { 0x3904, 0x00 }, + { 0x3905, 0x00 }, + { 0x3906, 0x00 }, + { 0x3907, 0x00 }, + { 0x3908, 0x00 }, + { 0x3909, 0x00 }, + { 0x3912, 0x00 }, + { 0x3930, 0x00 }, + { 0x3931, 0x00 }, + { 0x3933, 0x00 }, + { 0x3934, 0x00 }, + { 0x3935, 0x00 }, + { 0x3936, 0x00 }, + { 0x3937, 0x00 }, + { 0x30ac, 0x00 }, +}; + +static const struct imx319_reg mode_1640x922_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x08 }, + { 0x0343, 0x20 }, + { 0x0340, 0x18 }, + { 0x0341, 0x2a }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x01 }, + { 0x0347, 0x30 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x08 }, + { 0x034b, 0x6f }, + { 0x0220, 0x00 }, + { 0x0221, 0x11 }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x22 }, + { 0x0902, 0x0a }, + { 0x3140, 0x02 }, + { 0x3141, 0x00 }, + { 0x3f0d, 0x0a }, + { 0x3f14, 0x01 }, + { 0x3f3c, 0x02 }, + { 0x3f4d, 0x01 }, + { 0x3f4c, 0x01 }, + { 0x4254, 0x7f }, + { 0x0401, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x10 }, + { 0x0408, 0x00 }, + { 0x0409, 0x00 }, + { 0x040a, 0x00 }, + { 0x040b, 0x02 }, + { 0x040c, 0x06 }, + { 0x040d, 0x68 }, + { 0x040e, 0x03 }, + { 0x040f, 0x9a }, + { 0x034c, 0x06 }, + { 0x034d, 0x68 }, + { 0x034e, 0x03 }, + { 0x034f, 0x9a }, + { 0x3261, 0x00 }, + { 0x3264, 0x00 }, + { 0x3265, 0x10 }, + { 0x0301, 0x05 }, + { 0x0303, 0x04 }, + { 0x0305, 0x04 }, + { 0x0306, 0x01 }, + { 0x0307, 0x92 }, + { 0x0309, 0x0a }, + { 0x030b, 0x02 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0xfa }, + { 0x0310, 0x00 }, + { 0x0820, 0x0f }, + { 0x0821, 0x13 }, + { 0x0822, 0x33 }, + { 0x0823, 0x33 }, + { 0x3e20, 0x01 }, + { 0x3e37, 0x00 }, + { 0x3e3b, 0x01 }, + { 0x38a3, 0x01 }, + { 0x38a8, 0x00 }, + { 0x38a9, 0x00 }, + { 0x38aa, 0x00 }, + { 0x38ab, 0x00 }, + { 0x3234, 0x00 }, + { 0x3fc1, 0x00 }, + { 0x3235, 0x00 }, + { 0x3802, 0x00 }, + { 0x3143, 0x04 }, + { 0x360a, 0x00 }, + { 0x0b00, 0x00 }, + { 0x0106, 0x00 }, + { 0x0b05, 0x01 }, + { 0x0b06, 0x01 }, + { 0x3230, 0x00 }, + { 0x3602, 0x01 }, + { 0x3607, 0x01 }, + { 0x3c00, 0x00 }, + { 0x3c01, 0xba }, + { 0x3c02, 0xc8 }, + { 0x3c03, 0xaa }, + { 0x3c04, 0x91 }, + { 0x3c05, 0x54 }, + { 0x3c06, 0x26 }, + { 0x3c07, 0x20 }, + { 0x3c08, 0x51 }, + { 0x3d80, 0x00 }, + { 0x3f50, 0x00 }, + { 0x3f56, 0x00 }, + { 0x3f57, 0x30 }, + { 0x3f78, 0x00 }, + { 0x3f79, 0x34 }, + { 0x3f7c, 0x00 }, + { 0x3f7d, 0x00 }, + { 0x3fba, 0x00 }, + { 0x3fbb, 0x00 }, + { 0xa081, 0x04 }, + { 0xe014, 0x00 }, + { 0x0202, 0x04 }, + { 0x0203, 0xf6 }, + { 0x0224, 0x01 }, + { 0x0225, 0xf4 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x0216, 0x00 }, + { 0x0217, 0x00 }, + { 0x020e, 0x01 }, + { 0x020f, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x0218, 0x01 }, + { 0x0219, 0x00 }, + { 0x3614, 0x00 }, + { 0x3616, 0x0d }, + { 0x3617, 0x56 }, + { 0xb612, 0x20 }, + { 0xb613, 0x20 }, + { 0xb614, 0x20 }, + { 0xb615, 0x20 }, + { 0xb616, 0x0a }, + { 0xb617, 0x0a }, + { 0xb618, 0x20 }, + { 0xb619, 0x20 }, + { 0xb61a, 0x20 }, + { 0xb61b, 0x20 }, + { 0xb61c, 0x0a }, + { 0xb61d, 0x0a }, + { 0xb666, 0x30 }, + { 0xb667, 0x30 }, + { 0xb668, 0x30 }, + { 0xb669, 0x30 }, + { 0xb66a, 0x14 }, + { 0xb66b, 0x14 }, + { 0xb66c, 0x20 }, + { 0xb66d, 0x20 }, + { 0xb66e, 0x20 }, + { 0xb66f, 0x20 }, + { 0xb670, 0x10 }, + { 0xb671, 0x10 }, + { 0x3237, 0x00 }, + { 0x3900, 0x00 }, + { 0x3901, 0x00 }, + { 0x3902, 0x00 }, + { 0x3904, 0x00 }, + { 0x3905, 0x00 }, + { 0x3906, 0x00 }, + { 0x3907, 0x00 }, + { 0x3908, 0x00 }, + { 0x3909, 0x00 }, + { 0x3912, 0x00 }, + { 0x3930, 0x00 }, + { 0x3931, 0x00 }, + { 0x3933, 0x00 }, + { 0x3934, 0x00 }, + { 0x3935, 0x00 }, + { 0x3936, 0x00 }, + { 0x3937, 0x00 }, + { 0x30ac, 0x00 }, +}; + +static const struct imx319_reg mode_1296x736_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x08 }, + { 0x0343, 0x20 }, + { 0x0340, 0x18 }, + { 0x0341, 0x2a }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x01 }, + { 0x0347, 0xf0 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x07 }, + { 0x034b, 0xaf }, + { 0x0220, 0x00 }, + { 0x0221, 0x11 }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x22 }, + { 0x0902, 0x0a }, + { 0x3140, 0x02 }, + { 0x3141, 0x00 }, + { 0x3f0d, 0x0a }, + { 0x3f14, 0x01 }, + { 0x3f3c, 0x02 }, + { 0x3f4d, 0x01 }, + { 0x3f4c, 0x01 }, + { 0x4254, 0x7f }, + { 0x0401, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x10 }, + { 0x0408, 0x00 }, + { 0x0409, 0xac }, + { 0x040a, 0x00 }, + { 0x040b, 0x00 }, + { 0x040c, 0x05 }, + { 0x040d, 0x10 }, + { 0x040e, 0x02 }, + { 0x040f, 0xe0 }, + { 0x034c, 0x05 }, + { 0x034d, 0x10 }, + { 0x034e, 0x02 }, + { 0x034f, 0xe0 }, + { 0x3261, 0x00 }, + { 0x3264, 0x00 }, + { 0x3265, 0x10 }, + { 0x0301, 0x05 }, + { 0x0303, 0x04 }, + { 0x0305, 0x04 }, + { 0x0306, 0x01 }, + { 0x0307, 0x92 }, + { 0x0309, 0x0a }, + { 0x030b, 0x02 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0xfa }, + { 0x0310, 0x00 }, + { 0x0820, 0x0f }, + { 0x0821, 0x13 }, + { 0x0822, 0x33 }, + { 0x0823, 0x33 }, + { 0x3e20, 0x01 }, + { 0x3e37, 0x00 }, + { 0x3e3b, 0x01 }, + { 0x38a3, 0x01 }, + { 0x38a8, 0x00 }, + { 0x38a9, 0x00 }, + { 0x38aa, 0x00 }, + { 0x38ab, 0x00 }, + { 0x3234, 0x00 }, + { 0x3fc1, 0x00 }, + { 0x3235, 0x00 }, + { 0x3802, 0x00 }, + { 0x3143, 0x04 }, + { 0x360a, 0x00 }, + { 0x0b00, 0x00 }, + { 0x0106, 0x00 }, + { 0x0b05, 0x01 }, + { 0x0b06, 0x01 }, + { 0x3230, 0x00 }, + { 0x3602, 0x01 }, + { 0x3607, 0x01 }, + { 0x3c00, 0x00 }, + { 0x3c01, 0xba }, + { 0x3c02, 0xc8 }, + { 0x3c03, 0xaa }, + { 0x3c04, 0x91 }, + { 0x3c05, 0x54 }, + { 0x3c06, 0x26 }, + { 0x3c07, 0x20 }, + { 0x3c08, 0x51 }, + { 0x3d80, 0x00 }, + { 0x3f50, 0x00 }, + { 0x3f56, 0x00 }, + { 0x3f57, 0x30 }, + { 0x3f78, 0x00 }, + { 0x3f79, 0x34 }, + { 0x3f7c, 0x00 }, + { 0x3f7d, 0x00 }, + { 0x3fba, 0x00 }, + { 0x3fbb, 0x00 }, + { 0xa081, 0x04 }, + { 0xe014, 0x00 }, + { 0x0202, 0x04 }, + { 0x0203, 0xf6 }, + { 0x0224, 0x01 }, + { 0x0225, 0xf4 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x0216, 0x00 }, + { 0x0217, 0x00 }, + { 0x020e, 0x01 }, + { 0x020f, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x0218, 0x01 }, + { 0x0219, 0x00 }, + { 0x3614, 0x00 }, + { 0x3616, 0x0d }, + { 0x3617, 0x56 }, + { 0xb612, 0x20 }, + { 0xb613, 0x20 }, + { 0xb614, 0x20 }, + { 0xb615, 0x20 }, + { 0xb616, 0x0a }, + { 0xb617, 0x0a }, + { 0xb618, 0x20 }, + { 0xb619, 0x20 }, + { 0xb61a, 0x20 }, + { 0xb61b, 0x20 }, + { 0xb61c, 0x0a }, + { 0xb61d, 0x0a }, + { 0xb666, 0x30 }, + { 0xb667, 0x30 }, + { 0xb668, 0x30 }, + { 0xb669, 0x30 }, + { 0xb66a, 0x14 }, + { 0xb66b, 0x14 }, + { 0xb66c, 0x20 }, + { 0xb66d, 0x20 }, + { 0xb66e, 0x20 }, + { 0xb66f, 0x20 }, + { 0xb670, 0x10 }, + { 0xb671, 0x10 }, + { 0x3237, 0x00 }, + { 0x3900, 0x00 }, + { 0x3901, 0x00 }, + { 0x3902, 0x00 }, + { 0x3904, 0x00 }, + { 0x3905, 0x00 }, + { 0x3906, 0x00 }, + { 0x3907, 0x00 }, + { 0x3908, 0x00 }, + { 0x3909, 0x00 }, + { 0x3912, 0x00 }, + { 0x3930, 0x00 }, + { 0x3931, 0x00 }, + { 0x3933, 0x00 }, + { 0x3934, 0x00 }, + { 0x3935, 0x00 }, + { 0x3936, 0x00 }, + { 0x3937, 0x00 }, + { 0x30ac, 0x00 }, +}; + +static const struct imx319_reg mode_1280x720_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x08 }, + { 0x0343, 0x20 }, + { 0x0340, 0x18 }, + { 0x0341, 0x2a }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x02 }, + { 0x0347, 0x00 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x07 }, + { 0x034b, 0x9f }, + { 0x0220, 0x00 }, + { 0x0221, 0x11 }, + { 0x0381, 0x01 }, + { 0x0383, 0x01 }, + { 0x0385, 0x01 }, + { 0x0387, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x22 }, + { 0x0902, 0x0a }, + { 0x3140, 0x02 }, + { 0x3141, 0x00 }, + { 0x3f0d, 0x0a }, + { 0x3f14, 0x01 }, + { 0x3f3c, 0x02 }, + { 0x3f4d, 0x01 }, + { 0x3f4c, 0x01 }, + { 0x4254, 0x7f }, + { 0x0401, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x10 }, + { 0x0408, 0x00 }, + { 0x0409, 0xb4 }, + { 0x040a, 0x00 }, + { 0x040b, 0x00 }, + { 0x040c, 0x05 }, + { 0x040d, 0x00 }, + { 0x040e, 0x02 }, + { 0x040f, 0xd0 }, + { 0x034c, 0x05 }, + { 0x034d, 0x00 }, + { 0x034e, 0x02 }, + { 0x034f, 0xd0 }, + { 0x3261, 0x00 }, + { 0x3264, 0x00 }, + { 0x3265, 0x10 }, + { 0x0301, 0x05 }, + { 0x0303, 0x04 }, + { 0x0305, 0x04 }, + { 0x0306, 0x01 }, + { 0x0307, 0x92 }, + { 0x0309, 0x0a }, + { 0x030b, 0x02 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0xfa }, + { 0x0310, 0x00 }, + { 0x0820, 0x0f }, + { 0x0821, 0x13 }, + { 0x0822, 0x33 }, + { 0x0823, 0x33 }, + { 0x3e20, 0x01 }, + { 0x3e37, 0x00 }, + { 0x3e3b, 0x01 }, + { 0x38a3, 0x01 }, + { 0x38a8, 0x00 }, + { 0x38a9, 0x00 }, + { 0x38aa, 0x00 }, + { 0x38ab, 0x00 }, + { 0x3234, 0x00 }, + { 0x3fc1, 0x00 }, + { 0x3235, 0x00 }, + { 0x3802, 0x00 }, + { 0x3143, 0x04 }, + { 0x360a, 0x00 }, + { 0x0b00, 0x00 }, + { 0x0106, 0x00 }, + { 0x0b05, 0x01 }, + { 0x0b06, 0x01 }, + { 0x3230, 0x00 }, + { 0x3602, 0x01 }, + { 0x3607, 0x01 }, + { 0x3c00, 0x00 }, + { 0x3c01, 0xba }, + { 0x3c02, 0xc8 }, + { 0x3c03, 0xaa }, + { 0x3c04, 0x91 }, + { 0x3c05, 0x54 }, + { 0x3c06, 0x26 }, + { 0x3c07, 0x20 }, + { 0x3c08, 0x51 }, + { 0x3d80, 0x00 }, + { 0x3f50, 0x00 }, + { 0x3f56, 0x00 }, + { 0x3f57, 0x30 }, + { 0x3f78, 0x00 }, + { 0x3f79, 0x34 }, + { 0x3f7c, 0x00 }, + { 0x3f7d, 0x00 }, + { 0x3fba, 0x00 }, + { 0x3fbb, 0x00 }, + { 0xa081, 0x04 }, + { 0xe014, 0x00 }, + { 0x0202, 0x04 }, + { 0x0203, 0xf6 }, + { 0x0224, 0x01 }, + { 0x0225, 0xf4 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x0216, 0x00 }, + { 0x0217, 0x00 }, + { 0x020e, 0x01 }, + { 0x020f, 0x00 }, + { 0x0210, 0x01 }, + { 0x0211, 0x00 }, + { 0x0212, 0x01 }, + { 0x0213, 0x00 }, + { 0x0214, 0x01 }, + { 0x0215, 0x00 }, + { 0x0218, 0x01 }, + { 0x0219, 0x00 }, + { 0x3614, 0x00 }, + { 0x3616, 0x0d }, + { 0x3617, 0x56 }, + { 0xb612, 0x20 }, + { 0xb613, 0x20 }, + { 0xb614, 0x20 }, + { 0xb615, 0x20 }, + { 0xb616, 0x0a }, + { 0xb617, 0x0a }, + { 0xb618, 0x20 }, + { 0xb619, 0x20 }, + { 0xb61a, 0x20 }, + { 0xb61b, 0x20 }, + { 0xb61c, 0x0a }, + { 0xb61d, 0x0a }, + { 0xb666, 0x30 }, + { 0xb667, 0x30 }, + { 0xb668, 0x30 }, + { 0xb669, 0x30 }, + { 0xb66a, 0x14 }, + { 0xb66b, 0x14 }, + { 0xb66c, 0x20 }, + { 0xb66d, 0x20 }, + { 0xb66e, 0x20 }, + { 0xb66f, 0x20 }, + { 0xb670, 0x10 }, + { 0xb671, 0x10 }, + { 0x3237, 0x00 }, + { 0x3900, 0x00 }, + { 0x3901, 0x00 }, + { 0x3902, 0x00 }, + { 0x3904, 0x00 }, + { 0x3905, 0x00 }, + { 0x3906, 0x00 }, + { 0x3907, 0x00 }, + { 0x3908, 0x00 }, + { 0x3909, 0x00 }, + { 0x3912, 0x00 }, + { 0x3930, 0x00 }, + { 0x3931, 0x00 }, + { 0x3933, 0x00 }, + { 0x3934, 0x00 }, + { 0x3935, 0x00 }, + { 0x3936, 0x00 }, + { 0x3937, 0x00 }, + { 0x30ac, 0x00 }, +}; + +static const char * const imx319_test_pattern_menu[] = { + "Disabled", + "Solid Colour", + "Eight Vertical Colour Bars", + "Colour Bars With Fade to Grey", + "Pseudorandom Sequence (PN9)", +}; + +/* supported link frequencies */ +static const s64 link_freq_menu_items[] = { + IMX319_LINK_FREQ_DEFAULT, +}; + +/* Mode configs */ +static const struct imx319_mode supported_modes[] = { + { + .width = 3280, + .height = 2464, + .fll_def = 3242, + .fll_min = 3242, + .llp = 3968, + .link_freq_index = IMX319_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs), + .regs = mode_3280x2464_regs, + }, + }, + { + .width = 3264, + .height = 2448, + .fll_def = 3242, + .fll_min = 3242, + .llp = 3968, + .link_freq_index = IMX319_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3264x2448_regs), + .regs = mode_3264x2448_regs, + }, + }, + { + .width = 1936, + .height = 1096, + .fll_def = 3242, + .fll_min = 3242, + .llp = 3968, + .link_freq_index = IMX319_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1936x1096_regs), + .regs = mode_1936x1096_regs, + }, + }, + { + .width = 1920, + .height = 1080, + .fll_def = 3242, + .fll_min = 3242, + .llp = 3968, + .link_freq_index = IMX319_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs), + .regs = mode_1920x1080_regs, + }, + }, + { + .width = 1640, + .height = 1232, + .fll_def = 5146, + .fll_min = 5146, + .llp = 2500, + .link_freq_index = IMX319_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x1232_regs), + .regs = mode_1640x1232_regs, + }, + }, + { + .width = 1640, + .height = 922, + .fll_def = 5146, + .fll_min = 5146, + .llp = 2500, + .link_freq_index = IMX319_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x922_regs), + .regs = mode_1640x922_regs, + }, + }, + { + .width = 1296, + .height = 736, + .fll_def = 5146, + .fll_min = 5146, + .llp = 2500, + .link_freq_index = IMX319_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x736_regs), + .regs = mode_1296x736_regs, + }, + }, + { + .width = 1280, + .height = 720, + .fll_def = 5146, + .fll_min = 5146, + .llp = 2500, + .link_freq_index = IMX319_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, + }, + }, +}; + +static inline struct imx319 *to_imx319(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx319, sd); +} + +/* Get bayer order based on flip setting. */ +static u32 imx319_get_format_code(struct imx319 *imx319) +{ + /* + * Only one bayer order is supported. + * It depends on the flip settings. + */ + u32 code; + static const u32 codes[2][2] = { + { MEDIA_BUS_FMT_SRGGB10_1X10, MEDIA_BUS_FMT_SGRBG10_1X10, }, + { MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SBGGR10_1X10, }, + }; + + lockdep_assert_held(&imx319->mutex); + code = codes[imx319->vflip->val][imx319->hflip->val]; + + return code; +} + +/* Read registers up to 4 at a time */ +static int imx319_read_reg(struct imx319 *imx319, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx319->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = { 0 }; + int ret; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, addr_buf); + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 4 at a time */ +static int imx319_write_reg(struct imx319 *imx319, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx319->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx319_write_regs(struct imx319 *imx319, + const struct imx319_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx319->sd); + int ret; + u32 i; + + for (i = 0; i < len; i++) { + ret = imx319_write_reg(imx319, regs[i].address, 1, regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "write reg 0x%4.4x return err %d", + regs[i].address, ret); + return ret; + } + } + + return 0; +} + +/* Open sub-device */ +static int imx319_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct imx319 *imx319 = to_imx319(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + + mutex_lock(&imx319->mutex); + + /* Initialize try_fmt */ + try_fmt->width = imx319->cur_mode->width; + try_fmt->height = imx319->cur_mode->height; + try_fmt->code = imx319_get_format_code(imx319); + try_fmt->field = V4L2_FIELD_NONE; + + mutex_unlock(&imx319->mutex); + + return 0; +} + +static int imx319_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx319 *imx319 = container_of(ctrl->handler, + struct imx319, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx319->sd); + s64 max; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = imx319->cur_mode->height + ctrl->val - 18; + __v4l2_ctrl_modify_range(imx319->exposure, + imx319->exposure->minimum, + max, imx319->exposure->step, max); + break; + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + /* Analog gain = 1024/(1024 - ctrl->val) times */ + ret = imx319_write_reg(imx319, IMX319_REG_ANALOG_GAIN, 2, + ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx319_write_reg(imx319, IMX319_REG_DIG_GAIN_GLOBAL, 2, + ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx319_write_reg(imx319, IMX319_REG_EXPOSURE, 2, + ctrl->val); + break; + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ + ret = imx319_write_reg(imx319, IMX319_REG_FLL, 2, + imx319->cur_mode->height + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx319_write_reg(imx319, IMX319_REG_TEST_PATTERN, + 2, ctrl->val); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx319_write_reg(imx319, IMX319_REG_ORIENTATION, 1, + imx319->hflip->val | + imx319->vflip->val << 1); + break; + default: + ret = -EINVAL; + dev_info(&client->dev, "ctrl(id:0x%x,val:0x%x) is not handled", + ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx319_ctrl_ops = { + .s_ctrl = imx319_set_ctrl, +}; + +static int imx319_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx319 *imx319 = to_imx319(sd); + + if (code->index > 0) + return -EINVAL; + + mutex_lock(&imx319->mutex); + code->code = imx319_get_format_code(imx319); + mutex_unlock(&imx319->mutex); + + return 0; +} + +static int imx319_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx319 *imx319 = to_imx319(sd); + + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + mutex_lock(&imx319->mutex); + if (fse->code != imx319_get_format_code(imx319)) { + mutex_unlock(&imx319->mutex); + return -EINVAL; + } + mutex_unlock(&imx319->mutex); + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void imx319_update_pad_format(struct imx319 *imx319, + const struct imx319_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = imx319_get_format_code(imx319); + fmt->format.field = V4L2_FIELD_NONE; +} + +static int imx319_do_get_pad_format(struct imx319 *imx319, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + struct v4l2_subdev *sd = &imx319->sd; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + fmt->format = *framefmt; + } else { + imx319_update_pad_format(imx319, imx319->cur_mode, fmt); + } + + return 0; +} + +static int imx319_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx319 *imx319 = to_imx319(sd); + int ret; + + mutex_lock(&imx319->mutex); + ret = imx319_do_get_pad_format(imx319, cfg, fmt); + mutex_unlock(&imx319->mutex); + + return ret; +} + +static int +imx319_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx319 *imx319 = to_imx319(sd); + const struct imx319_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + s32 vblank_def; + s32 vblank_min; + s64 h_blank; + u64 pixel_rate; + u32 height; + + mutex_lock(&imx319->mutex); + + /* + * Only one bayer order is supported. + * It depends on the flip settings. + */ + fmt->format.code = imx319_get_format_code(imx319); + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + imx319_update_pad_format(imx319, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *framefmt = fmt->format; + } else { + imx319->cur_mode = mode; + pixel_rate = imx319->link_def_freq * 2 * 4; + do_div(pixel_rate, 10); + __v4l2_ctrl_s_ctrl_int64(imx319->pixel_rate, pixel_rate); + /* Update limits and set FPS to default */ + height = imx319->cur_mode->height; + vblank_def = imx319->cur_mode->fll_def - height; + vblank_min = imx319->cur_mode->fll_min - height; + height = IMX319_FLL_MAX - height; + __v4l2_ctrl_modify_range(imx319->vblank, vblank_min, height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(imx319->vblank, vblank_def); + h_blank = mode->llp - imx319->cur_mode->width; + /* + * Currently hblank is not changeable. + * So FPS control is done only by vblank. + */ + __v4l2_ctrl_modify_range(imx319->hblank, h_blank, + h_blank, 1, h_blank); + } + + mutex_unlock(&imx319->mutex); + + return 0; +} + +/* Start streaming */ +static int imx319_start_streaming(struct imx319 *imx319) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx319->sd); + const struct imx319_reg_list *reg_list; + int ret; + + /* Global Setting */ + reg_list = &imx319_global_setting; + ret = imx319_write_regs(imx319, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "failed to set global settings"); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &imx319->cur_mode->reg_list; + ret = imx319_write_regs(imx319, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "failed to set mode"); + return ret; + } + + /* set digital gain control to all color mode */ + ret = imx319_write_reg(imx319, IMX319_REG_DPGA_USE_GLOBAL_GAIN, 1, 1); + if (ret) + return ret; + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx319->sd.ctrl_handler); + if (ret) + return ret; + + return imx319_write_reg(imx319, IMX319_REG_MODE_SELECT, + 1, IMX319_MODE_STREAMING); +} + +/* Stop streaming */ +static int imx319_stop_streaming(struct imx319 *imx319) +{ + return imx319_write_reg(imx319, IMX319_REG_MODE_SELECT, + 1, IMX319_MODE_STANDBY); +} + +static int imx319_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx319 *imx319 = to_imx319(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&imx319->mutex); + if (imx319->streaming == enable) { + mutex_unlock(&imx319->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx319_start_streaming(imx319); + if (ret) + goto err_rpm_put; + } else { + imx319_stop_streaming(imx319); + pm_runtime_put(&client->dev); + } + + imx319->streaming = enable; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx319->vflip, enable); + __v4l2_ctrl_grab(imx319->hflip, enable); + + mutex_unlock(&imx319->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&imx319->mutex); + + return ret; +} + +static int __maybe_unused imx319_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx319 *imx319 = to_imx319(sd); + + if (imx319->streaming) + imx319_stop_streaming(imx319); + + return 0; +} + +static int __maybe_unused imx319_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx319 *imx319 = to_imx319(sd); + int ret; + + if (imx319->streaming) { + ret = imx319_start_streaming(imx319); + if (ret) + goto error; + } + + return 0; + +error: + imx319_stop_streaming(imx319); + imx319->streaming = 0; + return ret; +} + +/* Verify chip ID */ +static int imx319_identify_module(struct imx319 *imx319) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx319->sd); + int ret; + u32 val; + + ret = imx319_read_reg(imx319, IMX319_REG_CHIP_ID, 2, &val); + if (ret) + return ret; + + if (val != IMX319_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x", + IMX319_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_core_ops imx319_subdev_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx319_video_ops = { + .s_stream = imx319_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx319_pad_ops = { + .enum_mbus_code = imx319_enum_mbus_code, + .get_fmt = imx319_get_pad_format, + .set_fmt = imx319_set_pad_format, + .enum_frame_size = imx319_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx319_subdev_ops = { + .core = &imx319_subdev_core_ops, + .video = &imx319_video_ops, + .pad = &imx319_pad_ops, +}; + +static const struct media_entity_operations imx319_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops imx319_internal_ops = { + .open = imx319_open, +}; + +/* Initialize control handlers */ +static int imx319_init_controls(struct imx319 *imx319) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx319->sd); + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max; + s64 vblank_def; + s64 vblank_min; + s64 hblank; + u64 pixel_rate; + const struct imx319_mode *mode; + u32 max; + int ret; + + ctrl_hdlr = &imx319->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + ctrl_hdlr->lock = &imx319->mutex; + max = ARRAY_SIZE(link_freq_menu_items) - 1; + imx319->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx319_ctrl_ops, + V4L2_CID_LINK_FREQ, max, 0, + link_freq_menu_items); + if (imx319->link_freq) + imx319->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ + pixel_rate = imx319->link_def_freq * 2 * 4; + do_div(pixel_rate, 10); + /* By default, PIXEL_RATE is read only */ + imx319->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, + V4L2_CID_PIXEL_RATE, pixel_rate, + pixel_rate, 1, pixel_rate); + + /* Initial vblank/hblank/exposure parameters based on current mode */ + mode = imx319->cur_mode; + vblank_def = mode->fll_def - mode->height; + vblank_min = mode->fll_min - mode->height; + imx319->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, + V4L2_CID_VBLANK, vblank_min, + IMX319_FLL_MAX - mode->height, + 1, vblank_def); + + hblank = mode->llp - mode->width; + imx319->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, + 1, hblank); + if (imx319->hblank) + imx319->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* fll >= exposure time + adjust parameter (default value is 18) */ + exposure_max = mode->fll_def - 18; + imx319->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX319_EXPOSURE_MIN, exposure_max, + IMX319_EXPOSURE_STEP, + IMX319_EXPOSURE_DEFAULT); + + imx319->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + imx319->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX319_ANA_GAIN_MIN, IMX319_ANA_GAIN_MAX, + IMX319_ANA_GAIN_STEP, IMX319_ANA_GAIN_DEFAULT); + + /* Digital gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX319_DGTL_GAIN_MIN, IMX319_DGTL_GAIN_MAX, + IMX319_DGTL_GAIN_STEP, IMX319_DGTL_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx319_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx319_test_pattern_menu) - 1, + 0, 0, imx319_test_pattern_menu); + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "control init failed: %d", ret); + goto error; + } + + imx319->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static struct imx319_hwcfg *imx319_get_hwcfg(struct device *dev) +{ + struct imx319_hwcfg *cfg; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + unsigned int i; + int ret; + + if (!fwnode) + return NULL; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return NULL; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + if (ret) + goto out_err; + + cfg = devm_kzalloc(dev, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + goto out_err; + + ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &cfg->ext_clk); + if (ret) { + dev_err(dev, "can't get clock frequency"); + goto out_err; + } + + dev_dbg(dev, "ext clk: %d", cfg->ext_clk); + if (cfg->ext_clk != IMX319_EXT_CLK) { + dev_err(dev, "external clock %d is not supported", + cfg->ext_clk); + goto out_err; + } + + dev_dbg(dev, "num of link freqs: %d", bus_cfg.nr_of_link_frequencies); + if (!bus_cfg.nr_of_link_frequencies) { + dev_warn(dev, "no link frequencies defined"); + goto out_err; + } + + cfg->nr_of_link_freqs = bus_cfg.nr_of_link_frequencies; + cfg->link_freqs = devm_kcalloc(dev, + bus_cfg.nr_of_link_frequencies + 1, + sizeof(*cfg->link_freqs), GFP_KERNEL); + if (!cfg->link_freqs) + goto out_err; + + for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) { + cfg->link_freqs[i] = bus_cfg.link_frequencies[i]; + dev_dbg(dev, "link_freq[%d] = %lld", i, cfg->link_freqs[i]); + } + + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(ep); + return cfg; + +out_err: + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(ep); + return NULL; +} + +static int imx319_probe(struct i2c_client *client) +{ + struct imx319 *imx319; + int ret; + u32 i; + + imx319 = devm_kzalloc(&client->dev, sizeof(*imx319), GFP_KERNEL); + if (!imx319) + return -ENOMEM; + + mutex_init(&imx319->mutex); + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx319->sd, client, &imx319_subdev_ops); + + /* Check module identity */ + ret = imx319_identify_module(imx319); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d", ret); + goto error_probe; + } + + imx319->hwcfg = imx319_get_hwcfg(&client->dev); + if (!imx319->hwcfg) { + dev_err(&client->dev, "failed to get hwcfg"); + ret = -ENODEV; + goto error_probe; + } + + imx319->link_def_freq = link_freq_menu_items[IMX319_LINK_FREQ_INDEX]; + for (i = 0; i < imx319->hwcfg->nr_of_link_freqs; i++) { + if (imx319->hwcfg->link_freqs[i] == imx319->link_def_freq) { + dev_dbg(&client->dev, "link freq index %d matched", i); + break; + } + } + + if (i == imx319->hwcfg->nr_of_link_freqs) { + dev_err(&client->dev, "no link frequency supported"); + ret = -EINVAL; + goto error_probe; + } + + /* Set default mode to max resolution */ + imx319->cur_mode = &supported_modes[0]; + + ret = imx319_init_controls(imx319); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto error_probe; + } + + /* Initialize subdev */ + imx319->sd.internal_ops = &imx319_internal_ops; + imx319->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + imx319->sd.entity.ops = &imx319_subdev_entity_ops; + imx319->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + imx319->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&imx319->sd.entity, 1, &imx319->pad); + if (ret) { + dev_err(&client->dev, "failed to init entity pads: %d", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor_common(&imx319->sd); + if (ret < 0) + goto error_media_entity; + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&imx319->sd.entity); + +error_handler_free: + v4l2_ctrl_handler_free(imx319->sd.ctrl_handler); + +error_probe: + mutex_destroy(&imx319->mutex); + + return ret; +} + +static int imx319_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx319 *imx319 = to_imx319(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + mutex_destroy(&imx319->mutex); + + return 0; +} + +static const struct dev_pm_ops imx319_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx319_suspend, imx319_resume) +}; + +static const struct acpi_device_id imx319_acpi_ids[] = { + { "SONY319A" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(acpi, imx319_acpi_ids); + +static struct i2c_driver imx319_i2c_driver = { + .driver = { + .name = "imx319", + .pm = &imx319_pm_ops, + .acpi_match_table = ACPI_PTR(imx319_acpi_ids), + }, + .probe_new = imx319_probe, + .remove = imx319_remove, +}; +module_i2c_driver(imx319_i2c_driver); + +MODULE_AUTHOR("Qiu, Tianshu <tian.shu.qiu@intel.com>"); +MODULE_AUTHOR("Rapolu, Chiranjeevi <chiranjeevi.rapolu@intel.com>"); +MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>"); +MODULE_AUTHOR("Yang, Hyungwoo <hyungwoo.yang@intel.com>"); +MODULE_DESCRIPTION("Sony imx319 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx355.c b/drivers/media/i2c/imx355.c new file mode 100644 index 000000000..bed293b60 --- /dev/null +++ b/drivers/media/i2c/imx355.c @@ -0,0 +1,1860 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Intel Corporation + +#include <asm/unaligned.h> +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> + +#define IMX355_REG_MODE_SELECT 0x0100 +#define IMX355_MODE_STANDBY 0x00 +#define IMX355_MODE_STREAMING 0x01 + +/* Chip ID */ +#define IMX355_REG_CHIP_ID 0x0016 +#define IMX355_CHIP_ID 0x0355 + +/* V_TIMING internal */ +#define IMX355_REG_FLL 0x0340 +#define IMX355_FLL_MAX 0xffff + +/* Exposure control */ +#define IMX355_REG_EXPOSURE 0x0202 +#define IMX355_EXPOSURE_MIN 1 +#define IMX355_EXPOSURE_STEP 1 +#define IMX355_EXPOSURE_DEFAULT 0x0282 + +/* Analog gain control */ +#define IMX355_REG_ANALOG_GAIN 0x0204 +#define IMX355_ANA_GAIN_MIN 0 +#define IMX355_ANA_GAIN_MAX 960 +#define IMX355_ANA_GAIN_STEP 1 +#define IMX355_ANA_GAIN_DEFAULT 0 + +/* Digital gain control */ +#define IMX355_REG_DPGA_USE_GLOBAL_GAIN 0x3070 +#define IMX355_REG_DIG_GAIN_GLOBAL 0x020e +#define IMX355_DGTL_GAIN_MIN 256 +#define IMX355_DGTL_GAIN_MAX 4095 +#define IMX355_DGTL_GAIN_STEP 1 +#define IMX355_DGTL_GAIN_DEFAULT 256 + +/* Test Pattern Control */ +#define IMX355_REG_TEST_PATTERN 0x0600 +#define IMX355_TEST_PATTERN_DISABLED 0 +#define IMX355_TEST_PATTERN_SOLID_COLOR 1 +#define IMX355_TEST_PATTERN_COLOR_BARS 2 +#define IMX355_TEST_PATTERN_GRAY_COLOR_BARS 3 +#define IMX355_TEST_PATTERN_PN9 4 + +/* Flip Control */ +#define IMX355_REG_ORIENTATION 0x0101 + +/* default link frequency and external clock */ +#define IMX355_LINK_FREQ_DEFAULT 360000000 +#define IMX355_EXT_CLK 19200000 +#define IMX355_LINK_FREQ_INDEX 0 + +struct imx355_reg { + u16 address; + u8 val; +}; + +struct imx355_reg_list { + u32 num_of_regs; + const struct imx355_reg *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx355_mode { + /* Frame width */ + u32 width; + /* Frame height */ + u32 height; + + /* V-timing */ + u32 fll_def; + u32 fll_min; + + /* H-timing */ + u32 llp; + + /* index of link frequency */ + u32 link_freq_index; + + /* Default register values */ + struct imx355_reg_list reg_list; +}; + +struct imx355_hwcfg { + u32 ext_clk; /* sensor external clk */ + s64 *link_freqs; /* CSI-2 link frequencies */ + unsigned int nr_of_link_freqs; +}; + +struct imx355 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + + /* Current mode */ + const struct imx355_mode *cur_mode; + + struct imx355_hwcfg *hwcfg; + s64 link_def_freq; /* CSI-2 link default frequency */ + + /* + * Mutex for serialized access: + * Protect sensor set pad format and start/stop streaming safely. + * Protect access to sensor v4l2 controls. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static const struct imx355_reg imx355_global_regs[] = { + { 0x0136, 0x13 }, + { 0x0137, 0x33 }, + { 0x304e, 0x03 }, + { 0x4348, 0x16 }, + { 0x4350, 0x19 }, + { 0x4408, 0x0a }, + { 0x440c, 0x0b }, + { 0x4411, 0x5f }, + { 0x4412, 0x2c }, + { 0x4623, 0x00 }, + { 0x462c, 0x0f }, + { 0x462d, 0x00 }, + { 0x462e, 0x00 }, + { 0x4684, 0x54 }, + { 0x480a, 0x07 }, + { 0x4908, 0x07 }, + { 0x4909, 0x07 }, + { 0x490d, 0x0a }, + { 0x491e, 0x0f }, + { 0x4921, 0x06 }, + { 0x4923, 0x28 }, + { 0x4924, 0x28 }, + { 0x4925, 0x29 }, + { 0x4926, 0x29 }, + { 0x4927, 0x1f }, + { 0x4928, 0x20 }, + { 0x4929, 0x20 }, + { 0x492a, 0x20 }, + { 0x492c, 0x05 }, + { 0x492d, 0x06 }, + { 0x492e, 0x06 }, + { 0x492f, 0x06 }, + { 0x4930, 0x03 }, + { 0x4931, 0x04 }, + { 0x4932, 0x04 }, + { 0x4933, 0x05 }, + { 0x595e, 0x01 }, + { 0x5963, 0x01 }, + { 0x3030, 0x01 }, + { 0x3031, 0x01 }, + { 0x3045, 0x01 }, + { 0x4010, 0x00 }, + { 0x4011, 0x00 }, + { 0x4012, 0x00 }, + { 0x4013, 0x01 }, + { 0x68a8, 0xfe }, + { 0x68a9, 0xff }, + { 0x6888, 0x00 }, + { 0x6889, 0x00 }, + { 0x68b0, 0x00 }, + { 0x3058, 0x00 }, + { 0x305a, 0x00 }, +}; + +static const struct imx355_reg_list imx355_global_setting = { + .num_of_regs = ARRAY_SIZE(imx355_global_regs), + .regs = imx355_global_regs, +}; + +static const struct imx355_reg mode_3268x2448_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0e }, + { 0x0343, 0x58 }, + { 0x0340, 0x0a }, + { 0x0341, 0x37 }, + { 0x0344, 0x00 }, + { 0x0345, 0x08 }, + { 0x0346, 0x00 }, + { 0x0347, 0x08 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcb }, + { 0x034a, 0x09 }, + { 0x034b, 0x97 }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x00 }, + { 0x034c, 0x0c }, + { 0x034d, 0xc4 }, + { 0x034e, 0x09 }, + { 0x034f, 0x90 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_3264x2448_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0e }, + { 0x0343, 0x58 }, + { 0x0340, 0x0a }, + { 0x0341, 0x37 }, + { 0x0344, 0x00 }, + { 0x0345, 0x08 }, + { 0x0346, 0x00 }, + { 0x0347, 0x08 }, + { 0x0348, 0x0c }, + { 0x0349, 0xc7 }, + { 0x034a, 0x09 }, + { 0x034b, 0x97 }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x00 }, + { 0x034c, 0x0c }, + { 0x034d, 0xc0 }, + { 0x034e, 0x09 }, + { 0x034f, 0x90 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_3280x2464_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0e }, + { 0x0343, 0x58 }, + { 0x0340, 0x0a }, + { 0x0341, 0x37 }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x00 }, + { 0x0347, 0x00 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x09 }, + { 0x034b, 0x9f }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x00 }, + { 0x034c, 0x0c }, + { 0x034d, 0xd0 }, + { 0x034e, 0x09 }, + { 0x034f, 0xa0 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_1940x1096_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0e }, + { 0x0343, 0x58 }, + { 0x0340, 0x05 }, + { 0x0341, 0x1a }, + { 0x0344, 0x02 }, + { 0x0345, 0xa0 }, + { 0x0346, 0x02 }, + { 0x0347, 0xac }, + { 0x0348, 0x0a }, + { 0x0349, 0x33 }, + { 0x034a, 0x06 }, + { 0x034b, 0xf3 }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x00 }, + { 0x034c, 0x07 }, + { 0x034d, 0x94 }, + { 0x034e, 0x04 }, + { 0x034f, 0x48 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_1936x1096_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0e }, + { 0x0343, 0x58 }, + { 0x0340, 0x05 }, + { 0x0341, 0x1a }, + { 0x0344, 0x02 }, + { 0x0345, 0xa0 }, + { 0x0346, 0x02 }, + { 0x0347, 0xac }, + { 0x0348, 0x0a }, + { 0x0349, 0x2f }, + { 0x034a, 0x06 }, + { 0x034b, 0xf3 }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x00 }, + { 0x034c, 0x07 }, + { 0x034d, 0x90 }, + { 0x034e, 0x04 }, + { 0x034f, 0x48 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_1924x1080_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0e }, + { 0x0343, 0x58 }, + { 0x0340, 0x05 }, + { 0x0341, 0x1a }, + { 0x0344, 0x02 }, + { 0x0345, 0xa8 }, + { 0x0346, 0x02 }, + { 0x0347, 0xb4 }, + { 0x0348, 0x0a }, + { 0x0349, 0x2b }, + { 0x034a, 0x06 }, + { 0x034b, 0xeb }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x00 }, + { 0x034c, 0x07 }, + { 0x034d, 0x84 }, + { 0x034e, 0x04 }, + { 0x034f, 0x38 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_1920x1080_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0e }, + { 0x0343, 0x58 }, + { 0x0340, 0x05 }, + { 0x0341, 0x1a }, + { 0x0344, 0x02 }, + { 0x0345, 0xa8 }, + { 0x0346, 0x02 }, + { 0x0347, 0xb4 }, + { 0x0348, 0x0a }, + { 0x0349, 0x27 }, + { 0x034a, 0x06 }, + { 0x034b, 0xeb }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x00 }, + { 0x0901, 0x11 }, + { 0x0902, 0x00 }, + { 0x034c, 0x07 }, + { 0x034d, 0x80 }, + { 0x034e, 0x04 }, + { 0x034f, 0x38 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_1640x1232_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x07 }, + { 0x0343, 0x2c }, + { 0x0340, 0x05 }, + { 0x0341, 0x1a }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x00 }, + { 0x0347, 0x00 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x09 }, + { 0x034b, 0x9f }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x22 }, + { 0x0902, 0x00 }, + { 0x034c, 0x06 }, + { 0x034d, 0x68 }, + { 0x034e, 0x04 }, + { 0x034f, 0xd0 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_1640x922_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x07 }, + { 0x0343, 0x2c }, + { 0x0340, 0x05 }, + { 0x0341, 0x1a }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x01 }, + { 0x0347, 0x30 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x08 }, + { 0x034b, 0x63 }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x22 }, + { 0x0902, 0x00 }, + { 0x034c, 0x06 }, + { 0x034d, 0x68 }, + { 0x034e, 0x03 }, + { 0x034f, 0x9a }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_1300x736_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x07 }, + { 0x0343, 0x2c }, + { 0x0340, 0x05 }, + { 0x0341, 0x1a }, + { 0x0344, 0x01 }, + { 0x0345, 0x58 }, + { 0x0346, 0x01 }, + { 0x0347, 0xf0 }, + { 0x0348, 0x0b }, + { 0x0349, 0x7f }, + { 0x034a, 0x07 }, + { 0x034b, 0xaf }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x22 }, + { 0x0902, 0x00 }, + { 0x034c, 0x05 }, + { 0x034d, 0x14 }, + { 0x034e, 0x02 }, + { 0x034f, 0xe0 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_1296x736_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x07 }, + { 0x0343, 0x2c }, + { 0x0340, 0x05 }, + { 0x0341, 0x1a }, + { 0x0344, 0x01 }, + { 0x0345, 0x58 }, + { 0x0346, 0x01 }, + { 0x0347, 0xf0 }, + { 0x0348, 0x0b }, + { 0x0349, 0x77 }, + { 0x034a, 0x07 }, + { 0x034b, 0xaf }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x22 }, + { 0x0902, 0x00 }, + { 0x034c, 0x05 }, + { 0x034d, 0x10 }, + { 0x034e, 0x02 }, + { 0x034f, 0xe0 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_1284x720_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x07 }, + { 0x0343, 0x2c }, + { 0x0340, 0x05 }, + { 0x0341, 0x1a }, + { 0x0344, 0x01 }, + { 0x0345, 0x68 }, + { 0x0346, 0x02 }, + { 0x0347, 0x00 }, + { 0x0348, 0x0b }, + { 0x0349, 0x6f }, + { 0x034a, 0x07 }, + { 0x034b, 0x9f }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x22 }, + { 0x0902, 0x00 }, + { 0x034c, 0x05 }, + { 0x034d, 0x04 }, + { 0x034e, 0x02 }, + { 0x034f, 0xd0 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_1280x720_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x07 }, + { 0x0343, 0x2c }, + { 0x0340, 0x05 }, + { 0x0341, 0x1a }, + { 0x0344, 0x01 }, + { 0x0345, 0x68 }, + { 0x0346, 0x02 }, + { 0x0347, 0x00 }, + { 0x0348, 0x0b }, + { 0x0349, 0x67 }, + { 0x034a, 0x07 }, + { 0x034b, 0x9f }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x22 }, + { 0x0902, 0x00 }, + { 0x034c, 0x05 }, + { 0x034d, 0x00 }, + { 0x034e, 0x02 }, + { 0x034f, 0xd0 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x00 }, + { 0x0701, 0x10 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const struct imx355_reg mode_820x616_regs[] = { + { 0x0112, 0x0a }, + { 0x0113, 0x0a }, + { 0x0114, 0x03 }, + { 0x0342, 0x0e }, + { 0x0343, 0x58 }, + { 0x0340, 0x02 }, + { 0x0341, 0x8c }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x00 }, + { 0x0347, 0x00 }, + { 0x0348, 0x0c }, + { 0x0349, 0xcf }, + { 0x034a, 0x09 }, + { 0x034b, 0x9f }, + { 0x0220, 0x00 }, + { 0x0222, 0x01 }, + { 0x0900, 0x01 }, + { 0x0901, 0x44 }, + { 0x0902, 0x00 }, + { 0x034c, 0x03 }, + { 0x034d, 0x34 }, + { 0x034e, 0x02 }, + { 0x034f, 0x68 }, + { 0x0301, 0x05 }, + { 0x0303, 0x01 }, + { 0x0305, 0x02 }, + { 0x0306, 0x00 }, + { 0x0307, 0x78 }, + { 0x030b, 0x01 }, + { 0x030d, 0x02 }, + { 0x030e, 0x00 }, + { 0x030f, 0x4b }, + { 0x0310, 0x00 }, + { 0x0700, 0x02 }, + { 0x0701, 0x78 }, + { 0x0820, 0x0b }, + { 0x0821, 0x40 }, + { 0x3088, 0x04 }, + { 0x6813, 0x02 }, + { 0x6835, 0x07 }, + { 0x6836, 0x01 }, + { 0x6837, 0x04 }, + { 0x684d, 0x07 }, + { 0x684e, 0x01 }, + { 0x684f, 0x04 }, +}; + +static const char * const imx355_test_pattern_menu[] = { + "Disabled", + "Solid Colour", + "Eight Vertical Colour Bars", + "Colour Bars With Fade to Grey", + "Pseudorandom Sequence (PN9)", +}; + +/* supported link frequencies */ +static const s64 link_freq_menu_items[] = { + IMX355_LINK_FREQ_DEFAULT, +}; + +/* Mode configs */ +static const struct imx355_mode supported_modes[] = { + { + .width = 3280, + .height = 2464, + .fll_def = 2615, + .fll_min = 2615, + .llp = 3672, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs), + .regs = mode_3280x2464_regs, + }, + }, + { + .width = 3268, + .height = 2448, + .fll_def = 2615, + .fll_min = 2615, + .llp = 3672, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3268x2448_regs), + .regs = mode_3268x2448_regs, + }, + }, + { + .width = 3264, + .height = 2448, + .fll_def = 2615, + .fll_min = 2615, + .llp = 3672, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3264x2448_regs), + .regs = mode_3264x2448_regs, + }, + }, + { + .width = 1940, + .height = 1096, + .fll_def = 1306, + .fll_min = 1306, + .llp = 3672, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1940x1096_regs), + .regs = mode_1940x1096_regs, + }, + }, + { + .width = 1936, + .height = 1096, + .fll_def = 1306, + .fll_min = 1306, + .llp = 3672, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1936x1096_regs), + .regs = mode_1936x1096_regs, + }, + }, + { + .width = 1924, + .height = 1080, + .fll_def = 1306, + .fll_min = 1306, + .llp = 3672, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1924x1080_regs), + .regs = mode_1924x1080_regs, + }, + }, + { + .width = 1920, + .height = 1080, + .fll_def = 1306, + .fll_min = 1306, + .llp = 3672, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs), + .regs = mode_1920x1080_regs, + }, + }, + { + .width = 1640, + .height = 1232, + .fll_def = 1306, + .fll_min = 1306, + .llp = 1836, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x1232_regs), + .regs = mode_1640x1232_regs, + }, + }, + { + .width = 1640, + .height = 922, + .fll_def = 1306, + .fll_min = 1306, + .llp = 1836, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x922_regs), + .regs = mode_1640x922_regs, + }, + }, + { + .width = 1300, + .height = 736, + .fll_def = 1306, + .fll_min = 1306, + .llp = 1836, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1300x736_regs), + .regs = mode_1300x736_regs, + }, + }, + { + .width = 1296, + .height = 736, + .fll_def = 1306, + .fll_min = 1306, + .llp = 1836, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x736_regs), + .regs = mode_1296x736_regs, + }, + }, + { + .width = 1284, + .height = 720, + .fll_def = 1306, + .fll_min = 1306, + .llp = 1836, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1284x720_regs), + .regs = mode_1284x720_regs, + }, + }, + { + .width = 1280, + .height = 720, + .fll_def = 1306, + .fll_min = 1306, + .llp = 1836, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, + }, + }, + { + .width = 820, + .height = 616, + .fll_def = 652, + .fll_min = 652, + .llp = 3672, + .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_820x616_regs), + .regs = mode_820x616_regs, + }, + }, +}; + +static inline struct imx355 *to_imx355(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx355, sd); +} + +/* Get bayer order based on flip setting. */ +static u32 imx355_get_format_code(struct imx355 *imx355) +{ + /* + * Only one bayer order is supported. + * It depends on the flip settings. + */ + u32 code; + static const u32 codes[2][2] = { + { MEDIA_BUS_FMT_SRGGB10_1X10, MEDIA_BUS_FMT_SGRBG10_1X10, }, + { MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SBGGR10_1X10, }, + }; + + lockdep_assert_held(&imx355->mutex); + code = codes[imx355->vflip->val][imx355->hflip->val]; + + return code; +} + +/* Read registers up to 4 at a time */ +static int imx355_read_reg(struct imx355 *imx355, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = { 0 }; + int ret; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, addr_buf); + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 4 at a time */ +static int imx355_write_reg(struct imx355 *imx355, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx355_write_regs(struct imx355 *imx355, + const struct imx355_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); + int ret; + u32 i; + + for (i = 0; i < len; i++) { + ret = imx355_write_reg(imx355, regs[i].address, 1, regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "write reg 0x%4.4x return err %d", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Open sub-device */ +static int imx355_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct imx355 *imx355 = to_imx355(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + + mutex_lock(&imx355->mutex); + + /* Initialize try_fmt */ + try_fmt->width = imx355->cur_mode->width; + try_fmt->height = imx355->cur_mode->height; + try_fmt->code = imx355_get_format_code(imx355); + try_fmt->field = V4L2_FIELD_NONE; + + mutex_unlock(&imx355->mutex); + + return 0; +} + +static int imx355_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx355 *imx355 = container_of(ctrl->handler, + struct imx355, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); + s64 max; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = imx355->cur_mode->height + ctrl->val - 10; + __v4l2_ctrl_modify_range(imx355->exposure, + imx355->exposure->minimum, + max, imx355->exposure->step, max); + break; + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + /* Analog gain = 1024/(1024 - ctrl->val) times */ + ret = imx355_write_reg(imx355, IMX355_REG_ANALOG_GAIN, 2, + ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx355_write_reg(imx355, IMX355_REG_DIG_GAIN_GLOBAL, 2, + ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx355_write_reg(imx355, IMX355_REG_EXPOSURE, 2, + ctrl->val); + break; + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ + ret = imx355_write_reg(imx355, IMX355_REG_FLL, 2, + imx355->cur_mode->height + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx355_write_reg(imx355, IMX355_REG_TEST_PATTERN, + 2, ctrl->val); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx355_write_reg(imx355, IMX355_REG_ORIENTATION, 1, + imx355->hflip->val | + imx355->vflip->val << 1); + break; + default: + ret = -EINVAL; + dev_info(&client->dev, "ctrl(id:0x%x,val:0x%x) is not handled", + ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx355_ctrl_ops = { + .s_ctrl = imx355_set_ctrl, +}; + +static int imx355_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx355 *imx355 = to_imx355(sd); + + if (code->index > 0) + return -EINVAL; + + mutex_lock(&imx355->mutex); + code->code = imx355_get_format_code(imx355); + mutex_unlock(&imx355->mutex); + + return 0; +} + +static int imx355_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx355 *imx355 = to_imx355(sd); + + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + mutex_lock(&imx355->mutex); + if (fse->code != imx355_get_format_code(imx355)) { + mutex_unlock(&imx355->mutex); + return -EINVAL; + } + mutex_unlock(&imx355->mutex); + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void imx355_update_pad_format(struct imx355 *imx355, + const struct imx355_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = imx355_get_format_code(imx355); + fmt->format.field = V4L2_FIELD_NONE; +} + +static int imx355_do_get_pad_format(struct imx355 *imx355, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + struct v4l2_subdev *sd = &imx355->sd; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + fmt->format = *framefmt; + } else { + imx355_update_pad_format(imx355, imx355->cur_mode, fmt); + } + + return 0; +} + +static int imx355_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx355 *imx355 = to_imx355(sd); + int ret; + + mutex_lock(&imx355->mutex); + ret = imx355_do_get_pad_format(imx355, cfg, fmt); + mutex_unlock(&imx355->mutex); + + return ret; +} + +static int +imx355_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx355 *imx355 = to_imx355(sd); + const struct imx355_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + s32 vblank_def; + s32 vblank_min; + s64 h_blank; + u64 pixel_rate; + u32 height; + + mutex_lock(&imx355->mutex); + + /* + * Only one bayer order is supported. + * It depends on the flip settings. + */ + fmt->format.code = imx355_get_format_code(imx355); + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + imx355_update_pad_format(imx355, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *framefmt = fmt->format; + } else { + imx355->cur_mode = mode; + pixel_rate = imx355->link_def_freq * 2 * 4; + do_div(pixel_rate, 10); + __v4l2_ctrl_s_ctrl_int64(imx355->pixel_rate, pixel_rate); + /* Update limits and set FPS to default */ + height = imx355->cur_mode->height; + vblank_def = imx355->cur_mode->fll_def - height; + vblank_min = imx355->cur_mode->fll_min - height; + height = IMX355_FLL_MAX - height; + __v4l2_ctrl_modify_range(imx355->vblank, vblank_min, height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(imx355->vblank, vblank_def); + h_blank = mode->llp - imx355->cur_mode->width; + /* + * Currently hblank is not changeable. + * So FPS control is done only by vblank. + */ + __v4l2_ctrl_modify_range(imx355->hblank, h_blank, + h_blank, 1, h_blank); + } + + mutex_unlock(&imx355->mutex); + + return 0; +} + +/* Start streaming */ +static int imx355_start_streaming(struct imx355 *imx355) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); + const struct imx355_reg_list *reg_list; + int ret; + + /* Global Setting */ + reg_list = &imx355_global_setting; + ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "failed to set global settings"); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &imx355->cur_mode->reg_list; + ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "failed to set mode"); + return ret; + } + + /* set digital gain control to all color mode */ + ret = imx355_write_reg(imx355, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, 1); + if (ret) + return ret; + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler); + if (ret) + return ret; + + return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT, + 1, IMX355_MODE_STREAMING); +} + +/* Stop streaming */ +static int imx355_stop_streaming(struct imx355 *imx355) +{ + return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT, + 1, IMX355_MODE_STANDBY); +} + +static int imx355_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx355 *imx355 = to_imx355(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&imx355->mutex); + if (imx355->streaming == enable) { + mutex_unlock(&imx355->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx355_start_streaming(imx355); + if (ret) + goto err_rpm_put; + } else { + imx355_stop_streaming(imx355); + pm_runtime_put(&client->dev); + } + + imx355->streaming = enable; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx355->vflip, enable); + __v4l2_ctrl_grab(imx355->hflip, enable); + + mutex_unlock(&imx355->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&imx355->mutex); + + return ret; +} + +static int __maybe_unused imx355_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx355 *imx355 = to_imx355(sd); + + if (imx355->streaming) + imx355_stop_streaming(imx355); + + return 0; +} + +static int __maybe_unused imx355_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx355 *imx355 = to_imx355(sd); + int ret; + + if (imx355->streaming) { + ret = imx355_start_streaming(imx355); + if (ret) + goto error; + } + + return 0; + +error: + imx355_stop_streaming(imx355); + imx355->streaming = 0; + return ret; +} + +/* Verify chip ID */ +static int imx355_identify_module(struct imx355 *imx355) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); + int ret; + u32 val; + + ret = imx355_read_reg(imx355, IMX355_REG_CHIP_ID, 2, &val); + if (ret) + return ret; + + if (val != IMX355_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x", + IMX355_CHIP_ID, val); + return -EIO; + } + return 0; +} + +static const struct v4l2_subdev_core_ops imx355_subdev_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx355_video_ops = { + .s_stream = imx355_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx355_pad_ops = { + .enum_mbus_code = imx355_enum_mbus_code, + .get_fmt = imx355_get_pad_format, + .set_fmt = imx355_set_pad_format, + .enum_frame_size = imx355_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx355_subdev_ops = { + .core = &imx355_subdev_core_ops, + .video = &imx355_video_ops, + .pad = &imx355_pad_ops, +}; + +static const struct media_entity_operations imx355_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops imx355_internal_ops = { + .open = imx355_open, +}; + +/* Initialize control handlers */ +static int imx355_init_controls(struct imx355 *imx355) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max; + s64 vblank_def; + s64 vblank_min; + s64 hblank; + u64 pixel_rate; + const struct imx355_mode *mode; + u32 max; + int ret; + + ctrl_hdlr = &imx355->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + ctrl_hdlr->lock = &imx355->mutex; + max = ARRAY_SIZE(link_freq_menu_items) - 1; + imx355->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_LINK_FREQ, max, 0, + link_freq_menu_items); + if (imx355->link_freq) + imx355->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ + pixel_rate = imx355->link_def_freq * 2 * 4; + do_div(pixel_rate, 10); + /* By default, PIXEL_RATE is read only */ + imx355->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_PIXEL_RATE, pixel_rate, + pixel_rate, 1, pixel_rate); + + /* Initialize vblank/hblank/exposure parameters based on current mode */ + mode = imx355->cur_mode; + vblank_def = mode->fll_def - mode->height; + vblank_min = mode->fll_min - mode->height; + imx355->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_VBLANK, vblank_min, + IMX355_FLL_MAX - mode->height, + 1, vblank_def); + + hblank = mode->llp - mode->width; + imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, + 1, hblank); + if (imx355->hblank) + imx355->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* fll >= exposure time + adjust parameter (default value is 10) */ + exposure_max = mode->fll_def - 10; + imx355->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX355_EXPOSURE_MIN, exposure_max, + IMX355_EXPOSURE_STEP, + IMX355_EXPOSURE_DEFAULT); + + imx355->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + imx355->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX355_ANA_GAIN_MIN, IMX355_ANA_GAIN_MAX, + IMX355_ANA_GAIN_STEP, IMX355_ANA_GAIN_DEFAULT); + + /* Digital gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX355_DGTL_GAIN_MIN, IMX355_DGTL_GAIN_MAX, + IMX355_DGTL_GAIN_STEP, IMX355_DGTL_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx355_test_pattern_menu) - 1, + 0, 0, imx355_test_pattern_menu); + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "control init failed: %d", ret); + goto error; + } + + imx355->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static struct imx355_hwcfg *imx355_get_hwcfg(struct device *dev) +{ + struct imx355_hwcfg *cfg; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + unsigned int i; + int ret; + + if (!fwnode) + return NULL; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return NULL; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + if (ret) + goto out_err; + + cfg = devm_kzalloc(dev, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + goto out_err; + + ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &cfg->ext_clk); + if (ret) { + dev_err(dev, "can't get clock frequency"); + goto out_err; + } + + dev_dbg(dev, "ext clk: %d", cfg->ext_clk); + if (cfg->ext_clk != IMX355_EXT_CLK) { + dev_err(dev, "external clock %d is not supported", + cfg->ext_clk); + goto out_err; + } + + dev_dbg(dev, "num of link freqs: %d", bus_cfg.nr_of_link_frequencies); + if (!bus_cfg.nr_of_link_frequencies) { + dev_warn(dev, "no link frequencies defined"); + goto out_err; + } + + cfg->nr_of_link_freqs = bus_cfg.nr_of_link_frequencies; + cfg->link_freqs = devm_kcalloc(dev, + bus_cfg.nr_of_link_frequencies + 1, + sizeof(*cfg->link_freqs), GFP_KERNEL); + if (!cfg->link_freqs) + goto out_err; + + for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) { + cfg->link_freqs[i] = bus_cfg.link_frequencies[i]; + dev_dbg(dev, "link_freq[%d] = %lld", i, cfg->link_freqs[i]); + } + + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(ep); + return cfg; + +out_err: + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(ep); + return NULL; +} + +static int imx355_probe(struct i2c_client *client) +{ + struct imx355 *imx355; + int ret; + u32 i; + + imx355 = devm_kzalloc(&client->dev, sizeof(*imx355), GFP_KERNEL); + if (!imx355) + return -ENOMEM; + + mutex_init(&imx355->mutex); + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx355->sd, client, &imx355_subdev_ops); + + /* Check module identity */ + ret = imx355_identify_module(imx355); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d", ret); + goto error_probe; + } + + imx355->hwcfg = imx355_get_hwcfg(&client->dev); + if (!imx355->hwcfg) { + dev_err(&client->dev, "failed to get hwcfg"); + ret = -ENODEV; + goto error_probe; + } + + imx355->link_def_freq = link_freq_menu_items[IMX355_LINK_FREQ_INDEX]; + for (i = 0; i < imx355->hwcfg->nr_of_link_freqs; i++) { + if (imx355->hwcfg->link_freqs[i] == imx355->link_def_freq) { + dev_dbg(&client->dev, "link freq index %d matched", i); + break; + } + } + + if (i == imx355->hwcfg->nr_of_link_freqs) { + dev_err(&client->dev, "no link frequency supported"); + ret = -EINVAL; + goto error_probe; + } + + /* Set default mode to max resolution */ + imx355->cur_mode = &supported_modes[0]; + + ret = imx355_init_controls(imx355); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto error_probe; + } + + /* Initialize subdev */ + imx355->sd.internal_ops = &imx355_internal_ops; + imx355->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + imx355->sd.entity.ops = &imx355_subdev_entity_ops; + imx355->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + imx355->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&imx355->sd.entity, 1, &imx355->pad); + if (ret) { + dev_err(&client->dev, "failed to init entity pads: %d", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor_common(&imx355->sd); + if (ret < 0) + goto error_media_entity; + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&imx355->sd.entity); + +error_handler_free: + v4l2_ctrl_handler_free(imx355->sd.ctrl_handler); + +error_probe: + mutex_destroy(&imx355->mutex); + + return ret; +} + +static int imx355_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx355 *imx355 = to_imx355(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + mutex_destroy(&imx355->mutex); + + return 0; +} + +static const struct dev_pm_ops imx355_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx355_suspend, imx355_resume) +}; + +static const struct acpi_device_id imx355_acpi_ids[] = { + { "SONY355A" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(acpi, imx355_acpi_ids); + +static struct i2c_driver imx355_i2c_driver = { + .driver = { + .name = "imx355", + .pm = &imx355_pm_ops, + .acpi_match_table = ACPI_PTR(imx355_acpi_ids), + }, + .probe_new = imx355_probe, + .remove = imx355_remove, +}; +module_i2c_driver(imx355_i2c_driver); + +MODULE_AUTHOR("Qiu, Tianshu <tian.shu.qiu@intel.com>"); +MODULE_AUTHOR("Rapolu, Chiranjeevi <chiranjeevi.rapolu@intel.com>"); +MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>"); +MODULE_AUTHOR("Yang, Hyungwoo <hyungwoo.yang@intel.com>"); +MODULE_DESCRIPTION("Sony imx355 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ir-kbd-i2c.c b/drivers/media/i2c/ir-kbd-i2c.c new file mode 100644 index 000000000..566741735 --- /dev/null +++ b/drivers/media/i2c/ir-kbd-i2c.c @@ -0,0 +1,956 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * keyboard input driver for i2c IR remote controls + * + * Copyright (c) 2000-2003 Gerd Knorr <kraxel@bytesex.org> + * modified for PixelView (BT878P+W/FM) by + * Michal Kochanowicz <mkochano@pld.org.pl> + * Christoph Bartelmus <lirc@bartelmus.de> + * modified for KNC ONE TV Station/Anubis Typhoon TView Tuner by + * Ulrich Mueller <ulrich.mueller42@web.de> + * modified for em2820 based USB TV tuners by + * Markus Rechberger <mrechberger@gmail.com> + * modified for DViCO Fusion HDTV 5 RT GOLD by + * Chaogui Zhang <czhang1974@gmail.com> + * modified for MSI TV@nywhere Plus by + * Henry Wong <henry@stuffedcow.net> + * Mark Schultz <n9xmj@yahoo.com> + * Brian Rogers <brian_rogers@comcast.net> + * modified for AVerMedia Cardbus by + * Oldrich Jedlicka <oldium.pro@seznam.cz> + * Zilog Transmitter portions/ideas were derived from GPLv2+ sources: + * - drivers/char/pctv_zilogir.[ch] from Hauppauge Broadway product + * Copyright 2011 Hauppauge Computer works + * - drivers/staging/media/lirc/lirc_zilog.c + * Copyright (c) 2000 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * Michal Kochanowicz <mkochano@pld.org.pl> + * Christoph Bartelmus <lirc@bartelmus.de> + * Ulrich Mueller <ulrich.mueller42@web.de> + * Stefan Jahn <stefan@lkcc.org> + * Jerome Brock <jbrock@users.sourceforge.net> + * Thomas Reitmayr (treitmayr@yahoo.com) + * Mark Weaver <mark@npsl.co.uk> + * Jarod Wilson <jarod@redhat.com> + * Copyright (C) 2011 Andy Walls <awalls@md.metrocast.net> + */ + +#include <asm/unaligned.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> + +#include <media/rc-core.h> +#include <media/i2c/ir-kbd-i2c.h> + +#define FLAG_TX 1 +#define FLAG_HDPVR 2 + +static bool enable_hdpvr; +module_param(enable_hdpvr, bool, 0644); + +static int get_key_haup_common(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *ptoggle, int size) +{ + unsigned char buf[6]; + int start, range, toggle, dev, code, ircode, vendor; + + /* poll IR chip */ + if (size != i2c_master_recv(ir->c, buf, size)) + return -EIO; + + if (buf[0] & 0x80) { + int offset = (size == 6) ? 3 : 0; + + /* split rc5 data block ... */ + start = (buf[offset] >> 7) & 1; + range = (buf[offset] >> 6) & 1; + toggle = (buf[offset] >> 5) & 1; + dev = buf[offset] & 0x1f; + code = (buf[offset+1] >> 2) & 0x3f; + + /* rc5 has two start bits + * the first bit must be one + * the second bit defines the command range: + * 1 = 0-63, 0 = 64 - 127 + */ + if (!start) + /* no key pressed */ + return 0; + + /* filter out invalid key presses */ + ircode = (start << 12) | (toggle << 11) | (dev << 6) | code; + if ((ircode & 0x1fff) == 0x1fff) + return 0; + + if (!range) + code += 64; + + dev_dbg(&ir->rc->dev, + "ir hauppauge (rc5): s%d r%d t%d dev=%d code=%d\n", + start, range, toggle, dev, code); + + *protocol = RC_PROTO_RC5; + *scancode = RC_SCANCODE_RC5(dev, code); + *ptoggle = toggle; + + return 1; + } else if (size == 6 && (buf[0] & 0x40)) { + code = buf[4]; + dev = buf[3]; + vendor = get_unaligned_be16(buf + 1); + + if (vendor == 0x800f) { + *ptoggle = (dev & 0x80) != 0; + *protocol = RC_PROTO_RC6_MCE; + dev &= 0x7f; + dev_dbg(&ir->rc->dev, + "ir hauppauge (rc6-mce): t%d vendor=%d dev=%d code=%d\n", + *ptoggle, vendor, dev, code); + } else { + *ptoggle = 0; + *protocol = RC_PROTO_RC6_6A_32; + dev_dbg(&ir->rc->dev, + "ir hauppauge (rc6-6a-32): vendor=%d dev=%d code=%d\n", + vendor, dev, code); + } + + *scancode = RC_SCANCODE_RC6_6A(vendor, dev, code); + + return 1; + } + + return 0; +} + +static int get_key_haup(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + return get_key_haup_common(ir, protocol, scancode, toggle, 3); +} + +static int get_key_haup_xvr(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + int ret; + unsigned char buf[1] = { 0 }; + + /* + * This is the same apparent "are you ready?" poll command observed + * watching Windows driver traffic and implemented in lirc_zilog. With + * this added, we get far saner remote behavior with z8 chips on usb + * connected devices, even with the default polling interval of 100ms. + */ + ret = i2c_master_send(ir->c, buf, 1); + if (ret != 1) + return (ret < 0) ? ret : -EINVAL; + + return get_key_haup_common(ir, protocol, scancode, toggle, 6); +} + +static int get_key_pixelview(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + int rc; + unsigned char b; + + /* poll IR chip */ + rc = i2c_master_recv(ir->c, &b, 1); + if (rc != 1) { + dev_dbg(&ir->rc->dev, "read error\n"); + if (rc < 0) + return rc; + return -EIO; + } + + *protocol = RC_PROTO_OTHER; + *scancode = b; + *toggle = 0; + return 1; +} + +static int get_key_fusionhdtv(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + int rc; + unsigned char buf[4]; + + /* poll IR chip */ + rc = i2c_master_recv(ir->c, buf, 4); + if (rc != 4) { + dev_dbg(&ir->rc->dev, "read error\n"); + if (rc < 0) + return rc; + return -EIO; + } + + if (buf[0] != 0 || buf[1] != 0 || buf[2] != 0 || buf[3] != 0) + dev_dbg(&ir->rc->dev, "%s: %*ph\n", __func__, 4, buf); + + /* no key pressed or signal from other ir remote */ + if(buf[0] != 0x1 || buf[1] != 0xfe) + return 0; + + *protocol = RC_PROTO_UNKNOWN; + *scancode = buf[2]; + *toggle = 0; + return 1; +} + +static int get_key_knc1(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + int rc; + unsigned char b; + + /* poll IR chip */ + rc = i2c_master_recv(ir->c, &b, 1); + if (rc != 1) { + dev_dbg(&ir->rc->dev, "read error\n"); + if (rc < 0) + return rc; + return -EIO; + } + + /* it seems that 0xFE indicates that a button is still hold + down, while 0xff indicates that no button is hold + down. 0xfe sequences are sometimes interrupted by 0xFF */ + + dev_dbg(&ir->rc->dev, "key %02x\n", b); + + if (b == 0xff) + return 0; + + if (b == 0xfe) + /* keep old data */ + return 1; + + *protocol = RC_PROTO_UNKNOWN; + *scancode = b; + *toggle = 0; + return 1; +} + +static int get_key_avermedia_cardbus(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + unsigned char subaddr, key, keygroup; + struct i2c_msg msg[] = { { .addr = ir->c->addr, .flags = 0, + .buf = &subaddr, .len = 1}, + { .addr = ir->c->addr, .flags = I2C_M_RD, + .buf = &key, .len = 1} }; + subaddr = 0x0d; + if (2 != i2c_transfer(ir->c->adapter, msg, 2)) { + dev_dbg(&ir->rc->dev, "read error\n"); + return -EIO; + } + + if (key == 0xff) + return 0; + + subaddr = 0x0b; + msg[1].buf = &keygroup; + if (2 != i2c_transfer(ir->c->adapter, msg, 2)) { + dev_dbg(&ir->rc->dev, "read error\n"); + return -EIO; + } + + if (keygroup == 0xff) + return 0; + + dev_dbg(&ir->rc->dev, "read key 0x%02x/0x%02x\n", key, keygroup); + if (keygroup < 2 || keygroup > 4) { + dev_warn(&ir->rc->dev, "warning: invalid key group 0x%02x for key 0x%02x\n", + keygroup, key); + } + key |= (keygroup & 1) << 6; + + *protocol = RC_PROTO_UNKNOWN; + *scancode = key; + if (ir->c->addr == 0x41) /* AVerMedia EM78P153 */ + *scancode |= keygroup << 8; + *toggle = 0; + return 1; +} + +/* ----------------------------------------------------------------------- */ + +static int ir_key_poll(struct IR_i2c *ir) +{ + enum rc_proto protocol; + u32 scancode; + u8 toggle; + int rc; + + dev_dbg(&ir->rc->dev, "%s\n", __func__); + rc = ir->get_key(ir, &protocol, &scancode, &toggle); + if (rc < 0) { + dev_warn(&ir->rc->dev, "error %d\n", rc); + return rc; + } + + if (rc) { + dev_dbg(&ir->rc->dev, "%s: proto = 0x%04x, scancode = 0x%08x\n", + __func__, protocol, scancode); + rc_keydown(ir->rc, protocol, scancode, toggle); + } + return 0; +} + +static void ir_work(struct work_struct *work) +{ + int rc; + struct IR_i2c *ir = container_of(work, struct IR_i2c, work.work); + + /* + * If the transmit code is holding the lock, skip polling for + * IR, we'll get it to it next time round + */ + if (mutex_trylock(&ir->lock)) { + rc = ir_key_poll(ir); + mutex_unlock(&ir->lock); + if (rc == -ENODEV) { + rc_unregister_device(ir->rc); + ir->rc = NULL; + return; + } + } + + schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling_interval)); +} + +static int ir_open(struct rc_dev *dev) +{ + struct IR_i2c *ir = dev->priv; + + schedule_delayed_work(&ir->work, 0); + + return 0; +} + +static void ir_close(struct rc_dev *dev) +{ + struct IR_i2c *ir = dev->priv; + + cancel_delayed_work_sync(&ir->work); +} + +/* Zilog Transmit Interface */ +#define XTAL_FREQ 18432000 + +#define ZILOG_SEND 0x80 +#define ZILOG_UIR_END 0x40 +#define ZILOG_INIT_END 0x20 +#define ZILOG_LIR_END 0x10 + +#define ZILOG_STATUS_OK 0x80 +#define ZILOG_STATUS_TX 0x40 +#define ZILOG_STATUS_SET 0x20 + +/* + * As you can see here, very few different lengths of pulse and space + * can be encoded. This means that the hardware does not work well with + * recorded IR. It's best to work with generated IR, like from ir-ctl or + * the in-kernel encoders. + */ +struct code_block { + u8 length; + u16 pulse[7]; /* not aligned */ + u8 carrier_pulse; + u8 carrier_space; + u16 space[8]; /* not aligned */ + u8 codes[61]; + u8 csum[2]; +} __packed; + +static int send_data_block(struct IR_i2c *ir, int cmd, + struct code_block *code_block) +{ + int i, j, ret; + u8 buf[5], *p; + + p = &code_block->length; + for (i = 0; p < code_block->csum; i++) + code_block->csum[i & 1] ^= *p++; + + p = &code_block->length; + + for (i = 0; i < sizeof(*code_block);) { + int tosend = sizeof(*code_block) - i; + + if (tosend > 4) + tosend = 4; + buf[0] = i + 1; + for (j = 0; j < tosend; ++j) + buf[1 + j] = p[i + j]; + dev_dbg(&ir->rc->dev, "%*ph", tosend + 1, buf); + ret = i2c_master_send(ir->tx_c, buf, tosend + 1); + if (ret != tosend + 1) { + dev_dbg(&ir->rc->dev, + "i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EIO; + } + i += tosend; + } + + buf[0] = 0; + buf[1] = cmd; + ret = i2c_master_send(ir->tx_c, buf, 2); + if (ret != 2) { + dev_err(&ir->rc->dev, "i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EIO; + } + + usleep_range(2000, 5000); + + ret = i2c_master_send(ir->tx_c, buf, 1); + if (ret != 1) { + dev_err(&ir->rc->dev, "i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +static int zilog_init(struct IR_i2c *ir) +{ + struct code_block code_block = { .length = sizeof(code_block) }; + u8 buf[4]; + int ret; + + put_unaligned_be16(0x1000, &code_block.pulse[3]); + + ret = send_data_block(ir, ZILOG_INIT_END, &code_block); + if (ret) + return ret; + + ret = i2c_master_recv(ir->tx_c, buf, 4); + if (ret != 4) { + dev_err(&ir->c->dev, "failed to retrieve firmware version: %d\n", + ret); + return ret < 0 ? ret : -EIO; + } + + dev_info(&ir->c->dev, "Zilog/Hauppauge IR blaster firmware version %d.%d.%d\n", + buf[1], buf[2], buf[3]); + + return 0; +} + +/* + * If the last slot for pulse is the same as the current slot for pulse, + * then use slot no 7. + */ +static void copy_codes(u8 *dst, u8 *src, unsigned int count) +{ + u8 c, last = 0xff; + + while (count--) { + c = *src++; + if ((c & 0xf0) == last) { + *dst++ = 0x70 | (c & 0xf); + } else { + *dst++ = c; + last = c & 0xf0; + } + } +} + +/* + * When looking for repeats, we don't care about the trailing space. This + * is set to the shortest possible anyway. + */ +static int cmp_no_trail(u8 *a, u8 *b, unsigned int count) +{ + while (--count) { + if (*a++ != *b++) + return 1; + } + + return (*a & 0xf0) - (*b & 0xf0); +} + +static int find_slot(u16 *array, unsigned int size, u16 val) +{ + int i; + + for (i = 0; i < size; i++) { + if (get_unaligned_be16(&array[i]) == val) { + return i; + } else if (!array[i]) { + put_unaligned_be16(val, &array[i]); + return i; + } + } + + return -1; +} + +static int zilog_ir_format(struct rc_dev *rcdev, unsigned int *txbuf, + unsigned int count, struct code_block *code_block) +{ + struct IR_i2c *ir = rcdev->priv; + int rep, i, l, p = 0, s, c = 0; + bool repeating; + u8 codes[174]; + + code_block->carrier_pulse = DIV_ROUND_CLOSEST( + ir->duty_cycle * XTAL_FREQ / 1000, ir->carrier); + code_block->carrier_space = DIV_ROUND_CLOSEST( + (100 - ir->duty_cycle) * XTAL_FREQ / 1000, ir->carrier); + + for (i = 0; i < count; i++) { + if (c >= ARRAY_SIZE(codes) - 1) { + dev_warn(&rcdev->dev, "IR too long, cannot transmit\n"); + return -EINVAL; + } + + /* + * Lengths more than 142220us cannot be encoded; also + * this checks for multiply overflow + */ + if (txbuf[i] > 142220) + return -EINVAL; + + l = DIV_ROUND_CLOSEST((XTAL_FREQ / 1000) * txbuf[i], 40000); + + if (i & 1) { + s = find_slot(code_block->space, + ARRAY_SIZE(code_block->space), l); + if (s == -1) { + dev_warn(&rcdev->dev, "Too many different lengths spaces, cannot transmit"); + return -EINVAL; + } + + /* We have a pulse and space */ + codes[c++] = (p << 4) | s; + } else { + p = find_slot(code_block->pulse, + ARRAY_SIZE(code_block->pulse), l); + if (p == -1) { + dev_warn(&rcdev->dev, "Too many different lengths pulses, cannot transmit"); + return -EINVAL; + } + } + } + + /* We have to encode the trailing pulse. Find the shortest space */ + s = 0; + for (i = 1; i < ARRAY_SIZE(code_block->space); i++) { + u16 d = get_unaligned_be16(&code_block->space[i]); + + if (get_unaligned_be16(&code_block->space[s]) > d) + s = i; + } + + codes[c++] = (p << 4) | s; + + dev_dbg(&rcdev->dev, "generated %d codes\n", c); + + /* + * Are the last N codes (so pulse + space) repeating 3 times? + * if so we can shorten the codes list and use code 0xc0 to repeat + * them. + */ + repeating = false; + + for (rep = c / 3; rep >= 1; rep--) { + if (!memcmp(&codes[c - rep * 3], &codes[c - rep * 2], rep) && + !cmp_no_trail(&codes[c - rep], &codes[c - rep * 2], rep)) { + repeating = true; + break; + } + } + + if (repeating) { + /* first copy any leading non-repeating */ + int leading = c - rep * 3; + + if (leading >= ARRAY_SIZE(code_block->codes) - 3 - rep) { + dev_warn(&rcdev->dev, "IR too long, cannot transmit\n"); + return -EINVAL; + } + + dev_dbg(&rcdev->dev, "found trailing %d repeat\n", rep); + copy_codes(code_block->codes, codes, leading); + code_block->codes[leading] = 0x82; + copy_codes(code_block->codes + leading + 1, codes + leading, + rep); + c = leading + 1 + rep; + code_block->codes[c++] = 0xc0; + } else { + if (c >= ARRAY_SIZE(code_block->codes) - 3) { + dev_warn(&rcdev->dev, "IR too long, cannot transmit\n"); + return -EINVAL; + } + + dev_dbg(&rcdev->dev, "found no trailing repeat\n"); + code_block->codes[0] = 0x82; + copy_codes(code_block->codes + 1, codes, c); + c++; + code_block->codes[c++] = 0xc4; + } + + while (c < ARRAY_SIZE(code_block->codes)) + code_block->codes[c++] = 0x83; + + return 0; +} + +static int zilog_tx(struct rc_dev *rcdev, unsigned int *txbuf, + unsigned int count) +{ + struct IR_i2c *ir = rcdev->priv; + struct code_block code_block = { .length = sizeof(code_block) }; + u8 buf[2]; + int ret, i; + + ret = zilog_ir_format(rcdev, txbuf, count, &code_block); + if (ret) + return ret; + + ret = mutex_lock_interruptible(&ir->lock); + if (ret) + return ret; + + ret = send_data_block(ir, ZILOG_UIR_END, &code_block); + if (ret) + goto out_unlock; + + ret = i2c_master_recv(ir->tx_c, buf, 1); + if (ret != 1) { + dev_err(&ir->rc->dev, "i2c_master_recv failed with %d\n", ret); + goto out_unlock; + } + + dev_dbg(&ir->rc->dev, "code set status: %02x\n", buf[0]); + + if (buf[0] != (ZILOG_STATUS_OK | ZILOG_STATUS_SET)) { + dev_err(&ir->rc->dev, "unexpected IR TX response %02x\n", + buf[0]); + ret = -EIO; + goto out_unlock; + } + + buf[0] = 0x00; + buf[1] = ZILOG_SEND; + + ret = i2c_master_send(ir->tx_c, buf, 2); + if (ret != 2) { + dev_err(&ir->rc->dev, "i2c_master_send failed with %d\n", ret); + if (ret >= 0) + ret = -EIO; + goto out_unlock; + } + + dev_dbg(&ir->rc->dev, "send command sent\n"); + + /* + * This bit NAKs until the device is ready, so we retry it + * sleeping a bit each time. This seems to be what the windows + * driver does, approximately. + * Try for up to 1s. + */ + for (i = 0; i < 20; ++i) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(50)); + ret = i2c_master_send(ir->tx_c, buf, 1); + if (ret == 1) + break; + dev_dbg(&ir->rc->dev, + "NAK expected: i2c_master_send failed with %d (try %d)\n", + ret, i + 1); + } + + if (ret != 1) { + dev_err(&ir->rc->dev, + "IR TX chip never got ready: last i2c_master_send failed with %d\n", + ret); + if (ret >= 0) + ret = -EIO; + goto out_unlock; + } + + ret = i2c_master_recv(ir->tx_c, buf, 1); + if (ret != 1) { + dev_err(&ir->rc->dev, "i2c_master_recv failed with %d\n", ret); + ret = -EIO; + goto out_unlock; + } else if (buf[0] != ZILOG_STATUS_OK) { + dev_err(&ir->rc->dev, "unexpected IR TX response #2: %02x\n", + buf[0]); + ret = -EIO; + goto out_unlock; + } + dev_dbg(&ir->rc->dev, "transmit complete\n"); + + /* Oh good, it worked */ + ret = count; +out_unlock: + mutex_unlock(&ir->lock); + + return ret; +} + +static int zilog_tx_carrier(struct rc_dev *dev, u32 carrier) +{ + struct IR_i2c *ir = dev->priv; + + if (carrier > 500000 || carrier < 20000) + return -EINVAL; + + ir->carrier = carrier; + + return 0; +} + +static int zilog_tx_duty_cycle(struct rc_dev *dev, u32 duty_cycle) +{ + struct IR_i2c *ir = dev->priv; + + ir->duty_cycle = duty_cycle; + + return 0; +} + +static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + char *ir_codes = NULL; + const char *name = NULL; + u64 rc_proto = RC_PROTO_BIT_UNKNOWN; + struct IR_i2c *ir; + struct rc_dev *rc = NULL; + struct i2c_adapter *adap = client->adapter; + unsigned short addr = client->addr; + bool probe_tx = (id->driver_data & FLAG_TX) != 0; + int err; + + if ((id->driver_data & FLAG_HDPVR) && !enable_hdpvr) { + dev_err(&client->dev, "IR for HDPVR is known to cause problems during recording, use enable_hdpvr modparam to enable\n"); + return -ENODEV; + } + + ir = devm_kzalloc(&client->dev, sizeof(*ir), GFP_KERNEL); + if (!ir) + return -ENOMEM; + + ir->c = client; + ir->polling_interval = DEFAULT_POLLING_INTERVAL; + i2c_set_clientdata(client, ir); + + switch(addr) { + case 0x64: + name = "Pixelview"; + ir->get_key = get_key_pixelview; + rc_proto = RC_PROTO_BIT_OTHER; + ir_codes = RC_MAP_EMPTY; + break; + case 0x18: + case 0x1f: + case 0x1a: + name = "Hauppauge"; + ir->get_key = get_key_haup; + rc_proto = RC_PROTO_BIT_RC5; + ir_codes = RC_MAP_HAUPPAUGE; + break; + case 0x30: + name = "KNC One"; + ir->get_key = get_key_knc1; + rc_proto = RC_PROTO_BIT_OTHER; + ir_codes = RC_MAP_EMPTY; + break; + case 0x6b: + name = "FusionHDTV"; + ir->get_key = get_key_fusionhdtv; + rc_proto = RC_PROTO_BIT_UNKNOWN; + ir_codes = RC_MAP_FUSIONHDTV_MCE; + break; + case 0x40: + name = "AVerMedia Cardbus remote"; + ir->get_key = get_key_avermedia_cardbus; + rc_proto = RC_PROTO_BIT_OTHER; + ir_codes = RC_MAP_AVERMEDIA_CARDBUS; + break; + case 0x41: + name = "AVerMedia EM78P153"; + ir->get_key = get_key_avermedia_cardbus; + rc_proto = RC_PROTO_BIT_OTHER; + /* RM-KV remote, seems to be same as RM-K6 */ + ir_codes = RC_MAP_AVERMEDIA_M733A_RM_K6; + break; + case 0x71: + name = "Hauppauge/Zilog Z8"; + ir->get_key = get_key_haup_xvr; + rc_proto = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RC6_MCE | + RC_PROTO_BIT_RC6_6A_32; + ir_codes = RC_MAP_HAUPPAUGE; + ir->polling_interval = 125; + probe_tx = true; + break; + } + + /* Let the caller override settings */ + if (client->dev.platform_data) { + const struct IR_i2c_init_data *init_data = + client->dev.platform_data; + + ir_codes = init_data->ir_codes; + rc = init_data->rc_dev; + + name = init_data->name; + if (init_data->type) + rc_proto = init_data->type; + + if (init_data->polling_interval) + ir->polling_interval = init_data->polling_interval; + + switch (init_data->internal_get_key_func) { + case IR_KBD_GET_KEY_CUSTOM: + /* The bridge driver provided us its own function */ + ir->get_key = init_data->get_key; + break; + case IR_KBD_GET_KEY_PIXELVIEW: + ir->get_key = get_key_pixelview; + break; + case IR_KBD_GET_KEY_HAUP: + ir->get_key = get_key_haup; + break; + case IR_KBD_GET_KEY_KNC1: + ir->get_key = get_key_knc1; + break; + case IR_KBD_GET_KEY_FUSIONHDTV: + ir->get_key = get_key_fusionhdtv; + break; + case IR_KBD_GET_KEY_HAUP_XVR: + ir->get_key = get_key_haup_xvr; + break; + case IR_KBD_GET_KEY_AVERMEDIA_CARDBUS: + ir->get_key = get_key_avermedia_cardbus; + break; + } + } + + if (!rc) { + /* + * If platform_data doesn't specify rc_dev, initialize it + * internally + */ + rc = rc_allocate_device(RC_DRIVER_SCANCODE); + if (!rc) + return -ENOMEM; + } + ir->rc = rc; + + /* Make sure we are all setup before going on */ + if (!name || !ir->get_key || !rc_proto || !ir_codes) { + dev_warn(&client->dev, "Unsupported device at address 0x%02x\n", + addr); + err = -ENODEV; + goto err_out_free; + } + + ir->ir_codes = ir_codes; + + snprintf(ir->phys, sizeof(ir->phys), "%s/%s", dev_name(&adap->dev), + dev_name(&client->dev)); + + /* + * Initialize input_dev fields + * It doesn't make sense to allow overriding them via platform_data + */ + rc->input_id.bustype = BUS_I2C; + rc->input_phys = ir->phys; + rc->device_name = name; + rc->dev.parent = &client->dev; + rc->priv = ir; + rc->open = ir_open; + rc->close = ir_close; + + /* + * Initialize the other fields of rc_dev + */ + rc->map_name = ir->ir_codes; + rc->allowed_protocols = rc_proto; + if (!rc->driver_name) + rc->driver_name = KBUILD_MODNAME; + + mutex_init(&ir->lock); + + INIT_DELAYED_WORK(&ir->work, ir_work); + + if (probe_tx) { + ir->tx_c = i2c_new_dummy_device(client->adapter, 0x70); + if (IS_ERR(ir->tx_c)) { + dev_err(&client->dev, "failed to setup tx i2c address"); + err = PTR_ERR(ir->tx_c); + goto err_out_free; + } else if (!zilog_init(ir)) { + ir->carrier = 38000; + ir->duty_cycle = 40; + rc->tx_ir = zilog_tx; + rc->s_tx_carrier = zilog_tx_carrier; + rc->s_tx_duty_cycle = zilog_tx_duty_cycle; + } + } + + err = rc_register_device(rc); + if (err) + goto err_out_free; + + return 0; + + err_out_free: + if (!IS_ERR(ir->tx_c)) + i2c_unregister_device(ir->tx_c); + + /* Only frees rc if it were allocated internally */ + rc_free_device(rc); + return err; +} + +static int ir_remove(struct i2c_client *client) +{ + struct IR_i2c *ir = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&ir->work); + + i2c_unregister_device(ir->tx_c); + + rc_unregister_device(ir->rc); + + return 0; +} + +static const struct i2c_device_id ir_kbd_id[] = { + /* Generic entry for any IR receiver */ + { "ir_video", 0 }, + /* IR device specific entries should be added here */ + { "ir_z8f0811_haup", FLAG_TX }, + { "ir_z8f0811_hdpvr", FLAG_TX | FLAG_HDPVR }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ir_kbd_id); + +static struct i2c_driver ir_kbd_driver = { + .driver = { + .name = "ir-kbd-i2c", + }, + .probe = ir_probe, + .remove = ir_remove, + .id_table = ir_kbd_id, +}; + +module_i2c_driver(ir_kbd_driver); + +/* ----------------------------------------------------------------------- */ + +MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, Ulrich Mueller"); +MODULE_DESCRIPTION("input driver for i2c IR remote controls"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ks0127.c b/drivers/media/i2c/ks0127.c new file mode 100644 index 000000000..c077f53b9 --- /dev/null +++ b/drivers/media/i2c/ks0127.c @@ -0,0 +1,705 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Video Capture Driver (Video for Linux 1/2) + * for the Matrox Marvel G200,G400 and Rainbow Runner-G series + * + * This module is an interface to the KS0127 video decoder chip. + * + * Copyright (C) 1999 Ryan Drake <stiletto@mediaone.net> + * + ***************************************************************************** + * + * Modified and extended by + * Mike Bernson <mike@mlb.org> + * Gerard v.d. Horst + * Leon van Stuivenberg <l.vanstuivenberg@chello.nl> + * Gernot Ziegler <gz@lysator.liu.se> + * + * Version History: + * V1.0 Ryan Drake Initial version by Ryan Drake + * V1.1 Gerard v.d. Horst Added some debugoutput, reset the video-standard + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include "ks0127.h" + +MODULE_DESCRIPTION("KS0127 video decoder driver"); +MODULE_AUTHOR("Ryan Drake"); +MODULE_LICENSE("GPL"); + +/* Addresses */ +#define I2C_KS0127_ADDON 0xD8 +#define I2C_KS0127_ONBOARD 0xDA + + +/* ks0127 control registers */ +#define KS_STAT 0x00 +#define KS_CMDA 0x01 +#define KS_CMDB 0x02 +#define KS_CMDC 0x03 +#define KS_CMDD 0x04 +#define KS_HAVB 0x05 +#define KS_HAVE 0x06 +#define KS_HS1B 0x07 +#define KS_HS1E 0x08 +#define KS_HS2B 0x09 +#define KS_HS2E 0x0a +#define KS_AGC 0x0b +#define KS_HXTRA 0x0c +#define KS_CDEM 0x0d +#define KS_PORTAB 0x0e +#define KS_LUMA 0x0f +#define KS_CON 0x10 +#define KS_BRT 0x11 +#define KS_CHROMA 0x12 +#define KS_CHROMB 0x13 +#define KS_DEMOD 0x14 +#define KS_SAT 0x15 +#define KS_HUE 0x16 +#define KS_VERTIA 0x17 +#define KS_VERTIB 0x18 +#define KS_VERTIC 0x19 +#define KS_HSCLL 0x1a +#define KS_HSCLH 0x1b +#define KS_VSCLL 0x1c +#define KS_VSCLH 0x1d +#define KS_OFMTA 0x1e +#define KS_OFMTB 0x1f +#define KS_VBICTL 0x20 +#define KS_CCDAT2 0x21 +#define KS_CCDAT1 0x22 +#define KS_VBIL30 0x23 +#define KS_VBIL74 0x24 +#define KS_VBIL118 0x25 +#define KS_VBIL1512 0x26 +#define KS_TTFRAM 0x27 +#define KS_TESTA 0x28 +#define KS_UVOFFH 0x29 +#define KS_UVOFFL 0x2a +#define KS_UGAIN 0x2b +#define KS_VGAIN 0x2c +#define KS_VAVB 0x2d +#define KS_VAVE 0x2e +#define KS_CTRACK 0x2f +#define KS_POLCTL 0x30 +#define KS_REFCOD 0x31 +#define KS_INVALY 0x32 +#define KS_INVALU 0x33 +#define KS_INVALV 0x34 +#define KS_UNUSEY 0x35 +#define KS_UNUSEU 0x36 +#define KS_UNUSEV 0x37 +#define KS_USRSAV 0x38 +#define KS_USREAV 0x39 +#define KS_SHS1A 0x3a +#define KS_SHS1B 0x3b +#define KS_SHS1C 0x3c +#define KS_CMDE 0x3d +#define KS_VSDEL 0x3e +#define KS_CMDF 0x3f +#define KS_GAMMA0 0x40 +#define KS_GAMMA1 0x41 +#define KS_GAMMA2 0x42 +#define KS_GAMMA3 0x43 +#define KS_GAMMA4 0x44 +#define KS_GAMMA5 0x45 +#define KS_GAMMA6 0x46 +#define KS_GAMMA7 0x47 +#define KS_GAMMA8 0x48 +#define KS_GAMMA9 0x49 +#define KS_GAMMA10 0x4a +#define KS_GAMMA11 0x4b +#define KS_GAMMA12 0x4c +#define KS_GAMMA13 0x4d +#define KS_GAMMA14 0x4e +#define KS_GAMMA15 0x4f +#define KS_GAMMA16 0x50 +#define KS_GAMMA17 0x51 +#define KS_GAMMA18 0x52 +#define KS_GAMMA19 0x53 +#define KS_GAMMA20 0x54 +#define KS_GAMMA21 0x55 +#define KS_GAMMA22 0x56 +#define KS_GAMMA23 0x57 +#define KS_GAMMA24 0x58 +#define KS_GAMMA25 0x59 +#define KS_GAMMA26 0x5a +#define KS_GAMMA27 0x5b +#define KS_GAMMA28 0x5c +#define KS_GAMMA29 0x5d +#define KS_GAMMA30 0x5e +#define KS_GAMMA31 0x5f +#define KS_GAMMAD0 0x60 +#define KS_GAMMAD1 0x61 +#define KS_GAMMAD2 0x62 +#define KS_GAMMAD3 0x63 +#define KS_GAMMAD4 0x64 +#define KS_GAMMAD5 0x65 +#define KS_GAMMAD6 0x66 +#define KS_GAMMAD7 0x67 +#define KS_GAMMAD8 0x68 +#define KS_GAMMAD9 0x69 +#define KS_GAMMAD10 0x6a +#define KS_GAMMAD11 0x6b +#define KS_GAMMAD12 0x6c +#define KS_GAMMAD13 0x6d +#define KS_GAMMAD14 0x6e +#define KS_GAMMAD15 0x6f +#define KS_GAMMAD16 0x70 +#define KS_GAMMAD17 0x71 +#define KS_GAMMAD18 0x72 +#define KS_GAMMAD19 0x73 +#define KS_GAMMAD20 0x74 +#define KS_GAMMAD21 0x75 +#define KS_GAMMAD22 0x76 +#define KS_GAMMAD23 0x77 +#define KS_GAMMAD24 0x78 +#define KS_GAMMAD25 0x79 +#define KS_GAMMAD26 0x7a +#define KS_GAMMAD27 0x7b +#define KS_GAMMAD28 0x7c +#define KS_GAMMAD29 0x7d +#define KS_GAMMAD30 0x7e +#define KS_GAMMAD31 0x7f + + +/**************************************************************************** +* mga_dev : represents one ks0127 chip. +****************************************************************************/ + +struct adjust { + int contrast; + int bright; + int hue; + int ugain; + int vgain; +}; + +struct ks0127 { + struct v4l2_subdev sd; + v4l2_std_id norm; + u8 regs[256]; +}; + +static inline struct ks0127 *to_ks0127(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ks0127, sd); +} + + +static int debug; /* insmod parameter */ + +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug output"); + +static u8 reg_defaults[64]; + +static void init_reg_defaults(void) +{ + static int initialized; + u8 *table = reg_defaults; + + if (initialized) + return; + initialized = 1; + + table[KS_CMDA] = 0x2c; /* VSE=0, CCIR 601, autodetect standard */ + table[KS_CMDB] = 0x12; /* VALIGN=0, AGC control and input */ + table[KS_CMDC] = 0x00; /* Test options */ + /* clock & input select, write 1 to PORTA */ + table[KS_CMDD] = 0x01; + table[KS_HAVB] = 0x00; /* HAV Start Control */ + table[KS_HAVE] = 0x00; /* HAV End Control */ + table[KS_HS1B] = 0x10; /* HS1 Start Control */ + table[KS_HS1E] = 0x00; /* HS1 End Control */ + table[KS_HS2B] = 0x00; /* HS2 Start Control */ + table[KS_HS2E] = 0x00; /* HS2 End Control */ + table[KS_AGC] = 0x53; /* Manual setting for AGC */ + table[KS_HXTRA] = 0x00; /* Extra Bits for HAV and HS1/2 */ + table[KS_CDEM] = 0x00; /* Chroma Demodulation Control */ + table[KS_PORTAB] = 0x0f; /* port B is input, port A output GPPORT */ + table[KS_LUMA] = 0x01; /* Luma control */ + table[KS_CON] = 0x00; /* Contrast Control */ + table[KS_BRT] = 0x00; /* Brightness Control */ + table[KS_CHROMA] = 0x2a; /* Chroma control A */ + table[KS_CHROMB] = 0x90; /* Chroma control B */ + table[KS_DEMOD] = 0x00; /* Chroma Demodulation Control & Status */ + table[KS_SAT] = 0x00; /* Color Saturation Control*/ + table[KS_HUE] = 0x00; /* Hue Control */ + table[KS_VERTIA] = 0x00; /* Vertical Processing Control A */ + /* Vertical Processing Control B, luma 1 line delayed */ + table[KS_VERTIB] = 0x12; + table[KS_VERTIC] = 0x0b; /* Vertical Processing Control C */ + table[KS_HSCLL] = 0x00; /* Horizontal Scaling Ratio Low */ + table[KS_HSCLH] = 0x00; /* Horizontal Scaling Ratio High */ + table[KS_VSCLL] = 0x00; /* Vertical Scaling Ratio Low */ + table[KS_VSCLH] = 0x00; /* Vertical Scaling Ratio High */ + /* 16 bit YCbCr 4:2:2 output; I can't make the bt866 like 8 bit /Sam */ + table[KS_OFMTA] = 0x30; + table[KS_OFMTB] = 0x00; /* Output Control B */ + /* VBI Decoder Control; 4bit fmt: avoid Y overflow */ + table[KS_VBICTL] = 0x5d; + table[KS_CCDAT2] = 0x00; /* Read Only register */ + table[KS_CCDAT1] = 0x00; /* Read Only register */ + table[KS_VBIL30] = 0xa8; /* VBI data decoding options */ + table[KS_VBIL74] = 0xaa; /* VBI data decoding options */ + table[KS_VBIL118] = 0x2a; /* VBI data decoding options */ + table[KS_VBIL1512] = 0x00; /* VBI data decoding options */ + table[KS_TTFRAM] = 0x00; /* Teletext frame alignment pattern */ + table[KS_TESTA] = 0x00; /* test register, shouldn't be written */ + table[KS_UVOFFH] = 0x00; /* UV Offset Adjustment High */ + table[KS_UVOFFL] = 0x00; /* UV Offset Adjustment Low */ + table[KS_UGAIN] = 0x00; /* U Component Gain Adjustment */ + table[KS_VGAIN] = 0x00; /* V Component Gain Adjustment */ + table[KS_VAVB] = 0x07; /* VAV Begin */ + table[KS_VAVE] = 0x00; /* VAV End */ + table[KS_CTRACK] = 0x00; /* Chroma Tracking Control */ + table[KS_POLCTL] = 0x41; /* Timing Signal Polarity Control */ + table[KS_REFCOD] = 0x80; /* Reference Code Insertion Control */ + table[KS_INVALY] = 0x10; /* Invalid Y Code */ + table[KS_INVALU] = 0x80; /* Invalid U Code */ + table[KS_INVALV] = 0x80; /* Invalid V Code */ + table[KS_UNUSEY] = 0x10; /* Unused Y Code */ + table[KS_UNUSEU] = 0x80; /* Unused U Code */ + table[KS_UNUSEV] = 0x80; /* Unused V Code */ + table[KS_USRSAV] = 0x00; /* reserved */ + table[KS_USREAV] = 0x00; /* reserved */ + table[KS_SHS1A] = 0x00; /* User Defined SHS1 A */ + /* User Defined SHS1 B, ALT656=1 on 0127B */ + table[KS_SHS1B] = 0x80; + table[KS_SHS1C] = 0x00; /* User Defined SHS1 C */ + table[KS_CMDE] = 0x00; /* Command Register E */ + table[KS_VSDEL] = 0x00; /* VS Delay Control */ + /* Command Register F, update -immediately- */ + /* (there might come no vsync)*/ + table[KS_CMDF] = 0x02; +} + + +/* We need to manually read because of a bug in the KS0127 chip. + * + * An explanation from kayork@mail.utexas.edu: + * + * During I2C reads, the KS0127 only samples for a stop condition + * during the place where the acknowledge bit should be. Any standard + * I2C implementation (correctly) throws in another clock transition + * at the 9th bit, and the KS0127 will not recognize the stop condition + * and will continue to clock out data. + * + * So we have to do the read ourself. Big deal. + * workaround in i2c-algo-bit + */ + + +static u8 ks0127_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + char val = 0; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .len = sizeof(reg), + .buf = ® + }, + { + .addr = client->addr, + .flags = I2C_M_RD | I2C_M_NO_RD_ACK, + .len = sizeof(val), + .buf = &val + } + }; + int ret; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + v4l2_dbg(1, debug, sd, "read error\n"); + + return val; +} + + +static void ks0127_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ks0127 *ks = to_ks0127(sd); + char msg[] = { reg, val }; + + if (i2c_master_send(client, msg, sizeof(msg)) != sizeof(msg)) + v4l2_dbg(1, debug, sd, "write error\n"); + + ks->regs[reg] = val; +} + + +/* generic bit-twiddling */ +static void ks0127_and_or(struct v4l2_subdev *sd, u8 reg, u8 and_v, u8 or_v) +{ + struct ks0127 *ks = to_ks0127(sd); + + u8 val = ks->regs[reg]; + val = (val & and_v) | or_v; + ks0127_write(sd, reg, val); +} + + + +/**************************************************************************** +* ks0127 private api +****************************************************************************/ +static void ks0127_init(struct v4l2_subdev *sd) +{ + u8 *table = reg_defaults; + int i; + + v4l2_dbg(1, debug, sd, "reset\n"); + msleep(1); + + /* initialize all registers to known values */ + /* (except STAT, 0x21, 0x22, TEST and 0x38,0x39) */ + + for (i = 1; i < 33; i++) + ks0127_write(sd, i, table[i]); + + for (i = 35; i < 40; i++) + ks0127_write(sd, i, table[i]); + + for (i = 41; i < 56; i++) + ks0127_write(sd, i, table[i]); + + for (i = 58; i < 64; i++) + ks0127_write(sd, i, table[i]); + + + if ((ks0127_read(sd, KS_STAT) & 0x80) == 0) { + v4l2_dbg(1, debug, sd, "ks0122s found\n"); + return; + } + + switch (ks0127_read(sd, KS_CMDE) & 0x0f) { + case 0: + v4l2_dbg(1, debug, sd, "ks0127 found\n"); + break; + + case 9: + v4l2_dbg(1, debug, sd, "ks0127B Revision A found\n"); + break; + + default: + v4l2_dbg(1, debug, sd, "unknown revision\n"); + break; + } +} + +static int ks0127_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct ks0127 *ks = to_ks0127(sd); + + switch (input) { + case KS_INPUT_COMPOSITE_1: + case KS_INPUT_COMPOSITE_2: + case KS_INPUT_COMPOSITE_3: + case KS_INPUT_COMPOSITE_4: + case KS_INPUT_COMPOSITE_5: + case KS_INPUT_COMPOSITE_6: + v4l2_dbg(1, debug, sd, + "s_routing %d: Composite\n", input); + /* autodetect 50/60 Hz */ + ks0127_and_or(sd, KS_CMDA, 0xfc, 0x00); + /* VSE=0 */ + ks0127_and_or(sd, KS_CMDA, ~0x40, 0x00); + /* set input line */ + ks0127_and_or(sd, KS_CMDB, 0xb0, input); + /* non-freerunning mode */ + ks0127_and_or(sd, KS_CMDC, 0x70, 0x0a); + /* analog input */ + ks0127_and_or(sd, KS_CMDD, 0x03, 0x00); + /* enable chroma demodulation */ + ks0127_and_or(sd, KS_CTRACK, 0xcf, 0x00); + /* chroma trap, HYBWR=1 */ + ks0127_and_or(sd, KS_LUMA, 0x00, + (reg_defaults[KS_LUMA])|0x0c); + /* scaler fullbw, luma comb off */ + ks0127_and_or(sd, KS_VERTIA, 0x08, 0x81); + /* manual chroma comb .25 .5 .25 */ + ks0127_and_or(sd, KS_VERTIC, 0x0f, 0x90); + + /* chroma path delay */ + ks0127_and_or(sd, KS_CHROMB, 0x0f, 0x90); + + ks0127_write(sd, KS_UGAIN, reg_defaults[KS_UGAIN]); + ks0127_write(sd, KS_VGAIN, reg_defaults[KS_VGAIN]); + ks0127_write(sd, KS_UVOFFH, reg_defaults[KS_UVOFFH]); + ks0127_write(sd, KS_UVOFFL, reg_defaults[KS_UVOFFL]); + break; + + case KS_INPUT_SVIDEO_1: + case KS_INPUT_SVIDEO_2: + case KS_INPUT_SVIDEO_3: + v4l2_dbg(1, debug, sd, + "s_routing %d: S-Video\n", input); + /* autodetect 50/60 Hz */ + ks0127_and_or(sd, KS_CMDA, 0xfc, 0x00); + /* VSE=0 */ + ks0127_and_or(sd, KS_CMDA, ~0x40, 0x00); + /* set input line */ + ks0127_and_or(sd, KS_CMDB, 0xb0, input); + /* non-freerunning mode */ + ks0127_and_or(sd, KS_CMDC, 0x70, 0x0a); + /* analog input */ + ks0127_and_or(sd, KS_CMDD, 0x03, 0x00); + /* enable chroma demodulation */ + ks0127_and_or(sd, KS_CTRACK, 0xcf, 0x00); + ks0127_and_or(sd, KS_LUMA, 0x00, + reg_defaults[KS_LUMA]); + /* disable luma comb */ + ks0127_and_or(sd, KS_VERTIA, 0x08, + (reg_defaults[KS_VERTIA]&0xf0)|0x01); + ks0127_and_or(sd, KS_VERTIC, 0x0f, + reg_defaults[KS_VERTIC]&0xf0); + + ks0127_and_or(sd, KS_CHROMB, 0x0f, + reg_defaults[KS_CHROMB]&0xf0); + + ks0127_write(sd, KS_UGAIN, reg_defaults[KS_UGAIN]); + ks0127_write(sd, KS_VGAIN, reg_defaults[KS_VGAIN]); + ks0127_write(sd, KS_UVOFFH, reg_defaults[KS_UVOFFH]); + ks0127_write(sd, KS_UVOFFL, reg_defaults[KS_UVOFFL]); + break; + + case KS_INPUT_YUV656: + v4l2_dbg(1, debug, sd, "s_routing 15: YUV656\n"); + if (ks->norm & V4L2_STD_525_60) + /* force 60 Hz */ + ks0127_and_or(sd, KS_CMDA, 0xfc, 0x03); + else + /* force 50 Hz */ + ks0127_and_or(sd, KS_CMDA, 0xfc, 0x02); + + ks0127_and_or(sd, KS_CMDA, 0xff, 0x40); /* VSE=1 */ + /* set input line and VALIGN */ + ks0127_and_or(sd, KS_CMDB, 0xb0, (input | 0x40)); + /* freerunning mode, */ + /* TSTGEN = 1 TSTGFR=11 TSTGPH=0 TSTGPK=0 VMEM=1*/ + ks0127_and_or(sd, KS_CMDC, 0x70, 0x87); + /* digital input, SYNDIR = 0 INPSL=01 CLKDIR=0 EAV=0 */ + ks0127_and_or(sd, KS_CMDD, 0x03, 0x08); + /* disable chroma demodulation */ + ks0127_and_or(sd, KS_CTRACK, 0xcf, 0x30); + /* HYPK =01 CTRAP = 0 HYBWR=0 PED=1 RGBH=1 UNIT=1 */ + ks0127_and_or(sd, KS_LUMA, 0x00, 0x71); + ks0127_and_or(sd, KS_VERTIC, 0x0f, + reg_defaults[KS_VERTIC]&0xf0); + + /* scaler fullbw, luma comb off */ + ks0127_and_or(sd, KS_VERTIA, 0x08, 0x81); + + ks0127_and_or(sd, KS_CHROMB, 0x0f, + reg_defaults[KS_CHROMB]&0xf0); + + ks0127_and_or(sd, KS_CON, 0x00, 0x00); + ks0127_and_or(sd, KS_BRT, 0x00, 32); /* spec: 34 */ + /* spec: 229 (e5) */ + ks0127_and_or(sd, KS_SAT, 0x00, 0xe8); + ks0127_and_or(sd, KS_HUE, 0x00, 0); + + ks0127_and_or(sd, KS_UGAIN, 0x00, 238); + ks0127_and_or(sd, KS_VGAIN, 0x00, 0x00); + + /*UOFF:0x30, VOFF:0x30, TSTCGN=1 */ + ks0127_and_or(sd, KS_UVOFFH, 0x00, 0x4f); + ks0127_and_or(sd, KS_UVOFFL, 0x00, 0x00); + break; + + default: + v4l2_dbg(1, debug, sd, + "s_routing: Unknown input %d\n", input); + break; + } + + /* hack: CDMLPF sometimes spontaneously switches on; */ + /* force back off */ + ks0127_write(sd, KS_DEMOD, reg_defaults[KS_DEMOD]); + return 0; +} + +static int ks0127_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct ks0127 *ks = to_ks0127(sd); + + /* Set to automatic SECAM/Fsc mode */ + ks0127_and_or(sd, KS_DEMOD, 0xf0, 0x00); + + ks->norm = std; + if (std & V4L2_STD_NTSC) { + v4l2_dbg(1, debug, sd, + "s_std: NTSC_M\n"); + ks0127_and_or(sd, KS_CHROMA, 0x9f, 0x20); + } else if (std & V4L2_STD_PAL_N) { + v4l2_dbg(1, debug, sd, + "s_std: NTSC_N (fixme)\n"); + ks0127_and_or(sd, KS_CHROMA, 0x9f, 0x40); + } else if (std & V4L2_STD_PAL) { + v4l2_dbg(1, debug, sd, + "s_std: PAL_N\n"); + ks0127_and_or(sd, KS_CHROMA, 0x9f, 0x20); + } else if (std & V4L2_STD_PAL_M) { + v4l2_dbg(1, debug, sd, + "s_std: PAL_M (fixme)\n"); + ks0127_and_or(sd, KS_CHROMA, 0x9f, 0x40); + } else if (std & V4L2_STD_SECAM) { + v4l2_dbg(1, debug, sd, + "s_std: SECAM\n"); + + /* set to secam autodetection */ + ks0127_and_or(sd, KS_CHROMA, 0xdf, 0x20); + ks0127_and_or(sd, KS_DEMOD, 0xf0, 0x00); + schedule_timeout_interruptible(HZ/10+1); + + /* did it autodetect? */ + if (!(ks0127_read(sd, KS_DEMOD) & 0x40)) + /* force to secam mode */ + ks0127_and_or(sd, KS_DEMOD, 0xf0, 0x0f); + } else { + v4l2_dbg(1, debug, sd, "s_std: Unknown norm %llx\n", + (unsigned long long)std); + } + return 0; +} + +static int ks0127_s_stream(struct v4l2_subdev *sd, int enable) +{ + v4l2_dbg(1, debug, sd, "s_stream(%d)\n", enable); + if (enable) { + /* All output pins on */ + ks0127_and_or(sd, KS_OFMTA, 0xcf, 0x30); + /* Obey the OEN pin */ + ks0127_and_or(sd, KS_CDEM, 0x7f, 0x00); + } else { + /* Video output pins off */ + ks0127_and_or(sd, KS_OFMTA, 0xcf, 0x00); + /* Ignore the OEN pin */ + ks0127_and_or(sd, KS_CDEM, 0x7f, 0x80); + } + return 0; +} + +static int ks0127_status(struct v4l2_subdev *sd, u32 *pstatus, v4l2_std_id *pstd) +{ + int stat = V4L2_IN_ST_NO_SIGNAL; + u8 status; + v4l2_std_id std = pstd ? *pstd : V4L2_STD_ALL; + + status = ks0127_read(sd, KS_STAT); + if (!(status & 0x20)) /* NOVID not set */ + stat = 0; + if (!(status & 0x01)) { /* CLOCK set */ + stat |= V4L2_IN_ST_NO_COLOR; + std = V4L2_STD_UNKNOWN; + } else { + if ((status & 0x08)) /* PALDET set */ + std &= V4L2_STD_PAL; + else + std &= V4L2_STD_NTSC; + } + if ((status & 0x10)) /* PALDET set */ + std &= V4L2_STD_525_60; + else + std &= V4L2_STD_625_50; + if (pstd) + *pstd = std; + if (pstatus) + *pstatus = stat; + return 0; +} + +static int ks0127_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + v4l2_dbg(1, debug, sd, "querystd\n"); + return ks0127_status(sd, NULL, std); +} + +static int ks0127_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + v4l2_dbg(1, debug, sd, "g_input_status\n"); + return ks0127_status(sd, status, NULL); +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_video_ops ks0127_video_ops = { + .s_std = ks0127_s_std, + .s_routing = ks0127_s_routing, + .s_stream = ks0127_s_stream, + .querystd = ks0127_querystd, + .g_input_status = ks0127_g_input_status, +}; + +static const struct v4l2_subdev_ops ks0127_ops = { + .video = &ks0127_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + + +static int ks0127_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct ks0127 *ks; + struct v4l2_subdev *sd; + + v4l_info(client, "%s chip found @ 0x%x (%s)\n", + client->addr == (I2C_KS0127_ADDON >> 1) ? "addon" : "on-board", + client->addr << 1, client->adapter->name); + + ks = devm_kzalloc(&client->dev, sizeof(*ks), GFP_KERNEL); + if (ks == NULL) + return -ENOMEM; + sd = &ks->sd; + v4l2_i2c_subdev_init(sd, client, &ks0127_ops); + + /* power up */ + init_reg_defaults(); + ks0127_write(sd, KS_CMDA, 0x2c); + mdelay(10); + + /* reset the device */ + ks0127_init(sd); + return 0; +} + +static int ks0127_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + ks0127_write(sd, KS_OFMTA, 0x20); /* tristate */ + ks0127_write(sd, KS_CMDA, 0x2c | 0x80); /* power down */ + return 0; +} + +static const struct i2c_device_id ks0127_id[] = { + { "ks0127", 0 }, + { "ks0127b", 0 }, + { "ks0122s", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ks0127_id); + +static struct i2c_driver ks0127_driver = { + .driver = { + .name = "ks0127", + }, + .probe = ks0127_probe, + .remove = ks0127_remove, + .id_table = ks0127_id, +}; + +module_i2c_driver(ks0127_driver); diff --git a/drivers/media/i2c/ks0127.h b/drivers/media/i2c/ks0127.h new file mode 100644 index 000000000..333d1d1e9 --- /dev/null +++ b/drivers/media/i2c/ks0127.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Video Capture Driver ( Video for Linux 1/2 ) + * for the Matrox Marvel G200,G400 and Rainbow Runner-G series + * + * This module is an interface to the KS0127 video decoder chip. + * + * Copyright (C) 1999 Ryan Drake <stiletto@mediaone.net> + */ + +#ifndef KS0127_H +#define KS0127_H + +/* input channels */ +#define KS_INPUT_COMPOSITE_1 0 +#define KS_INPUT_COMPOSITE_2 1 +#define KS_INPUT_COMPOSITE_3 2 +#define KS_INPUT_COMPOSITE_4 4 +#define KS_INPUT_COMPOSITE_5 5 +#define KS_INPUT_COMPOSITE_6 6 + +#define KS_INPUT_SVIDEO_1 8 +#define KS_INPUT_SVIDEO_2 9 +#define KS_INPUT_SVIDEO_3 10 + +#define KS_INPUT_YUV656 15 +#define KS_INPUT_COUNT 10 + +/* output channels */ +#define KS_OUTPUT_YUV656E 0 +#define KS_OUTPUT_EXV 1 + +/* video standards */ +#define KS_STD_NTSC_N 112 /* 50 Hz NTSC */ +#define KS_STD_PAL_M 113 /* 60 Hz PAL */ + +#endif /* KS0127_H */ + diff --git a/drivers/media/i2c/lm3560.c b/drivers/media/i2c/lm3560.c new file mode 100644 index 000000000..9e34ccce4 --- /dev/null +++ b/drivers/media/i2c/lm3560.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/lm3560.c + * General device driver for TI lm3559, lm3560, FLASH LED Driver + * + * Copyright (C) 2013 Texas Instruments + * + * Contact: Daniel Jeong <gshark.jeong@gmail.com> + * Ldd-Mlp <ldd-mlp@list.ti.com> + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/videodev2.h> +#include <media/i2c/lm3560.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +/* registers definitions */ +#define REG_ENABLE 0x10 +#define REG_TORCH_BR 0xa0 +#define REG_FLASH_BR 0xb0 +#define REG_FLASH_TOUT 0xc0 +#define REG_FLAG 0xd0 +#define REG_CONFIG1 0xe0 + +/* fault mask */ +#define FAULT_TIMEOUT (1<<0) +#define FAULT_OVERTEMP (1<<1) +#define FAULT_SHORT_CIRCUIT (1<<2) + +enum led_enable { + MODE_SHDN = 0x0, + MODE_TORCH = 0x2, + MODE_FLASH = 0x3, +}; + +/** + * struct lm3560_flash + * + * @dev: pointer to &struct device + * @pdata: platform data + * @regmap: reg. map for i2c + * @lock: muxtex for serial access. + * @led_mode: V4L2 LED mode + * @ctrls_led: V4L2 controls + * @subdev_led: V4L2 subdev + */ +struct lm3560_flash { + struct device *dev; + struct lm3560_platform_data *pdata; + struct regmap *regmap; + struct mutex lock; + + enum v4l2_flash_led_mode led_mode; + struct v4l2_ctrl_handler ctrls_led[LM3560_LED_MAX]; + struct v4l2_subdev subdev_led[LM3560_LED_MAX]; +}; + +#define to_lm3560_flash(_ctrl, _no) \ + container_of(_ctrl->handler, struct lm3560_flash, ctrls_led[_no]) + +/* enable mode control */ +static int lm3560_mode_ctrl(struct lm3560_flash *flash) +{ + int rval = -EINVAL; + + switch (flash->led_mode) { + case V4L2_FLASH_LED_MODE_NONE: + rval = regmap_update_bits(flash->regmap, + REG_ENABLE, 0x03, MODE_SHDN); + break; + case V4L2_FLASH_LED_MODE_TORCH: + rval = regmap_update_bits(flash->regmap, + REG_ENABLE, 0x03, MODE_TORCH); + break; + case V4L2_FLASH_LED_MODE_FLASH: + rval = regmap_update_bits(flash->regmap, + REG_ENABLE, 0x03, MODE_FLASH); + break; + } + return rval; +} + +/* led1/2 enable/disable */ +static int lm3560_enable_ctrl(struct lm3560_flash *flash, + enum lm3560_led_id led_no, bool on) +{ + int rval; + + if (led_no == LM3560_LED0) { + if (on) + rval = regmap_update_bits(flash->regmap, + REG_ENABLE, 0x08, 0x08); + else + rval = regmap_update_bits(flash->regmap, + REG_ENABLE, 0x08, 0x00); + } else { + if (on) + rval = regmap_update_bits(flash->regmap, + REG_ENABLE, 0x10, 0x10); + else + rval = regmap_update_bits(flash->regmap, + REG_ENABLE, 0x10, 0x00); + } + return rval; +} + +/* torch1/2 brightness control */ +static int lm3560_torch_brt_ctrl(struct lm3560_flash *flash, + enum lm3560_led_id led_no, unsigned int brt) +{ + int rval; + u8 br_bits; + + if (brt < LM3560_TORCH_BRT_MIN) + return lm3560_enable_ctrl(flash, led_no, false); + else + rval = lm3560_enable_ctrl(flash, led_no, true); + + br_bits = LM3560_TORCH_BRT_uA_TO_REG(brt); + if (led_no == LM3560_LED0) + rval = regmap_update_bits(flash->regmap, + REG_TORCH_BR, 0x07, br_bits); + else + rval = regmap_update_bits(flash->regmap, + REG_TORCH_BR, 0x38, br_bits << 3); + + return rval; +} + +/* flash1/2 brightness control */ +static int lm3560_flash_brt_ctrl(struct lm3560_flash *flash, + enum lm3560_led_id led_no, unsigned int brt) +{ + int rval; + u8 br_bits; + + if (brt < LM3560_FLASH_BRT_MIN) + return lm3560_enable_ctrl(flash, led_no, false); + else + rval = lm3560_enable_ctrl(flash, led_no, true); + + br_bits = LM3560_FLASH_BRT_uA_TO_REG(brt); + if (led_no == LM3560_LED0) + rval = regmap_update_bits(flash->regmap, + REG_FLASH_BR, 0x0f, br_bits); + else + rval = regmap_update_bits(flash->regmap, + REG_FLASH_BR, 0xf0, br_bits << 4); + + return rval; +} + +/* v4l2 controls */ +static int lm3560_get_ctrl(struct v4l2_ctrl *ctrl, enum lm3560_led_id led_no) +{ + struct lm3560_flash *flash = to_lm3560_flash(ctrl, led_no); + int rval = -EINVAL; + + mutex_lock(&flash->lock); + + if (ctrl->id == V4L2_CID_FLASH_FAULT) { + s32 fault = 0; + unsigned int reg_val; + rval = regmap_read(flash->regmap, REG_FLAG, ®_val); + if (rval < 0) + goto out; + if (reg_val & FAULT_SHORT_CIRCUIT) + fault |= V4L2_FLASH_FAULT_SHORT_CIRCUIT; + if (reg_val & FAULT_OVERTEMP) + fault |= V4L2_FLASH_FAULT_OVER_TEMPERATURE; + if (reg_val & FAULT_TIMEOUT) + fault |= V4L2_FLASH_FAULT_TIMEOUT; + ctrl->cur.val = fault; + } + +out: + mutex_unlock(&flash->lock); + return rval; +} + +static int lm3560_set_ctrl(struct v4l2_ctrl *ctrl, enum lm3560_led_id led_no) +{ + struct lm3560_flash *flash = to_lm3560_flash(ctrl, led_no); + u8 tout_bits; + int rval = -EINVAL; + + mutex_lock(&flash->lock); + + switch (ctrl->id) { + case V4L2_CID_FLASH_LED_MODE: + flash->led_mode = ctrl->val; + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) + rval = lm3560_mode_ctrl(flash); + break; + + case V4L2_CID_FLASH_STROBE_SOURCE: + rval = regmap_update_bits(flash->regmap, + REG_CONFIG1, 0x04, (ctrl->val) << 2); + if (rval < 0) + goto err_out; + break; + + case V4L2_CID_FLASH_STROBE: + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) { + rval = -EBUSY; + goto err_out; + } + flash->led_mode = V4L2_FLASH_LED_MODE_FLASH; + rval = lm3560_mode_ctrl(flash); + break; + + case V4L2_CID_FLASH_STROBE_STOP: + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) { + rval = -EBUSY; + goto err_out; + } + flash->led_mode = V4L2_FLASH_LED_MODE_NONE; + rval = lm3560_mode_ctrl(flash); + break; + + case V4L2_CID_FLASH_TIMEOUT: + tout_bits = LM3560_FLASH_TOUT_ms_TO_REG(ctrl->val); + rval = regmap_update_bits(flash->regmap, + REG_FLASH_TOUT, 0x1f, tout_bits); + break; + + case V4L2_CID_FLASH_INTENSITY: + rval = lm3560_flash_brt_ctrl(flash, led_no, ctrl->val); + break; + + case V4L2_CID_FLASH_TORCH_INTENSITY: + rval = lm3560_torch_brt_ctrl(flash, led_no, ctrl->val); + break; + } + +err_out: + mutex_unlock(&flash->lock); + return rval; +} + +static int lm3560_led1_get_ctrl(struct v4l2_ctrl *ctrl) +{ + return lm3560_get_ctrl(ctrl, LM3560_LED1); +} + +static int lm3560_led1_set_ctrl(struct v4l2_ctrl *ctrl) +{ + return lm3560_set_ctrl(ctrl, LM3560_LED1); +} + +static int lm3560_led0_get_ctrl(struct v4l2_ctrl *ctrl) +{ + return lm3560_get_ctrl(ctrl, LM3560_LED0); +} + +static int lm3560_led0_set_ctrl(struct v4l2_ctrl *ctrl) +{ + return lm3560_set_ctrl(ctrl, LM3560_LED0); +} + +static const struct v4l2_ctrl_ops lm3560_led_ctrl_ops[LM3560_LED_MAX] = { + [LM3560_LED0] = { + .g_volatile_ctrl = lm3560_led0_get_ctrl, + .s_ctrl = lm3560_led0_set_ctrl, + }, + [LM3560_LED1] = { + .g_volatile_ctrl = lm3560_led1_get_ctrl, + .s_ctrl = lm3560_led1_set_ctrl, + } +}; + +static int lm3560_init_controls(struct lm3560_flash *flash, + enum lm3560_led_id led_no) +{ + struct v4l2_ctrl *fault; + u32 max_flash_brt = flash->pdata->max_flash_brt[led_no]; + u32 max_torch_brt = flash->pdata->max_torch_brt[led_no]; + struct v4l2_ctrl_handler *hdl = &flash->ctrls_led[led_no]; + const struct v4l2_ctrl_ops *ops = &lm3560_led_ctrl_ops[led_no]; + + v4l2_ctrl_handler_init(hdl, 8); + + /* flash mode */ + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_FLASH_LED_MODE, + V4L2_FLASH_LED_MODE_TORCH, ~0x7, + V4L2_FLASH_LED_MODE_NONE); + flash->led_mode = V4L2_FLASH_LED_MODE_NONE; + + /* flash source */ + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_FLASH_STROBE_SOURCE, + 0x1, ~0x3, V4L2_FLASH_STROBE_SOURCE_SOFTWARE); + + /* flash strobe */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_STROBE, 0, 0, 0, 0); + + /* flash strobe stop */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_STROBE_STOP, 0, 0, 0, 0); + + /* flash strobe timeout */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_TIMEOUT, + LM3560_FLASH_TOUT_MIN, + flash->pdata->max_flash_timeout, + LM3560_FLASH_TOUT_STEP, + flash->pdata->max_flash_timeout); + + /* flash brt */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_INTENSITY, + LM3560_FLASH_BRT_MIN, max_flash_brt, + LM3560_FLASH_BRT_STEP, max_flash_brt); + + /* torch brt */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_TORCH_INTENSITY, + LM3560_TORCH_BRT_MIN, max_torch_brt, + LM3560_TORCH_BRT_STEP, max_torch_brt); + + /* fault */ + fault = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_FAULT, 0, + V4L2_FLASH_FAULT_OVER_VOLTAGE + | V4L2_FLASH_FAULT_OVER_TEMPERATURE + | V4L2_FLASH_FAULT_SHORT_CIRCUIT + | V4L2_FLASH_FAULT_TIMEOUT, 0, 0); + if (fault != NULL) + fault->flags |= V4L2_CTRL_FLAG_VOLATILE; + + if (hdl->error) + return hdl->error; + + flash->subdev_led[led_no].ctrl_handler = hdl; + return 0; +} + +/* initialize device */ +static const struct v4l2_subdev_ops lm3560_ops = { + .core = NULL, +}; + +static const struct regmap_config lm3560_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xFF, +}; + +static int lm3560_subdev_init(struct lm3560_flash *flash, + enum lm3560_led_id led_no, char *led_name) +{ + struct i2c_client *client = to_i2c_client(flash->dev); + int rval; + + v4l2_i2c_subdev_init(&flash->subdev_led[led_no], client, &lm3560_ops); + flash->subdev_led[led_no].flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + strscpy(flash->subdev_led[led_no].name, led_name, + sizeof(flash->subdev_led[led_no].name)); + rval = lm3560_init_controls(flash, led_no); + if (rval) + goto err_out; + rval = media_entity_pads_init(&flash->subdev_led[led_no].entity, 0, NULL); + if (rval < 0) + goto err_out; + flash->subdev_led[led_no].entity.function = MEDIA_ENT_F_FLASH; + + return rval; + +err_out: + v4l2_ctrl_handler_free(&flash->ctrls_led[led_no]); + return rval; +} + +static int lm3560_init_device(struct lm3560_flash *flash) +{ + int rval; + unsigned int reg_val; + + /* set peak current */ + rval = regmap_update_bits(flash->regmap, + REG_FLASH_TOUT, 0x60, flash->pdata->peak); + if (rval < 0) + return rval; + /* output disable */ + flash->led_mode = V4L2_FLASH_LED_MODE_NONE; + rval = lm3560_mode_ctrl(flash); + if (rval < 0) + return rval; + /* reset faults */ + rval = regmap_read(flash->regmap, REG_FLAG, ®_val); + return rval; +} + +static int lm3560_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct lm3560_flash *flash; + struct lm3560_platform_data *pdata = dev_get_platdata(&client->dev); + int rval; + + flash = devm_kzalloc(&client->dev, sizeof(*flash), GFP_KERNEL); + if (flash == NULL) + return -ENOMEM; + + flash->regmap = devm_regmap_init_i2c(client, &lm3560_regmap); + if (IS_ERR(flash->regmap)) { + rval = PTR_ERR(flash->regmap); + return rval; + } + + /* if there is no platform data, use chip default value */ + if (pdata == NULL) { + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) + return -ENODEV; + pdata->peak = LM3560_PEAK_3600mA; + pdata->max_flash_timeout = LM3560_FLASH_TOUT_MAX; + /* led 1 */ + pdata->max_flash_brt[LM3560_LED0] = LM3560_FLASH_BRT_MAX; + pdata->max_torch_brt[LM3560_LED0] = LM3560_TORCH_BRT_MAX; + /* led 2 */ + pdata->max_flash_brt[LM3560_LED1] = LM3560_FLASH_BRT_MAX; + pdata->max_torch_brt[LM3560_LED1] = LM3560_TORCH_BRT_MAX; + } + flash->pdata = pdata; + flash->dev = &client->dev; + mutex_init(&flash->lock); + + rval = lm3560_subdev_init(flash, LM3560_LED0, "lm3560-led0"); + if (rval < 0) + return rval; + + rval = lm3560_subdev_init(flash, LM3560_LED1, "lm3560-led1"); + if (rval < 0) + return rval; + + rval = lm3560_init_device(flash); + if (rval < 0) + return rval; + + i2c_set_clientdata(client, flash); + + return 0; +} + +static int lm3560_remove(struct i2c_client *client) +{ + struct lm3560_flash *flash = i2c_get_clientdata(client); + unsigned int i; + + for (i = LM3560_LED0; i < LM3560_LED_MAX; i++) { + v4l2_device_unregister_subdev(&flash->subdev_led[i]); + v4l2_ctrl_handler_free(&flash->ctrls_led[i]); + media_entity_cleanup(&flash->subdev_led[i].entity); + } + + return 0; +} + +static const struct i2c_device_id lm3560_id_table[] = { + {LM3559_NAME, 0}, + {LM3560_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lm3560_id_table); + +static struct i2c_driver lm3560_i2c_driver = { + .driver = { + .name = LM3560_NAME, + .pm = NULL, + }, + .probe = lm3560_probe, + .remove = lm3560_remove, + .id_table = lm3560_id_table, +}; + +module_i2c_driver(lm3560_i2c_driver); + +MODULE_AUTHOR("Daniel Jeong <gshark.jeong@gmail.com>"); +MODULE_AUTHOR("Ldd Mlp <ldd-mlp@list.ti.com>"); +MODULE_DESCRIPTION("Texas Instruments LM3560 LED flash driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/lm3646.c b/drivers/media/i2c/lm3646.c new file mode 100644 index 000000000..c76ccf67a --- /dev/null +++ b/drivers/media/i2c/lm3646.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/lm3646.c + * General device driver for TI lm3646, Dual FLASH LED Driver + * + * Copyright (C) 2014 Texas Instruments + * + * Contact: Daniel Jeong <gshark.jeong@gmail.com> + * Ldd-Mlp <ldd-mlp@list.ti.com> + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/videodev2.h> +#include <media/i2c/lm3646.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +/* registers definitions */ +#define REG_ENABLE 0x01 +#define REG_TORCH_BR 0x05 +#define REG_FLASH_BR 0x05 +#define REG_FLASH_TOUT 0x04 +#define REG_FLAG 0x08 +#define REG_STROBE_SRC 0x06 +#define REG_LED1_FLASH_BR 0x06 +#define REG_LED1_TORCH_BR 0x07 + +#define MASK_ENABLE 0x03 +#define MASK_TORCH_BR 0x70 +#define MASK_FLASH_BR 0x0F +#define MASK_FLASH_TOUT 0x07 +#define MASK_FLAG 0xFF +#define MASK_STROBE_SRC 0x80 + +/* Fault Mask */ +#define FAULT_TIMEOUT (1<<0) +#define FAULT_SHORT_CIRCUIT (1<<1) +#define FAULT_UVLO (1<<2) +#define FAULT_IVFM (1<<3) +#define FAULT_OCP (1<<4) +#define FAULT_OVERTEMP (1<<5) +#define FAULT_NTC_TRIP (1<<6) +#define FAULT_OVP (1<<7) + +enum led_mode { + MODE_SHDN = 0x0, + MODE_TORCH = 0x2, + MODE_FLASH = 0x3, +}; + +/* + * struct lm3646_flash + * + * @pdata: platform data + * @regmap: reg. map for i2c + * @lock: muxtex for serial access. + * @led_mode: V4L2 LED mode + * @ctrls_led: V4L2 controls + * @subdev_led: V4L2 subdev + * @mode_reg : mode register value + */ +struct lm3646_flash { + struct device *dev; + struct lm3646_platform_data *pdata; + struct regmap *regmap; + + struct v4l2_ctrl_handler ctrls_led; + struct v4l2_subdev subdev_led; + + u8 mode_reg; +}; + +#define to_lm3646_flash(_ctrl) \ + container_of(_ctrl->handler, struct lm3646_flash, ctrls_led) + +/* enable mode control */ +static int lm3646_mode_ctrl(struct lm3646_flash *flash, + enum v4l2_flash_led_mode led_mode) +{ + switch (led_mode) { + case V4L2_FLASH_LED_MODE_NONE: + return regmap_write(flash->regmap, + REG_ENABLE, flash->mode_reg | MODE_SHDN); + case V4L2_FLASH_LED_MODE_TORCH: + return regmap_write(flash->regmap, + REG_ENABLE, flash->mode_reg | MODE_TORCH); + case V4L2_FLASH_LED_MODE_FLASH: + return regmap_write(flash->regmap, + REG_ENABLE, flash->mode_reg | MODE_FLASH); + } + return -EINVAL; +} + +/* V4L2 controls */ +static int lm3646_get_ctrl(struct v4l2_ctrl *ctrl) +{ + struct lm3646_flash *flash = to_lm3646_flash(ctrl); + unsigned int reg_val; + int rval; + + if (ctrl->id != V4L2_CID_FLASH_FAULT) + return -EINVAL; + + rval = regmap_read(flash->regmap, REG_FLAG, ®_val); + if (rval < 0) + return rval; + + ctrl->val = 0; + if (reg_val & FAULT_TIMEOUT) + ctrl->val |= V4L2_FLASH_FAULT_TIMEOUT; + if (reg_val & FAULT_SHORT_CIRCUIT) + ctrl->val |= V4L2_FLASH_FAULT_SHORT_CIRCUIT; + if (reg_val & FAULT_UVLO) + ctrl->val |= V4L2_FLASH_FAULT_UNDER_VOLTAGE; + if (reg_val & FAULT_IVFM) + ctrl->val |= V4L2_FLASH_FAULT_INPUT_VOLTAGE; + if (reg_val & FAULT_OCP) + ctrl->val |= V4L2_FLASH_FAULT_OVER_CURRENT; + if (reg_val & FAULT_OVERTEMP) + ctrl->val |= V4L2_FLASH_FAULT_OVER_TEMPERATURE; + if (reg_val & FAULT_NTC_TRIP) + ctrl->val |= V4L2_FLASH_FAULT_LED_OVER_TEMPERATURE; + if (reg_val & FAULT_OVP) + ctrl->val |= V4L2_FLASH_FAULT_OVER_VOLTAGE; + + return 0; +} + +static int lm3646_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct lm3646_flash *flash = to_lm3646_flash(ctrl); + unsigned int reg_val; + int rval; + + switch (ctrl->id) { + case V4L2_CID_FLASH_LED_MODE: + + if (ctrl->val != V4L2_FLASH_LED_MODE_FLASH) + return lm3646_mode_ctrl(flash, ctrl->val); + /* switch to SHDN mode before flash strobe on */ + return lm3646_mode_ctrl(flash, V4L2_FLASH_LED_MODE_NONE); + + case V4L2_CID_FLASH_STROBE_SOURCE: + return regmap_update_bits(flash->regmap, + REG_STROBE_SRC, MASK_STROBE_SRC, + (ctrl->val) << 7); + + case V4L2_CID_FLASH_STROBE: + + /* read and check current mode of chip to start flash */ + rval = regmap_read(flash->regmap, REG_ENABLE, ®_val); + if (rval < 0 || ((reg_val & MASK_ENABLE) != MODE_SHDN)) + return rval; + /* flash on */ + return lm3646_mode_ctrl(flash, V4L2_FLASH_LED_MODE_FLASH); + + case V4L2_CID_FLASH_STROBE_STOP: + + /* + * flash mode will be turned automatically + * from FLASH mode to SHDN mode after flash duration timeout + * read and check current mode of chip to stop flash + */ + rval = regmap_read(flash->regmap, REG_ENABLE, ®_val); + if (rval < 0) + return rval; + if ((reg_val & MASK_ENABLE) == MODE_FLASH) + return lm3646_mode_ctrl(flash, + V4L2_FLASH_LED_MODE_NONE); + return rval; + + case V4L2_CID_FLASH_TIMEOUT: + return regmap_update_bits(flash->regmap, + REG_FLASH_TOUT, MASK_FLASH_TOUT, + LM3646_FLASH_TOUT_ms_TO_REG + (ctrl->val)); + + case V4L2_CID_FLASH_INTENSITY: + return regmap_update_bits(flash->regmap, + REG_FLASH_BR, MASK_FLASH_BR, + LM3646_TOTAL_FLASH_BRT_uA_TO_REG + (ctrl->val)); + + case V4L2_CID_FLASH_TORCH_INTENSITY: + return regmap_update_bits(flash->regmap, + REG_TORCH_BR, MASK_TORCH_BR, + LM3646_TOTAL_TORCH_BRT_uA_TO_REG + (ctrl->val) << 4); + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops lm3646_led_ctrl_ops = { + .g_volatile_ctrl = lm3646_get_ctrl, + .s_ctrl = lm3646_set_ctrl, +}; + +static int lm3646_init_controls(struct lm3646_flash *flash) +{ + struct v4l2_ctrl *fault; + struct v4l2_ctrl_handler *hdl = &flash->ctrls_led; + const struct v4l2_ctrl_ops *ops = &lm3646_led_ctrl_ops; + + v4l2_ctrl_handler_init(hdl, 8); + /* flash mode */ + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_FLASH_LED_MODE, + V4L2_FLASH_LED_MODE_TORCH, ~0x7, + V4L2_FLASH_LED_MODE_NONE); + + /* flash source */ + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_FLASH_STROBE_SOURCE, + 0x1, ~0x3, V4L2_FLASH_STROBE_SOURCE_SOFTWARE); + + /* flash strobe */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_STROBE, 0, 0, 0, 0); + /* flash strobe stop */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_STROBE_STOP, 0, 0, 0, 0); + + /* flash strobe timeout */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_TIMEOUT, + LM3646_FLASH_TOUT_MIN, + LM3646_FLASH_TOUT_MAX, + LM3646_FLASH_TOUT_STEP, flash->pdata->flash_timeout); + + /* max flash current */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_INTENSITY, + LM3646_TOTAL_FLASH_BRT_MIN, + LM3646_TOTAL_FLASH_BRT_MAX, + LM3646_TOTAL_FLASH_BRT_STEP, + LM3646_TOTAL_FLASH_BRT_MAX); + + /* max torch current */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_TORCH_INTENSITY, + LM3646_TOTAL_TORCH_BRT_MIN, + LM3646_TOTAL_TORCH_BRT_MAX, + LM3646_TOTAL_TORCH_BRT_STEP, + LM3646_TOTAL_TORCH_BRT_MAX); + + /* fault */ + fault = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_FAULT, 0, + V4L2_FLASH_FAULT_OVER_VOLTAGE + | V4L2_FLASH_FAULT_OVER_TEMPERATURE + | V4L2_FLASH_FAULT_SHORT_CIRCUIT + | V4L2_FLASH_FAULT_TIMEOUT, 0, 0); + if (fault != NULL) + fault->flags |= V4L2_CTRL_FLAG_VOLATILE; + + if (hdl->error) + return hdl->error; + + flash->subdev_led.ctrl_handler = hdl; + return 0; +} + +/* initialize device */ +static const struct v4l2_subdev_ops lm3646_ops = { + .core = NULL, +}; + +static const struct regmap_config lm3646_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xFF, +}; + +static int lm3646_subdev_init(struct lm3646_flash *flash) +{ + struct i2c_client *client = to_i2c_client(flash->dev); + int rval; + + v4l2_i2c_subdev_init(&flash->subdev_led, client, &lm3646_ops); + flash->subdev_led.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + strscpy(flash->subdev_led.name, LM3646_NAME, + sizeof(flash->subdev_led.name)); + rval = lm3646_init_controls(flash); + if (rval) + goto err_out; + rval = media_entity_pads_init(&flash->subdev_led.entity, 0, NULL); + if (rval < 0) + goto err_out; + flash->subdev_led.entity.function = MEDIA_ENT_F_FLASH; + return rval; + +err_out: + v4l2_ctrl_handler_free(&flash->ctrls_led); + return rval; +} + +static int lm3646_init_device(struct lm3646_flash *flash) +{ + unsigned int reg_val; + int rval; + + /* read the value of mode register to reduce redundant i2c accesses */ + rval = regmap_read(flash->regmap, REG_ENABLE, ®_val); + if (rval < 0) + return rval; + flash->mode_reg = reg_val & 0xfc; + + /* output disable */ + rval = lm3646_mode_ctrl(flash, V4L2_FLASH_LED_MODE_NONE); + if (rval < 0) + return rval; + + /* + * LED1 flash current setting + * LED2 flash current = Total(Max) flash current - LED1 flash current + */ + rval = regmap_update_bits(flash->regmap, + REG_LED1_FLASH_BR, 0x7F, + LM3646_LED1_FLASH_BRT_uA_TO_REG + (flash->pdata->led1_flash_brt)); + + if (rval < 0) + return rval; + + /* + * LED1 torch current setting + * LED2 torch current = Total(Max) torch current - LED1 torch current + */ + rval = regmap_update_bits(flash->regmap, + REG_LED1_TORCH_BR, 0x7F, + LM3646_LED1_TORCH_BRT_uA_TO_REG + (flash->pdata->led1_torch_brt)); + if (rval < 0) + return rval; + + /* Reset flag register */ + return regmap_read(flash->regmap, REG_FLAG, ®_val); +} + +static int lm3646_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct lm3646_flash *flash; + struct lm3646_platform_data *pdata = dev_get_platdata(&client->dev); + int rval; + + flash = devm_kzalloc(&client->dev, sizeof(*flash), GFP_KERNEL); + if (flash == NULL) + return -ENOMEM; + + flash->regmap = devm_regmap_init_i2c(client, &lm3646_regmap); + if (IS_ERR(flash->regmap)) + return PTR_ERR(flash->regmap); + + /* check device tree if there is no platform data */ + if (pdata == NULL) { + pdata = devm_kzalloc(&client->dev, + sizeof(struct lm3646_platform_data), + GFP_KERNEL); + if (pdata == NULL) + return -ENOMEM; + /* use default data in case of no platform data */ + pdata->flash_timeout = LM3646_FLASH_TOUT_MAX; + pdata->led1_torch_brt = LM3646_LED1_TORCH_BRT_MAX; + pdata->led1_flash_brt = LM3646_LED1_FLASH_BRT_MAX; + } + flash->pdata = pdata; + flash->dev = &client->dev; + + rval = lm3646_subdev_init(flash); + if (rval < 0) + return rval; + + rval = lm3646_init_device(flash); + if (rval < 0) + return rval; + + i2c_set_clientdata(client, flash); + + return 0; +} + +static int lm3646_remove(struct i2c_client *client) +{ + struct lm3646_flash *flash = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(&flash->subdev_led); + v4l2_ctrl_handler_free(&flash->ctrls_led); + media_entity_cleanup(&flash->subdev_led.entity); + + return 0; +} + +static const struct i2c_device_id lm3646_id_table[] = { + {LM3646_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lm3646_id_table); + +static struct i2c_driver lm3646_i2c_driver = { + .driver = { + .name = LM3646_NAME, + }, + .probe = lm3646_probe, + .remove = lm3646_remove, + .id_table = lm3646_id_table, +}; + +module_i2c_driver(lm3646_i2c_driver); + +MODULE_AUTHOR("Daniel Jeong <gshark.jeong@gmail.com>"); +MODULE_AUTHOR("Ldd Mlp <ldd-mlp@list.ti.com>"); +MODULE_DESCRIPTION("Texas Instruments LM3646 Dual Flash LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/m52790.c b/drivers/media/i2c/m52790.c new file mode 100644 index 000000000..0a1efc141 --- /dev/null +++ b/drivers/media/i2c/m52790.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * m52790 i2c ivtv driver. + * Copyright (C) 2007 Hans Verkuil + * + * A/V source switching Mitsubishi M52790SP/FP + */ + + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/i2c/m52790.h> +#include <media/v4l2-device.h> + +MODULE_DESCRIPTION("i2c device driver for m52790 A/V switch"); +MODULE_AUTHOR("Hans Verkuil"); +MODULE_LICENSE("GPL"); + + +struct m52790_state { + struct v4l2_subdev sd; + u16 input; + u16 output; +}; + +static inline struct m52790_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct m52790_state, sd); +} + +/* ----------------------------------------------------------------------- */ + +static int m52790_write(struct v4l2_subdev *sd) +{ + struct m52790_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + u8 sw1 = (state->input | state->output) & 0xff; + u8 sw2 = (state->input | state->output) >> 8; + + return i2c_smbus_write_byte_data(client, sw1, sw2); +} + +/* Note: audio and video are linked and cannot be switched separately. + So audio and video routing commands are identical for this chip. + In theory the video amplifier and audio modes could be handled + separately for the output, but that seems to be overkill right now. + The same holds for implementing an audio mute control, this is now + part of the audio output routing. The normal case is that another + chip takes care of the actual muting so making it part of the + output routing seems to be the right thing to do for now. */ +static int m52790_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct m52790_state *state = to_state(sd); + + state->input = input; + state->output = output; + m52790_write(sd); + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int m52790_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct m52790_state *state = to_state(sd); + + if (reg->reg != 0) + return -EINVAL; + reg->size = 1; + reg->val = state->input | state->output; + return 0; +} + +static int m52790_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + struct m52790_state *state = to_state(sd); + + if (reg->reg != 0) + return -EINVAL; + state->input = reg->val & 0x0303; + state->output = reg->val & ~0x0303; + m52790_write(sd); + return 0; +} +#endif + +static int m52790_log_status(struct v4l2_subdev *sd) +{ + struct m52790_state *state = to_state(sd); + + v4l2_info(sd, "Switch 1: %02x\n", + (state->input | state->output) & 0xff); + v4l2_info(sd, "Switch 2: %02x\n", + (state->input | state->output) >> 8); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops m52790_core_ops = { + .log_status = m52790_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = m52790_g_register, + .s_register = m52790_s_register, +#endif +}; + +static const struct v4l2_subdev_audio_ops m52790_audio_ops = { + .s_routing = m52790_s_routing, +}; + +static const struct v4l2_subdev_video_ops m52790_video_ops = { + .s_routing = m52790_s_routing, +}; + +static const struct v4l2_subdev_ops m52790_ops = { + .core = &m52790_core_ops, + .audio = &m52790_audio_ops, + .video = &m52790_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + +/* i2c implementation */ + +static int m52790_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct m52790_state *state; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &m52790_ops); + state->input = M52790_IN_TUNER; + state->output = M52790_OUT_STEREO; + m52790_write(sd); + return 0; +} + +static int m52790_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id m52790_id[] = { + { "m52790", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, m52790_id); + +static struct i2c_driver m52790_driver = { + .driver = { + .name = "m52790", + }, + .probe = m52790_probe, + .remove = m52790_remove, + .id_table = m52790_id, +}; + +module_i2c_driver(m52790_driver); diff --git a/drivers/media/i2c/m5mols/Kconfig b/drivers/media/i2c/m5mols/Kconfig new file mode 100644 index 000000000..6f0ef33b7 --- /dev/null +++ b/drivers/media/i2c/m5mols/Kconfig @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_M5MOLS + tristate "Fujitsu M-5MOLS 8MP sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This driver supports Fujitsu M-5MOLS camera sensor with ISP diff --git a/drivers/media/i2c/m5mols/Makefile b/drivers/media/i2c/m5mols/Makefile new file mode 100644 index 000000000..13fa8ec29 --- /dev/null +++ b/drivers/media/i2c/m5mols/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +m5mols-objs := m5mols_core.o m5mols_controls.o m5mols_capture.o + +obj-$(CONFIG_VIDEO_M5MOLS) += m5mols.o diff --git a/drivers/media/i2c/m5mols/m5mols.h b/drivers/media/i2c/m5mols/m5mols.h new file mode 100644 index 000000000..4023906d2 --- /dev/null +++ b/drivers/media/i2c/m5mols/m5mols.h @@ -0,0 +1,344 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Header for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim <riverful.kim@samsung.com> + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim <dongsoo45.kim@samsung.com> + */ + +#ifndef M5MOLS_H +#define M5MOLS_H + +#include <linux/sizes.h> +#include <media/v4l2-subdev.h> +#include "m5mols_reg.h" + + +/* An amount of data transmitted in addition to the value + * determined by CAPP_JPEG_SIZE_MAX register. + */ +#define M5MOLS_JPEG_TAGS_SIZE 0x20000 +#define M5MOLS_MAIN_JPEG_SIZE_MAX (5 * SZ_1M) + +extern int m5mols_debug; + +enum m5mols_restype { + M5MOLS_RESTYPE_MONITOR, + M5MOLS_RESTYPE_CAPTURE, + M5MOLS_RESTYPE_MAX, +}; + +/** + * struct m5mols_resolution - structure for the resolution + * @type: resolution type according to the pixel code + * @width: width of the resolution + * @height: height of the resolution + * @reg: resolution preset register value + */ +struct m5mols_resolution { + u8 reg; + enum m5mols_restype type; + u16 width; + u16 height; +}; + +/** + * struct m5mols_exif - structure for the EXIF information of M-5MOLS + * @exposure_time: exposure time register value + * @shutter_speed: speed of the shutter register value + * @aperture: aperture register value + * @exposure_bias: it calls also EV bias + * @iso_speed: ISO register value + * @flash: status register value of the flash + * @sdr: status register value of the Subject Distance Range + * @qval: not written exact meaning in document + */ +struct m5mols_exif { + u32 exposure_time; + u32 shutter_speed; + u32 aperture; + u32 brightness; + u32 exposure_bias; + u16 iso_speed; + u16 flash; + u16 sdr; + u16 qval; +}; + +/** + * struct m5mols_capture - Structure for the capture capability + * @exif: EXIF information + * @buf_size: internal JPEG frame buffer size, in bytes + * @main: size in bytes of the main image + * @thumb: size in bytes of the thumb image, if it was accompanied + * @total: total size in bytes of the produced image + */ +struct m5mols_capture { + struct m5mols_exif exif; + unsigned int buf_size; + u32 main; + u32 thumb; + u32 total; +}; + +/** + * struct m5mols_scenemode - structure for the scenemode capability + * @metering: metering light register value + * @ev_bias: EV bias register value + * @wb_mode: mode which means the WhiteBalance is Auto or Manual + * @wb_preset: whitebalance preset register value in the Manual mode + * @chroma_en: register value whether the Chroma capability is enabled or not + * @chroma_lvl: chroma's level register value + * @edge_en: register value Whether the Edge capability is enabled or not + * @edge_lvl: edge's level register value + * @af_range: Auto Focus's range + * @fd_mode: Face Detection mode + * @mcc: Multi-axis Color Conversion which means emotion color + * @light: status of the Light + * @flash: status of the Flash + * @tone: Tone color which means Contrast + * @iso: ISO register value + * @capt_mode: Mode of the Image Stabilization while the camera capturing + * @wdr: Wide Dynamic Range register value + * + * The each value according to each scenemode is recommended in the documents. + */ +struct m5mols_scenemode { + u8 metering; + u8 ev_bias; + u8 wb_mode; + u8 wb_preset; + u8 chroma_en; + u8 chroma_lvl; + u8 edge_en; + u8 edge_lvl; + u8 af_range; + u8 fd_mode; + u8 mcc; + u8 light; + u8 flash; + u8 tone; + u8 iso; + u8 capt_mode; + u8 wdr; +}; + +/** + * struct m5mols_version - firmware version information + * @customer: customer information + * @project: version of project information according to customer + * @fw: firmware revision + * @hw: hardware revision + * @param: version of the parameter + * @awb: Auto WhiteBalance algorithm version + * @str: information about manufacturer and packaging vendor + * @af: Auto Focus version + * + * The register offset starts the customer version at 0x0, and it ends + * the awb version at 0x09. The customer, project information occupies 1 bytes + * each. And also the fw, hw, param, awb each requires 2 bytes. The str is + * unique string associated with firmware's version. It includes information + * about manufacturer and the vendor of the sensor's packaging. The least + * significant 2 bytes of the string indicate packaging manufacturer. + */ +#define VERSION_STRING_SIZE 22 +struct m5mols_version { + u8 customer; + u8 project; + u16 fw; + u16 hw; + u16 param; + u16 awb; + u8 str[VERSION_STRING_SIZE]; + u8 af; +}; + +/** + * struct m5mols_info - M-5MOLS driver data structure + * @pdata: platform data + * @sd: v4l-subdev instance + * @pad: media pad + * @irq_waitq: waitqueue for the capture + * @irq_done: set to 1 in the interrupt handler + * @handle: control handler + * @auto_exposure: auto/manual exposure control + * @exposure_bias: exposure compensation control + * @exposure: manual exposure control + * @metering: exposure metering control + * @auto_iso: auto/manual ISO sensitivity control + * @iso: manual ISO sensitivity control + * @auto_wb: auto white balance control + * @lock_3a: 3A lock control + * @colorfx: color effect control + * @saturation: saturation control + * @zoom: zoom control + * @wdr: wide dynamic range control + * @stabilization: image stabilization control + * @jpeg_quality: JPEG compression quality control + * @set_power: optional power callback to the board code + * @lock: mutex protecting the structure fields below + * @ffmt: current fmt according to resolution type + * @res_type: current resolution type + * @ver: information of the version + * @cap: the capture mode attributes + * @isp_ready: 1 when the ISP controller has completed booting + * @power: current sensor's power status + * @ctrl_sync: 1 when the control handler state is restored in H/W + * @resolution: register value for current resolution + * @mode: register value for current operation mode + */ +struct m5mols_info { + const struct m5mols_platform_data *pdata; + struct v4l2_subdev sd; + struct media_pad pad; + + wait_queue_head_t irq_waitq; + atomic_t irq_done; + + struct v4l2_ctrl_handler handle; + struct { + /* exposure/exposure bias/auto exposure cluster */ + struct v4l2_ctrl *auto_exposure; + struct v4l2_ctrl *exposure_bias; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *metering; + }; + struct { + /* iso/auto iso cluster */ + struct v4l2_ctrl *auto_iso; + struct v4l2_ctrl *iso; + }; + struct v4l2_ctrl *auto_wb; + + struct v4l2_ctrl *lock_3a; + struct v4l2_ctrl *colorfx; + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *zoom; + struct v4l2_ctrl *wdr; + struct v4l2_ctrl *stabilization; + struct v4l2_ctrl *jpeg_quality; + + int (*set_power)(struct device *dev, int on); + + struct mutex lock; + + struct v4l2_mbus_framefmt ffmt[M5MOLS_RESTYPE_MAX]; + int res_type; + + struct m5mols_version ver; + struct m5mols_capture cap; + + unsigned int isp_ready:1; + unsigned int power:1; + unsigned int ctrl_sync:1; + + u8 resolution; + u8 mode; +}; + +#define is_available_af(__info) (__info->ver.af) +#define is_code(__code, __type) (__code == m5mols_default_ffmt[__type].code) +#define is_manufacturer(__info, __manufacturer) \ + (__info->ver.str[0] == __manufacturer[0] && \ + __info->ver.str[1] == __manufacturer[1]) +/* + * I2C operation of the M-5MOLS + * + * The I2C read operation of the M-5MOLS requires 2 messages. The first + * message sends the information about the command, command category, and total + * message size. The second message is used to retrieve the data specified in + * the first message + * + * 1st message 2nd message + * +-------+---+----------+-----+-------+ +------+------+------+------+ + * | size1 | R | category | cmd | size2 | | d[0] | d[1] | d[2] | d[3] | + * +-------+---+----------+-----+-------+ +------+------+------+------+ + * - size1: message data size(5 in this case) + * - size2: desired buffer size of the 2nd message + * - d[0..3]: according to size2 + * + * The I2C write operation needs just one message. The message includes + * category, command, total size, and desired data. + * + * 1st message + * +-------+---+----------+-----+------+------+------+------+ + * | size1 | W | category | cmd | d[0] | d[1] | d[2] | d[3] | + * +-------+---+----------+-----+------+------+------+------+ + * - d[0..3]: according to size1 + */ +int m5mols_read_u8(struct v4l2_subdev *sd, u32 reg_comb, u8 *val); +int m5mols_read_u16(struct v4l2_subdev *sd, u32 reg_comb, u16 *val); +int m5mols_read_u32(struct v4l2_subdev *sd, u32 reg_comb, u32 *val); +int m5mols_write(struct v4l2_subdev *sd, u32 reg_comb, u32 val); + +int m5mols_busy_wait(struct v4l2_subdev *sd, u32 reg, u32 value, u32 mask, + int timeout); + +/* Mask value for busy waiting until M-5MOLS I2C interface is initialized */ +#define M5MOLS_I2C_RDY_WAIT_FL (1 << 16) +/* ISP state transition timeout, in ms */ +#define M5MOLS_MODE_CHANGE_TIMEOUT 200 +#define M5MOLS_BUSY_WAIT_DEF_TIMEOUT 250 + +/* + * Mode operation of the M-5MOLS + * + * Changing the mode of the M-5MOLS is needed right executing order. + * There are three modes(PARAMETER, MONITOR, CAPTURE) which can be changed + * by user. There are various categories associated with each mode. + * + * +============================================================+ + * | mode | category | + * +============================================================+ + * | FLASH | FLASH(only after Stand-by or Power-on) | + * | SYSTEM | SYSTEM(only after sensor arm-booting) | + * | PARAMETER | PARAMETER | + * | MONITOR | MONITOR(preview), Auto Focus, Face Detection | + * | CAPTURE | Single CAPTURE, Preview(recording) | + * +============================================================+ + * + * The available executing order between each modes are as follows: + * PARAMETER <---> MONITOR <---> CAPTURE + */ +int m5mols_set_mode(struct m5mols_info *info, u8 mode); + +int m5mols_enable_interrupt(struct v4l2_subdev *sd, u8 reg); +int m5mols_wait_interrupt(struct v4l2_subdev *sd, u8 condition, u32 timeout); +int m5mols_restore_controls(struct m5mols_info *info); +int m5mols_start_capture(struct m5mols_info *info); +int m5mols_do_scenemode(struct m5mols_info *info, u8 mode); +int m5mols_lock_3a(struct m5mols_info *info, bool lock); +int m5mols_set_ctrl(struct v4l2_ctrl *ctrl); +int m5mols_init_controls(struct v4l2_subdev *sd); + +/* The firmware function */ +int m5mols_update_fw(struct v4l2_subdev *sd, + int (*set_power)(struct m5mols_info *, bool)); + +static inline struct m5mols_info *to_m5mols(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct m5mols_info, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + struct m5mols_info *info = container_of(ctrl->handler, + struct m5mols_info, handle); + return &info->sd; +} + +static inline void m5mols_set_ctrl_mode(struct v4l2_ctrl *ctrl, + unsigned int mode) +{ + ctrl->priv = (void *)(uintptr_t)mode; +} + +static inline unsigned int m5mols_get_ctrl_mode(struct v4l2_ctrl *ctrl) +{ + return (unsigned int)(uintptr_t)ctrl->priv; +} + +#endif /* M5MOLS_H */ diff --git a/drivers/media/i2c/m5mols/m5mols_capture.c b/drivers/media/i2c/m5mols/m5mols_capture.c new file mode 100644 index 000000000..e1b1d689c --- /dev/null +++ b/drivers/media/i2c/m5mols/m5mols_capture.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * The Capture code for Fujitsu M-5MOLS ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim <riverful.kim@samsung.com> + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim <dongsoo45.kim@samsung.com> + */ + +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/videodev2.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/i2c/m5mols.h> +#include <media/drv-intf/exynos-fimc.h> + +#include "m5mols.h" +#include "m5mols_reg.h" + +/** + * m5mols_read_rational - I2C read of a rational number + * @sd: sub-device, as pointed by struct v4l2_subdev + * @addr_num: numerator register + * @addr_den: denominator register + * @val: place to store the division result + * + * Read numerator and denominator from registers @addr_num and @addr_den + * respectively and return the division result in @val. + */ +static int m5mols_read_rational(struct v4l2_subdev *sd, u32 addr_num, + u32 addr_den, u32 *val) +{ + u32 num, den; + + int ret = m5mols_read_u32(sd, addr_num, &num); + if (!ret) + ret = m5mols_read_u32(sd, addr_den, &den); + if (ret) + return ret; + *val = den == 0 ? 0 : num / den; + return ret; +} + +/** + * m5mols_capture_info - Gather captured image information + * @info: M-5MOLS driver data structure + * + * For now it gathers only EXIF information and file size. + */ +static int m5mols_capture_info(struct m5mols_info *info) +{ + struct m5mols_exif *exif = &info->cap.exif; + struct v4l2_subdev *sd = &info->sd; + int ret; + + ret = m5mols_read_rational(sd, EXIF_INFO_EXPTIME_NU, + EXIF_INFO_EXPTIME_DE, &exif->exposure_time); + if (ret) + return ret; + ret = m5mols_read_rational(sd, EXIF_INFO_TV_NU, EXIF_INFO_TV_DE, + &exif->shutter_speed); + if (ret) + return ret; + ret = m5mols_read_rational(sd, EXIF_INFO_AV_NU, EXIF_INFO_AV_DE, + &exif->aperture); + if (ret) + return ret; + ret = m5mols_read_rational(sd, EXIF_INFO_BV_NU, EXIF_INFO_BV_DE, + &exif->brightness); + if (ret) + return ret; + ret = m5mols_read_rational(sd, EXIF_INFO_EBV_NU, EXIF_INFO_EBV_DE, + &exif->exposure_bias); + if (ret) + return ret; + + ret = m5mols_read_u16(sd, EXIF_INFO_ISO, &exif->iso_speed); + if (!ret) + ret = m5mols_read_u16(sd, EXIF_INFO_FLASH, &exif->flash); + if (!ret) + ret = m5mols_read_u16(sd, EXIF_INFO_SDR, &exif->sdr); + if (!ret) + ret = m5mols_read_u16(sd, EXIF_INFO_QVAL, &exif->qval); + if (ret) + return ret; + + if (!ret) + ret = m5mols_read_u32(sd, CAPC_IMAGE_SIZE, &info->cap.main); + if (!ret) + ret = m5mols_read_u32(sd, CAPC_THUMB_SIZE, &info->cap.thumb); + if (!ret) + info->cap.total = info->cap.main + info->cap.thumb; + + return ret; +} + +int m5mols_start_capture(struct m5mols_info *info) +{ + unsigned int framesize = info->cap.buf_size - M5MOLS_JPEG_TAGS_SIZE; + struct v4l2_subdev *sd = &info->sd; + int ret; + + /* + * Synchronize the controls, set the capture frame resolution and color + * format. The frame capture is initiated during switching from Monitor + * to Capture mode. + */ + ret = m5mols_set_mode(info, REG_MONITOR); + if (!ret) + ret = m5mols_restore_controls(info); + if (!ret) + ret = m5mols_write(sd, CAPP_YUVOUT_MAIN, REG_JPEG); + if (!ret) + ret = m5mols_write(sd, CAPP_MAIN_IMAGE_SIZE, info->resolution); + if (!ret) + ret = m5mols_write(sd, CAPP_JPEG_SIZE_MAX, framesize); + if (!ret) + ret = m5mols_set_mode(info, REG_CAPTURE); + if (!ret) + /* Wait until a frame is captured to ISP internal memory */ + ret = m5mols_wait_interrupt(sd, REG_INT_CAPTURE, 2000); + if (ret) + return ret; + + /* + * Initiate the captured data transfer to a MIPI-CSI receiver. + */ + ret = m5mols_write(sd, CAPC_SEL_FRAME, 1); + if (!ret) + ret = m5mols_write(sd, CAPC_START, REG_CAP_START_MAIN); + if (!ret) { + bool captured = false; + unsigned int size; + + /* Wait for the capture completion interrupt */ + ret = m5mols_wait_interrupt(sd, REG_INT_CAPTURE, 2000); + if (!ret) { + captured = true; + ret = m5mols_capture_info(info); + } + size = captured ? info->cap.main : 0; + v4l2_dbg(1, m5mols_debug, sd, "%s: size: %d, thumb.: %d B\n", + __func__, size, info->cap.thumb); + + v4l2_subdev_notify(sd, S5P_FIMC_TX_END_NOTIFY, &size); + } + + return ret; +} diff --git a/drivers/media/i2c/m5mols/m5mols_controls.c b/drivers/media/i2c/m5mols/m5mols_controls.c new file mode 100644 index 000000000..b45e0e08b --- /dev/null +++ b/drivers/media/i2c/m5mols/m5mols_controls.c @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Controls for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim <riverful.kim@samsung.com> + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim <dongsoo45.kim@samsung.com> + */ + +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <media/v4l2-ctrls.h> + +#include "m5mols.h" +#include "m5mols_reg.h" + +static struct m5mols_scenemode m5mols_default_scenemode[] = { + [REG_SCENE_NORMAL] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_NORMAL, REG_LIGHT_OFF, REG_FLASH_OFF, + 5, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_PORTRAIT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 4, + REG_AF_NORMAL, BIT_FD_EN | BIT_FD_DRAW_FACE_FRAME, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_LANDSCAPE] = { + REG_AE_ALL, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 6, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_SPORTS] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_PARTY_INDOOR] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_200, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_BEACH_SNOW] = { + REG_AE_CENTER, REG_AE_INDEX_10_POS, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_50, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_SUNSET] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_PRESET, + REG_AWB_DAYLIGHT, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_DAWN_DUSK] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_PRESET, + REG_AWB_FLUORESCENT_1, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_FALL] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 5, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_NIGHT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_AGAINST_LIGHT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_FIRE] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_50, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_TEXT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 7, + REG_AF_MACRO, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_ANTI_SHAKE, REG_WDR_ON, + }, + [REG_SCENE_CANDLE] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, +}; + +/** + * m5mols_do_scenemode() - Change current scenemode + * @info: M-5MOLS driver data structure + * @mode: Desired mode of the scenemode + * + * WARNING: The execution order is important. Do not change the order. + */ +int m5mols_do_scenemode(struct m5mols_info *info, u8 mode) +{ + struct v4l2_subdev *sd = &info->sd; + struct m5mols_scenemode scenemode = m5mols_default_scenemode[mode]; + int ret; + + if (mode > REG_SCENE_CANDLE) + return -EINVAL; + + ret = v4l2_ctrl_s_ctrl(info->lock_3a, 0); + if (!ret) + ret = m5mols_write(sd, AE_EV_PRESET_MONITOR, mode); + if (!ret) + ret = m5mols_write(sd, AE_EV_PRESET_CAPTURE, mode); + if (!ret) + ret = m5mols_write(sd, AE_MODE, scenemode.metering); + if (!ret) + ret = m5mols_write(sd, AE_INDEX, scenemode.ev_bias); + if (!ret) + ret = m5mols_write(sd, AWB_MODE, scenemode.wb_mode); + if (!ret) + ret = m5mols_write(sd, AWB_MANUAL, scenemode.wb_preset); + if (!ret) + ret = m5mols_write(sd, MON_CHROMA_EN, scenemode.chroma_en); + if (!ret) + ret = m5mols_write(sd, MON_CHROMA_LVL, scenemode.chroma_lvl); + if (!ret) + ret = m5mols_write(sd, MON_EDGE_EN, scenemode.edge_en); + if (!ret) + ret = m5mols_write(sd, MON_EDGE_LVL, scenemode.edge_lvl); + if (!ret && is_available_af(info)) + ret = m5mols_write(sd, AF_MODE, scenemode.af_range); + if (!ret && is_available_af(info)) + ret = m5mols_write(sd, FD_CTL, scenemode.fd_mode); + if (!ret) + ret = m5mols_write(sd, MON_TONE_CTL, scenemode.tone); + if (!ret) + ret = m5mols_write(sd, AE_ISO, scenemode.iso); + if (!ret) + ret = m5mols_set_mode(info, REG_CAPTURE); + if (!ret) + ret = m5mols_write(sd, CAPP_WDR_EN, scenemode.wdr); + if (!ret) + ret = m5mols_write(sd, CAPP_MCC_MODE, scenemode.mcc); + if (!ret) + ret = m5mols_write(sd, CAPP_LIGHT_CTRL, scenemode.light); + if (!ret) + ret = m5mols_write(sd, CAPP_FLASH_CTRL, scenemode.flash); + if (!ret) + ret = m5mols_write(sd, CAPC_MODE, scenemode.capt_mode); + if (!ret) + ret = m5mols_set_mode(info, REG_MONITOR); + + return ret; +} + +static int m5mols_3a_lock(struct m5mols_info *info, struct v4l2_ctrl *ctrl) +{ + bool af_lock = ctrl->val & V4L2_LOCK_FOCUS; + int ret = 0; + + if ((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_EXPOSURE) { + bool ae_lock = ctrl->val & V4L2_LOCK_EXPOSURE; + + ret = m5mols_write(&info->sd, AE_LOCK, ae_lock ? + REG_AE_LOCK : REG_AE_UNLOCK); + if (ret) + return ret; + } + + if (((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_WHITE_BALANCE) + && info->auto_wb->val) { + bool awb_lock = ctrl->val & V4L2_LOCK_WHITE_BALANCE; + + ret = m5mols_write(&info->sd, AWB_LOCK, awb_lock ? + REG_AWB_LOCK : REG_AWB_UNLOCK); + if (ret) + return ret; + } + + if (!info->ver.af || !af_lock) + return ret; + + if ((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_FOCUS) + ret = m5mols_write(&info->sd, AF_EXECUTE, REG_AF_STOP); + + return ret; +} + +static int m5mols_set_metering_mode(struct m5mols_info *info, int mode) +{ + unsigned int metering; + + switch (mode) { + case V4L2_EXPOSURE_METERING_CENTER_WEIGHTED: + metering = REG_AE_CENTER; + break; + case V4L2_EXPOSURE_METERING_SPOT: + metering = REG_AE_SPOT; + break; + default: + metering = REG_AE_ALL; + break; + } + + return m5mols_write(&info->sd, AE_MODE, metering); +} + +static int m5mols_set_exposure(struct m5mols_info *info, int exposure) +{ + struct v4l2_subdev *sd = &info->sd; + int ret = 0; + + if (exposure == V4L2_EXPOSURE_AUTO) { + /* Unlock auto exposure */ + info->lock_3a->val &= ~V4L2_LOCK_EXPOSURE; + m5mols_3a_lock(info, info->lock_3a); + + ret = m5mols_set_metering_mode(info, info->metering->val); + if (ret < 0) + return ret; + + v4l2_dbg(1, m5mols_debug, sd, + "%s: exposure bias: %#x, metering: %#x\n", + __func__, info->exposure_bias->val, + info->metering->val); + + return m5mols_write(sd, AE_INDEX, info->exposure_bias->val); + } + + if (exposure == V4L2_EXPOSURE_MANUAL) { + ret = m5mols_write(sd, AE_MODE, REG_AE_OFF); + if (ret == 0) + ret = m5mols_write(sd, AE_MAN_GAIN_MON, + info->exposure->val); + if (ret == 0) + ret = m5mols_write(sd, AE_MAN_GAIN_CAP, + info->exposure->val); + + v4l2_dbg(1, m5mols_debug, sd, "%s: exposure: %#x\n", + __func__, info->exposure->val); + } + + return ret; +} + +static int m5mols_set_white_balance(struct m5mols_info *info, int val) +{ + static const unsigned short wb[][2] = { + { V4L2_WHITE_BALANCE_INCANDESCENT, REG_AWB_INCANDESCENT }, + { V4L2_WHITE_BALANCE_FLUORESCENT, REG_AWB_FLUORESCENT_1 }, + { V4L2_WHITE_BALANCE_FLUORESCENT_H, REG_AWB_FLUORESCENT_2 }, + { V4L2_WHITE_BALANCE_HORIZON, REG_AWB_HORIZON }, + { V4L2_WHITE_BALANCE_DAYLIGHT, REG_AWB_DAYLIGHT }, + { V4L2_WHITE_BALANCE_FLASH, REG_AWB_LEDLIGHT }, + { V4L2_WHITE_BALANCE_CLOUDY, REG_AWB_CLOUDY }, + { V4L2_WHITE_BALANCE_SHADE, REG_AWB_SHADE }, + { V4L2_WHITE_BALANCE_AUTO, REG_AWB_AUTO }, + }; + int i; + struct v4l2_subdev *sd = &info->sd; + int ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(wb); i++) { + int awb; + if (wb[i][0] != val) + continue; + + v4l2_dbg(1, m5mols_debug, sd, + "Setting white balance to: %#x\n", wb[i][0]); + + awb = wb[i][0] == V4L2_WHITE_BALANCE_AUTO; + ret = m5mols_write(sd, AWB_MODE, awb ? REG_AWB_AUTO : + REG_AWB_PRESET); + if (ret < 0) + return ret; + + if (!awb) + ret = m5mols_write(sd, AWB_MANUAL, wb[i][1]); + } + + return ret; +} + +static int m5mols_set_saturation(struct m5mols_info *info, int val) +{ + int ret = m5mols_write(&info->sd, MON_CHROMA_LVL, val); + if (ret < 0) + return ret; + + return m5mols_write(&info->sd, MON_CHROMA_EN, REG_CHROMA_ON); +} + +static int m5mols_set_color_effect(struct m5mols_info *info, int val) +{ + unsigned int m_effect = REG_COLOR_EFFECT_OFF; + unsigned int p_effect = REG_EFFECT_OFF; + unsigned int cfix_r = 0, cfix_b = 0; + struct v4l2_subdev *sd = &info->sd; + int ret = 0; + + switch (val) { + case V4L2_COLORFX_BW: + m_effect = REG_COLOR_EFFECT_ON; + break; + case V4L2_COLORFX_NEGATIVE: + p_effect = REG_EFFECT_NEGA; + break; + case V4L2_COLORFX_EMBOSS: + p_effect = REG_EFFECT_EMBOSS; + break; + case V4L2_COLORFX_SEPIA: + m_effect = REG_COLOR_EFFECT_ON; + cfix_r = REG_CFIXR_SEPIA; + cfix_b = REG_CFIXB_SEPIA; + break; + } + + ret = m5mols_write(sd, PARM_EFFECT, p_effect); + if (!ret) + ret = m5mols_write(sd, MON_EFFECT, m_effect); + + if (ret == 0 && m_effect == REG_COLOR_EFFECT_ON) { + ret = m5mols_write(sd, MON_CFIXR, cfix_r); + if (!ret) + ret = m5mols_write(sd, MON_CFIXB, cfix_b); + } + + v4l2_dbg(1, m5mols_debug, sd, + "p_effect: %#x, m_effect: %#x, r: %#x, b: %#x (%d)\n", + p_effect, m_effect, cfix_r, cfix_b, ret); + + return ret; +} + +static int m5mols_set_iso(struct m5mols_info *info, int auto_iso) +{ + u32 iso = auto_iso ? 0 : info->iso->val + 1; + + return m5mols_write(&info->sd, AE_ISO, iso); +} + +static int m5mols_set_wdr(struct m5mols_info *info, int wdr) +{ + int ret; + + ret = m5mols_write(&info->sd, MON_TONE_CTL, wdr ? 9 : 5); + if (ret < 0) + return ret; + + ret = m5mols_set_mode(info, REG_CAPTURE); + if (ret < 0) + return ret; + + return m5mols_write(&info->sd, CAPP_WDR_EN, wdr); +} + +static int m5mols_set_stabilization(struct m5mols_info *info, int val) +{ + struct v4l2_subdev *sd = &info->sd; + unsigned int evp = val ? 0xe : 0x0; + int ret; + + ret = m5mols_write(sd, AE_EV_PRESET_MONITOR, evp); + if (ret < 0) + return ret; + + return m5mols_write(sd, AE_EV_PRESET_CAPTURE, evp); +} + +static int m5mols_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct m5mols_info *info = to_m5mols(sd); + int ret = 0; + u8 status = REG_ISO_AUTO; + + v4l2_dbg(1, m5mols_debug, sd, "%s: ctrl: %s (%d)\n", + __func__, ctrl->name, info->isp_ready); + + if (!info->isp_ready) + return -EBUSY; + + switch (ctrl->id) { + case V4L2_CID_ISO_SENSITIVITY_AUTO: + ret = m5mols_read_u8(sd, AE_ISO, &status); + if (ret == 0) + ctrl->val = !status; + if (status != REG_ISO_AUTO) + info->iso->val = status - 1; + break; + + case V4L2_CID_3A_LOCK: + ctrl->val &= ~0x7; + + ret = m5mols_read_u8(sd, AE_LOCK, &status); + if (ret) + return ret; + if (status) + info->lock_3a->val |= V4L2_LOCK_EXPOSURE; + + ret = m5mols_read_u8(sd, AWB_LOCK, &status); + if (ret) + return ret; + if (status) + info->lock_3a->val |= V4L2_LOCK_EXPOSURE; + + ret = m5mols_read_u8(sd, AF_EXECUTE, &status); + if (!status) + info->lock_3a->val |= V4L2_LOCK_EXPOSURE; + break; + } + + return ret; +} + +static int m5mols_s_ctrl(struct v4l2_ctrl *ctrl) +{ + unsigned int ctrl_mode = m5mols_get_ctrl_mode(ctrl); + struct v4l2_subdev *sd = to_sd(ctrl); + struct m5mols_info *info = to_m5mols(sd); + int last_mode = info->mode; + int ret = 0; + + /* + * If needed, defer restoring the controls until + * the device is fully initialized. + */ + if (!info->isp_ready) { + info->ctrl_sync = 0; + return 0; + } + + v4l2_dbg(1, m5mols_debug, sd, "%s: %s, val: %d, priv: %p\n", + __func__, ctrl->name, ctrl->val, ctrl->priv); + + if (ctrl_mode && ctrl_mode != info->mode) { + ret = m5mols_set_mode(info, ctrl_mode); + if (ret < 0) + return ret; + } + + switch (ctrl->id) { + case V4L2_CID_3A_LOCK: + ret = m5mols_3a_lock(info, ctrl); + break; + + case V4L2_CID_ZOOM_ABSOLUTE: + ret = m5mols_write(sd, MON_ZOOM, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + ret = m5mols_set_exposure(info, ctrl->val); + break; + + case V4L2_CID_ISO_SENSITIVITY: + ret = m5mols_set_iso(info, ctrl->val); + break; + + case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE: + ret = m5mols_set_white_balance(info, ctrl->val); + break; + + case V4L2_CID_SATURATION: + ret = m5mols_set_saturation(info, ctrl->val); + break; + + case V4L2_CID_COLORFX: + ret = m5mols_set_color_effect(info, ctrl->val); + break; + + case V4L2_CID_WIDE_DYNAMIC_RANGE: + ret = m5mols_set_wdr(info, ctrl->val); + break; + + case V4L2_CID_IMAGE_STABILIZATION: + ret = m5mols_set_stabilization(info, ctrl->val); + break; + + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + ret = m5mols_write(sd, CAPP_JPEG_RATIO, ctrl->val); + break; + } + + if (ret == 0 && info->mode != last_mode) + ret = m5mols_set_mode(info, last_mode); + + return ret; +} + +static const struct v4l2_ctrl_ops m5mols_ctrl_ops = { + .g_volatile_ctrl = m5mols_g_volatile_ctrl, + .s_ctrl = m5mols_s_ctrl, +}; + +/* Supported manual ISO values */ +static const s64 iso_qmenu[] = { + /* AE_ISO: 0x01...0x07 (ISO: 50...3200) */ + 50000, 100000, 200000, 400000, 800000, 1600000, 3200000 +}; + +/* Supported Exposure Bias values, -2.0EV...+2.0EV */ +static const s64 ev_bias_qmenu[] = { + /* AE_INDEX: 0x00...0x08 */ + -2000, -1500, -1000, -500, 0, 500, 1000, 1500, 2000 +}; + +int m5mols_init_controls(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + u16 exposure_max; + u16 zoom_step; + int ret; + + /* Determine the firmware dependent control range and step values */ + ret = m5mols_read_u16(sd, AE_MAX_GAIN_MON, &exposure_max); + if (ret < 0) + return ret; + + zoom_step = is_manufacturer(info, REG_SAMSUNG_OPTICS) ? 31 : 1; + v4l2_ctrl_handler_init(&info->handle, 20); + + info->auto_wb = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, + 9, ~0x3fe, V4L2_WHITE_BALANCE_AUTO); + + /* Exposure control cluster */ + info->auto_exposure = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, + 1, ~0x03, V4L2_EXPOSURE_AUTO); + + info->exposure = v4l2_ctrl_new_std(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_EXPOSURE, + 0, exposure_max, 1, exposure_max / 2); + + info->exposure_bias = v4l2_ctrl_new_int_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_AUTO_EXPOSURE_BIAS, + ARRAY_SIZE(ev_bias_qmenu) - 1, + ARRAY_SIZE(ev_bias_qmenu)/2 - 1, + ev_bias_qmenu); + + info->metering = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_EXPOSURE_METERING, + 2, ~0x7, V4L2_EXPOSURE_METERING_AVERAGE); + + /* ISO control cluster */ + info->auto_iso = v4l2_ctrl_new_std_menu(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_ISO_SENSITIVITY_AUTO, 1, ~0x03, 1); + + info->iso = v4l2_ctrl_new_int_menu(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_ISO_SENSITIVITY, ARRAY_SIZE(iso_qmenu) - 1, + ARRAY_SIZE(iso_qmenu)/2 - 1, iso_qmenu); + + info->saturation = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_SATURATION, 1, 5, 1, 3); + + info->zoom = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_ZOOM_ABSOLUTE, 1, 70, zoom_step, 1); + + info->colorfx = v4l2_ctrl_new_std_menu(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_COLORFX, 4, 0, V4L2_COLORFX_NONE); + + info->wdr = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_WIDE_DYNAMIC_RANGE, 0, 1, 1, 0); + + info->stabilization = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_IMAGE_STABILIZATION, 0, 1, 1, 0); + + info->jpeg_quality = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, 1, 100, 1, 80); + + info->lock_3a = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_3A_LOCK, 0, 0x7, 0, 0); + + if (info->handle.error) { + int ret = info->handle.error; + v4l2_err(sd, "Failed to initialize controls: %d\n", ret); + v4l2_ctrl_handler_free(&info->handle); + return ret; + } + + v4l2_ctrl_auto_cluster(4, &info->auto_exposure, 1, false); + info->auto_iso->flags |= V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_UPDATE; + v4l2_ctrl_auto_cluster(2, &info->auto_iso, 0, false); + + info->lock_3a->flags |= V4L2_CTRL_FLAG_VOLATILE; + + m5mols_set_ctrl_mode(info->auto_exposure, REG_PARAMETER); + m5mols_set_ctrl_mode(info->auto_wb, REG_PARAMETER); + m5mols_set_ctrl_mode(info->colorfx, REG_MONITOR); + + sd->ctrl_handler = &info->handle; + + return 0; +} diff --git a/drivers/media/i2c/m5mols/m5mols_core.c b/drivers/media/i2c/m5mols/m5mols_core.c new file mode 100644 index 000000000..dcf9e4d4e --- /dev/null +++ b/drivers/media/i2c/m5mols/m5mols_core.c @@ -0,0 +1,1058 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim <riverful.kim@samsung.com> + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim <dongsoo45.kim@samsung.com> + */ + +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/videodev2.h> +#include <linux/module.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/i2c/m5mols.h> + +#include "m5mols.h" +#include "m5mols_reg.h" + +int m5mols_debug; +module_param(m5mols_debug, int, 0644); + +#define MODULE_NAME "M5MOLS" +#define M5MOLS_I2C_CHECK_RETRY 500 + +/* The regulator consumer names for external voltage regulators */ +static struct regulator_bulk_data supplies[] = { + { + .supply = "core", /* ARM core power, 1.2V */ + }, { + .supply = "dig_18", /* digital power 1, 1.8V */ + }, { + .supply = "d_sensor", /* sensor power 1, 1.8V */ + }, { + .supply = "dig_28", /* digital power 2, 2.8V */ + }, { + .supply = "a_sensor", /* analog power */ + }, { + .supply = "dig_12", /* digital power 3, 1.2V */ + }, +}; + +static struct v4l2_mbus_framefmt m5mols_default_ffmt[M5MOLS_RESTYPE_MAX] = { + [M5MOLS_RESTYPE_MONITOR] = { + .width = 1920, + .height = 1080, + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_JPEG, + }, + [M5MOLS_RESTYPE_CAPTURE] = { + .width = 1920, + .height = 1080, + .code = MEDIA_BUS_FMT_JPEG_1X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_JPEG, + }, +}; +#define SIZE_DEFAULT_FFMT ARRAY_SIZE(m5mols_default_ffmt) + +static const struct m5mols_resolution m5mols_reg_res[] = { + { 0x01, M5MOLS_RESTYPE_MONITOR, 128, 96 }, /* SUB-QCIF */ + { 0x03, M5MOLS_RESTYPE_MONITOR, 160, 120 }, /* QQVGA */ + { 0x05, M5MOLS_RESTYPE_MONITOR, 176, 144 }, /* QCIF */ + { 0x06, M5MOLS_RESTYPE_MONITOR, 176, 176 }, + { 0x08, M5MOLS_RESTYPE_MONITOR, 240, 320 }, /* QVGA */ + { 0x09, M5MOLS_RESTYPE_MONITOR, 320, 240 }, /* QVGA */ + { 0x0c, M5MOLS_RESTYPE_MONITOR, 240, 400 }, /* WQVGA */ + { 0x0d, M5MOLS_RESTYPE_MONITOR, 400, 240 }, /* WQVGA */ + { 0x0e, M5MOLS_RESTYPE_MONITOR, 352, 288 }, /* CIF */ + { 0x13, M5MOLS_RESTYPE_MONITOR, 480, 360 }, + { 0x15, M5MOLS_RESTYPE_MONITOR, 640, 360 }, /* qHD */ + { 0x17, M5MOLS_RESTYPE_MONITOR, 640, 480 }, /* VGA */ + { 0x18, M5MOLS_RESTYPE_MONITOR, 720, 480 }, + { 0x1a, M5MOLS_RESTYPE_MONITOR, 800, 480 }, /* WVGA */ + { 0x1f, M5MOLS_RESTYPE_MONITOR, 800, 600 }, /* SVGA */ + { 0x21, M5MOLS_RESTYPE_MONITOR, 1280, 720 }, /* HD */ + { 0x25, M5MOLS_RESTYPE_MONITOR, 1920, 1080 }, /* 1080p */ + { 0x29, M5MOLS_RESTYPE_MONITOR, 3264, 2448 }, /* 2.63fps 8M */ + { 0x39, M5MOLS_RESTYPE_MONITOR, 800, 602 }, /* AHS_MON debug */ + + { 0x02, M5MOLS_RESTYPE_CAPTURE, 320, 240 }, /* QVGA */ + { 0x04, M5MOLS_RESTYPE_CAPTURE, 400, 240 }, /* WQVGA */ + { 0x07, M5MOLS_RESTYPE_CAPTURE, 480, 360 }, + { 0x08, M5MOLS_RESTYPE_CAPTURE, 640, 360 }, /* qHD */ + { 0x09, M5MOLS_RESTYPE_CAPTURE, 640, 480 }, /* VGA */ + { 0x0a, M5MOLS_RESTYPE_CAPTURE, 800, 480 }, /* WVGA */ + { 0x10, M5MOLS_RESTYPE_CAPTURE, 1280, 720 }, /* HD */ + { 0x14, M5MOLS_RESTYPE_CAPTURE, 1280, 960 }, /* 1M */ + { 0x17, M5MOLS_RESTYPE_CAPTURE, 1600, 1200 }, /* 2M */ + { 0x19, M5MOLS_RESTYPE_CAPTURE, 1920, 1080 }, /* Full-HD */ + { 0x1a, M5MOLS_RESTYPE_CAPTURE, 2048, 1152 }, /* 3Mega */ + { 0x1b, M5MOLS_RESTYPE_CAPTURE, 2048, 1536 }, + { 0x1c, M5MOLS_RESTYPE_CAPTURE, 2560, 1440 }, /* 4Mega */ + { 0x1d, M5MOLS_RESTYPE_CAPTURE, 2560, 1536 }, + { 0x1f, M5MOLS_RESTYPE_CAPTURE, 2560, 1920 }, /* 5Mega */ + { 0x21, M5MOLS_RESTYPE_CAPTURE, 3264, 1836 }, /* 6Mega */ + { 0x22, M5MOLS_RESTYPE_CAPTURE, 3264, 1960 }, + { 0x25, M5MOLS_RESTYPE_CAPTURE, 3264, 2448 }, /* 8Mega */ +}; + +/** + * m5mols_swap_byte - an byte array to integer conversion function + * @data: byte array + * @length: size in bytes of I2C packet defined in the M-5MOLS datasheet + * + * Convert I2C data byte array with performing any required byte + * reordering to assure proper values for each data type, regardless + * of the architecture endianness. + */ +static u32 m5mols_swap_byte(u8 *data, u8 length) +{ + if (length == 1) + return *data; + else if (length == 2) + return be16_to_cpu(*((__be16 *)data)); + else + return be32_to_cpu(*((__be32 *)data)); +} + +/** + * m5mols_read - I2C read function + * @sd: sub-device, as pointed by struct v4l2_subdev + * @size: desired size of I2C packet + * @reg: combination of size, category and command for the I2C packet + * @val: read value + * + * Returns 0 on success, or else negative errno. + */ +static int m5mols_read(struct v4l2_subdev *sd, u32 size, u32 reg, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct m5mols_info *info = to_m5mols(sd); + u8 rbuf[M5MOLS_I2C_MAX_SIZE + 1]; + u8 category = I2C_CATEGORY(reg); + u8 cmd = I2C_COMMAND(reg); + struct i2c_msg msg[2]; + u8 wbuf[5]; + int ret; + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 5; + msg[0].buf = wbuf; + wbuf[0] = 5; + wbuf[1] = M5MOLS_BYTE_READ; + wbuf[2] = category; + wbuf[3] = cmd; + wbuf[4] = size; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = size + 1; + msg[1].buf = rbuf; + + /* minimum stabilization time */ + usleep_range(200, 300); + + ret = i2c_transfer(client->adapter, msg, 2); + + if (ret == 2) { + *val = m5mols_swap_byte(&rbuf[1], size); + return 0; + } + + if (info->isp_ready) + v4l2_err(sd, "read failed: size:%d cat:%02x cmd:%02x. %d\n", + size, category, cmd, ret); + + return ret < 0 ? ret : -EIO; +} + +int m5mols_read_u8(struct v4l2_subdev *sd, u32 reg, u8 *val) +{ + u32 val_32; + int ret; + + if (I2C_SIZE(reg) != 1) { + v4l2_err(sd, "Wrong data size\n"); + return -EINVAL; + } + + ret = m5mols_read(sd, I2C_SIZE(reg), reg, &val_32); + if (ret) + return ret; + + *val = (u8)val_32; + return ret; +} + +int m5mols_read_u16(struct v4l2_subdev *sd, u32 reg, u16 *val) +{ + u32 val_32; + int ret; + + if (I2C_SIZE(reg) != 2) { + v4l2_err(sd, "Wrong data size\n"); + return -EINVAL; + } + + ret = m5mols_read(sd, I2C_SIZE(reg), reg, &val_32); + if (ret) + return ret; + + *val = (u16)val_32; + return ret; +} + +int m5mols_read_u32(struct v4l2_subdev *sd, u32 reg, u32 *val) +{ + if (I2C_SIZE(reg) != 4) { + v4l2_err(sd, "Wrong data size\n"); + return -EINVAL; + } + + return m5mols_read(sd, I2C_SIZE(reg), reg, val); +} + +/** + * m5mols_write - I2C command write function + * @sd: sub-device, as pointed by struct v4l2_subdev + * @reg: combination of size, category and command for the I2C packet + * @val: value to write + * + * Returns 0 on success, or else negative errno. + */ +int m5mols_write(struct v4l2_subdev *sd, u32 reg, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct m5mols_info *info = to_m5mols(sd); + u8 wbuf[M5MOLS_I2C_MAX_SIZE + 4]; + u8 category = I2C_CATEGORY(reg); + u8 cmd = I2C_COMMAND(reg); + u8 size = I2C_SIZE(reg); + u32 *buf = (u32 *)&wbuf[4]; + struct i2c_msg msg[1]; + int ret; + + if (!client->adapter) + return -ENODEV; + + if (size != 1 && size != 2 && size != 4) { + v4l2_err(sd, "Wrong data size\n"); + return -EINVAL; + } + + msg->addr = client->addr; + msg->flags = 0; + msg->len = (u16)size + 4; + msg->buf = wbuf; + wbuf[0] = size + 4; + wbuf[1] = M5MOLS_BYTE_WRITE; + wbuf[2] = category; + wbuf[3] = cmd; + + *buf = m5mols_swap_byte((u8 *)&val, size); + + /* minimum stabilization time */ + usleep_range(200, 300); + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret == 1) + return 0; + + if (info->isp_ready) + v4l2_err(sd, "write failed: cat:%02x cmd:%02x ret:%d\n", + category, cmd, ret); + + return ret < 0 ? ret : -EIO; +} + +/** + * m5mols_busy_wait - Busy waiting with I2C register polling + * @sd: sub-device, as pointed by struct v4l2_subdev + * @reg: the I2C_REG() address of an 8-bit status register to check + * @value: expected status register value + * @mask: bit mask for the read status register value + * @timeout: timeout in milliseconds, or -1 for default timeout + * + * The @reg register value is ORed with @mask before comparing with @value. + * + * Return: 0 if the requested condition became true within less than + * @timeout ms, or else negative errno. + */ +int m5mols_busy_wait(struct v4l2_subdev *sd, u32 reg, u32 value, u32 mask, + int timeout) +{ + int ms = timeout < 0 ? M5MOLS_BUSY_WAIT_DEF_TIMEOUT : timeout; + unsigned long end = jiffies + msecs_to_jiffies(ms); + u8 status; + + do { + int ret = m5mols_read_u8(sd, reg, &status); + + if (ret < 0 && !(mask & M5MOLS_I2C_RDY_WAIT_FL)) + return ret; + if (!ret && (status & mask & 0xff) == (value & 0xff)) + return 0; + usleep_range(100, 250); + } while (ms > 0 && time_is_after_jiffies(end)); + + return -EBUSY; +} + +/** + * m5mols_enable_interrupt - Clear interrupt pending bits and unmask interrupts + * @sd: sub-device, as pointed by struct v4l2_subdev + * @reg: combination of size, category and command for the I2C packet + * + * Before writing desired interrupt value the INT_FACTOR register should + * be read to clear pending interrupts. + */ +int m5mols_enable_interrupt(struct v4l2_subdev *sd, u8 reg) +{ + struct m5mols_info *info = to_m5mols(sd); + u8 mask = is_available_af(info) ? REG_INT_AF : 0; + u8 dummy; + int ret; + + ret = m5mols_read_u8(sd, SYSTEM_INT_FACTOR, &dummy); + if (!ret) + ret = m5mols_write(sd, SYSTEM_INT_ENABLE, reg & ~mask); + return ret; +} + +int m5mols_wait_interrupt(struct v4l2_subdev *sd, u8 irq_mask, u32 timeout) +{ + struct m5mols_info *info = to_m5mols(sd); + + int ret = wait_event_interruptible_timeout(info->irq_waitq, + atomic_add_unless(&info->irq_done, -1, 0), + msecs_to_jiffies(timeout)); + if (ret <= 0) + return ret ? ret : -ETIMEDOUT; + + return m5mols_busy_wait(sd, SYSTEM_INT_FACTOR, irq_mask, + M5MOLS_I2C_RDY_WAIT_FL | irq_mask, -1); +} + +/** + * m5mols_reg_mode - Write the mode and check busy status + * @sd: sub-device, as pointed by struct v4l2_subdev + * @mode: the required operation mode + * + * It always accompanies a little delay changing the M-5MOLS mode, so it is + * needed checking current busy status to guarantee right mode. + */ +static int m5mols_reg_mode(struct v4l2_subdev *sd, u8 mode) +{ + int ret = m5mols_write(sd, SYSTEM_SYSMODE, mode); + if (ret < 0) + return ret; + return m5mols_busy_wait(sd, SYSTEM_SYSMODE, mode, 0xff, + M5MOLS_MODE_CHANGE_TIMEOUT); +} + +/** + * m5mols_set_mode - set the M-5MOLS controller mode + * @info: M-5MOLS driver data structure + * @mode: the required operation mode + * + * The commands of M-5MOLS are grouped into specific modes. Each functionality + * can be guaranteed only when the sensor is operating in mode which a command + * belongs to. + */ +int m5mols_set_mode(struct m5mols_info *info, u8 mode) +{ + struct v4l2_subdev *sd = &info->sd; + int ret = -EINVAL; + u8 reg; + + if (mode < REG_PARAMETER || mode > REG_CAPTURE) + return ret; + + ret = m5mols_read_u8(sd, SYSTEM_SYSMODE, ®); + if (ret || reg == mode) + return ret; + + switch (reg) { + case REG_PARAMETER: + ret = m5mols_reg_mode(sd, REG_MONITOR); + if (mode == REG_MONITOR) + break; + if (!ret) + ret = m5mols_reg_mode(sd, REG_CAPTURE); + break; + + case REG_MONITOR: + if (mode == REG_PARAMETER) { + ret = m5mols_reg_mode(sd, REG_PARAMETER); + break; + } + + ret = m5mols_reg_mode(sd, REG_CAPTURE); + break; + + case REG_CAPTURE: + ret = m5mols_reg_mode(sd, REG_MONITOR); + if (mode == REG_MONITOR) + break; + if (!ret) + ret = m5mols_reg_mode(sd, REG_PARAMETER); + break; + + default: + v4l2_warn(sd, "Wrong mode: %d\n", mode); + } + + if (!ret) + info->mode = mode; + + return ret; +} + +/** + * m5mols_get_version - retrieve full revisions information of M-5MOLS + * @sd: sub-device, as pointed by struct v4l2_subdev + * + * The version information includes revisions of hardware and firmware, + * AutoFocus alghorithm version and the version string. + */ +static int m5mols_get_version(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + struct m5mols_version *ver = &info->ver; + u8 *str = ver->str; + int i; + int ret; + + ret = m5mols_read_u8(sd, SYSTEM_VER_CUSTOMER, &ver->customer); + if (!ret) + ret = m5mols_read_u8(sd, SYSTEM_VER_PROJECT, &ver->project); + if (!ret) + ret = m5mols_read_u16(sd, SYSTEM_VER_FIRMWARE, &ver->fw); + if (!ret) + ret = m5mols_read_u16(sd, SYSTEM_VER_HARDWARE, &ver->hw); + if (!ret) + ret = m5mols_read_u16(sd, SYSTEM_VER_PARAMETER, &ver->param); + if (!ret) + ret = m5mols_read_u16(sd, SYSTEM_VER_AWB, &ver->awb); + if (!ret) + ret = m5mols_read_u8(sd, AF_VERSION, &ver->af); + if (ret) + return ret; + + for (i = 0; i < VERSION_STRING_SIZE; i++) { + ret = m5mols_read_u8(sd, SYSTEM_VER_STRING, &str[i]); + if (ret) + return ret; + } + + v4l2_info(sd, "Manufacturer\t[%s]\n", + is_manufacturer(info, REG_SAMSUNG_ELECTRO) ? + "Samsung Electro-Mechanics" : + is_manufacturer(info, REG_SAMSUNG_OPTICS) ? + "Samsung Fiber-Optics" : + is_manufacturer(info, REG_SAMSUNG_TECHWIN) ? + "Samsung Techwin" : "None"); + v4l2_info(sd, "Customer/Project\t[0x%02x/0x%02x]\n", + info->ver.customer, info->ver.project); + + if (!is_available_af(info)) + v4l2_info(sd, "No support Auto Focus on this firmware\n"); + + return ret; +} + +/** + * __find_restype - Lookup M-5MOLS resolution type according to pixel code + * @code: pixel code + */ +static enum m5mols_restype __find_restype(u32 code) +{ + enum m5mols_restype type = M5MOLS_RESTYPE_MONITOR; + + do { + if (code == m5mols_default_ffmt[type].code) + return type; + } while (++type != SIZE_DEFAULT_FFMT); + + return 0; +} + +/** + * __find_resolution - Lookup preset and type of M-5MOLS's resolution + * @sd: sub-device, as pointed by struct v4l2_subdev + * @mf: pixel format to find/negotiate the resolution preset for + * @type: M-5MOLS resolution type + * @resolution: M-5MOLS resolution preset register value + * + * Find nearest resolution matching resolution preset and adjust mf + * to supported values. + */ +static int __find_resolution(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf, + enum m5mols_restype *type, + u32 *resolution) +{ + const struct m5mols_resolution *fsize = &m5mols_reg_res[0]; + const struct m5mols_resolution *match = NULL; + enum m5mols_restype stype = __find_restype(mf->code); + int i = ARRAY_SIZE(m5mols_reg_res); + unsigned int min_err = ~0; + + while (i--) { + int err; + if (stype == fsize->type) { + err = abs(fsize->width - mf->width) + + abs(fsize->height - mf->height); + + if (err < min_err) { + min_err = err; + match = fsize; + } + } + fsize++; + } + if (match) { + mf->width = match->width; + mf->height = match->height; + *resolution = match->reg; + *type = stype; + return 0; + } + + return -EINVAL; +} + +static struct v4l2_mbus_framefmt *__find_format(struct m5mols_info *info, + struct v4l2_subdev_pad_config *cfg, + enum v4l2_subdev_format_whence which, + enum m5mols_restype type) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return cfg ? v4l2_subdev_get_try_format(&info->sd, cfg, 0) : NULL; + + return &info->ffmt[type]; +} + +static int m5mols_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *format; + int ret = 0; + + mutex_lock(&info->lock); + + format = __find_format(info, cfg, fmt->which, info->res_type); + if (format) + fmt->format = *format; + else + ret = -EINVAL; + + mutex_unlock(&info->lock); + return ret; +} + +static int m5mols_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *format = &fmt->format; + struct v4l2_mbus_framefmt *sfmt; + enum m5mols_restype type; + u32 resolution = 0; + int ret; + + ret = __find_resolution(sd, format, &type, &resolution); + if (ret < 0) + return ret; + + sfmt = __find_format(info, cfg, fmt->which, type); + if (!sfmt) + return 0; + + mutex_lock(&info->lock); + + format->code = m5mols_default_ffmt[type].code; + format->colorspace = V4L2_COLORSPACE_JPEG; + format->field = V4L2_FIELD_NONE; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + *sfmt = *format; + info->resolution = resolution; + info->res_type = type; + } + + mutex_unlock(&info->lock); + return ret; +} + +static int m5mols_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct m5mols_info *info = to_m5mols(sd); + + if (pad != 0 || fd == NULL) + return -EINVAL; + + mutex_lock(&info->lock); + /* + * .get_frame_desc is only used for compressed formats, + * thus we always return the capture frame parameters here. + */ + fd->entry[0].length = info->cap.buf_size; + fd->entry[0].pixelcode = info->ffmt[M5MOLS_RESTYPE_CAPTURE].code; + mutex_unlock(&info->lock); + + fd->entry[0].flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + fd->num_entries = 1; + + return 0; +} + +static int m5mols_set_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *mf = &info->ffmt[M5MOLS_RESTYPE_CAPTURE]; + + if (pad != 0 || fd == NULL) + return -EINVAL; + + fd->entry[0].flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + fd->num_entries = 1; + fd->entry[0].length = clamp_t(u32, fd->entry[0].length, + mf->width * mf->height, + M5MOLS_MAIN_JPEG_SIZE_MAX); + mutex_lock(&info->lock); + info->cap.buf_size = fd->entry[0].length; + mutex_unlock(&info->lock); + + return 0; +} + + +static int m5mols_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (!code || code->index >= SIZE_DEFAULT_FFMT) + return -EINVAL; + + code->code = m5mols_default_ffmt[code->index].code; + + return 0; +} + +static const struct v4l2_subdev_pad_ops m5mols_pad_ops = { + .enum_mbus_code = m5mols_enum_mbus_code, + .get_fmt = m5mols_get_fmt, + .set_fmt = m5mols_set_fmt, + .get_frame_desc = m5mols_get_frame_desc, + .set_frame_desc = m5mols_set_frame_desc, +}; + +/** + * m5mols_restore_controls - Apply current control values to the registers + * @info: M-5MOLS driver data structure + * + * m5mols_do_scenemode() handles all parameters for which there is yet no + * individual control. It should be replaced at some point by setting each + * control individually, in required register set up order. + */ +int m5mols_restore_controls(struct m5mols_info *info) +{ + int ret; + + if (info->ctrl_sync) + return 0; + + ret = m5mols_do_scenemode(info, REG_SCENE_NORMAL); + if (ret) + return ret; + + ret = v4l2_ctrl_handler_setup(&info->handle); + info->ctrl_sync = !ret; + + return ret; +} + +/** + * m5mols_start_monitor - Start the monitor mode + * @info: M-5MOLS driver data structure + * + * Before applying the controls setup the resolution and frame rate + * in PARAMETER mode, and then switch over to MONITOR mode. + */ +static int m5mols_start_monitor(struct m5mols_info *info) +{ + struct v4l2_subdev *sd = &info->sd; + int ret; + + ret = m5mols_set_mode(info, REG_PARAMETER); + if (!ret) + ret = m5mols_write(sd, PARM_MON_SIZE, info->resolution); + if (!ret) + ret = m5mols_write(sd, PARM_MON_FPS, REG_FPS_30); + if (!ret) + ret = m5mols_set_mode(info, REG_MONITOR); + if (!ret) + ret = m5mols_restore_controls(info); + + return ret; +} + +static int m5mols_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct m5mols_info *info = to_m5mols(sd); + u32 code; + int ret; + + mutex_lock(&info->lock); + code = info->ffmt[info->res_type].code; + + if (enable) { + if (is_code(code, M5MOLS_RESTYPE_MONITOR)) + ret = m5mols_start_monitor(info); + else if (is_code(code, M5MOLS_RESTYPE_CAPTURE)) + ret = m5mols_start_capture(info); + else + ret = -EINVAL; + } else { + ret = m5mols_set_mode(info, REG_PARAMETER); + } + + mutex_unlock(&info->lock); + return ret; +} + +static const struct v4l2_subdev_video_ops m5mols_video_ops = { + .s_stream = m5mols_s_stream, +}; + +static int m5mols_sensor_power(struct m5mols_info *info, bool enable) +{ + struct v4l2_subdev *sd = &info->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + const struct m5mols_platform_data *pdata = info->pdata; + int ret; + + if (info->power == enable) + return 0; + + if (enable) { + if (info->set_power) { + ret = info->set_power(&client->dev, 1); + if (ret) + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(supplies), supplies); + if (ret) { + if (info->set_power) + info->set_power(&client->dev, 0); + return ret; + } + + gpio_set_value(pdata->gpio_reset, !pdata->reset_polarity); + info->power = 1; + + return ret; + } + + ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies); + if (ret) + return ret; + + if (info->set_power) + info->set_power(&client->dev, 0); + + gpio_set_value(pdata->gpio_reset, pdata->reset_polarity); + + info->isp_ready = 0; + info->power = 0; + + return ret; +} + +/* m5mols_update_fw - optional firmware update routine */ +int __attribute__ ((weak)) m5mols_update_fw(struct v4l2_subdev *sd, + int (*set_power)(struct m5mols_info *, bool)) +{ + return 0; +} + +/** + * m5mols_fw_start - M-5MOLS internal ARM controller initialization + * @sd: sub-device, as pointed by struct v4l2_subdev + * + * Execute the M-5MOLS internal ARM controller initialization sequence. + * This function should be called after the supply voltage has been + * applied and before any requests to the device are made. + */ +static int m5mols_fw_start(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + int ret; + + atomic_set(&info->irq_done, 0); + /* Wait until I2C slave is initialized in Flash Writer mode */ + ret = m5mols_busy_wait(sd, FLASH_CAM_START, REG_IN_FLASH_MODE, + M5MOLS_I2C_RDY_WAIT_FL | 0xff, -1); + if (!ret) + ret = m5mols_write(sd, FLASH_CAM_START, REG_START_ARM_BOOT); + if (!ret) + ret = m5mols_wait_interrupt(sd, REG_INT_MODE, 2000); + if (ret < 0) + return ret; + + info->isp_ready = 1; + + ret = m5mols_get_version(sd); + if (!ret) + ret = m5mols_update_fw(sd, m5mols_sensor_power); + if (ret) + return ret; + + v4l2_dbg(1, m5mols_debug, sd, "Success ARM Booting\n"); + + ret = m5mols_write(sd, PARM_INTERFACE, REG_INTERFACE_MIPI); + if (!ret) + ret = m5mols_enable_interrupt(sd, + REG_INT_AF | REG_INT_CAPTURE); + + return ret; +} + +/* Execute the lens soft-landing algorithm */ +static int m5mols_auto_focus_stop(struct m5mols_info *info) +{ + int ret; + + ret = m5mols_write(&info->sd, AF_EXECUTE, REG_AF_STOP); + if (!ret) + ret = m5mols_write(&info->sd, AF_MODE, REG_AF_POWEROFF); + if (!ret) + ret = m5mols_busy_wait(&info->sd, SYSTEM_STATUS, REG_AF_IDLE, + 0xff, -1); + return ret; +} + +/** + * m5mols_s_power - Main sensor power control function + * @sd: sub-device, as pointed by struct v4l2_subdev + * @on: if true, powers on the device; powers off otherwise. + * + * To prevent breaking the lens when the sensor is powered off the Soft-Landing + * algorithm is called where available. The Soft-Landing algorithm availability + * dependends on the firmware provider. + */ +static int m5mols_s_power(struct v4l2_subdev *sd, int on) +{ + struct m5mols_info *info = to_m5mols(sd); + int ret; + + mutex_lock(&info->lock); + + if (on) { + ret = m5mols_sensor_power(info, true); + if (!ret) + ret = m5mols_fw_start(sd); + } else { + if (is_manufacturer(info, REG_SAMSUNG_TECHWIN)) { + ret = m5mols_set_mode(info, REG_MONITOR); + if (!ret) + ret = m5mols_auto_focus_stop(info); + if (ret < 0) + v4l2_warn(sd, "Soft landing lens failed\n"); + } + ret = m5mols_sensor_power(info, false); + + info->ctrl_sync = 0; + } + + mutex_unlock(&info->lock); + return ret; +} + +static int m5mols_log_status(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + + v4l2_ctrl_handler_log_status(&info->handle, sd->name); + + return 0; +} + +static const struct v4l2_subdev_core_ops m5mols_core_ops = { + .s_power = m5mols_s_power, + .log_status = m5mols_log_status, +}; + +/* + * V4L2 subdev internal operations + */ +static int m5mols_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format = v4l2_subdev_get_try_format(sd, fh->pad, 0); + + *format = m5mols_default_ffmt[0]; + return 0; +} + +static const struct v4l2_subdev_internal_ops m5mols_subdev_internal_ops = { + .open = m5mols_open, +}; + +static const struct v4l2_subdev_ops m5mols_ops = { + .core = &m5mols_core_ops, + .pad = &m5mols_pad_ops, + .video = &m5mols_video_ops, +}; + +static irqreturn_t m5mols_irq_handler(int irq, void *data) +{ + struct m5mols_info *info = to_m5mols(data); + + atomic_set(&info->irq_done, 1); + wake_up_interruptible(&info->irq_waitq); + + return IRQ_HANDLED; +} + +static int m5mols_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct m5mols_platform_data *pdata = client->dev.platform_data; + unsigned long gpio_flags; + struct m5mols_info *info; + struct v4l2_subdev *sd; + int ret; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + if (!gpio_is_valid(pdata->gpio_reset)) { + dev_err(&client->dev, "No valid RESET GPIO specified\n"); + return -EINVAL; + } + + if (!client->irq) { + dev_err(&client->dev, "Interrupt not assigned\n"); + return -EINVAL; + } + + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdata = pdata; + info->set_power = pdata->set_power; + + gpio_flags = pdata->reset_polarity + ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + ret = devm_gpio_request_one(&client->dev, pdata->gpio_reset, gpio_flags, + "M5MOLS_NRST"); + if (ret) { + dev_err(&client->dev, "Failed to request gpio: %d\n", ret); + return ret; + } + + ret = devm_regulator_bulk_get(&client->dev, ARRAY_SIZE(supplies), + supplies); + if (ret) { + dev_err(&client->dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + sd = &info->sd; + v4l2_i2c_subdev_init(sd, client, &m5mols_ops); + /* Static name; NEVER use in new drivers! */ + strscpy(sd->name, MODULE_NAME, sizeof(sd->name)); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + sd->internal_ops = &m5mols_subdev_internal_ops; + info->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, 1, &info->pad); + if (ret < 0) + return ret; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + + init_waitqueue_head(&info->irq_waitq); + mutex_init(&info->lock); + + ret = devm_request_irq(&client->dev, client->irq, m5mols_irq_handler, + IRQF_TRIGGER_RISING, MODULE_NAME, sd); + if (ret) { + dev_err(&client->dev, "Interrupt request failed: %d\n", ret); + goto error; + } + info->res_type = M5MOLS_RESTYPE_MONITOR; + info->ffmt[0] = m5mols_default_ffmt[0]; + info->ffmt[1] = m5mols_default_ffmt[1]; + + ret = m5mols_sensor_power(info, true); + if (ret) + goto error; + + ret = m5mols_fw_start(sd); + if (!ret) + ret = m5mols_init_controls(sd); + + ret = m5mols_sensor_power(info, false); + if (!ret) + return 0; +error: + media_entity_cleanup(&sd->entity); + return ret; +} + +static int m5mols_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct i2c_device_id m5mols_id[] = { + { MODULE_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, m5mols_id); + +static struct i2c_driver m5mols_i2c_driver = { + .driver = { + .name = MODULE_NAME, + }, + .probe = m5mols_probe, + .remove = m5mols_remove, + .id_table = m5mols_id, +}; + +module_i2c_driver(m5mols_i2c_driver); + +MODULE_AUTHOR("HeungJun Kim <riverful.kim@samsung.com>"); +MODULE_AUTHOR("Dongsoo Kim <dongsoo45.kim@samsung.com>"); +MODULE_DESCRIPTION("Fujitsu M-5MOLS 8M Pixel camera driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/m5mols/m5mols_reg.h b/drivers/media/i2c/m5mols/m5mols_reg.h new file mode 100644 index 000000000..947ee3381 --- /dev/null +++ b/drivers/media/i2c/m5mols/m5mols_reg.h @@ -0,0 +1,359 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Register map for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim <riverful.kim@samsung.com> + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim <dongsoo45.kim@samsung.com> + */ + +#ifndef M5MOLS_REG_H +#define M5MOLS_REG_H + +#define M5MOLS_I2C_MAX_SIZE 4 +#define M5MOLS_BYTE_READ 0x01 +#define M5MOLS_BYTE_WRITE 0x02 + +#define I2C_CATEGORY(__cat) ((__cat >> 16) & 0xff) +#define I2C_COMMAND(__comm) ((__comm >> 8) & 0xff) +#define I2C_SIZE(__reg_s) ((__reg_s) & 0xff) +#define I2C_REG(__cat, __cmd, __reg_s) ((__cat << 16) | (__cmd << 8) | __reg_s) + +/* + * Category section register + * + * The category means set including relevant command of M-5MOLS. + */ +#define CAT_SYSTEM 0x00 +#define CAT_PARAM 0x01 +#define CAT_MONITOR 0x02 +#define CAT_AE 0x03 +#define CAT_WB 0x06 +#define CAT_EXIF 0x07 +#define CAT_FD 0x09 +#define CAT_LENS 0x0a +#define CAT_CAPT_PARM 0x0b +#define CAT_CAPT_CTRL 0x0c +#define CAT_FLASH 0x0f /* related to FW, revisions, booting */ + +/* + * Category 0 - SYSTEM mode + * + * The SYSTEM mode in the M-5MOLS means area available to handle with the whole + * & all-round system of sensor. It deals with version/interrupt/setting mode & + * even sensor's status. Especially, the M-5MOLS sensor with ISP varies by + * packaging & manufacturer, even the customer and project code. And the + * function details may vary among them. The version information helps to + * determine what methods shall be used in the driver. + * + * There is many registers between customer version address and awb one. For + * more specific contents, see definition if file m5mols.h. + */ +#define SYSTEM_VER_CUSTOMER I2C_REG(CAT_SYSTEM, 0x00, 1) +#define SYSTEM_VER_PROJECT I2C_REG(CAT_SYSTEM, 0x01, 1) +#define SYSTEM_VER_FIRMWARE I2C_REG(CAT_SYSTEM, 0x02, 2) +#define SYSTEM_VER_HARDWARE I2C_REG(CAT_SYSTEM, 0x04, 2) +#define SYSTEM_VER_PARAMETER I2C_REG(CAT_SYSTEM, 0x06, 2) +#define SYSTEM_VER_AWB I2C_REG(CAT_SYSTEM, 0x08, 2) + +#define SYSTEM_SYSMODE I2C_REG(CAT_SYSTEM, 0x0b, 1) +#define REG_SYSINIT 0x00 /* SYSTEM mode */ +#define REG_PARAMETER 0x01 /* PARAMETER mode */ +#define REG_MONITOR 0x02 /* MONITOR mode */ +#define REG_CAPTURE 0x03 /* CAPTURE mode */ + +#define SYSTEM_CMD(__cmd) I2C_REG(CAT_SYSTEM, cmd, 1) +#define SYSTEM_VER_STRING I2C_REG(CAT_SYSTEM, 0x0a, 1) +#define REG_SAMSUNG_ELECTRO "SE" /* Samsung Electro-Mechanics */ +#define REG_SAMSUNG_OPTICS "OP" /* Samsung Fiber-Optics */ +#define REG_SAMSUNG_TECHWIN "TB" /* Samsung Techwin */ +/* SYSTEM mode status */ +#define SYSTEM_STATUS I2C_REG(CAT_SYSTEM, 0x0c, 1) + +/* Interrupt pending register */ +#define SYSTEM_INT_FACTOR I2C_REG(CAT_SYSTEM, 0x10, 1) +/* interrupt enable register */ +#define SYSTEM_INT_ENABLE I2C_REG(CAT_SYSTEM, 0x11, 1) +#define REG_INT_MODE (1 << 0) +#define REG_INT_AF (1 << 1) +#define REG_INT_ZOOM (1 << 2) +#define REG_INT_CAPTURE (1 << 3) +#define REG_INT_FRAMESYNC (1 << 4) +#define REG_INT_FD (1 << 5) +#define REG_INT_LENS_INIT (1 << 6) +#define REG_INT_SOUND (1 << 7) +#define REG_INT_MASK 0x0f + +/* + * category 1 - PARAMETER mode + * + * This category supports function of camera features of M-5MOLS. It means we + * can handle with preview(MONITOR) resolution size/frame per second/interface + * between the sensor and the Application Processor/even the image effect. + */ + +/* Resolution in the MONITOR mode */ +#define PARM_MON_SIZE I2C_REG(CAT_PARAM, 0x01, 1) + +/* Frame rate */ +#define PARM_MON_FPS I2C_REG(CAT_PARAM, 0x02, 1) +#define REG_FPS_30 0x02 + +/* Video bus between the sensor and a host processor */ +#define PARM_INTERFACE I2C_REG(CAT_PARAM, 0x00, 1) +#define REG_INTERFACE_MIPI 0x02 + +/* Image effects */ +#define PARM_EFFECT I2C_REG(CAT_PARAM, 0x0b, 1) +#define REG_EFFECT_OFF 0x00 +#define REG_EFFECT_NEGA 0x01 +#define REG_EFFECT_EMBOSS 0x06 +#define REG_EFFECT_OUTLINE 0x07 +#define REG_EFFECT_WATERCOLOR 0x08 + +/* + * Category 2 - MONITOR mode + * + * The MONITOR mode is same as preview mode as we said. The M-5MOLS has another + * mode named "Preview", but this preview mode is used at the case specific + * vider-recording mode. This mmode supports only YUYV format. On the other + * hand, the JPEG & RAW formats is supports by CAPTURE mode. And, there are + * another options like zoom/color effect(different with effect in PARAMETER + * mode)/anti hand shaking algorithm. + */ + +/* Target digital zoom position */ +#define MON_ZOOM I2C_REG(CAT_MONITOR, 0x01, 1) + +/* CR value for color effect */ +#define MON_CFIXR I2C_REG(CAT_MONITOR, 0x0a, 1) +/* CB value for color effect */ +#define MON_CFIXB I2C_REG(CAT_MONITOR, 0x09, 1) +#define REG_CFIXB_SEPIA 0xd8 +#define REG_CFIXR_SEPIA 0x18 + +#define MON_EFFECT I2C_REG(CAT_MONITOR, 0x0b, 1) +#define REG_COLOR_EFFECT_OFF 0x00 +#define REG_COLOR_EFFECT_ON 0x01 + +/* Chroma enable */ +#define MON_CHROMA_EN I2C_REG(CAT_MONITOR, 0x10, 1) +/* Chroma level */ +#define MON_CHROMA_LVL I2C_REG(CAT_MONITOR, 0x0f, 1) +#define REG_CHROMA_OFF 0x00 +#define REG_CHROMA_ON 0x01 + +/* Sharpness on/off */ +#define MON_EDGE_EN I2C_REG(CAT_MONITOR, 0x12, 1) +/* Sharpness level */ +#define MON_EDGE_LVL I2C_REG(CAT_MONITOR, 0x11, 1) +#define REG_EDGE_OFF 0x00 +#define REG_EDGE_ON 0x01 + +/* Set color tone (contrast) */ +#define MON_TONE_CTL I2C_REG(CAT_MONITOR, 0x25, 1) + +/* + * Category 3 - Auto Exposure + * + * The M-5MOLS exposure capbility is detailed as which is similar to digital + * camera. This category supports AE locking/various AE mode(range of exposure) + * /ISO/flickering/EV bias/shutter/meteoring, and anything else. And the + * maximum/minimum exposure gain value depending on M-5MOLS firmware, may be + * different. So, this category also provide getting the max/min values. And, + * each MONITOR and CAPTURE mode has each gain/shutter/max exposure values. + */ + +/* Auto Exposure locking */ +#define AE_LOCK I2C_REG(CAT_AE, 0x00, 1) +#define REG_AE_UNLOCK 0x00 +#define REG_AE_LOCK 0x01 + +/* Auto Exposure algorithm mode */ +#define AE_MODE I2C_REG(CAT_AE, 0x01, 1) +#define REG_AE_OFF 0x00 /* AE off */ +#define REG_AE_ALL 0x01 /* calc AE in all block integral */ +#define REG_AE_CENTER 0x03 /* calc AE in center weighted */ +#define REG_AE_SPOT 0x06 /* calc AE in specific spot */ + +#define AE_ISO I2C_REG(CAT_AE, 0x05, 1) +#define REG_ISO_AUTO 0x00 +#define REG_ISO_50 0x01 +#define REG_ISO_100 0x02 +#define REG_ISO_200 0x03 +#define REG_ISO_400 0x04 +#define REG_ISO_800 0x05 + +/* EV (scenemode) preset for MONITOR */ +#define AE_EV_PRESET_MONITOR I2C_REG(CAT_AE, 0x0a, 1) +/* EV (scenemode) preset for CAPTURE */ +#define AE_EV_PRESET_CAPTURE I2C_REG(CAT_AE, 0x0b, 1) +#define REG_SCENE_NORMAL 0x00 +#define REG_SCENE_PORTRAIT 0x01 +#define REG_SCENE_LANDSCAPE 0x02 +#define REG_SCENE_SPORTS 0x03 +#define REG_SCENE_PARTY_INDOOR 0x04 +#define REG_SCENE_BEACH_SNOW 0x05 +#define REG_SCENE_SUNSET 0x06 +#define REG_SCENE_DAWN_DUSK 0x07 +#define REG_SCENE_FALL 0x08 +#define REG_SCENE_NIGHT 0x09 +#define REG_SCENE_AGAINST_LIGHT 0x0a +#define REG_SCENE_FIRE 0x0b +#define REG_SCENE_TEXT 0x0c +#define REG_SCENE_CANDLE 0x0d + +/* Manual gain in MONITOR mode */ +#define AE_MAN_GAIN_MON I2C_REG(CAT_AE, 0x12, 2) +/* Maximum gain in MONITOR mode */ +#define AE_MAX_GAIN_MON I2C_REG(CAT_AE, 0x1a, 2) +/* Manual gain in CAPTURE mode */ +#define AE_MAN_GAIN_CAP I2C_REG(CAT_AE, 0x26, 2) + +#define AE_INDEX I2C_REG(CAT_AE, 0x38, 1) +#define REG_AE_INDEX_20_NEG 0x00 +#define REG_AE_INDEX_15_NEG 0x01 +#define REG_AE_INDEX_10_NEG 0x02 +#define REG_AE_INDEX_05_NEG 0x03 +#define REG_AE_INDEX_00 0x04 +#define REG_AE_INDEX_05_POS 0x05 +#define REG_AE_INDEX_10_POS 0x06 +#define REG_AE_INDEX_15_POS 0x07 +#define REG_AE_INDEX_20_POS 0x08 + +/* + * Category 6 - White Balance + */ + +/* Auto Whitebalance locking */ +#define AWB_LOCK I2C_REG(CAT_WB, 0x00, 1) +#define REG_AWB_UNLOCK 0x00 +#define REG_AWB_LOCK 0x01 + +#define AWB_MODE I2C_REG(CAT_WB, 0x02, 1) +#define REG_AWB_AUTO 0x01 /* AWB off */ +#define REG_AWB_PRESET 0x02 /* AWB preset */ + +/* Manual WB (preset) */ +#define AWB_MANUAL I2C_REG(CAT_WB, 0x03, 1) +#define REG_AWB_INCANDESCENT 0x01 +#define REG_AWB_FLUORESCENT_1 0x02 +#define REG_AWB_FLUORESCENT_2 0x03 +#define REG_AWB_DAYLIGHT 0x04 +#define REG_AWB_CLOUDY 0x05 +#define REG_AWB_SHADE 0x06 +#define REG_AWB_HORIZON 0x07 +#define REG_AWB_LEDLIGHT 0x09 + +/* + * Category 7 - EXIF information + */ +#define EXIF_INFO_EXPTIME_NU I2C_REG(CAT_EXIF, 0x00, 4) +#define EXIF_INFO_EXPTIME_DE I2C_REG(CAT_EXIF, 0x04, 4) +#define EXIF_INFO_TV_NU I2C_REG(CAT_EXIF, 0x08, 4) +#define EXIF_INFO_TV_DE I2C_REG(CAT_EXIF, 0x0c, 4) +#define EXIF_INFO_AV_NU I2C_REG(CAT_EXIF, 0x10, 4) +#define EXIF_INFO_AV_DE I2C_REG(CAT_EXIF, 0x14, 4) +#define EXIF_INFO_BV_NU I2C_REG(CAT_EXIF, 0x18, 4) +#define EXIF_INFO_BV_DE I2C_REG(CAT_EXIF, 0x1c, 4) +#define EXIF_INFO_EBV_NU I2C_REG(CAT_EXIF, 0x20, 4) +#define EXIF_INFO_EBV_DE I2C_REG(CAT_EXIF, 0x24, 4) +#define EXIF_INFO_ISO I2C_REG(CAT_EXIF, 0x28, 2) +#define EXIF_INFO_FLASH I2C_REG(CAT_EXIF, 0x2a, 2) +#define EXIF_INFO_SDR I2C_REG(CAT_EXIF, 0x2c, 2) +#define EXIF_INFO_QVAL I2C_REG(CAT_EXIF, 0x2e, 2) + +/* + * Category 9 - Face Detection + */ +#define FD_CTL I2C_REG(CAT_FD, 0x00, 1) +#define BIT_FD_EN 0 +#define BIT_FD_DRAW_FACE_FRAME 4 +#define BIT_FD_DRAW_SMILE_LVL 6 +#define REG_FD(shift) (1 << shift) +#define REG_FD_OFF 0x0 + +/* + * Category A - Lens Parameter + */ +#define AF_MODE I2C_REG(CAT_LENS, 0x01, 1) +#define REG_AF_NORMAL 0x00 /* Normal AF, one time */ +#define REG_AF_MACRO 0x01 /* Macro AF, one time */ +#define REG_AF_POWEROFF 0x07 + +#define AF_EXECUTE I2C_REG(CAT_LENS, 0x02, 1) +#define REG_AF_STOP 0x00 +#define REG_AF_EXE_AUTO 0x01 +#define REG_AF_EXE_CAF 0x02 + +#define AF_STATUS I2C_REG(CAT_LENS, 0x03, 1) +#define REG_AF_FAIL 0x00 +#define REG_AF_SUCCESS 0x02 +#define REG_AF_IDLE 0x04 +#define REG_AF_BUSY 0x05 + +#define AF_VERSION I2C_REG(CAT_LENS, 0x0a, 1) + +/* + * Category B - CAPTURE Parameter + */ +#define CAPP_YUVOUT_MAIN I2C_REG(CAT_CAPT_PARM, 0x00, 1) +#define REG_YUV422 0x00 +#define REG_BAYER10 0x05 +#define REG_BAYER8 0x06 +#define REG_JPEG 0x10 + +#define CAPP_MAIN_IMAGE_SIZE I2C_REG(CAT_CAPT_PARM, 0x01, 1) +#define CAPP_JPEG_SIZE_MAX I2C_REG(CAT_CAPT_PARM, 0x0f, 4) +#define CAPP_JPEG_RATIO I2C_REG(CAT_CAPT_PARM, 0x17, 1) + +#define CAPP_MCC_MODE I2C_REG(CAT_CAPT_PARM, 0x1d, 1) +#define REG_MCC_OFF 0x00 +#define REG_MCC_NORMAL 0x01 + +#define CAPP_WDR_EN I2C_REG(CAT_CAPT_PARM, 0x2c, 1) +#define REG_WDR_OFF 0x00 +#define REG_WDR_ON 0x01 +#define REG_WDR_AUTO 0x02 + +#define CAPP_LIGHT_CTRL I2C_REG(CAT_CAPT_PARM, 0x40, 1) +#define REG_LIGHT_OFF 0x00 +#define REG_LIGHT_ON 0x01 +#define REG_LIGHT_AUTO 0x02 + +#define CAPP_FLASH_CTRL I2C_REG(CAT_CAPT_PARM, 0x41, 1) +#define REG_FLASH_OFF 0x00 +#define REG_FLASH_ON 0x01 +#define REG_FLASH_AUTO 0x02 + +/* + * Category C - CAPTURE Control + */ +#define CAPC_MODE I2C_REG(CAT_CAPT_CTRL, 0x00, 1) +#define REG_CAP_NONE 0x00 +#define REG_CAP_ANTI_SHAKE 0x02 + +/* Select single- or multi-shot capture */ +#define CAPC_SEL_FRAME I2C_REG(CAT_CAPT_CTRL, 0x06, 1) + +#define CAPC_START I2C_REG(CAT_CAPT_CTRL, 0x09, 1) +#define REG_CAP_START_MAIN 0x01 +#define REG_CAP_START_THUMB 0x03 + +#define CAPC_IMAGE_SIZE I2C_REG(CAT_CAPT_CTRL, 0x0d, 4) +#define CAPC_THUMB_SIZE I2C_REG(CAT_CAPT_CTRL, 0x11, 4) + +/* + * Category F - Flash + * + * This mode provides functions about internal flash stuff and system startup. + */ + +/* Starts internal ARM core booting after power-up */ +#define FLASH_CAM_START I2C_REG(CAT_FLASH, 0x12, 1) +#define REG_START_ARM_BOOT 0x01 /* write value */ +#define REG_IN_FLASH_MODE 0x00 /* read value */ + +#endif /* M5MOLS_REG_H */ diff --git a/drivers/media/i2c/max2175.c b/drivers/media/i2c/max2175.c new file mode 100644 index 000000000..661208c9b --- /dev/null +++ b/drivers/media/i2c/max2175.c @@ -0,0 +1,1444 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Maxim Integrated MAX2175 RF to Bits tuner driver + * + * This driver & most of the hard coded values are based on the reference + * application delivered by Maxim for this device. + * + * Copyright (C) 2016 Maxim Integrated Products + * Copyright (C) 2017 Renesas Electronics Corporation + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/max2175.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#include "max2175.h" + +#define DRIVER_NAME "max2175" + +#define mxm_dbg(ctx, fmt, arg...) dev_dbg(&ctx->client->dev, fmt, ## arg) +#define mxm_err(ctx, fmt, arg...) dev_err(&ctx->client->dev, fmt, ## arg) + +/* Rx mode */ +struct max2175_rxmode { + enum max2175_band band; /* Associated band */ + u32 freq; /* Default freq in Hz */ + u8 i2s_word_size; /* Bit value */ +}; + +/* Register map to define preset values */ +struct max2175_reg_map { + u8 idx; /* Register index */ + u8 val; /* Register value */ +}; + +static const struct max2175_rxmode eu_rx_modes[] = { + /* EU modes */ + [MAX2175_EU_FM_1_2] = { MAX2175_BAND_FM, 98256000, 1 }, + [MAX2175_DAB_1_2] = { MAX2175_BAND_VHF, 182640000, 0 }, +}; + +static const struct max2175_rxmode na_rx_modes[] = { + /* NA modes */ + [MAX2175_NA_FM_1_0] = { MAX2175_BAND_FM, 98255520, 1 }, + [MAX2175_NA_FM_2_0] = { MAX2175_BAND_FM, 98255520, 6 }, +}; + +/* + * Preset values: + * Based on Maxim MAX2175 Register Table revision: 130p10 + */ +static const u8 full_fm_eu_1p0[] = { + 0x15, 0x04, 0xb8, 0xe3, 0x35, 0x18, 0x7c, 0x00, + 0x00, 0x7d, 0x40, 0x08, 0x70, 0x7a, 0x88, 0x91, + 0x61, 0x61, 0x61, 0x61, 0x5a, 0x0f, 0x34, 0x1c, + 0x14, 0x88, 0x33, 0x02, 0x00, 0x09, 0x00, 0x65, + 0x9f, 0x2b, 0x80, 0x00, 0x95, 0x05, 0x2c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x4a, 0x08, 0xa8, 0x0e, 0x0e, 0x2f, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x5e, 0xa9, + 0xae, 0xbb, 0x57, 0x18, 0x3b, 0x03, 0x3b, 0x64, + 0x40, 0x60, 0x00, 0x2a, 0xbf, 0x3f, 0xff, 0x9f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0xff, 0xfc, 0xef, 0x1c, 0x40, 0x00, 0x00, 0x02, + 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, + 0x00, 0x47, 0x00, 0x00, 0x11, 0x3f, 0x22, 0x00, + 0xf1, 0x00, 0x41, 0x03, 0xb0, 0x00, 0x00, 0x00, + 0x1b, +}; + +static const u8 full_fm_na_1p0[] = { + 0x13, 0x08, 0x8d, 0xc0, 0x35, 0x18, 0x7d, 0x3f, + 0x7d, 0x75, 0x40, 0x08, 0x70, 0x7a, 0x88, 0x91, + 0x61, 0x61, 0x61, 0x61, 0x5c, 0x0f, 0x34, 0x1c, + 0x14, 0x88, 0x33, 0x02, 0x00, 0x01, 0x00, 0x65, + 0x9f, 0x2b, 0x80, 0x00, 0x95, 0x05, 0x2c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x4a, 0x08, 0xa8, 0x0e, 0x0e, 0xaf, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x5e, 0xa9, + 0xae, 0xbb, 0x57, 0x18, 0x3b, 0x03, 0x3b, 0x64, + 0x40, 0x60, 0x00, 0x2a, 0xbf, 0x3f, 0xff, 0x9f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0xff, 0xfc, 0xef, 0x1c, 0x40, 0x00, 0x00, 0x02, + 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, + 0x00, 0x35, 0x00, 0x00, 0x11, 0x3f, 0x22, 0x00, + 0xf1, 0x00, 0x41, 0x03, 0xb0, 0x00, 0x00, 0x00, + 0x1b, +}; + +/* DAB1.2 settings */ +static const struct max2175_reg_map dab12_map[] = { + { 0x01, 0x13 }, { 0x02, 0x0d }, { 0x03, 0x15 }, { 0x04, 0x55 }, + { 0x05, 0x0a }, { 0x06, 0xa0 }, { 0x07, 0x40 }, { 0x08, 0x00 }, + { 0x09, 0x00 }, { 0x0a, 0x7d }, { 0x0b, 0x4a }, { 0x0c, 0x28 }, + { 0x0e, 0x43 }, { 0x0f, 0xb5 }, { 0x10, 0x31 }, { 0x11, 0x9e }, + { 0x12, 0x68 }, { 0x13, 0x9e }, { 0x14, 0x68 }, { 0x15, 0x58 }, + { 0x16, 0x2f }, { 0x17, 0x3f }, { 0x18, 0x40 }, { 0x1a, 0x88 }, + { 0x1b, 0xaa }, { 0x1c, 0x9a }, { 0x1d, 0x00 }, { 0x1e, 0x00 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x00 }, { 0x26, 0x00 }, + { 0x27, 0x00 }, { 0x32, 0x08 }, { 0x33, 0xf8 }, { 0x36, 0x2d }, + { 0x37, 0x7e }, { 0x55, 0xaf }, { 0x56, 0x3f }, { 0x57, 0xf8 }, + { 0x58, 0x99 }, { 0x76, 0x00 }, { 0x77, 0x00 }, { 0x78, 0x02 }, + { 0x79, 0x40 }, { 0x82, 0x00 }, { 0x83, 0x00 }, { 0x85, 0x00 }, + { 0x86, 0x20 }, +}; + +/* EU FM 1.2 settings */ +static const struct max2175_reg_map fmeu1p2_map[] = { + { 0x01, 0x15 }, { 0x02, 0x04 }, { 0x03, 0xb8 }, { 0x04, 0xe3 }, + { 0x05, 0x35 }, { 0x06, 0x18 }, { 0x07, 0x7c }, { 0x08, 0x00 }, + { 0x09, 0x00 }, { 0x0a, 0x73 }, { 0x0b, 0x40 }, { 0x0c, 0x08 }, + { 0x0e, 0x7a }, { 0x0f, 0x88 }, { 0x10, 0x91 }, { 0x11, 0x61 }, + { 0x12, 0x61 }, { 0x13, 0x61 }, { 0x14, 0x61 }, { 0x15, 0x5a }, + { 0x16, 0x0f }, { 0x17, 0x34 }, { 0x18, 0x1c }, { 0x1a, 0x88 }, + { 0x1b, 0x33 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x1e, 0x01 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x95 }, { 0x26, 0x05 }, + { 0x27, 0x2c }, { 0x32, 0x08 }, { 0x33, 0xa8 }, { 0x36, 0x2f }, + { 0x37, 0x7e }, { 0x55, 0xbf }, { 0x56, 0x3f }, { 0x57, 0xff }, + { 0x58, 0x9f }, { 0x76, 0xac }, { 0x77, 0x40 }, { 0x78, 0x00 }, + { 0x79, 0x00 }, { 0x82, 0x47 }, { 0x83, 0x00 }, { 0x85, 0x11 }, + { 0x86, 0x3f }, +}; + +/* FM NA 1.0 settings */ +static const struct max2175_reg_map fmna1p0_map[] = { + { 0x01, 0x13 }, { 0x02, 0x08 }, { 0x03, 0x8d }, { 0x04, 0xc0 }, + { 0x05, 0x35 }, { 0x06, 0x18 }, { 0x07, 0x7d }, { 0x08, 0x3f }, + { 0x09, 0x7d }, { 0x0a, 0x75 }, { 0x0b, 0x40 }, { 0x0c, 0x08 }, + { 0x0e, 0x7a }, { 0x0f, 0x88 }, { 0x10, 0x91 }, { 0x11, 0x61 }, + { 0x12, 0x61 }, { 0x13, 0x61 }, { 0x14, 0x61 }, { 0x15, 0x5c }, + { 0x16, 0x0f }, { 0x17, 0x34 }, { 0x18, 0x1c }, { 0x1a, 0x88 }, + { 0x1b, 0x33 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x1e, 0x01 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x95 }, { 0x26, 0x05 }, + { 0x27, 0x2c }, { 0x32, 0x08 }, { 0x33, 0xa8 }, { 0x36, 0xaf }, + { 0x37, 0x7e }, { 0x55, 0xbf }, { 0x56, 0x3f }, { 0x57, 0xff }, + { 0x58, 0x9f }, { 0x76, 0xa6 }, { 0x77, 0x40 }, { 0x78, 0x00 }, + { 0x79, 0x00 }, { 0x82, 0x35 }, { 0x83, 0x00 }, { 0x85, 0x11 }, + { 0x86, 0x3f }, +}; + +/* FM NA 2.0 settings */ +static const struct max2175_reg_map fmna2p0_map[] = { + { 0x01, 0x13 }, { 0x02, 0x08 }, { 0x03, 0x8d }, { 0x04, 0xc0 }, + { 0x05, 0x35 }, { 0x06, 0x18 }, { 0x07, 0x7c }, { 0x08, 0x54 }, + { 0x09, 0xa7 }, { 0x0a, 0x55 }, { 0x0b, 0x42 }, { 0x0c, 0x48 }, + { 0x0e, 0x7a }, { 0x0f, 0x88 }, { 0x10, 0x91 }, { 0x11, 0x61 }, + { 0x12, 0x61 }, { 0x13, 0x61 }, { 0x14, 0x61 }, { 0x15, 0x5c }, + { 0x16, 0x0f }, { 0x17, 0x34 }, { 0x18, 0x1c }, { 0x1a, 0x88 }, + { 0x1b, 0x33 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x1e, 0x01 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x95 }, { 0x26, 0x05 }, + { 0x27, 0x2c }, { 0x32, 0x08 }, { 0x33, 0xa8 }, { 0x36, 0xaf }, + { 0x37, 0x7e }, { 0x55, 0xbf }, { 0x56, 0x3f }, { 0x57, 0xff }, + { 0x58, 0x9f }, { 0x76, 0xac }, { 0x77, 0xc0 }, { 0x78, 0x00 }, + { 0x79, 0x00 }, { 0x82, 0x6b }, { 0x83, 0x00 }, { 0x85, 0x11 }, + { 0x86, 0x3f }, +}; + +static const u16 ch_coeff_dab1[] = { + 0x001c, 0x0007, 0xffcd, 0x0056, 0xffa4, 0x0033, 0x0027, 0xff61, + 0x010e, 0xfec0, 0x0106, 0xffb8, 0xff1c, 0x023c, 0xfcb2, 0x039b, + 0xfd4e, 0x0055, 0x036a, 0xf7de, 0x0d21, 0xee72, 0x1499, 0x6a51, +}; + +static const u16 ch_coeff_fmeu[] = { + 0x0000, 0xffff, 0x0001, 0x0002, 0xfffa, 0xffff, 0x0015, 0xffec, + 0xffde, 0x0054, 0xfff9, 0xff52, 0x00b8, 0x00a2, 0xfe0a, 0x00af, + 0x02e3, 0xfc14, 0xfe89, 0x089d, 0xfa2e, 0xf30f, 0x25be, 0x4eb6, +}; + +static const u16 eq_coeff_fmeu1_ra02_m6db[] = { + 0x0040, 0xffc6, 0xfffa, 0x002c, 0x000d, 0xff90, 0x0037, 0x006e, + 0xffc0, 0xff5b, 0x006a, 0x00f0, 0xff57, 0xfe94, 0x0112, 0x0252, + 0xfe0c, 0xfc6a, 0x0385, 0x0553, 0xfa49, 0xf789, 0x0b91, 0x1a10, +}; + +static const u16 ch_coeff_fmna[] = { + 0x0001, 0x0003, 0xfffe, 0xfff4, 0x0000, 0x001f, 0x000c, 0xffbc, + 0xffd3, 0x007d, 0x0075, 0xff33, 0xff01, 0x0131, 0x01ef, 0xfe60, + 0xfc7a, 0x020e, 0x0656, 0xfd94, 0xf395, 0x02ab, 0x2857, 0x3d3f, +}; + +static const u16 eq_coeff_fmna1_ra02_m6db[] = { + 0xfff1, 0xffe1, 0xffef, 0x000e, 0x0030, 0x002f, 0xfff6, 0xffa7, + 0xff9d, 0x000a, 0x00a2, 0x00b5, 0xffea, 0xfed9, 0xfec5, 0x003d, + 0x0217, 0x021b, 0xff5a, 0xfc2b, 0xfcbd, 0x02c4, 0x0ac3, 0x0e85, +}; + +static const u8 adc_presets[2][23] = { + { + 0x83, 0x00, 0xcf, 0xb4, 0x0f, 0x2c, 0x0c, 0x49, + 0x00, 0x00, 0x00, 0x8c, 0x02, 0x02, 0x00, 0x04, + 0xec, 0x82, 0x4b, 0xcc, 0x01, 0x88, 0x0c, + }, + { + 0x83, 0x00, 0xcf, 0xb4, 0x0f, 0x2c, 0x0c, 0x49, + 0x00, 0x00, 0x00, 0x8c, 0x02, 0x20, 0x33, 0x8c, + 0x57, 0xd7, 0x59, 0xb7, 0x65, 0x0e, 0x0c, + }, +}; + +/* Tuner bands */ +static const struct v4l2_frequency_band eu_bands_rf = { + .tuner = 0, + .type = V4L2_TUNER_RF, + .index = 0, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 65000000, + .rangehigh = 240000000, +}; + +static const struct v4l2_frequency_band na_bands_rf = { + .tuner = 0, + .type = V4L2_TUNER_RF, + .index = 0, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 65000000, + .rangehigh = 108000000, +}; + +/* Regmap settings */ +static const struct regmap_range max2175_regmap_volatile_range[] = { + regmap_reg_range(0x30, 0x35), + regmap_reg_range(0x3a, 0x45), + regmap_reg_range(0x59, 0x5e), + regmap_reg_range(0x73, 0x75), +}; + +static const struct regmap_access_table max2175_volatile_regs = { + .yes_ranges = max2175_regmap_volatile_range, + .n_yes_ranges = ARRAY_SIZE(max2175_regmap_volatile_range), +}; + +static const struct reg_default max2175_reg_defaults[] = { + { 0x00, 0x07}, +}; + +static const struct regmap_config max2175_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .reg_defaults = max2175_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(max2175_reg_defaults), + .volatile_table = &max2175_volatile_regs, + .cache_type = REGCACHE_FLAT, +}; + +struct max2175 { + struct v4l2_subdev sd; /* Sub-device */ + struct i2c_client *client; /* I2C client */ + + /* Controls */ + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_ctrl *lna_gain; /* LNA gain value */ + struct v4l2_ctrl *if_gain; /* I/F gain value */ + struct v4l2_ctrl *pll_lock; /* PLL lock */ + struct v4l2_ctrl *i2s_en; /* I2S output enable */ + struct v4l2_ctrl *hsls; /* High-side/Low-side polarity */ + struct v4l2_ctrl *rx_mode; /* Receive mode */ + + /* Regmap */ + struct regmap *regmap; + + /* Cached configuration */ + u32 freq; /* Tuned freq In Hz */ + const struct max2175_rxmode *rx_modes; /* EU or NA modes */ + const struct v4l2_frequency_band *bands_rf; /* EU or NA bands */ + + /* Device settings */ + unsigned long xtal_freq; /* Ref Oscillator freq in Hz */ + u32 decim_ratio; + bool master; /* Master/Slave */ + bool am_hiz; /* AM Hi-Z filter */ + + /* ROM values */ + u8 rom_bbf_bw_am; + u8 rom_bbf_bw_fm; + u8 rom_bbf_bw_dab; + + /* Driver private variables */ + bool mode_resolved; /* Flag to sanity check settings */ +}; + +static inline struct max2175 *max2175_from_sd(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max2175, sd); +} + +static inline struct max2175 *max2175_from_ctrl_hdl(struct v4l2_ctrl_handler *h) +{ + return container_of(h, struct max2175, ctrl_hdl); +} + +/* Get bitval of a given val */ +static inline u8 max2175_get_bitval(u8 val, u8 msb, u8 lsb) +{ + return (val & GENMASK(msb, lsb)) >> lsb; +} + +/* Read/Write bit(s) on top of regmap */ +static int max2175_read(struct max2175 *ctx, u8 idx, u8 *val) +{ + u32 regval; + int ret; + + ret = regmap_read(ctx->regmap, idx, ®val); + if (ret) + mxm_err(ctx, "read ret(%d): idx 0x%02x\n", ret, idx); + else + *val = regval; + + return ret; +} + +static int max2175_write(struct max2175 *ctx, u8 idx, u8 val) +{ + int ret; + + ret = regmap_write(ctx->regmap, idx, val); + if (ret) + mxm_err(ctx, "write ret(%d): idx 0x%02x val 0x%02x\n", + ret, idx, val); + + return ret; +} + +static u8 max2175_read_bits(struct max2175 *ctx, u8 idx, u8 msb, u8 lsb) +{ + u8 val; + + if (max2175_read(ctx, idx, &val)) + return 0; + + return max2175_get_bitval(val, msb, lsb); +} + +static int max2175_write_bits(struct max2175 *ctx, u8 idx, + u8 msb, u8 lsb, u8 newval) +{ + int ret = regmap_update_bits(ctx->regmap, idx, GENMASK(msb, lsb), + newval << lsb); + + if (ret) + mxm_err(ctx, "wbits ret(%d): idx 0x%02x\n", ret, idx); + + return ret; +} + +static int max2175_write_bit(struct max2175 *ctx, u8 idx, u8 bit, u8 newval) +{ + return max2175_write_bits(ctx, idx, bit, bit, newval); +} + +/* Checks expected pattern every msec until timeout */ +static int max2175_poll_timeout(struct max2175 *ctx, u8 idx, u8 msb, u8 lsb, + u8 exp_bitval, u32 timeout_us) +{ + unsigned int val; + + return regmap_read_poll_timeout(ctx->regmap, idx, val, + (max2175_get_bitval(val, msb, lsb) == exp_bitval), + 1000, timeout_us); +} + +static int max2175_poll_csm_ready(struct max2175 *ctx) +{ + int ret; + + ret = max2175_poll_timeout(ctx, 69, 1, 1, 0, 50000); + if (ret) + mxm_err(ctx, "csm not ready\n"); + + return ret; +} + +#define MAX2175_IS_BAND_AM(ctx) \ + (max2175_read_bits(ctx, 5, 1, 0) == MAX2175_BAND_AM) + +#define MAX2175_IS_BAND_VHF(ctx) \ + (max2175_read_bits(ctx, 5, 1, 0) == MAX2175_BAND_VHF) + +#define MAX2175_IS_FM_MODE(ctx) \ + (max2175_read_bits(ctx, 12, 5, 4) == 0) + +#define MAX2175_IS_FMHD_MODE(ctx) \ + (max2175_read_bits(ctx, 12, 5, 4) == 1) + +#define MAX2175_IS_DAB_MODE(ctx) \ + (max2175_read_bits(ctx, 12, 5, 4) == 2) + +static int max2175_band_from_freq(u32 freq) +{ + if (freq >= 144000 && freq <= 26100000) + return MAX2175_BAND_AM; + else if (freq >= 65000000 && freq <= 108000000) + return MAX2175_BAND_FM; + + return MAX2175_BAND_VHF; +} + +static void max2175_i2s_enable(struct max2175 *ctx, bool enable) +{ + if (enable) + /* Stuff bits are zeroed */ + max2175_write_bits(ctx, 104, 3, 0, 2); + else + /* Keep SCK alive */ + max2175_write_bits(ctx, 104, 3, 0, 9); + mxm_dbg(ctx, "i2s %sabled\n", enable ? "en" : "dis"); +} + +static void max2175_set_filter_coeffs(struct max2175 *ctx, u8 m_sel, + u8 bank, const u16 *coeffs) +{ + unsigned int i; + u8 coeff_addr, upper_address = 24; + + mxm_dbg(ctx, "set_filter_coeffs: m_sel %d bank %d\n", m_sel, bank); + max2175_write_bits(ctx, 114, 5, 4, m_sel); + + if (m_sel == 2) + upper_address = 12; + + for (i = 0; i < upper_address; i++) { + coeff_addr = i + bank * 24; + max2175_write(ctx, 115, coeffs[i] >> 8); + max2175_write(ctx, 116, coeffs[i]); + max2175_write(ctx, 117, coeff_addr | 1 << 7); + } + max2175_write_bit(ctx, 117, 7, 0); +} + +static void max2175_load_fmeu_1p2(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fmeu1p2_map); i++) + max2175_write(ctx, fmeu1p2_map[i].idx, fmeu1p2_map[i].val); + + ctx->decim_ratio = 36; + + /* Load the Channel Filter Coefficients into channel filter bank #2 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, ch_coeff_fmeu); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmeu1_ra02_m6db); +} + +static void max2175_load_dab_1p2(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dab12_map); i++) + max2175_write(ctx, dab12_map[i].idx, dab12_map[i].val); + + ctx->decim_ratio = 1; + + /* Load the Channel Filter Coefficients into channel filter bank #2 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 2, ch_coeff_dab1); +} + +static void max2175_load_fmna_1p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fmna1p0_map); i++) + max2175_write(ctx, fmna1p0_map[i].idx, fmna1p0_map[i].val); +} + +static void max2175_load_fmna_2p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fmna2p0_map); i++) + max2175_write(ctx, fmna2p0_map[i].idx, fmna2p0_map[i].val); +} + +static void max2175_set_bbfilter(struct max2175 *ctx) +{ + if (MAX2175_IS_BAND_AM(ctx)) { + max2175_write_bits(ctx, 12, 3, 0, ctx->rom_bbf_bw_am); + mxm_dbg(ctx, "set_bbfilter AM: rom %d\n", ctx->rom_bbf_bw_am); + } else if (MAX2175_IS_DAB_MODE(ctx)) { + max2175_write_bits(ctx, 12, 3, 0, ctx->rom_bbf_bw_dab); + mxm_dbg(ctx, "set_bbfilter DAB: rom %d\n", ctx->rom_bbf_bw_dab); + } else { + max2175_write_bits(ctx, 12, 3, 0, ctx->rom_bbf_bw_fm); + mxm_dbg(ctx, "set_bbfilter FM: rom %d\n", ctx->rom_bbf_bw_fm); + } +} + +static int max2175_set_csm_mode(struct max2175 *ctx, + enum max2175_csm_mode new_mode) +{ + int ret = max2175_poll_csm_ready(ctx); + + if (ret) + return ret; + + max2175_write_bits(ctx, 0, 2, 0, new_mode); + mxm_dbg(ctx, "set csm new mode %d\n", new_mode); + + /* Wait for a fixed settle down time depending on new mode */ + switch (new_mode) { + case MAX2175_PRESET_TUNE: + usleep_range(51100, 51500); /* 51.1ms */ + break; + /* + * Other mode switches need different sleep values depending on band & + * mode + */ + default: + break; + } + + return max2175_poll_csm_ready(ctx); +} + +static int max2175_csm_action(struct max2175 *ctx, + enum max2175_csm_mode action) +{ + int ret; + + mxm_dbg(ctx, "csm_action: %d\n", action); + + /* Other actions can be added in future when needed */ + ret = max2175_set_csm_mode(ctx, MAX2175_LOAD_TO_BUFFER); + if (ret) + return ret; + + return max2175_set_csm_mode(ctx, MAX2175_PRESET_TUNE); +} + +static int max2175_set_lo_freq(struct max2175 *ctx, u32 lo_freq) +{ + u8 lo_mult, loband_bits = 0, vcodiv_bits = 0; + u32 int_desired, frac_desired; + enum max2175_band band; + int ret; + + band = max2175_read_bits(ctx, 5, 1, 0); + switch (band) { + case MAX2175_BAND_AM: + lo_mult = 16; + break; + case MAX2175_BAND_FM: + if (lo_freq <= 74700000) { + lo_mult = 16; + } else if (lo_freq > 74700000 && lo_freq <= 110000000) { + loband_bits = 1; + lo_mult = 8; + } else { + loband_bits = 1; + vcodiv_bits = 3; + lo_mult = 8; + } + break; + case MAX2175_BAND_VHF: + if (lo_freq <= 210000000) + vcodiv_bits = 2; + else + vcodiv_bits = 1; + + loband_bits = 2; + lo_mult = 4; + break; + default: + loband_bits = 3; + vcodiv_bits = 2; + lo_mult = 2; + break; + } + + if (band == MAX2175_BAND_L) + lo_freq /= lo_mult; + else + lo_freq *= lo_mult; + + int_desired = lo_freq / ctx->xtal_freq; + frac_desired = div64_ul((u64)(lo_freq % ctx->xtal_freq) << 20, + ctx->xtal_freq); + + /* Check CSM is not busy */ + ret = max2175_poll_csm_ready(ctx); + if (ret) + return ret; + + mxm_dbg(ctx, "lo_mult %u int %u frac %u\n", + lo_mult, int_desired, frac_desired); + + /* Write the calculated values to the appropriate registers */ + max2175_write(ctx, 1, int_desired); + max2175_write_bits(ctx, 2, 3, 0, (frac_desired >> 16) & 0xf); + max2175_write(ctx, 3, frac_desired >> 8); + max2175_write(ctx, 4, frac_desired); + max2175_write_bits(ctx, 5, 3, 2, loband_bits); + max2175_write_bits(ctx, 6, 7, 6, vcodiv_bits); + + return ret; +} + +/* + * Helper similar to DIV_ROUND_CLOSEST but an inline function that accepts s64 + * dividend and s32 divisor + */ +static inline s64 max2175_round_closest(s64 dividend, s32 divisor) +{ + if ((dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0)) + return div_s64(dividend + divisor / 2, divisor); + + return div_s64(dividend - divisor / 2, divisor); +} + +static int max2175_set_nco_freq(struct max2175 *ctx, s32 nco_freq) +{ + s32 clock_rate = ctx->xtal_freq / ctx->decim_ratio; + u32 nco_reg, abs_nco_freq = abs(nco_freq); + s64 nco_val_desired; + int ret; + + if (abs_nco_freq < clock_rate / 2) { + nco_val_desired = 2 * nco_freq; + } else { + nco_val_desired = 2LL * (clock_rate - abs_nco_freq); + if (nco_freq < 0) + nco_val_desired = -nco_val_desired; + } + + nco_reg = max2175_round_closest(nco_val_desired << 20, clock_rate); + + if (nco_freq < 0) + nco_reg += 0x200000; + + /* Check CSM is not busy */ + ret = max2175_poll_csm_ready(ctx); + if (ret) + return ret; + + mxm_dbg(ctx, "freq %d desired %lld reg %u\n", + nco_freq, nco_val_desired, nco_reg); + + /* Write the calculated values to the appropriate registers */ + max2175_write_bits(ctx, 7, 4, 0, (nco_reg >> 16) & 0x1f); + max2175_write(ctx, 8, nco_reg >> 8); + max2175_write(ctx, 9, nco_reg); + + return ret; +} + +static int max2175_set_rf_freq_non_am_bands(struct max2175 *ctx, u64 freq, + u32 lo_pos) +{ + s64 adj_freq, low_if_freq; + int ret; + + mxm_dbg(ctx, "rf_freq: non AM bands\n"); + + if (MAX2175_IS_FM_MODE(ctx)) + low_if_freq = 128000; + else if (MAX2175_IS_FMHD_MODE(ctx)) + low_if_freq = 228000; + else + return max2175_set_lo_freq(ctx, freq); + + if (MAX2175_IS_BAND_VHF(ctx) == (lo_pos == MAX2175_LO_ABOVE_DESIRED)) + adj_freq = freq + low_if_freq; + else + adj_freq = freq - low_if_freq; + + ret = max2175_set_lo_freq(ctx, adj_freq); + if (ret) + return ret; + + return max2175_set_nco_freq(ctx, -low_if_freq); +} + +static int max2175_set_rf_freq(struct max2175 *ctx, u64 freq, u32 lo_pos) +{ + int ret; + + if (MAX2175_IS_BAND_AM(ctx)) + ret = max2175_set_nco_freq(ctx, freq); + else + ret = max2175_set_rf_freq_non_am_bands(ctx, freq, lo_pos); + + mxm_dbg(ctx, "set_rf_freq: ret %d freq %llu\n", ret, freq); + + return ret; +} + +static int max2175_tune_rf_freq(struct max2175 *ctx, u64 freq, u32 hsls) +{ + int ret; + + ret = max2175_set_rf_freq(ctx, freq, hsls); + if (ret) + return ret; + + ret = max2175_csm_action(ctx, MAX2175_BUFFER_PLUS_PRESET_TUNE); + if (ret) + return ret; + + mxm_dbg(ctx, "tune_rf_freq: old %u new %llu\n", ctx->freq, freq); + ctx->freq = freq; + + return ret; +} + +static void max2175_set_hsls(struct max2175 *ctx, u32 lo_pos) +{ + mxm_dbg(ctx, "set_hsls: lo_pos %u\n", lo_pos); + + if ((lo_pos == MAX2175_LO_BELOW_DESIRED) == MAX2175_IS_BAND_VHF(ctx)) + max2175_write_bit(ctx, 5, 4, 1); + else + max2175_write_bit(ctx, 5, 4, 0); +} + +static void max2175_set_eu_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + switch (rx_mode) { + case MAX2175_EU_FM_1_2: + max2175_load_fmeu_1p2(ctx); + break; + + case MAX2175_DAB_1_2: + max2175_load_dab_1p2(ctx); + break; + } + /* Master is the default setting */ + if (!ctx->master) + max2175_write_bit(ctx, 30, 7, 1); +} + +static void max2175_set_na_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + switch (rx_mode) { + case MAX2175_NA_FM_1_0: + max2175_load_fmna_1p0(ctx); + break; + case MAX2175_NA_FM_2_0: + max2175_load_fmna_2p0(ctx); + break; + } + /* Master is the default setting */ + if (!ctx->master) + max2175_write_bit(ctx, 30, 7, 1); + + ctx->decim_ratio = 27; + + /* Load the Channel Filter Coefficients into channel filter bank #2 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, ch_coeff_fmna); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmna1_ra02_m6db); +} + +static int max2175_set_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + mxm_dbg(ctx, "set_rx_mode: %u am_hiz %u\n", rx_mode, ctx->am_hiz); + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) + max2175_set_eu_rx_mode(ctx, rx_mode); + else + max2175_set_na_rx_mode(ctx, rx_mode); + + if (ctx->am_hiz) { + mxm_dbg(ctx, "setting AM HiZ related config\n"); + max2175_write_bit(ctx, 50, 5, 1); + max2175_write_bit(ctx, 90, 7, 1); + max2175_write_bits(ctx, 73, 1, 0, 2); + max2175_write_bits(ctx, 80, 5, 0, 33); + } + + /* Load BB filter trim values saved in ROM */ + max2175_set_bbfilter(ctx); + + /* Set HSLS */ + max2175_set_hsls(ctx, ctx->hsls->cur.val); + + /* Use i2s enable settings */ + max2175_i2s_enable(ctx, ctx->i2s_en->cur.val); + + ctx->mode_resolved = true; + + return 0; +} + +static int max2175_rx_mode_from_freq(struct max2175 *ctx, u32 freq, u32 *mode) +{ + unsigned int i; + int band = max2175_band_from_freq(freq); + + /* Pick the first match always */ + for (i = 0; i <= ctx->rx_mode->maximum; i++) { + if (ctx->rx_modes[i].band == band) { + *mode = i; + mxm_dbg(ctx, "rx_mode_from_freq: freq %u mode %d\n", + freq, *mode); + return 0; + } + } + + return -EINVAL; +} + +static bool max2175_freq_rx_mode_valid(struct max2175 *ctx, + u32 mode, u32 freq) +{ + int band = max2175_band_from_freq(freq); + + return (ctx->rx_modes[mode].band == band); +} + +static void max2175_load_adc_presets(struct max2175 *ctx) +{ + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(adc_presets); i++) + for (j = 0; j < ARRAY_SIZE(adc_presets[0]); j++) + max2175_write(ctx, 146 + j + i * 55, adc_presets[i][j]); +} + +static int max2175_init_power_manager(struct max2175 *ctx) +{ + int ret; + + /* Execute on-chip power-up/calibration */ + max2175_write_bit(ctx, 99, 2, 0); + usleep_range(1000, 1500); + max2175_write_bit(ctx, 99, 2, 1); + + /* Wait for the power manager to finish. */ + ret = max2175_poll_timeout(ctx, 69, 7, 7, 1, 50000); + if (ret) + mxm_err(ctx, "init pm failed\n"); + + return ret; +} + +static int max2175_recalibrate_adc(struct max2175 *ctx) +{ + int ret; + + /* ADC Re-calibration */ + max2175_write(ctx, 150, 0xff); + max2175_write(ctx, 205, 0xff); + max2175_write(ctx, 147, 0x20); + max2175_write(ctx, 147, 0x00); + max2175_write(ctx, 202, 0x20); + max2175_write(ctx, 202, 0x00); + + ret = max2175_poll_timeout(ctx, 69, 4, 3, 3, 50000); + if (ret) + mxm_err(ctx, "adc recalibration failed\n"); + + return ret; +} + +static u8 max2175_read_rom(struct max2175 *ctx, u8 row) +{ + u8 data = 0; + + max2175_write_bit(ctx, 56, 4, 0); + max2175_write_bits(ctx, 56, 3, 0, row); + + usleep_range(2000, 2500); + max2175_read(ctx, 58, &data); + + max2175_write_bits(ctx, 56, 3, 0, 0); + + mxm_dbg(ctx, "read_rom: row %d data 0x%02x\n", row, data); + + return data; +} + +static void max2175_load_from_rom(struct max2175 *ctx) +{ + u8 data = 0; + + data = max2175_read_rom(ctx, 0); + ctx->rom_bbf_bw_am = data & 0x0f; + max2175_write_bits(ctx, 81, 3, 0, data >> 4); + + data = max2175_read_rom(ctx, 1); + ctx->rom_bbf_bw_fm = data & 0x0f; + ctx->rom_bbf_bw_dab = data >> 4; + + data = max2175_read_rom(ctx, 2); + max2175_write_bits(ctx, 82, 4, 0, data & 0x1f); + max2175_write_bits(ctx, 82, 7, 5, data >> 5); + + data = max2175_read_rom(ctx, 3); + if (ctx->am_hiz) { + data &= 0x0f; + data |= (max2175_read_rom(ctx, 7) & 0x40) >> 2; + if (!data) + data |= 2; + } else { + data = (data & 0xf0) >> 4; + data |= (max2175_read_rom(ctx, 7) & 0x80) >> 3; + if (!data) + data |= 30; + } + max2175_write_bits(ctx, 80, 5, 0, data + 31); + + data = max2175_read_rom(ctx, 6); + max2175_write_bits(ctx, 81, 7, 6, data >> 6); +} + +static void max2175_load_full_fm_eu_1p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(full_fm_eu_1p0); i++) + max2175_write(ctx, i + 1, full_fm_eu_1p0[i]); + + usleep_range(5000, 5500); + ctx->decim_ratio = 36; +} + +static void max2175_load_full_fm_na_1p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(full_fm_na_1p0); i++) + max2175_write(ctx, i + 1, full_fm_na_1p0[i]); + + usleep_range(5000, 5500); + ctx->decim_ratio = 27; +} + +static int max2175_core_init(struct max2175 *ctx, u32 refout_bits) +{ + int ret; + + /* MAX2175 uses 36.864MHz clock for EU & 40.154MHz for NA region */ + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) + max2175_load_full_fm_eu_1p0(ctx); + else + max2175_load_full_fm_na_1p0(ctx); + + /* The default settings assume master */ + if (!ctx->master) + max2175_write_bit(ctx, 30, 7, 1); + + mxm_dbg(ctx, "refout_bits %u\n", refout_bits); + + /* Set REFOUT */ + max2175_write_bits(ctx, 56, 7, 5, refout_bits); + + /* ADC Reset */ + max2175_write_bit(ctx, 99, 1, 0); + usleep_range(1000, 1500); + max2175_write_bit(ctx, 99, 1, 1); + + /* Load ADC preset values */ + max2175_load_adc_presets(ctx); + + /* Initialize the power management state machine */ + ret = max2175_init_power_manager(ctx); + if (ret) + return ret; + + /* Recalibrate ADC */ + ret = max2175_recalibrate_adc(ctx); + if (ret) + return ret; + + /* Load ROM values to appropriate registers */ + max2175_load_from_rom(ctx); + + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) { + /* Load FIR coefficients into bank 0 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, + ch_coeff_fmeu); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmeu1_ra02_m6db); + } else { + /* Load FIR coefficients into bank 0 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, + ch_coeff_fmna); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmna1_ra02_m6db); + } + mxm_dbg(ctx, "core initialized\n"); + + return 0; +} + +static void max2175_s_ctrl_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + /* Load mode. Range check already done */ + max2175_set_rx_mode(ctx, rx_mode); + + mxm_dbg(ctx, "s_ctrl_rx_mode: %u curr freq %u\n", rx_mode, ctx->freq); + + /* Check if current freq valid for mode & update */ + if (max2175_freq_rx_mode_valid(ctx, rx_mode, ctx->freq)) + max2175_tune_rf_freq(ctx, ctx->freq, ctx->hsls->cur.val); + else + /* Use default freq of mode if current freq is not valid */ + max2175_tune_rf_freq(ctx, ctx->rx_modes[rx_mode].freq, + ctx->hsls->cur.val); +} + +static int max2175_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct max2175 *ctx = max2175_from_ctrl_hdl(ctrl->handler); + + mxm_dbg(ctx, "s_ctrl: id 0x%x, val %u\n", ctrl->id, ctrl->val); + switch (ctrl->id) { + case V4L2_CID_MAX2175_I2S_ENABLE: + max2175_i2s_enable(ctx, ctrl->val); + break; + case V4L2_CID_MAX2175_HSLS: + max2175_set_hsls(ctx, ctrl->val); + break; + case V4L2_CID_MAX2175_RX_MODE: + max2175_s_ctrl_rx_mode(ctx, ctrl->val); + break; + } + + return 0; +} + +static u32 max2175_get_lna_gain(struct max2175 *ctx) +{ + enum max2175_band band = max2175_read_bits(ctx, 5, 1, 0); + + switch (band) { + case MAX2175_BAND_AM: + return max2175_read_bits(ctx, 51, 3, 0); + case MAX2175_BAND_FM: + return max2175_read_bits(ctx, 50, 3, 0); + case MAX2175_BAND_VHF: + return max2175_read_bits(ctx, 52, 5, 0); + default: + return 0; + } +} + +static int max2175_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct max2175 *ctx = max2175_from_ctrl_hdl(ctrl->handler); + + switch (ctrl->id) { + case V4L2_CID_RF_TUNER_LNA_GAIN: + ctrl->val = max2175_get_lna_gain(ctx); + break; + case V4L2_CID_RF_TUNER_IF_GAIN: + ctrl->val = max2175_read_bits(ctx, 49, 4, 0); + break; + case V4L2_CID_RF_TUNER_PLL_LOCK: + ctrl->val = (max2175_read_bits(ctx, 60, 7, 6) == 3); + break; + } + + return 0; +}; + +static int max2175_set_freq_and_mode(struct max2175 *ctx, u32 freq) +{ + u32 rx_mode; + int ret; + + /* Get band from frequency */ + ret = max2175_rx_mode_from_freq(ctx, freq, &rx_mode); + if (ret) + return ret; + + mxm_dbg(ctx, "set_freq_and_mode: freq %u rx_mode %d\n", freq, rx_mode); + + /* Load mode */ + max2175_set_rx_mode(ctx, rx_mode); + ctx->rx_mode->cur.val = rx_mode; + + /* Tune to the new freq given */ + return max2175_tune_rf_freq(ctx, freq, ctx->hsls->cur.val); +} + +static int max2175_s_frequency(struct v4l2_subdev *sd, + const struct v4l2_frequency *vf) +{ + struct max2175 *ctx = max2175_from_sd(sd); + u32 freq; + int ret = 0; + + mxm_dbg(ctx, "s_freq: new %u curr %u, mode_resolved %d\n", + vf->frequency, ctx->freq, ctx->mode_resolved); + + if (vf->tuner != 0) + return -EINVAL; + + freq = clamp(vf->frequency, ctx->bands_rf->rangelow, + ctx->bands_rf->rangehigh); + + /* Check new freq valid for rx_mode if already resolved */ + if (ctx->mode_resolved && + max2175_freq_rx_mode_valid(ctx, ctx->rx_mode->cur.val, freq)) + ret = max2175_tune_rf_freq(ctx, freq, ctx->hsls->cur.val); + else + /* Find default rx_mode for freq and tune to it */ + ret = max2175_set_freq_and_mode(ctx, freq); + + mxm_dbg(ctx, "s_freq: ret %d curr %u mode_resolved %d mode %u\n", + ret, ctx->freq, ctx->mode_resolved, ctx->rx_mode->cur.val); + + return ret; +} + +static int max2175_g_frequency(struct v4l2_subdev *sd, + struct v4l2_frequency *vf) +{ + struct max2175 *ctx = max2175_from_sd(sd); + int ret = 0; + + if (vf->tuner != 0) + return -EINVAL; + + /* RF freq */ + vf->type = V4L2_TUNER_RF; + vf->frequency = ctx->freq; + + return ret; +} + +static int max2175_enum_freq_bands(struct v4l2_subdev *sd, + struct v4l2_frequency_band *band) +{ + struct max2175 *ctx = max2175_from_sd(sd); + + if (band->tuner != 0 || band->index != 0) + return -EINVAL; + + *band = *ctx->bands_rf; + + return 0; +} + +static int max2175_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct max2175 *ctx = max2175_from_sd(sd); + + if (vt->index > 0) + return -EINVAL; + + strscpy(vt->name, "RF", sizeof(vt->name)); + vt->type = V4L2_TUNER_RF; + vt->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS; + vt->rangelow = ctx->bands_rf->rangelow; + vt->rangehigh = ctx->bands_rf->rangehigh; + + return 0; +} + +static int max2175_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + /* Check tuner index is valid */ + if (vt->index > 0) + return -EINVAL; + + return 0; +} + +static const struct v4l2_subdev_tuner_ops max2175_tuner_ops = { + .s_frequency = max2175_s_frequency, + .g_frequency = max2175_g_frequency, + .enum_freq_bands = max2175_enum_freq_bands, + .g_tuner = max2175_g_tuner, + .s_tuner = max2175_s_tuner, +}; + +static const struct v4l2_subdev_ops max2175_ops = { + .tuner = &max2175_tuner_ops, +}; + +static const struct v4l2_ctrl_ops max2175_ctrl_ops = { + .s_ctrl = max2175_s_ctrl, + .g_volatile_ctrl = max2175_g_volatile_ctrl, +}; + +/* + * I2S output enable/disable configuration. This is a private control. + * Refer to Documentation/userspace-api/media/drivers/max2175.rst for more details. + */ +static const struct v4l2_ctrl_config max2175_i2s_en = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_I2S_ENABLE, + .name = "I2S Enable", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, + .is_private = 1, +}; + +/* + * HSLS value control LO freq adjacent location configuration. + * Refer to Documentation/userspace-api/media/drivers/max2175.rst for more details. + */ +static const struct v4l2_ctrl_config max2175_hsls = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_HSLS, + .name = "HSLS Above/Below Desired", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +/* + * Rx modes below are a set of preset configurations that decides the tuner's + * sck and sample rate of transmission. They are separate for EU & NA regions. + * Refer to Documentation/userspace-api/media/drivers/max2175.rst for more details. + */ +static const char * const max2175_ctrl_eu_rx_modes[] = { + [MAX2175_EU_FM_1_2] = "EU FM 1.2", + [MAX2175_DAB_1_2] = "DAB 1.2", +}; + +static const char * const max2175_ctrl_na_rx_modes[] = { + [MAX2175_NA_FM_1_0] = "NA FM 1.0", + [MAX2175_NA_FM_2_0] = "NA FM 2.0", +}; + +static const struct v4l2_ctrl_config max2175_eu_rx_mode = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_RX_MODE, + .name = "RX Mode", + .type = V4L2_CTRL_TYPE_MENU, + .max = ARRAY_SIZE(max2175_ctrl_eu_rx_modes) - 1, + .def = 0, + .qmenu = max2175_ctrl_eu_rx_modes, +}; + +static const struct v4l2_ctrl_config max2175_na_rx_mode = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_RX_MODE, + .name = "RX Mode", + .type = V4L2_CTRL_TYPE_MENU, + .max = ARRAY_SIZE(max2175_ctrl_na_rx_modes) - 1, + .def = 0, + .qmenu = max2175_ctrl_na_rx_modes, +}; + +static int max2175_refout_load_to_bits(struct i2c_client *client, u32 load, + u32 *bits) +{ + if (load <= 40) + *bits = load / 10; + else if (load >= 60 && load <= 70) + *bits = load / 10 - 1; + else + return -EINVAL; + + return 0; +} + +static int max2175_probe(struct i2c_client *client) +{ + bool master = true, am_hiz = false; + u32 refout_load, refout_bits = 0; /* REFOUT disabled */ + struct v4l2_ctrl_handler *hdl; + struct fwnode_handle *fwnode; + struct device_node *np; + struct v4l2_subdev *sd; + struct regmap *regmap; + struct max2175 *ctx; + struct clk *clk; + int ret; + + /* Parse DT properties */ + np = of_parse_phandle(client->dev.of_node, "maxim,master", 0); + if (np) { + master = false; /* Slave tuner */ + of_node_put(np); + } + + fwnode = of_fwnode_handle(client->dev.of_node); + if (fwnode_property_present(fwnode, "maxim,am-hiz-filter")) + am_hiz = true; + + if (!fwnode_property_read_u32(fwnode, "maxim,refout-load", + &refout_load)) { + ret = max2175_refout_load_to_bits(client, refout_load, + &refout_bits); + if (ret) { + dev_err(&client->dev, "invalid refout_load %u\n", + refout_load); + return -EINVAL; + } + } + + clk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(&client->dev, "cannot get clock %d\n", ret); + return ret; + } + + regmap = devm_regmap_init_i2c(client, &max2175_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&client->dev, "regmap init failed %d\n", ret); + return -ENODEV; + } + + /* Alloc tuner context */ + ctx = devm_kzalloc(&client->dev, sizeof(*ctx), GFP_KERNEL); + if (ctx == NULL) + return -ENOMEM; + + sd = &ctx->sd; + ctx->master = master; + ctx->am_hiz = am_hiz; + ctx->mode_resolved = false; + ctx->regmap = regmap; + ctx->xtal_freq = clk_get_rate(clk); + dev_info(&client->dev, "xtal freq %luHz\n", ctx->xtal_freq); + + v4l2_i2c_subdev_init(sd, client, &max2175_ops); + ctx->client = client; + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + /* Controls */ + hdl = &ctx->ctrl_hdl; + ret = v4l2_ctrl_handler_init(hdl, 7); + if (ret) + return ret; + + ctx->lna_gain = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_LNA_GAIN, + 0, 63, 1, 0); + ctx->lna_gain->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->if_gain = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_IF_GAIN, + 0, 31, 1, 0); + ctx->if_gain->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->pll_lock = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_PLL_LOCK, + 0, 1, 1, 0); + ctx->pll_lock->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->i2s_en = v4l2_ctrl_new_custom(hdl, &max2175_i2s_en, NULL); + ctx->hsls = v4l2_ctrl_new_custom(hdl, &max2175_hsls, NULL); + + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) { + ctx->rx_mode = v4l2_ctrl_new_custom(hdl, + &max2175_eu_rx_mode, NULL); + ctx->rx_modes = eu_rx_modes; + ctx->bands_rf = &eu_bands_rf; + } else { + ctx->rx_mode = v4l2_ctrl_new_custom(hdl, + &max2175_na_rx_mode, NULL); + ctx->rx_modes = na_rx_modes; + ctx->bands_rf = &na_bands_rf; + } + ctx->sd.ctrl_handler = &ctx->ctrl_hdl; + + /* Set the defaults */ + ctx->freq = ctx->bands_rf->rangelow; + + /* Register subdev */ + ret = v4l2_async_register_subdev(sd); + if (ret) { + dev_err(&client->dev, "register subdev failed\n"); + goto err_reg; + } + + /* Initialize device */ + ret = max2175_core_init(ctx, refout_bits); + if (ret) + goto err_init; + + ret = v4l2_ctrl_handler_setup(hdl); + if (ret) + goto err_init; + + return 0; + +err_init: + v4l2_async_unregister_subdev(sd); +err_reg: + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + + return ret; +} + +static int max2175_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct max2175 *ctx = max2175_from_sd(sd); + + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + v4l2_async_unregister_subdev(sd); + + return 0; +} + +static const struct i2c_device_id max2175_id[] = { + { DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, max2175_id); + +static const struct of_device_id max2175_of_ids[] = { + { .compatible = "maxim,max2175", }, + { } +}; +MODULE_DEVICE_TABLE(of, max2175_of_ids); + +static struct i2c_driver max2175_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = max2175_of_ids, + }, + .probe_new = max2175_probe, + .remove = max2175_remove, + .id_table = max2175_id, +}; + +module_i2c_driver(max2175_driver); + +MODULE_DESCRIPTION("Maxim MAX2175 RF to Bits tuner driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>"); diff --git a/drivers/media/i2c/max2175.h b/drivers/media/i2c/max2175.h new file mode 100644 index 000000000..4c722ea3e --- /dev/null +++ b/drivers/media/i2c/max2175.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Maxim Integrated MAX2175 RF to Bits tuner driver + * + * This driver & most of the hard coded values are based on the reference + * application delivered by Maxim for this device. + * + * Copyright (C) 2016 Maxim Integrated Products + * Copyright (C) 2017 Renesas Electronics Corporation + */ + +#ifndef __MAX2175_H__ +#define __MAX2175_H__ + +#define MAX2175_EU_XTAL_FREQ 36864000 /* In Hz */ +#define MAX2175_NA_XTAL_FREQ 40186125 /* In Hz */ + +enum max2175_region { + MAX2175_REGION_EU = 0, /* Europe */ + MAX2175_REGION_NA, /* North America */ +}; + +enum max2175_band { + MAX2175_BAND_AM = 0, + MAX2175_BAND_FM, + MAX2175_BAND_VHF, + MAX2175_BAND_L, +}; + +enum max2175_eu_mode { + /* EU modes */ + MAX2175_EU_FM_1_2 = 0, + MAX2175_DAB_1_2, + + /* + * Other possible modes to add in future + * MAX2175_DAB_1_0, + * MAX2175_DAB_1_3, + * MAX2175_EU_FM_2_2, + * MAX2175_EU_FMHD_4_0, + * MAX2175_EU_AM_1_0, + * MAX2175_EU_AM_2_2, + */ +}; + +enum max2175_na_mode { + /* NA modes */ + MAX2175_NA_FM_1_0 = 0, + MAX2175_NA_FM_2_0, + + /* + * Other possible modes to add in future + * MAX2175_NA_FMHD_1_0, + * MAX2175_NA_FMHD_1_2, + * MAX2175_NA_AM_1_0, + * MAX2175_NA_AM_1_2, + */ +}; + +/* Supported I2S modes */ +enum { + MAX2175_I2S_MODE0 = 0, + MAX2175_I2S_MODE1, + MAX2175_I2S_MODE2, + MAX2175_I2S_MODE3, + MAX2175_I2S_MODE4, +}; + +/* Coefficient table groups */ +enum { + MAX2175_CH_MSEL = 0, + MAX2175_EQ_MSEL, + MAX2175_AA_MSEL, +}; + +/* HSLS LO injection polarity */ +enum { + MAX2175_LO_BELOW_DESIRED = 0, + MAX2175_LO_ABOVE_DESIRED, +}; + +/* Channel FSM modes */ +enum max2175_csm_mode { + MAX2175_LOAD_TO_BUFFER = 0, + MAX2175_PRESET_TUNE, + MAX2175_SEARCH, + MAX2175_AF_UPDATE, + MAX2175_JUMP_FAST_TUNE, + MAX2175_CHECK, + MAX2175_LOAD_AND_SWAP, + MAX2175_END, + MAX2175_BUFFER_PLUS_PRESET_TUNE, + MAX2175_BUFFER_PLUS_SEARCH, + MAX2175_BUFFER_PLUS_AF_UPDATE, + MAX2175_BUFFER_PLUS_JUMP_FAST_TUNE, + MAX2175_BUFFER_PLUS_CHECK, + MAX2175_BUFFER_PLUS_LOAD_AND_SWAP, + MAX2175_NO_ACTION +}; + +#endif /* __MAX2175_H__ */ diff --git a/drivers/media/i2c/max9271.c b/drivers/media/i2c/max9271.c new file mode 100644 index 000000000..c247db569 --- /dev/null +++ b/drivers/media/i2c/max9271.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017-2020 Jacopo Mondi + * Copyright (C) 2017-2020 Kieran Bingham + * Copyright (C) 2017-2020 Laurent Pinchart + * Copyright (C) 2017-2020 Niklas Söderlund + * Copyright (C) 2016 Renesas Electronics Corporation + * Copyright (C) 2015 Cogent Embedded, Inc. + * + * This file exports functions to control the Maxim MAX9271 GMSL serializer + * chip. This is not a self-contained driver, as MAX9271 is usually embedded in + * camera modules with at least one image sensor and optional additional + * components, such as uController units or ISPs/DSPs. + * + * Drivers for the camera modules (i.e. rdacm20/21) are expected to use + * functions exported from this library driver to maximize code re-use. + */ + +#include <linux/delay.h> +#include <linux/i2c.h> + +#include "max9271.h" + +static int max9271_read(struct max9271_device *dev, u8 reg) +{ + int ret; + + dev_dbg(&dev->client->dev, "%s(0x%02x)\n", __func__, reg); + + ret = i2c_smbus_read_byte_data(dev->client, reg); + if (ret < 0) + dev_dbg(&dev->client->dev, + "%s: register 0x%02x read failed (%d)\n", + __func__, reg, ret); + + return ret; +} + +static int max9271_write(struct max9271_device *dev, u8 reg, u8 val) +{ + int ret; + + dev_dbg(&dev->client->dev, "%s(0x%02x, 0x%02x)\n", __func__, reg, val); + + ret = i2c_smbus_write_byte_data(dev->client, reg, val); + if (ret < 0) + dev_err(&dev->client->dev, + "%s: register 0x%02x write failed (%d)\n", + __func__, reg, ret); + + return ret; +} + +/* + * max9271_pclk_detect() - Detect valid pixel clock from image sensor + * + * Wait up to 10ms for a valid pixel clock. + * + * Returns 0 for success, < 0 for pixel clock not properly detected + */ +static int max9271_pclk_detect(struct max9271_device *dev) +{ + unsigned int i; + int ret; + + for (i = 0; i < 100; i++) { + ret = max9271_read(dev, 0x15); + if (ret < 0) + return ret; + + if (ret & MAX9271_PCLKDET) + return 0; + + usleep_range(50, 100); + } + + dev_err(&dev->client->dev, "Unable to detect valid pixel clock\n"); + + return -EIO; +} + +int max9271_set_serial_link(struct max9271_device *dev, bool enable) +{ + int ret; + u8 val = MAX9271_REVCCEN | MAX9271_FWDCCEN; + + if (enable) { + ret = max9271_pclk_detect(dev); + if (ret) + return ret; + + val |= MAX9271_SEREN; + } else { + val |= MAX9271_CLINKEN; + } + + /* + * The serializer temporarily disables the reverse control channel for + * 350µs after starting/stopping the forward serial link, but the + * deserializer synchronization time isn't clearly documented. + * + * According to the serializer datasheet we should wait 3ms, while + * according to the deserializer datasheet we should wait 5ms. + * + * Short delays here appear to show bit-errors in the writes following. + * Therefore a conservative delay seems best here. + */ + max9271_write(dev, 0x04, val); + usleep_range(5000, 8000); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_set_serial_link); + +int max9271_configure_i2c(struct max9271_device *dev, u8 i2c_config) +{ + int ret; + + ret = max9271_write(dev, 0x0d, i2c_config); + if (ret) + return ret; + + /* The delay required after an I2C bus configuration change is not + * characterized in the serializer manual. Sleep up to 5msec to + * stay safe. + */ + usleep_range(3500, 5000); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_configure_i2c); + +int max9271_set_high_threshold(struct max9271_device *dev, bool enable) +{ + int ret; + + ret = max9271_read(dev, 0x08); + if (ret < 0) + return ret; + + /* + * Enable or disable reverse channel high threshold to increase + * immunity to power supply noise. + */ + max9271_write(dev, 0x08, enable ? ret | BIT(0) : ret & ~BIT(0)); + usleep_range(2000, 2500); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_set_high_threshold); + +int max9271_configure_gmsl_link(struct max9271_device *dev) +{ + /* + * Configure the GMSL link: + * + * - Double input mode, high data rate, 24-bit mode + * - Latch input data on PCLKIN rising edge + * - Enable HS/VS encoding + * - 1-bit parity error detection + * + * TODO: Make the GMSL link configuration parametric. + */ + max9271_write(dev, 0x07, MAX9271_DBL | MAX9271_HVEN | + MAX9271_EDC_1BIT_PARITY); + usleep_range(5000, 8000); + + /* + * Adjust spread spectrum to +4% and auto-detect pixel clock + * and serial link rate. + */ + max9271_write(dev, 0x02, MAX9271_SPREAD_SPECT_4 | MAX9271_R02_RES | + MAX9271_PCLK_AUTODETECT | MAX9271_SERIAL_AUTODETECT); + usleep_range(5000, 8000); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_configure_gmsl_link); + +int max9271_set_gpios(struct max9271_device *dev, u8 gpio_mask) +{ + int ret; + + ret = max9271_read(dev, 0x0f); + if (ret < 0) + return 0; + + ret |= gpio_mask; + ret = max9271_write(dev, 0x0f, ret); + if (ret < 0) { + dev_err(&dev->client->dev, "Failed to set gpio (%d)\n", ret); + return ret; + } + + usleep_range(3500, 5000); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_set_gpios); + +int max9271_clear_gpios(struct max9271_device *dev, u8 gpio_mask) +{ + int ret; + + ret = max9271_read(dev, 0x0f); + if (ret < 0) + return 0; + + ret &= ~gpio_mask; + ret = max9271_write(dev, 0x0f, ret); + if (ret < 0) { + dev_err(&dev->client->dev, "Failed to clear gpio (%d)\n", ret); + return ret; + } + + usleep_range(3500, 5000); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_clear_gpios); + +int max9271_enable_gpios(struct max9271_device *dev, u8 gpio_mask) +{ + int ret; + + ret = max9271_read(dev, 0x0e); + if (ret < 0) + return 0; + + /* BIT(0) reserved: GPO is always enabled. */ + ret |= (gpio_mask & ~BIT(0)); + ret = max9271_write(dev, 0x0e, ret); + if (ret < 0) { + dev_err(&dev->client->dev, "Failed to enable gpio (%d)\n", ret); + return ret; + } + + usleep_range(3500, 5000); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_enable_gpios); + +int max9271_disable_gpios(struct max9271_device *dev, u8 gpio_mask) +{ + int ret; + + ret = max9271_read(dev, 0x0e); + if (ret < 0) + return 0; + + /* BIT(0) reserved: GPO cannot be disabled */ + ret &= ~(gpio_mask | BIT(0)); + ret = max9271_write(dev, 0x0e, ret); + if (ret < 0) { + dev_err(&dev->client->dev, "Failed to disable gpio (%d)\n", ret); + return ret; + } + + usleep_range(3500, 5000); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_disable_gpios); + +int max9271_verify_id(struct max9271_device *dev) +{ + int ret; + + ret = max9271_read(dev, 0x1e); + if (ret < 0) { + dev_err(&dev->client->dev, "MAX9271 ID read failed (%d)\n", + ret); + return ret; + } + + if (ret != MAX9271_ID) { + dev_err(&dev->client->dev, "MAX9271 ID mismatch (0x%02x)\n", + ret); + return -ENXIO; + } + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_verify_id); + +int max9271_set_address(struct max9271_device *dev, u8 addr) +{ + int ret; + + ret = max9271_write(dev, 0x00, addr << 1); + if (ret < 0) { + dev_err(&dev->client->dev, + "MAX9271 I2C address change failed (%d)\n", ret); + return ret; + } + usleep_range(3500, 5000); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_set_address); + +int max9271_set_deserializer_address(struct max9271_device *dev, u8 addr) +{ + int ret; + + ret = max9271_write(dev, 0x01, addr << 1); + if (ret < 0) { + dev_err(&dev->client->dev, + "MAX9271 deserializer address set failed (%d)\n", ret); + return ret; + } + usleep_range(3500, 5000); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_set_deserializer_address); + +int max9271_set_translation(struct max9271_device *dev, u8 source, u8 dest) +{ + int ret; + + ret = max9271_write(dev, 0x09, source << 1); + if (ret < 0) { + dev_err(&dev->client->dev, + "MAX9271 I2C translation setup failed (%d)\n", ret); + return ret; + } + usleep_range(3500, 5000); + + ret = max9271_write(dev, 0x0a, dest << 1); + if (ret < 0) { + dev_err(&dev->client->dev, + "MAX9271 I2C translation setup failed (%d)\n", ret); + return ret; + } + usleep_range(3500, 5000); + + return 0; +} +EXPORT_SYMBOL_GPL(max9271_set_translation); diff --git a/drivers/media/i2c/max9271.h b/drivers/media/i2c/max9271.h new file mode 100644 index 000000000..d78fb2144 --- /dev/null +++ b/drivers/media/i2c/max9271.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2017-2020 Jacopo Mondi + * Copyright (C) 2017-2020 Kieran Bingham + * Copyright (C) 2017-2020 Laurent Pinchart + * Copyright (C) 2017-2020 Niklas Söderlund + * Copyright (C) 2016 Renesas Electronics Corporation + * Copyright (C) 2015 Cogent Embedded, Inc. + */ + +#include <linux/i2c.h> + +#define MAX9271_DEFAULT_ADDR 0x40 + +/* Register 0x02 */ +#define MAX9271_SPREAD_SPECT_0 (0 << 5) +#define MAX9271_SPREAD_SPECT_05 (1 << 5) +#define MAX9271_SPREAD_SPECT_15 (2 << 5) +#define MAX9271_SPREAD_SPECT_1 (5 << 5) +#define MAX9271_SPREAD_SPECT_2 (3 << 5) +#define MAX9271_SPREAD_SPECT_3 (6 << 5) +#define MAX9271_SPREAD_SPECT_4 (7 << 5) +#define MAX9271_R02_RES BIT(4) +#define MAX9271_PCLK_AUTODETECT (3 << 2) +#define MAX9271_SERIAL_AUTODETECT (0x03) +/* Register 0x04 */ +#define MAX9271_SEREN BIT(7) +#define MAX9271_CLINKEN BIT(6) +#define MAX9271_PRBSEN BIT(5) +#define MAX9271_SLEEP BIT(4) +#define MAX9271_INTTYPE_I2C (0 << 2) +#define MAX9271_INTTYPE_UART (1 << 2) +#define MAX9271_INTTYPE_NONE (2 << 2) +#define MAX9271_REVCCEN BIT(1) +#define MAX9271_FWDCCEN BIT(0) +/* Register 0x07 */ +#define MAX9271_DBL BIT(7) +#define MAX9271_DRS BIT(6) +#define MAX9271_BWS BIT(5) +#define MAX9271_ES BIT(4) +#define MAX9271_HVEN BIT(2) +#define MAX9271_EDC_1BIT_PARITY (0 << 0) +#define MAX9271_EDC_6BIT_CRC (1 << 0) +#define MAX9271_EDC_6BIT_HAMMING (2 << 0) +/* Register 0x08 */ +#define MAX9271_INVVS BIT(7) +#define MAX9271_INVHS BIT(6) +#define MAX9271_REV_LOGAIN BIT(3) +#define MAX9271_REV_HIVTH BIT(0) +/* Register 0x09 */ +#define MAX9271_ID 0x09 +/* Register 0x0d */ +#define MAX9271_I2CLOCACK BIT(7) +#define MAX9271_I2CSLVSH_1046NS_469NS (3 << 5) +#define MAX9271_I2CSLVSH_938NS_352NS (2 << 5) +#define MAX9271_I2CSLVSH_469NS_234NS (1 << 5) +#define MAX9271_I2CSLVSH_352NS_117NS (0 << 5) +#define MAX9271_I2CMSTBT_837KBPS (7 << 2) +#define MAX9271_I2CMSTBT_533KBPS (6 << 2) +#define MAX9271_I2CMSTBT_339KBPS (5 << 2) +#define MAX9271_I2CMSTBT_173KBPS (4 << 2) +#define MAX9271_I2CMSTBT_105KBPS (3 << 2) +#define MAX9271_I2CMSTBT_84KBPS (2 << 2) +#define MAX9271_I2CMSTBT_28KBPS (1 << 2) +#define MAX9271_I2CMSTBT_8KBPS (0 << 2) +#define MAX9271_I2CSLVTO_NONE (3 << 0) +#define MAX9271_I2CSLVTO_1024US (2 << 0) +#define MAX9271_I2CSLVTO_256US (1 << 0) +#define MAX9271_I2CSLVTO_64US (0 << 0) +/* Register 0x0f */ +#define MAX9271_GPIO5OUT BIT(5) +#define MAX9271_GPIO4OUT BIT(4) +#define MAX9271_GPIO3OUT BIT(3) +#define MAX9271_GPIO2OUT BIT(2) +#define MAX9271_GPIO1OUT BIT(1) +#define MAX9271_GPO BIT(0) +/* Register 0x15 */ +#define MAX9271_PCLKDET BIT(0) + +/** + * struct max9271_device - max9271 device + * @client: The i2c client for the max9271 instance + */ +struct max9271_device { + struct i2c_client *client; +}; + +/** + * max9271_set_serial_link() - Enable/disable serial link + * @dev: The max9271 device + * @enable: Serial link enable/disable flag + * + * Return 0 on success or a negative error code on failure + */ +int max9271_set_serial_link(struct max9271_device *dev, bool enable); + +/** + * max9271_configure_i2c() - Configure I2C bus parameters + * @dev: The max9271 device + * @i2c_config: The I2C bus configuration bit mask + * + * Configure MAX9271 I2C interface. The bus configuration provided in the + * @i2c_config parameter shall be assembled using bit values defined by the + * MAX9271_I2C* macros. + * + * Return 0 on success or a negative error code on failure + */ +int max9271_configure_i2c(struct max9271_device *dev, u8 i2c_config); + +/** + * max9271_set_high_threshold() - Enable or disable reverse channel high + * threshold + * @dev: The max9271 device + * @enable: High threshold enable/disable flag + * + * Return 0 on success or a negative error code on failure + */ +int max9271_set_high_threshold(struct max9271_device *dev, bool enable); + +/** + * max9271_configure_gmsl_link() - Configure the GMSL link + * @dev: The max9271 device + * + * FIXME: the GMSL link configuration is currently hardcoded and performed + * by programming registers 0x04, 0x07 and 0x02. + * + * Return 0 on success or a negative error code on failure + */ +int max9271_configure_gmsl_link(struct max9271_device *dev); + +/** + * max9271_set_gpios() - Set gpio lines to physical high value + * @dev: The max9271 device + * @gpio_mask: The mask of gpio lines to set to high value + * + * The @gpio_mask parameter shall be assembled using the MAX9271_GP[IO|O]* + * bit values. + * + * Return 0 on success or a negative error code on failure + */ +int max9271_set_gpios(struct max9271_device *dev, u8 gpio_mask); + +/** + * max9271_clear_gpios() - Set gpio lines to physical low value + * @dev: The max9271 device + * @gpio_mask: The mask of gpio lines to set to low value + * + * The @gpio_mask parameter shall be assembled using the MAX9271_GP[IO|O]* + * bit values. + * + * Return 0 on success or a negative error code on failure + */ +int max9271_clear_gpios(struct max9271_device *dev, u8 gpio_mask); + +/** + * max9271_enable_gpios() - Enable gpio lines + * @dev: The max9271 device + * @gpio_mask: The mask of gpio lines to enable + * + * The @gpio_mask parameter shall be assembled using the MAX9271_GPIO* + * bit values. GPO line is always enabled by default. + * + * Return 0 on success or a negative error code on failure + */ +int max9271_enable_gpios(struct max9271_device *dev, u8 gpio_mask); + +/** + * max9271_disable_gpios() - Disable gpio lines + * @dev: The max9271 device + * @gpio_mask: The mask of gpio lines to disable + * + * The @gpio_mask parameter shall be assembled using the MAX9271_GPIO* + * bit values. GPO line is always enabled by default and cannot be disabled. + * + * Return 0 on success or a negative error code on failure + */ +int max9271_disable_gpios(struct max9271_device *dev, u8 gpio_mask); + +/** + * max9271_verify_id() - Read and verify MAX9271 id + * @dev: The max9271 device + * + * Return 0 on success or a negative error code on failure + */ +int max9271_verify_id(struct max9271_device *dev); + +/** + * max9271_set_address() - Program a new I2C address + * @dev: The max9271 device + * @addr: The new I2C address in 7-bit format + * + * This function only takes care of programming the new I2C address @addr to + * in the MAX9271 chip registers, it is responsiblity of the caller to set + * the i2c address client to the @addr value to be able to communicate with + * the MAX9271 chip using the I2C framework APIs after this function returns. + * + * Return 0 on success or a negative error code on failure + */ +int max9271_set_address(struct max9271_device *dev, u8 addr); + +/** + * max9271_set_deserializer_address() - Program the remote deserializer address + * @dev: The max9271 device + * @addr: The deserializer I2C address in 7-bit format + * + * Return 0 on success or a negative error code on failure + */ +int max9271_set_deserializer_address(struct max9271_device *dev, u8 addr); + +/** + * max9271_set_translation() - Program I2C address translation + * @dev: The max9271 device + * @source: The I2C source address + * @dest: The I2C destination address + * + * Program address translation from @source to @dest. This is required to + * communicate with local devices that do not support address reprogramming. + * + * TODO: The device supports translation of two address, this function currently + * supports a single one. + * + * Return 0 on success or a negative error code on failure + */ +int max9271_set_translation(struct max9271_device *dev, u8 source, u8 dest); diff --git a/drivers/media/i2c/max9286.c b/drivers/media/i2c/max9286.c new file mode 100644 index 000000000..a358eb3fe --- /dev/null +++ b/drivers/media/i2c/max9286.c @@ -0,0 +1,1323 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Maxim MAX9286 GMSL Deserializer Driver + * + * Copyright (C) 2017-2019 Jacopo Mondi + * Copyright (C) 2017-2019 Kieran Bingham + * Copyright (C) 2017-2019 Laurent Pinchart + * Copyright (C) 2017-2019 Niklas Söderlund + * Copyright (C) 2016 Renesas Electronics Corporation + * Copyright (C) 2015 Cogent Embedded, Inc. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fwnode.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> +#include <linux/i2c.h> +#include <linux/i2c-mux.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +/* Register 0x00 */ +#define MAX9286_MSTLINKSEL_AUTO (7 << 5) +#define MAX9286_MSTLINKSEL(n) ((n) << 5) +#define MAX9286_EN_VS_GEN BIT(4) +#define MAX9286_LINKEN(n) (1 << (n)) +/* Register 0x01 */ +#define MAX9286_FSYNCMODE_ECU (3 << 6) +#define MAX9286_FSYNCMODE_EXT (2 << 6) +#define MAX9286_FSYNCMODE_INT_OUT (1 << 6) +#define MAX9286_FSYNCMODE_INT_HIZ (0 << 6) +#define MAX9286_GPIEN BIT(5) +#define MAX9286_ENLMO_RSTFSYNC BIT(2) +#define MAX9286_FSYNCMETH_AUTO (2 << 0) +#define MAX9286_FSYNCMETH_SEMI_AUTO (1 << 0) +#define MAX9286_FSYNCMETH_MANUAL (0 << 0) +#define MAX9286_REG_FSYNC_PERIOD_L 0x06 +#define MAX9286_REG_FSYNC_PERIOD_M 0x07 +#define MAX9286_REG_FSYNC_PERIOD_H 0x08 +/* Register 0x0a */ +#define MAX9286_FWDCCEN(n) (1 << ((n) + 4)) +#define MAX9286_REVCCEN(n) (1 << (n)) +/* Register 0x0c */ +#define MAX9286_HVEN BIT(7) +#define MAX9286_EDC_6BIT_HAMMING (2 << 5) +#define MAX9286_EDC_6BIT_CRC (1 << 5) +#define MAX9286_EDC_1BIT_PARITY (0 << 5) +#define MAX9286_DESEL BIT(4) +#define MAX9286_INVVS BIT(3) +#define MAX9286_INVHS BIT(2) +#define MAX9286_HVSRC_D0 (2 << 0) +#define MAX9286_HVSRC_D14 (1 << 0) +#define MAX9286_HVSRC_D18 (0 << 0) +/* Register 0x0f */ +#define MAX9286_0X0F_RESERVED BIT(3) +/* Register 0x12 */ +#define MAX9286_CSILANECNT(n) (((n) - 1) << 6) +#define MAX9286_CSIDBL BIT(5) +#define MAX9286_DBL BIT(4) +#define MAX9286_DATATYPE_USER_8BIT (11 << 0) +#define MAX9286_DATATYPE_USER_YUV_12BIT (10 << 0) +#define MAX9286_DATATYPE_USER_24BIT (9 << 0) +#define MAX9286_DATATYPE_RAW14 (8 << 0) +#define MAX9286_DATATYPE_RAW11 (7 << 0) +#define MAX9286_DATATYPE_RAW10 (6 << 0) +#define MAX9286_DATATYPE_RAW8 (5 << 0) +#define MAX9286_DATATYPE_YUV422_10BIT (4 << 0) +#define MAX9286_DATATYPE_YUV422_8BIT (3 << 0) +#define MAX9286_DATATYPE_RGB555 (2 << 0) +#define MAX9286_DATATYPE_RGB565 (1 << 0) +#define MAX9286_DATATYPE_RGB888 (0 << 0) +/* Register 0x15 */ +#define MAX9286_VC(n) ((n) << 5) +#define MAX9286_VCTYPE BIT(4) +#define MAX9286_CSIOUTEN BIT(3) +#define MAX9286_0X15_RESV (3 << 0) +/* Register 0x1b */ +#define MAX9286_SWITCHIN(n) (1 << ((n) + 4)) +#define MAX9286_ENEQ(n) (1 << (n)) +/* Register 0x27 */ +#define MAX9286_LOCKED BIT(7) +/* Register 0x31 */ +#define MAX9286_FSYNC_LOCKED BIT(6) +/* Register 0x34 */ +#define MAX9286_I2CLOCACK BIT(7) +#define MAX9286_I2CSLVSH_1046NS_469NS (3 << 5) +#define MAX9286_I2CSLVSH_938NS_352NS (2 << 5) +#define MAX9286_I2CSLVSH_469NS_234NS (1 << 5) +#define MAX9286_I2CSLVSH_352NS_117NS (0 << 5) +#define MAX9286_I2CMSTBT_837KBPS (7 << 2) +#define MAX9286_I2CMSTBT_533KBPS (6 << 2) +#define MAX9286_I2CMSTBT_339KBPS (5 << 2) +#define MAX9286_I2CMSTBT_173KBPS (4 << 2) +#define MAX9286_I2CMSTBT_105KBPS (3 << 2) +#define MAX9286_I2CMSTBT_84KBPS (2 << 2) +#define MAX9286_I2CMSTBT_28KBPS (1 << 2) +#define MAX9286_I2CMSTBT_8KBPS (0 << 2) +#define MAX9286_I2CSLVTO_NONE (3 << 0) +#define MAX9286_I2CSLVTO_1024US (2 << 0) +#define MAX9286_I2CSLVTO_256US (1 << 0) +#define MAX9286_I2CSLVTO_64US (0 << 0) +/* Register 0x3b */ +#define MAX9286_REV_TRF(n) ((n) << 4) +#define MAX9286_REV_AMP(n) ((((n) - 30) / 10) << 1) /* in mV */ +#define MAX9286_REV_AMP_X BIT(0) +/* Register 0x3f */ +#define MAX9286_EN_REV_CFG BIT(6) +#define MAX9286_REV_FLEN(n) ((n) - 20) +/* Register 0x49 */ +#define MAX9286_VIDEO_DETECT_MASK 0x0f +/* Register 0x69 */ +#define MAX9286_LFLTBMONMASKED BIT(7) +#define MAX9286_LOCKMONMASKED BIT(6) +#define MAX9286_AUTOCOMBACKEN BIT(5) +#define MAX9286_AUTOMASKEN BIT(4) +#define MAX9286_MASKLINK(n) ((n) << 0) + +/* + * The sink and source pads are created to match the OF graph port numbers so + * that their indexes can be used interchangeably. + */ +#define MAX9286_NUM_GMSL 4 +#define MAX9286_N_SINKS 4 +#define MAX9286_N_PADS 5 +#define MAX9286_SRC_PAD 4 + +struct max9286_source { + struct v4l2_subdev *sd; + struct fwnode_handle *fwnode; +}; + +struct max9286_asd { + struct v4l2_async_subdev base; + struct max9286_source *source; +}; + +static inline struct max9286_asd *to_max9286_asd(struct v4l2_async_subdev *asd) +{ + return container_of(asd, struct max9286_asd, base); +} + +struct max9286_priv { + struct i2c_client *client; + struct gpio_desc *gpiod_pwdn; + struct v4l2_subdev sd; + struct media_pad pads[MAX9286_N_PADS]; + struct regulator *regulator; + + struct gpio_chip gpio; + u8 gpio_state; + + struct i2c_mux_core *mux; + unsigned int mux_channel; + bool mux_open; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *pixelrate; + + struct v4l2_mbus_framefmt fmt[MAX9286_N_SINKS]; + + /* Protects controls and fmt structures */ + struct mutex mutex; + + unsigned int nsources; + unsigned int source_mask; + unsigned int route_mask; + unsigned int bound_sources; + unsigned int csi2_data_lanes; + struct max9286_source sources[MAX9286_NUM_GMSL]; + struct v4l2_async_notifier notifier; +}; + +static struct max9286_source *next_source(struct max9286_priv *priv, + struct max9286_source *source) +{ + if (!source) + source = &priv->sources[0]; + else + source++; + + for (; source < &priv->sources[MAX9286_NUM_GMSL]; source++) { + if (source->fwnode) + return source; + } + + return NULL; +} + +#define for_each_source(priv, source) \ + for ((source) = NULL; ((source) = next_source((priv), (source))); ) + +#define to_index(priv, source) ((source) - &(priv)->sources[0]) + +static inline struct max9286_priv *sd_to_max9286(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max9286_priv, sd); +} + +/* ----------------------------------------------------------------------------- + * I2C IO + */ + +static int max9286_read(struct max9286_priv *priv, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(priv->client, reg); + if (ret < 0) + dev_err(&priv->client->dev, + "%s: register 0x%02x read failed (%d)\n", + __func__, reg, ret); + + return ret; +} + +static int max9286_write(struct max9286_priv *priv, u8 reg, u8 val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(priv->client, reg, val); + if (ret < 0) + dev_err(&priv->client->dev, + "%s: register 0x%02x write failed (%d)\n", + __func__, reg, ret); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * I2C Multiplexer + */ + +static void max9286_i2c_mux_configure(struct max9286_priv *priv, u8 conf) +{ + max9286_write(priv, 0x0a, conf); + + /* + * We must sleep after any change to the forward or reverse channel + * configuration. + */ + usleep_range(3000, 5000); +} + +static void max9286_i2c_mux_open(struct max9286_priv *priv) +{ + /* Open all channels on the MAX9286 */ + max9286_i2c_mux_configure(priv, 0xff); + + priv->mux_open = true; +} + +static void max9286_i2c_mux_close(struct max9286_priv *priv) +{ + /* + * Ensure that both the forward and reverse channel are disabled on the + * mux, and that the channel ID is invalidated to ensure we reconfigure + * on the next max9286_i2c_mux_select() call. + */ + max9286_i2c_mux_configure(priv, 0x00); + + priv->mux_open = false; + priv->mux_channel = -1; +} + +static int max9286_i2c_mux_select(struct i2c_mux_core *muxc, u32 chan) +{ + struct max9286_priv *priv = i2c_mux_priv(muxc); + + /* Channel select is disabled when configured in the opened state. */ + if (priv->mux_open) + return 0; + + if (priv->mux_channel == chan) + return 0; + + priv->mux_channel = chan; + + max9286_i2c_mux_configure(priv, + MAX9286_FWDCCEN(chan) | + MAX9286_REVCCEN(chan)); + + return 0; +} + +static int max9286_i2c_mux_init(struct max9286_priv *priv) +{ + struct max9286_source *source; + int ret; + + if (!i2c_check_functionality(priv->client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -ENODEV; + + priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev, + priv->nsources, 0, I2C_MUX_LOCKED, + max9286_i2c_mux_select, NULL); + if (!priv->mux) + return -ENOMEM; + + priv->mux->priv = priv; + + for_each_source(priv, source) { + unsigned int index = to_index(priv, source); + + ret = i2c_mux_add_adapter(priv->mux, 0, index, 0); + if (ret < 0) + goto error; + } + + return 0; + +error: + i2c_mux_del_adapters(priv->mux); + return ret; +} + +static void max9286_configure_i2c(struct max9286_priv *priv, bool localack) +{ + u8 config = MAX9286_I2CSLVSH_469NS_234NS | MAX9286_I2CSLVTO_1024US | + MAX9286_I2CMSTBT_105KBPS; + + if (localack) + config |= MAX9286_I2CLOCACK; + + max9286_write(priv, 0x34, config); + usleep_range(3000, 5000); +} + +/* + * max9286_check_video_links() - Make sure video links are detected and locked + * + * Performs safety checks on video link status. Make sure they are detected + * and all enabled links are locked. + * + * Returns 0 for success, -EIO for errors. + */ +static int max9286_check_video_links(struct max9286_priv *priv) +{ + unsigned int i; + int ret; + + /* + * Make sure valid video links are detected. + * The delay is not characterized in de-serializer manual, wait up + * to 5 ms. + */ + for (i = 0; i < 10; i++) { + ret = max9286_read(priv, 0x49); + if (ret < 0) + return -EIO; + + if ((ret & MAX9286_VIDEO_DETECT_MASK) == priv->source_mask) + break; + + usleep_range(350, 500); + } + + if (i == 10) { + dev_err(&priv->client->dev, + "Unable to detect video links: 0x%02x\n", ret); + return -EIO; + } + + /* Make sure all enabled links are locked (4ms max). */ + for (i = 0; i < 10; i++) { + ret = max9286_read(priv, 0x27); + if (ret < 0) + return -EIO; + + if (ret & MAX9286_LOCKED) + break; + + usleep_range(350, 450); + } + + if (i == 10) { + dev_err(&priv->client->dev, "Not all enabled links locked\n"); + return -EIO; + } + + return 0; +} + +/* + * max9286_check_config_link() - Detect and wait for configuration links + * + * Determine if the configuration channel is up and settled for a link. + * + * Returns 0 for success, -EIO for errors. + */ +static int max9286_check_config_link(struct max9286_priv *priv, + unsigned int source_mask) +{ + unsigned int conflink_mask = (source_mask & 0x0f) << 4; + unsigned int i; + int ret; + + /* + * Make sure requested configuration links are detected. + * The delay is not characterized in the chip manual: wait up + * to 5 milliseconds. + */ + for (i = 0; i < 10; i++) { + ret = max9286_read(priv, 0x49); + if (ret < 0) + return -EIO; + + ret &= 0xf0; + if (ret == conflink_mask) + break; + + usleep_range(350, 500); + } + + if (ret != conflink_mask) { + dev_err(&priv->client->dev, + "Unable to detect configuration links: 0x%02x expected 0x%02x\n", + ret, conflink_mask); + return -EIO; + } + + dev_info(&priv->client->dev, + "Successfully detected configuration links after %u loops: 0x%02x\n", + i, conflink_mask); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdev + */ + +static int max9286_set_pixelrate(struct max9286_priv *priv) +{ + struct max9286_source *source = NULL; + u64 pixelrate = 0; + + for_each_source(priv, source) { + struct v4l2_ctrl *ctrl; + u64 source_rate = 0; + + /* Pixel rate is mandatory to be reported by sources. */ + ctrl = v4l2_ctrl_find(source->sd->ctrl_handler, + V4L2_CID_PIXEL_RATE); + if (!ctrl) { + pixelrate = 0; + break; + } + + /* All source must report the same pixel rate. */ + source_rate = v4l2_ctrl_g_ctrl_int64(ctrl); + if (!pixelrate) { + pixelrate = source_rate; + } else if (pixelrate != source_rate) { + dev_err(&priv->client->dev, + "Unable to calculate pixel rate\n"); + return -EINVAL; + } + } + + if (!pixelrate) { + dev_err(&priv->client->dev, + "No pixel rate control available in sources\n"); + return -EINVAL; + } + + /* + * The CSI-2 transmitter pixel rate is the single source rate multiplied + * by the number of available sources. + */ + return v4l2_ctrl_s_ctrl_int64(priv->pixelrate, + pixelrate * priv->nsources); +} + +static int max9286_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct max9286_priv *priv = sd_to_max9286(notifier->sd); + struct max9286_source *source = to_max9286_asd(asd)->source; + unsigned int index = to_index(priv, source); + unsigned int src_pad; + int ret; + + ret = media_entity_get_fwnode_pad(&subdev->entity, + source->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(&priv->client->dev, + "Failed to find pad for %s\n", subdev->name); + return ret; + } + + priv->bound_sources |= BIT(index); + source->sd = subdev; + src_pad = ret; + + ret = media_create_pad_link(&source->sd->entity, src_pad, + &priv->sd.entity, index, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(&priv->client->dev, + "Unable to link %s:%u -> %s:%u\n", + source->sd->name, src_pad, priv->sd.name, index); + return ret; + } + + dev_dbg(&priv->client->dev, "Bound %s pad: %u on index %u\n", + subdev->name, src_pad, index); + + /* + * We can only register v4l2_async_notifiers, which do not provide a + * means to register a complete callback. bound_sources allows us to + * identify when all remote serializers have completed their probe. + */ + if (priv->bound_sources != priv->source_mask) + return 0; + + /* + * All enabled sources have probed and enabled their reverse control + * channels: + * + * - Verify all configuration links are properly detected + * - Disable auto-ack as communication on the control channel are now + * stable. + */ + max9286_check_config_link(priv, priv->source_mask); + + /* + * Re-configure I2C with local acknowledge disabled after cameras have + * probed. + */ + max9286_configure_i2c(priv, false); + + return max9286_set_pixelrate(priv); +} + +static void max9286_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct max9286_priv *priv = sd_to_max9286(notifier->sd); + struct max9286_source *source = to_max9286_asd(asd)->source; + unsigned int index = to_index(priv, source); + + source->sd = NULL; + priv->bound_sources &= ~BIT(index); +} + +static const struct v4l2_async_notifier_operations max9286_notify_ops = { + .bound = max9286_notify_bound, + .unbind = max9286_notify_unbind, +}; + +static int max9286_v4l2_notifier_register(struct max9286_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct max9286_source *source = NULL; + int ret; + + if (!priv->nsources) + return 0; + + v4l2_async_notifier_init(&priv->notifier); + + for_each_source(priv, source) { + unsigned int i = to_index(priv, source); + struct v4l2_async_subdev *asd; + + asd = v4l2_async_notifier_add_fwnode_subdev(&priv->notifier, + source->fwnode, + sizeof(struct max9286_asd)); + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev for source %u: %ld", + i, PTR_ERR(asd)); + v4l2_async_notifier_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + to_max9286_asd(asd)->source = source; + } + + priv->notifier.ops = &max9286_notify_ops; + + ret = v4l2_async_subdev_notifier_register(&priv->sd, &priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_notifier_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void max9286_v4l2_notifier_unregister(struct max9286_priv *priv) +{ + if (!priv->nsources) + return; + + v4l2_async_notifier_unregister(&priv->notifier); + v4l2_async_notifier_cleanup(&priv->notifier); +} + +static int max9286_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct max9286_priv *priv = sd_to_max9286(sd); + struct max9286_source *source; + unsigned int i; + bool sync = false; + int ret; + + if (enable) { + /* + * The frame sync between cameras is transmitted across the + * reverse channel as GPIO. We must open all channels while + * streaming to allow this synchronisation signal to be shared. + */ + max9286_i2c_mux_open(priv); + + /* Start all cameras. */ + for_each_source(priv, source) { + ret = v4l2_subdev_call(source->sd, video, s_stream, 1); + if (ret) + return ret; + } + + ret = max9286_check_video_links(priv); + if (ret) + return ret; + + /* + * Wait until frame synchronization is locked. + * + * Manual says frame sync locking should take ~6 VTS. + * From practical experience at least 8 are required. Give + * 12 complete frames time (~400ms at 30 fps) to achieve frame + * locking before returning error. + */ + for (i = 0; i < 40; i++) { + if (max9286_read(priv, 0x31) & MAX9286_FSYNC_LOCKED) { + sync = true; + break; + } + usleep_range(9000, 11000); + } + + if (!sync) { + dev_err(&priv->client->dev, + "Failed to get frame synchronization\n"); + return -EXDEV; /* Invalid cross-device link */ + } + + /* + * Enable CSI output, VC set according to link number. + * Bit 7 must be set (chip manual says it's 0 and reserved). + */ + max9286_write(priv, 0x15, 0x80 | MAX9286_VCTYPE | + MAX9286_CSIOUTEN | MAX9286_0X15_RESV); + } else { + max9286_write(priv, 0x15, MAX9286_VCTYPE | MAX9286_0X15_RESV); + + /* Stop all cameras. */ + for_each_source(priv, source) + v4l2_subdev_call(source->sd, video, s_stream, 0); + + max9286_i2c_mux_close(priv); + } + + return 0; +} + +static int max9286_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_1X16; + + return 0; +} + +static struct v4l2_mbus_framefmt * +max9286_get_pad_format(struct max9286_priv *priv, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&priv->sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &priv->fmt[pad]; + default: + return NULL; + } +} + +static int max9286_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct max9286_priv *priv = sd_to_max9286(sd); + struct v4l2_mbus_framefmt *cfg_fmt; + + if (format->pad == MAX9286_SRC_PAD) + return -EINVAL; + + /* Refuse non YUV422 formats as we hardcode DT to 8 bit YUV422 */ + switch (format->format.code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_VYUY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_YVYU8_1X16: + break; + default: + format->format.code = MEDIA_BUS_FMT_UYVY8_1X16; + break; + } + + cfg_fmt = max9286_get_pad_format(priv, cfg, format->pad, format->which); + if (!cfg_fmt) + return -EINVAL; + + mutex_lock(&priv->mutex); + *cfg_fmt = format->format; + mutex_unlock(&priv->mutex); + + return 0; +} + +static int max9286_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct max9286_priv *priv = sd_to_max9286(sd); + struct v4l2_mbus_framefmt *cfg_fmt; + unsigned int pad = format->pad; + + /* + * Multiplexed Stream Support: Support link validation by returning the + * format of the first bound link. All links must have the same format, + * as we do not support mixing and matching of cameras connected to the + * max9286. + */ + if (pad == MAX9286_SRC_PAD) + pad = __ffs(priv->bound_sources); + + cfg_fmt = max9286_get_pad_format(priv, cfg, pad, format->which); + if (!cfg_fmt) + return -EINVAL; + + mutex_lock(&priv->mutex); + format->format = *cfg_fmt; + mutex_unlock(&priv->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops max9286_video_ops = { + .s_stream = max9286_s_stream, +}; + +static const struct v4l2_subdev_pad_ops max9286_pad_ops = { + .enum_mbus_code = max9286_enum_mbus_code, + .get_fmt = max9286_get_fmt, + .set_fmt = max9286_set_fmt, +}; + +static const struct v4l2_subdev_ops max9286_subdev_ops = { + .video = &max9286_video_ops, + .pad = &max9286_pad_ops, +}; + +static void max9286_init_format(struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = 1280; + fmt->height = 800; + fmt->code = MEDIA_BUS_FMT_UYVY8_1X16; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->field = V4L2_FIELD_NONE; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int max9286_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format; + unsigned int i; + + for (i = 0; i < MAX9286_N_SINKS; i++) { + format = v4l2_subdev_get_try_format(subdev, fh->pad, i); + max9286_init_format(format); + } + + return 0; +} + +static const struct v4l2_subdev_internal_ops max9286_subdev_internal_ops = { + .open = max9286_open, +}; + +static int max9286_s_ctrl(struct v4l2_ctrl *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_PIXEL_RATE: + return 0; + default: + return -EINVAL; + } +} + +static const struct v4l2_ctrl_ops max9286_ctrl_ops = { + .s_ctrl = max9286_s_ctrl, +}; + +static int max9286_v4l2_register(struct max9286_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct fwnode_handle *ep; + int ret; + int i; + + /* Register v4l2 async notifiers for connected Camera subdevices */ + ret = max9286_v4l2_notifier_register(priv); + if (ret) { + dev_err(dev, "Unable to register V4L2 async notifiers\n"); + return ret; + } + + /* Configure V4L2 for the MAX9286 itself */ + + for (i = 0; i < MAX9286_N_SINKS; i++) + max9286_init_format(&priv->fmt[i]); + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &max9286_subdev_ops); + priv->sd.internal_ops = &max9286_subdev_internal_ops; + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + v4l2_ctrl_handler_init(&priv->ctrls, 1); + priv->pixelrate = v4l2_ctrl_new_std(&priv->ctrls, + &max9286_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 1, INT_MAX, 1, 50000000); + + priv->sd.ctrl_handler = &priv->ctrls; + ret = priv->ctrls.error; + if (ret) + goto err_async; + + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + + priv->pads[MAX9286_SRC_PAD].flags = MEDIA_PAD_FL_SOURCE; + for (i = 0; i < MAX9286_SRC_PAD; i++) + priv->pads[i].flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&priv->sd.entity, MAX9286_N_PADS, + priv->pads); + if (ret) + goto err_async; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), MAX9286_SRC_PAD, + 0, 0); + if (!ep) { + dev_err(dev, "Unable to retrieve endpoint on \"port@4\"\n"); + ret = -ENOENT; + goto err_async; + } + priv->sd.fwnode = ep; + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret < 0) { + dev_err(dev, "Unable to register subdevice\n"); + goto err_put_node; + } + + return 0; + +err_put_node: + fwnode_handle_put(ep); +err_async: + v4l2_ctrl_handler_free(&priv->ctrls); + max9286_v4l2_notifier_unregister(priv); + + return ret; +} + +static void max9286_v4l2_unregister(struct max9286_priv *priv) +{ + fwnode_handle_put(priv->sd.fwnode); + v4l2_ctrl_handler_free(&priv->ctrls); + v4l2_async_unregister_subdev(&priv->sd); + max9286_v4l2_notifier_unregister(priv); +} + +/* ----------------------------------------------------------------------------- + * Probe/Remove + */ + +static int max9286_setup(struct max9286_priv *priv) +{ + /* + * Link ordering values for all enabled links combinations. Orders must + * be assigned sequentially from 0 to the number of enabled links + * without leaving any hole for disabled links. We thus assign orders to + * enabled links first, and use the remaining order values for disabled + * links are all links must have a different order value; + */ + static const u8 link_order[] = { + (3 << 6) | (2 << 4) | (1 << 2) | (0 << 0), /* xxxx */ + (3 << 6) | (2 << 4) | (1 << 2) | (0 << 0), /* xxx0 */ + (3 << 6) | (2 << 4) | (0 << 2) | (1 << 0), /* xx0x */ + (3 << 6) | (2 << 4) | (1 << 2) | (0 << 0), /* xx10 */ + (3 << 6) | (0 << 4) | (2 << 2) | (1 << 0), /* x0xx */ + (3 << 6) | (1 << 4) | (2 << 2) | (0 << 0), /* x1x0 */ + (3 << 6) | (1 << 4) | (0 << 2) | (2 << 0), /* x10x */ + (3 << 6) | (1 << 4) | (1 << 2) | (0 << 0), /* x210 */ + (0 << 6) | (3 << 4) | (2 << 2) | (1 << 0), /* 0xxx */ + (1 << 6) | (3 << 4) | (2 << 2) | (0 << 0), /* 1xx0 */ + (1 << 6) | (3 << 4) | (0 << 2) | (2 << 0), /* 1x0x */ + (2 << 6) | (3 << 4) | (1 << 2) | (0 << 0), /* 2x10 */ + (1 << 6) | (0 << 4) | (3 << 2) | (2 << 0), /* 10xx */ + (2 << 6) | (1 << 4) | (3 << 2) | (0 << 0), /* 21x0 */ + (2 << 6) | (1 << 4) | (0 << 2) | (3 << 0), /* 210x */ + (3 << 6) | (2 << 4) | (1 << 2) | (0 << 0), /* 3210 */ + }; + + /* + * Set the I2C bus speed. + * + * Enable I2C Local Acknowledge during the probe sequences of the camera + * only. This should be disabled after the mux is initialised. + */ + max9286_configure_i2c(priv, true); + + /* + * Reverse channel setup. + * + * - Enable custom reverse channel configuration (through register 0x3f) + * and set the first pulse length to 35 clock cycles. + * - Increase the reverse channel amplitude to 170mV to accommodate the + * high threshold enabled by the serializer driver. + */ + max9286_write(priv, 0x3f, MAX9286_EN_REV_CFG | MAX9286_REV_FLEN(35)); + max9286_write(priv, 0x3b, MAX9286_REV_TRF(1) | MAX9286_REV_AMP(70) | + MAX9286_REV_AMP_X); + usleep_range(2000, 2500); + + /* + * Enable GMSL links, mask unused ones and autodetect link + * used as CSI clock source. + */ + max9286_write(priv, 0x00, MAX9286_MSTLINKSEL_AUTO | priv->route_mask); + max9286_write(priv, 0x0b, link_order[priv->route_mask]); + max9286_write(priv, 0x69, (0xf & ~priv->route_mask)); + + /* + * Video format setup: + * Disable CSI output, VC is set according to Link number. + */ + max9286_write(priv, 0x15, MAX9286_VCTYPE | MAX9286_0X15_RESV); + + /* Enable CSI-2 Lane D0-D3 only, DBL mode, YUV422 8-bit. */ + max9286_write(priv, 0x12, MAX9286_CSIDBL | MAX9286_DBL | + MAX9286_CSILANECNT(priv->csi2_data_lanes) | + MAX9286_DATATYPE_YUV422_8BIT); + + /* Automatic: FRAMESYNC taken from the slowest Link. */ + max9286_write(priv, 0x01, MAX9286_FSYNCMODE_INT_HIZ | + MAX9286_FSYNCMETH_AUTO); + + /* Enable HS/VS encoding, use D14/15 for HS/VS, invert VS. */ + max9286_write(priv, 0x0c, MAX9286_HVEN | MAX9286_INVVS | + MAX9286_HVSRC_D14); + + /* + * The overlap window seems to provide additional validation by tracking + * the delay between vsync and frame sync, generating an error if the + * delay is bigger than the programmed window, though it's not yet clear + * what value should be set. + * + * As it's an optional value and can be disabled, we do so by setting + * a 0 overlap value. + */ + max9286_write(priv, 0x63, 0); + max9286_write(priv, 0x64, 0); + + /* + * Wait for 2ms to allow the link to resynchronize after the + * configuration change. + */ + usleep_range(2000, 5000); + + return 0; +} + +static void max9286_gpio_set(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct max9286_priv *priv = gpiochip_get_data(chip); + + if (value) + priv->gpio_state |= BIT(offset); + else + priv->gpio_state &= ~BIT(offset); + + max9286_write(priv, 0x0f, MAX9286_0X0F_RESERVED | priv->gpio_state); +} + +static int max9286_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct max9286_priv *priv = gpiochip_get_data(chip); + + return priv->gpio_state & BIT(offset); +} + +static int max9286_register_gpio(struct max9286_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct gpio_chip *gpio = &priv->gpio; + int ret; + + /* Configure the GPIO */ + gpio->label = dev_name(dev); + gpio->parent = dev; + gpio->owner = THIS_MODULE; + gpio->of_node = dev->of_node; + gpio->ngpio = 2; + gpio->base = -1; + gpio->set = max9286_gpio_set; + gpio->get = max9286_gpio_get; + gpio->can_sleep = true; + + /* GPIO values default to high */ + priv->gpio_state = BIT(0) | BIT(1); + + ret = devm_gpiochip_add_data(dev, gpio, priv); + if (ret) + dev_err(dev, "Unable to create gpio_chip\n"); + + return ret; +} + +static int max9286_init(struct device *dev) +{ + struct max9286_priv *priv; + struct i2c_client *client; + int ret; + + client = to_i2c_client(dev); + priv = i2c_get_clientdata(client); + + /* Enable the bus power. */ + ret = regulator_enable(priv->regulator); + if (ret < 0) { + dev_err(&client->dev, "Unable to turn PoC on\n"); + return ret; + } + + ret = max9286_setup(priv); + if (ret) { + dev_err(dev, "Unable to setup max9286\n"); + goto err_regulator; + } + + /* + * Register all V4L2 interactions for the MAX9286 and notifiers for + * any subdevices connected. + */ + ret = max9286_v4l2_register(priv); + if (ret) { + dev_err(dev, "Failed to register with V4L2\n"); + goto err_regulator; + } + + ret = max9286_i2c_mux_init(priv); + if (ret) { + dev_err(dev, "Unable to initialize I2C multiplexer\n"); + goto err_v4l2_register; + } + + /* Leave the mux channels disabled until they are selected. */ + max9286_i2c_mux_close(priv); + + return 0; + +err_v4l2_register: + max9286_v4l2_unregister(priv); +err_regulator: + regulator_disable(priv->regulator); + + return ret; +} + +static void max9286_cleanup_dt(struct max9286_priv *priv) +{ + struct max9286_source *source; + + for_each_source(priv, source) { + fwnode_handle_put(source->fwnode); + source->fwnode = NULL; + } +} + +static int max9286_parse_dt(struct max9286_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct device_node *i2c_mux; + struct device_node *node = NULL; + unsigned int i2c_mux_mask = 0; + + /* Balance the of_node_put() performed by of_find_node_by_name(). */ + of_node_get(dev->of_node); + i2c_mux = of_find_node_by_name(dev->of_node, "i2c-mux"); + if (!i2c_mux) { + dev_err(dev, "Failed to find i2c-mux node\n"); + return -EINVAL; + } + + /* Identify which i2c-mux channels are enabled */ + for_each_child_of_node(i2c_mux, node) { + u32 id = 0; + + of_property_read_u32(node, "reg", &id); + if (id >= MAX9286_NUM_GMSL) + continue; + + if (!of_device_is_available(node)) { + dev_dbg(dev, "Skipping disabled I2C bus port %u\n", id); + continue; + } + + i2c_mux_mask |= BIT(id); + } + of_node_put(i2c_mux); + + /* Parse the endpoints */ + for_each_endpoint_of_node(dev->of_node, node) { + struct max9286_source *source; + struct of_endpoint ep; + + of_graph_parse_endpoint(node, &ep); + dev_dbg(dev, "Endpoint %pOF on port %d", + ep.local_node, ep.port); + + if (ep.port > MAX9286_NUM_GMSL) { + dev_err(dev, "Invalid endpoint %s on port %d", + of_node_full_name(ep.local_node), ep.port); + continue; + } + + /* For the source endpoint just parse the bus configuration. */ + if (ep.port == MAX9286_SRC_PAD) { + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret; + + ret = v4l2_fwnode_endpoint_parse( + of_fwnode_handle(node), &vep); + if (ret) { + of_node_put(node); + return ret; + } + + priv->csi2_data_lanes = + vep.bus.mipi_csi2.num_data_lanes; + + continue; + } + + /* Skip if the corresponding GMSL link is unavailable. */ + if (!(i2c_mux_mask & BIT(ep.port))) + continue; + + if (priv->sources[ep.port].fwnode) { + dev_err(dev, + "Multiple port endpoints are not supported: %d", + ep.port); + + continue; + } + + source = &priv->sources[ep.port]; + source->fwnode = fwnode_graph_get_remote_endpoint( + of_fwnode_handle(node)); + if (!source->fwnode) { + dev_err(dev, + "Endpoint %pOF has no remote endpoint connection\n", + ep.local_node); + + continue; + } + + priv->source_mask |= BIT(ep.port); + priv->nsources++; + } + + priv->route_mask = priv->source_mask; + + return 0; +} + +static int max9286_probe(struct i2c_client *client) +{ + struct max9286_priv *priv; + int ret; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->mutex); + + priv->client = client; + i2c_set_clientdata(client, priv); + + priv->gpiod_pwdn = devm_gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_pwdn)) + return PTR_ERR(priv->gpiod_pwdn); + + gpiod_set_consumer_name(priv->gpiod_pwdn, "max9286-pwdn"); + gpiod_set_value_cansleep(priv->gpiod_pwdn, 1); + + /* Wait at least 4ms before the I2C lines latch to the address */ + if (priv->gpiod_pwdn) + usleep_range(4000, 5000); + + /* + * The MAX9286 starts by default with all ports enabled, we disable all + * ports early to ensure that all channels are disabled if we error out + * and keep the bus consistent. + */ + max9286_i2c_mux_close(priv); + + /* + * The MAX9286 initialises with auto-acknowledge enabled by default. + * This can be invasive to other transactions on the same bus, so + * disable it early. It will be enabled only as and when needed. + */ + max9286_configure_i2c(priv, false); + + ret = max9286_register_gpio(priv); + if (ret) + goto err_powerdown; + + priv->regulator = devm_regulator_get(&client->dev, "poc"); + if (IS_ERR(priv->regulator)) { + if (PTR_ERR(priv->regulator) != -EPROBE_DEFER) + dev_err(&client->dev, + "Unable to get PoC regulator (%ld)\n", + PTR_ERR(priv->regulator)); + ret = PTR_ERR(priv->regulator); + goto err_powerdown; + } + + ret = max9286_parse_dt(priv); + if (ret) + goto err_powerdown; + + ret = max9286_init(&client->dev); + if (ret < 0) + goto err_cleanup_dt; + + return 0; + +err_cleanup_dt: + max9286_cleanup_dt(priv); +err_powerdown: + gpiod_set_value_cansleep(priv->gpiod_pwdn, 0); + + return ret; +} + +static int max9286_remove(struct i2c_client *client) +{ + struct max9286_priv *priv = i2c_get_clientdata(client); + + i2c_mux_del_adapters(priv->mux); + + max9286_v4l2_unregister(priv); + + regulator_disable(priv->regulator); + + gpiod_set_value_cansleep(priv->gpiod_pwdn, 0); + + max9286_cleanup_dt(priv); + + return 0; +} + +static const struct of_device_id max9286_dt_ids[] = { + { .compatible = "maxim,max9286" }, + {}, +}; +MODULE_DEVICE_TABLE(of, max9286_dt_ids); + +static struct i2c_driver max9286_i2c_driver = { + .driver = { + .name = "max9286", + .of_match_table = of_match_ptr(max9286_dt_ids), + }, + .probe_new = max9286_probe, + .remove = max9286_remove, +}; + +module_i2c_driver(max9286_i2c_driver); + +MODULE_DESCRIPTION("Maxim MAX9286 GMSL Deserializer Driver"); +MODULE_AUTHOR("Jacopo Mondi, Kieran Bingham, Laurent Pinchart, Niklas Söderlund, Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ml86v7667.c b/drivers/media/i2c/ml86v7667.c new file mode 100644 index 000000000..ff2123353 --- /dev/null +++ b/drivers/media/i2c/ml86v7667.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OKI Semiconductor ML86V7667 video decoder driver + * + * Author: Vladimir Barinov <source@cogentembedded.com> + * Copyright (C) 2013 Cogent Embedded, Inc. + * Copyright (C) 2013 Renesas Solutions Corp. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> + +#define DRV_NAME "ml86v7667" + +/* Subaddresses */ +#define MRA_REG 0x00 /* Mode Register A */ +#define MRC_REG 0x02 /* Mode Register C */ +#define LUMC_REG 0x0C /* Luminance Control */ +#define CLC_REG 0x10 /* Contrast level control */ +#define SSEPL_REG 0x11 /* Sync separation level */ +#define CHRCA_REG 0x12 /* Chrominance Control A */ +#define ACCC_REG 0x14 /* ACC Loop filter & Chrominance control */ +#define ACCRC_REG 0x15 /* ACC Reference level control */ +#define HUE_REG 0x16 /* Hue control */ +#define ADC2_REG 0x1F /* ADC Register 2 */ +#define PLLR1_REG 0x20 /* PLL Register 1 */ +#define STATUS_REG 0x2C /* STATUS Register */ + +/* Mode Register A register bits */ +#define MRA_OUTPUT_MODE_MASK (3 << 6) +#define MRA_ITUR_BT601 (1 << 6) +#define MRA_ITUR_BT656 (0 << 6) +#define MRA_INPUT_MODE_MASK (7 << 3) +#define MRA_PAL_BT601 (4 << 3) +#define MRA_NTSC_BT601 (0 << 3) +#define MRA_REGISTER_MODE (1 << 0) + +/* Mode Register C register bits */ +#define MRC_AUTOSELECT (1 << 7) + +/* Luminance Control register bits */ +#define LUMC_ONOFF_SHIFT 7 +#define LUMC_ONOFF_MASK (1 << 7) + +/* Contrast level control register bits */ +#define CLC_CONTRAST_ONOFF (1 << 7) +#define CLC_CONTRAST_MASK 0x0F + +/* Sync separation level register bits */ +#define SSEPL_LUMINANCE_ONOFF (1 << 7) +#define SSEPL_LUMINANCE_MASK 0x7F + +/* Chrominance Control A register bits */ +#define CHRCA_MODE_SHIFT 6 +#define CHRCA_MODE_MASK (1 << 6) + +/* ACC Loop filter & Chrominance control register bits */ +#define ACCC_CHROMA_CR_SHIFT 3 +#define ACCC_CHROMA_CR_MASK (7 << 3) +#define ACCC_CHROMA_CB_SHIFT 0 +#define ACCC_CHROMA_CB_MASK (7 << 0) + +/* ACC Reference level control register bits */ +#define ACCRC_CHROMA_MASK 0xfc +#define ACCRC_CHROMA_SHIFT 2 + +/* ADC Register 2 register bits */ +#define ADC2_CLAMP_VOLTAGE_MASK (7 << 1) +#define ADC2_CLAMP_VOLTAGE(n) ((n & 7) << 1) + +/* PLL Register 1 register bits */ +#define PLLR1_FIXED_CLOCK (1 << 7) + +/* STATUS Register register bits */ +#define STATUS_HLOCK_DETECT (1 << 3) +#define STATUS_NTSCPAL (1 << 2) + +struct ml86v7667_priv { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + v4l2_std_id std; +}; + +static inline struct ml86v7667_priv *to_ml86v7667(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct ml86v7667_priv, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ml86v7667_priv, hdl)->sd; +} + +static int ml86v7667_mask_set(struct i2c_client *client, const u8 reg, + const u8 mask, const u8 data) +{ + int val = i2c_smbus_read_byte_data(client, reg); + if (val < 0) + return val; + + val = (val & ~mask) | (data & mask); + return i2c_smbus_write_byte_data(client, reg, val); +} + +static int ml86v7667_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = -EINVAL; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ret = ml86v7667_mask_set(client, SSEPL_REG, + SSEPL_LUMINANCE_MASK, ctrl->val); + break; + case V4L2_CID_CONTRAST: + ret = ml86v7667_mask_set(client, CLC_REG, + CLC_CONTRAST_MASK, ctrl->val); + break; + case V4L2_CID_CHROMA_GAIN: + ret = ml86v7667_mask_set(client, ACCRC_REG, ACCRC_CHROMA_MASK, + ctrl->val << ACCRC_CHROMA_SHIFT); + break; + case V4L2_CID_HUE: + ret = ml86v7667_mask_set(client, HUE_REG, ~0, ctrl->val); + break; + case V4L2_CID_RED_BALANCE: + ret = ml86v7667_mask_set(client, ACCC_REG, + ACCC_CHROMA_CR_MASK, + ctrl->val << ACCC_CHROMA_CR_SHIFT); + break; + case V4L2_CID_BLUE_BALANCE: + ret = ml86v7667_mask_set(client, ACCC_REG, + ACCC_CHROMA_CB_MASK, + ctrl->val << ACCC_CHROMA_CB_SHIFT); + break; + case V4L2_CID_SHARPNESS: + ret = ml86v7667_mask_set(client, LUMC_REG, + LUMC_ONOFF_MASK, + ctrl->val << LUMC_ONOFF_SHIFT); + break; + case V4L2_CID_COLOR_KILLER: + ret = ml86v7667_mask_set(client, CHRCA_REG, + CHRCA_MODE_MASK, + ctrl->val << CHRCA_MODE_SHIFT); + break; + } + + return ret; +} + +static int ml86v7667_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int status; + + status = i2c_smbus_read_byte_data(client, STATUS_REG); + if (status < 0) + return status; + + if (status & STATUS_HLOCK_DETECT) + *std &= status & STATUS_NTSCPAL ? V4L2_STD_625_50 : V4L2_STD_525_60; + else + *std = V4L2_STD_UNKNOWN; + + return 0; +} + +static int ml86v7667_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int status_reg; + + status_reg = i2c_smbus_read_byte_data(client, STATUS_REG); + if (status_reg < 0) + return status_reg; + + *status = status_reg & STATUS_HLOCK_DETECT ? 0 : V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +static int ml86v7667_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_YUYV8_2X8; + + return 0; +} + +static int ml86v7667_fill_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ml86v7667_priv *priv = to_ml86v7667(sd); + struct v4l2_mbus_framefmt *fmt = &format->format; + + if (format->pad) + return -EINVAL; + + fmt->code = MEDIA_BUS_FMT_YUYV8_2X8; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + /* The top field is always transferred first by the chip */ + fmt->field = V4L2_FIELD_INTERLACED_TB; + fmt->width = 720; + fmt->height = priv->std & V4L2_STD_525_60 ? 480 : 576; + + return 0; +} + +static int ml86v7667_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + cfg->flags = V4L2_MBUS_MASTER | V4L2_MBUS_PCLK_SAMPLE_RISING | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_BT656; + + return 0; +} + +static int ml86v7667_g_std(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct ml86v7667_priv *priv = to_ml86v7667(sd); + + *std = priv->std; + + return 0; +} + +static int ml86v7667_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct ml86v7667_priv *priv = to_ml86v7667(sd); + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + int ret; + u8 mode; + + /* PAL/NTSC ITU-R BT.601 input mode */ + mode = std & V4L2_STD_525_60 ? MRA_NTSC_BT601 : MRA_PAL_BT601; + ret = ml86v7667_mask_set(client, MRA_REG, MRA_INPUT_MODE_MASK, mode); + if (ret < 0) + return ret; + + priv->std = std; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ml86v7667_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + ret = i2c_smbus_read_byte_data(client, (u8)reg->reg); + if (ret < 0) + return ret; + + reg->val = ret; + reg->size = sizeof(u8); + + return 0; +} + +static int ml86v7667_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, (u8)reg->reg, (u8)reg->val); +} +#endif + +static const struct v4l2_ctrl_ops ml86v7667_ctrl_ops = { + .s_ctrl = ml86v7667_s_ctrl, +}; + +static const struct v4l2_subdev_video_ops ml86v7667_subdev_video_ops = { + .g_std = ml86v7667_g_std, + .s_std = ml86v7667_s_std, + .querystd = ml86v7667_querystd, + .g_input_status = ml86v7667_g_input_status, +}; + +static const struct v4l2_subdev_pad_ops ml86v7667_subdev_pad_ops = { + .enum_mbus_code = ml86v7667_enum_mbus_code, + .get_fmt = ml86v7667_fill_fmt, + .set_fmt = ml86v7667_fill_fmt, + .get_mbus_config = ml86v7667_get_mbus_config, +}; + +static const struct v4l2_subdev_core_ops ml86v7667_subdev_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ml86v7667_g_register, + .s_register = ml86v7667_s_register, +#endif +}; + +static const struct v4l2_subdev_ops ml86v7667_subdev_ops = { + .core = &ml86v7667_subdev_core_ops, + .video = &ml86v7667_subdev_video_ops, + .pad = &ml86v7667_subdev_pad_ops, +}; + +static int ml86v7667_init(struct ml86v7667_priv *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + int val; + int ret; + + /* BT.656-4 output mode, register mode */ + ret = ml86v7667_mask_set(client, MRA_REG, + MRA_OUTPUT_MODE_MASK | MRA_REGISTER_MODE, + MRA_ITUR_BT656 | MRA_REGISTER_MODE); + + /* PLL circuit fixed clock, 32MHz */ + ret |= ml86v7667_mask_set(client, PLLR1_REG, PLLR1_FIXED_CLOCK, + PLLR1_FIXED_CLOCK); + + /* ADC2 clamping voltage maximum */ + ret |= ml86v7667_mask_set(client, ADC2_REG, ADC2_CLAMP_VOLTAGE_MASK, + ADC2_CLAMP_VOLTAGE(7)); + + /* enable luminance function */ + ret |= ml86v7667_mask_set(client, SSEPL_REG, SSEPL_LUMINANCE_ONOFF, + SSEPL_LUMINANCE_ONOFF); + + /* enable contrast function */ + ret |= ml86v7667_mask_set(client, CLC_REG, CLC_CONTRAST_ONOFF, 0); + + /* + * PAL/NTSC autodetection is enabled after reset, + * set the autodetected std in manual std mode and + * disable autodetection + */ + val = i2c_smbus_read_byte_data(client, STATUS_REG); + if (val < 0) + return val; + + priv->std = val & STATUS_NTSCPAL ? V4L2_STD_625_50 : V4L2_STD_525_60; + ret |= ml86v7667_mask_set(client, MRC_REG, MRC_AUTOSELECT, 0); + + val = priv->std & V4L2_STD_525_60 ? MRA_NTSC_BT601 : MRA_PAL_BT601; + ret |= ml86v7667_mask_set(client, MRA_REG, MRA_INPUT_MODE_MASK, val); + + return ret; +} + +static int ml86v7667_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ml86v7667_priv *priv; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + v4l2_i2c_subdev_init(&priv->sd, client, &ml86v7667_subdev_ops); + + v4l2_ctrl_handler_init(&priv->hdl, 8); + v4l2_ctrl_new_std(&priv->hdl, &ml86v7667_ctrl_ops, + V4L2_CID_BRIGHTNESS, -64, 63, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ml86v7667_ctrl_ops, + V4L2_CID_CONTRAST, -8, 7, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ml86v7667_ctrl_ops, + V4L2_CID_CHROMA_GAIN, -32, 31, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ml86v7667_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ml86v7667_ctrl_ops, + V4L2_CID_RED_BALANCE, -4, 3, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ml86v7667_ctrl_ops, + V4L2_CID_BLUE_BALANCE, -4, 3, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ml86v7667_ctrl_ops, + V4L2_CID_SHARPNESS, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ml86v7667_ctrl_ops, + V4L2_CID_COLOR_KILLER, 0, 1, 1, 0); + priv->sd.ctrl_handler = &priv->hdl; + + ret = priv->hdl.error; + if (ret) + goto cleanup; + + v4l2_ctrl_handler_setup(&priv->hdl); + + ret = ml86v7667_init(priv); + if (ret) + goto cleanup; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr, client->adapter->name); + return 0; + +cleanup: + v4l2_ctrl_handler_free(&priv->hdl); + v4l2_device_unregister_subdev(&priv->sd); + v4l_err(client, "failed to probe @ 0x%02x (%s)\n", + client->addr, client->adapter->name); + return ret; +} + +static int ml86v7667_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ml86v7667_priv *priv = to_ml86v7667(sd); + + v4l2_ctrl_handler_free(&priv->hdl); + v4l2_device_unregister_subdev(&priv->sd); + + return 0; +} + +static const struct i2c_device_id ml86v7667_id[] = { + {DRV_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ml86v7667_id); + +static struct i2c_driver ml86v7667_i2c_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = ml86v7667_probe, + .remove = ml86v7667_remove, + .id_table = ml86v7667_id, +}; + +module_i2c_driver(ml86v7667_i2c_driver); + +MODULE_DESCRIPTION("OKI Semiconductor ML86V7667 video decoder driver"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/msp3400-driver.c b/drivers/media/i2c/msp3400-driver.c new file mode 100644 index 000000000..39530d435 --- /dev/null +++ b/drivers/media/i2c/msp3400-driver.c @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Programming the mspx4xx sound processor family + * + * (c) 1997-2001 Gerd Knorr <kraxel@bytesex.org> + * + * what works and what doesn't: + * + * AM-Mono + * Support for Hauppauge cards added (decoding handled by tuner) added by + * Frederic Crozat <fcrozat@mail.dotcom.fr> + * + * FM-Mono + * should work. The stereo modes are backward compatible to FM-mono, + * therefore FM-Mono should be always available. + * + * FM-Stereo (B/G, used in germany) + * should work, with autodetect + * + * FM-Stereo (satellite) + * should work, no autodetect (i.e. default is mono, but you can + * switch to stereo -- untested) + * + * NICAM (B/G, L , used in UK, Scandinavia, Spain and France) + * should work, with autodetect. Support for NICAM was added by + * Pekka Pietikainen <pp@netppl.fi> + * + * TODO: + * - better SAT support + * + * 980623 Thomas Sailer (sailer@ife.ee.ethz.ch) + * using soundcore instead of OSS + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/drv-intf/msp3400.h> +#include <media/i2c/tvaudio.h> +#include "msp3400-driver.h" + +/* ---------------------------------------------------------------------- */ + +MODULE_DESCRIPTION("device driver for msp34xx TV sound processor"); +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("GPL"); + +/* module parameters */ +static int opmode = OPMODE_AUTO; +int msp_debug; /* msp_debug output */ +bool msp_once; /* no continuous stereo monitoring */ +bool msp_amsound; /* hard-wire AM sound at 6.5 Hz (france), + the autoscan seems work well only with FM... */ +int msp_standard = 1; /* Override auto detect of audio msp_standard, + if needed. */ +bool msp_dolby; + +int msp_stereo_thresh = 0x190; /* a2 threshold for stereo/bilingual + (msp34xxg only) 0x00a0-0x03c0 */ + +/* read-only */ +module_param(opmode, int, 0444); + +/* read-write */ +module_param_named(once, msp_once, bool, 0644); +module_param_named(debug, msp_debug, int, 0644); +module_param_named(stereo_threshold, msp_stereo_thresh, int, 0644); +module_param_named(standard, msp_standard, int, 0644); +module_param_named(amsound, msp_amsound, bool, 0644); +module_param_named(dolby, msp_dolby, bool, 0644); + +MODULE_PARM_DESC(opmode, "Forces a MSP3400 opmode. 0=Manual, 1=Autodetect, 2=Autodetect and autoselect"); +MODULE_PARM_DESC(once, "No continuous stereo monitoring"); +MODULE_PARM_DESC(debug, "Enable debug messages [0-3]"); +MODULE_PARM_DESC(stereo_threshold, "Sets signal threshold to activate stereo"); +MODULE_PARM_DESC(standard, "Specify audio standard: 32 = NTSC, 64 = radio, Default: Autodetect"); +MODULE_PARM_DESC(amsound, "Hardwire AM sound at 6.5Hz (France), FM can autoscan"); +MODULE_PARM_DESC(dolby, "Activates Dolby processing"); + +/* ---------------------------------------------------------------------- */ + +/* control subaddress */ +#define I2C_MSP_CONTROL 0x00 +/* demodulator unit subaddress */ +#define I2C_MSP_DEM 0x10 +/* DSP unit subaddress */ +#define I2C_MSP_DSP 0x12 + + +/* ----------------------------------------------------------------------- */ +/* functions for talking to the MSP3400C Sound processor */ + +int msp_reset(struct i2c_client *client) +{ + /* reset and read revision code */ + static u8 reset_off[3] = { I2C_MSP_CONTROL, 0x80, 0x00 }; + static u8 reset_on[3] = { I2C_MSP_CONTROL, 0x00, 0x00 }; + static u8 write[3] = { I2C_MSP_DSP + 1, 0x00, 0x1e }; + u8 read[2]; + struct i2c_msg reset[2] = { + { + .addr = client->addr, + .flags = I2C_M_IGNORE_NAK, + .len = 3, + .buf = reset_off + }, + { + .addr = client->addr, + .flags = I2C_M_IGNORE_NAK, + .len = 3, + .buf = reset_on + }, + }; + struct i2c_msg test[2] = { + { + .addr = client->addr, + .len = 3, + .buf = write + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 2, + .buf = read + }, + }; + + dev_dbg_lvl(&client->dev, 3, msp_debug, "msp_reset\n"); + if (i2c_transfer(client->adapter, &reset[0], 1) != 1 || + i2c_transfer(client->adapter, &reset[1], 1) != 1 || + i2c_transfer(client->adapter, test, 2) != 2) { + dev_err(&client->dev, "chip reset failed\n"); + return -1; + } + return 0; +} + +static int msp_read(struct i2c_client *client, int dev, int addr) +{ + int err, retval; + u8 write[3]; + u8 read[2]; + struct i2c_msg msgs[2] = { + { + .addr = client->addr, + .len = 3, + .buf = write + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 2, + .buf = read + } + }; + + write[0] = dev + 1; + write[1] = addr >> 8; + write[2] = addr & 0xff; + + for (err = 0; err < 3; err++) { + if (i2c_transfer(client->adapter, msgs, 2) == 2) + break; + dev_warn(&client->dev, "I/O error #%d (read 0x%02x/0x%02x)\n", err, + dev, addr); + schedule_timeout_interruptible(msecs_to_jiffies(10)); + } + if (err == 3) { + dev_warn(&client->dev, "resetting chip, sound will go off.\n"); + msp_reset(client); + return -1; + } + retval = read[0] << 8 | read[1]; + dev_dbg_lvl(&client->dev, 3, msp_debug, "msp_read(0x%x, 0x%x): 0x%x\n", + dev, addr, retval); + return retval; +} + +int msp_read_dem(struct i2c_client *client, int addr) +{ + return msp_read(client, I2C_MSP_DEM, addr); +} + +int msp_read_dsp(struct i2c_client *client, int addr) +{ + return msp_read(client, I2C_MSP_DSP, addr); +} + +static int msp_write(struct i2c_client *client, int dev, int addr, int val) +{ + int err; + u8 buffer[5]; + + buffer[0] = dev; + buffer[1] = addr >> 8; + buffer[2] = addr & 0xff; + buffer[3] = val >> 8; + buffer[4] = val & 0xff; + + dev_dbg_lvl(&client->dev, 3, msp_debug, "msp_write(0x%x, 0x%x, 0x%x)\n", + dev, addr, val); + for (err = 0; err < 3; err++) { + if (i2c_master_send(client, buffer, 5) == 5) + break; + dev_warn(&client->dev, "I/O error #%d (write 0x%02x/0x%02x)\n", err, + dev, addr); + schedule_timeout_interruptible(msecs_to_jiffies(10)); + } + if (err == 3) { + dev_warn(&client->dev, "resetting chip, sound will go off.\n"); + msp_reset(client); + return -1; + } + return 0; +} + +int msp_write_dem(struct i2c_client *client, int addr, int val) +{ + return msp_write(client, I2C_MSP_DEM, addr, val); +} + +int msp_write_dsp(struct i2c_client *client, int addr, int val) +{ + return msp_write(client, I2C_MSP_DSP, addr, val); +} + +/* ----------------------------------------------------------------------- * + * bits 9 8 5 - SCART DSP input Select: + * 0 0 0 - SCART 1 to DSP input (reset position) + * 0 1 0 - MONO to DSP input + * 1 0 0 - SCART 2 to DSP input + * 1 1 1 - Mute DSP input + * + * bits 11 10 6 - SCART 1 Output Select: + * 0 0 0 - undefined (reset position) + * 0 1 0 - SCART 2 Input to SCART 1 Output (for devices with 2 SCARTS) + * 1 0 0 - MONO input to SCART 1 Output + * 1 1 0 - SCART 1 DA to SCART 1 Output + * 0 0 1 - SCART 2 DA to SCART 1 Output + * 0 1 1 - SCART 1 Input to SCART 1 Output + * 1 1 1 - Mute SCART 1 Output + * + * bits 13 12 7 - SCART 2 Output Select (for devices with 2 Output SCART): + * 0 0 0 - SCART 1 DA to SCART 2 Output (reset position) + * 0 1 0 - SCART 1 Input to SCART 2 Output + * 1 0 0 - MONO input to SCART 2 Output + * 0 0 1 - SCART 2 DA to SCART 2 Output + * 0 1 1 - SCART 2 Input to SCART 2 Output + * 1 1 0 - Mute SCART 2 Output + * + * Bits 4 to 0 should be zero. + * ----------------------------------------------------------------------- */ + +static int scarts[3][9] = { + /* MASK IN1 IN2 IN3 IN4 IN1_DA IN2_DA MONO MUTE */ + /* SCART DSP Input select */ + { 0x0320, 0x0000, 0x0200, 0x0300, 0x0020, -1, -1, 0x0100, 0x0320 }, + /* SCART1 Output select */ + { 0x0c40, 0x0440, 0x0400, 0x0000, 0x0840, 0x0c00, 0x0040, 0x0800, 0x0c40 }, + /* SCART2 Output select */ + { 0x3080, 0x1000, 0x1080, 0x2080, 0x3080, 0x0000, 0x0080, 0x2000, 0x3000 }, +}; + +static char *scart_names[] = { + "in1", "in2", "in3", "in4", "in1 da", "in2 da", "mono", "mute" +}; + +void msp_set_scart(struct i2c_client *client, int in, int out) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + state->in_scart = in; + + if (in >= 0 && in <= 7 && out >= 0 && out <= 2) { + if (-1 == scarts[out][in + 1]) + return; + + state->acb &= ~scarts[out][0]; + state->acb |= scarts[out][in + 1]; + } else + state->acb = 0xf60; /* Mute Input and SCART 1 Output */ + + dev_dbg_lvl(&client->dev, 1, msp_debug, "scart switch: %s => %d (ACB=0x%04x)\n", + scart_names[in], out, state->acb); + msp_write_dsp(client, 0x13, state->acb); + + /* Sets I2S speed 0 = 1.024 Mbps, 1 = 2.048 Mbps */ + if (state->has_i2s_conf) + msp_write_dem(client, 0x40, state->i2s_mode); +} + +/* ------------------------------------------------------------------------ */ + +static void msp_wake_thread(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + if (NULL == state->kthread) + return; + state->watch_stereo = 0; + state->restart = 1; + wake_up_interruptible(&state->wq); +} + +int msp_sleep(struct msp_state *state, int timeout) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&state->wq, &wait); + if (!kthread_should_stop()) { + if (timeout < 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } else { + schedule_timeout_interruptible + (msecs_to_jiffies(timeout)); + } + } + + remove_wait_queue(&state->wq, &wait); + try_to_freeze(); + return state->restart; +} + +/* ------------------------------------------------------------------------ */ + +static int msp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct msp_state *state = ctrl_to_state(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(&state->sd); + int val = ctrl->val; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: { + /* audio volume cluster */ + int reallymuted = state->muted->val | state->scan_in_progress; + + if (!reallymuted) + val = (val * 0x7f / 65535) << 8; + + dev_dbg_lvl(&client->dev, 1, msp_debug, "mute=%s scanning=%s volume=%d\n", + state->muted->val ? "on" : "off", + state->scan_in_progress ? "yes" : "no", + state->volume->val); + + msp_write_dsp(client, 0x0000, val); + msp_write_dsp(client, 0x0007, reallymuted ? 0x1 : (val | 0x1)); + if (state->has_scart2_out_volume) + msp_write_dsp(client, 0x0040, reallymuted ? 0x1 : (val | 0x1)); + if (state->has_headphones) + msp_write_dsp(client, 0x0006, val); + break; + } + + case V4L2_CID_AUDIO_BASS: + val = ((val - 32768) * 0x60 / 65535) << 8; + msp_write_dsp(client, 0x0002, val); + if (state->has_headphones) + msp_write_dsp(client, 0x0031, val); + break; + + case V4L2_CID_AUDIO_TREBLE: + val = ((val - 32768) * 0x60 / 65535) << 8; + msp_write_dsp(client, 0x0003, val); + if (state->has_headphones) + msp_write_dsp(client, 0x0032, val); + break; + + case V4L2_CID_AUDIO_LOUDNESS: + val = val ? ((5 * 4) << 8) : 0; + msp_write_dsp(client, 0x0004, val); + if (state->has_headphones) + msp_write_dsp(client, 0x0033, val); + break; + + case V4L2_CID_AUDIO_BALANCE: + val = (u8)((val / 256) - 128); + msp_write_dsp(client, 0x0001, val << 8); + if (state->has_headphones) + msp_write_dsp(client, 0x0030, val << 8); + break; + + default: + return -EINVAL; + } + return 0; +} + +void msp_update_volume(struct msp_state *state) +{ + /* Force an update of the volume/mute cluster */ + v4l2_ctrl_lock(state->volume); + state->volume->val = state->volume->cur.val; + state->muted->val = state->muted->cur.val; + msp_s_ctrl(state->volume); + v4l2_ctrl_unlock(state->volume); +} + +/* --- v4l2 ioctls --- */ +static int msp_s_radio(struct v4l2_subdev *sd) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (state->radio) + return 0; + state->radio = 1; + dev_dbg_lvl(&client->dev, 1, msp_debug, "switching to radio mode\n"); + state->watch_stereo = 0; + switch (state->opmode) { + case OPMODE_MANUAL: + /* set msp3400 to FM radio mode */ + msp3400c_set_mode(client, MSP_MODE_FM_RADIO); + msp3400c_set_carrier(client, MSP_CARRIER(10.7), + MSP_CARRIER(10.7)); + msp_update_volume(state); + break; + case OPMODE_AUTODETECT: + case OPMODE_AUTOSELECT: + /* the thread will do for us */ + msp_wake_thread(client); + break; + } + return 0; +} + +static int msp_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *freq) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* new channel -- kick audio carrier scan */ + msp_wake_thread(client); + return 0; +} + +static int msp_querystd(struct v4l2_subdev *sd, v4l2_std_id *id) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + *id &= state->detected_std; + + dev_dbg_lvl(&client->dev, 2, msp_debug, + "detected standard: %s(0x%08Lx)\n", + msp_standard_std_name(state->std), state->detected_std); + + return 0; +} + +static int msp_s_std(struct v4l2_subdev *sd, v4l2_std_id id) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int update = state->radio || state->v4l2_std != id; + + state->v4l2_std = id; + state->radio = 0; + if (update) + msp_wake_thread(client); + return 0; +} + +static int msp_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int tuner = (input >> 3) & 1; + int sc_in = input & 0x7; + int sc1_out = output & 0xf; + int sc2_out = (output >> 4) & 0xf; + u16 val, reg; + int i; + int extern_input = 1; + + if (state->route_in == input && state->route_out == output) + return 0; + state->route_in = input; + state->route_out = output; + /* check if the tuner input is used */ + for (i = 0; i < 5; i++) { + if (((input >> (4 + i * 4)) & 0xf) == 0) + extern_input = 0; + } + state->mode = extern_input ? MSP_MODE_EXTERN : MSP_MODE_AM_DETECT; + state->rxsubchans = V4L2_TUNER_SUB_STEREO; + msp_set_scart(client, sc_in, 0); + msp_set_scart(client, sc1_out, 1); + msp_set_scart(client, sc2_out, 2); + msp_set_audmode(client); + reg = (state->opmode == OPMODE_AUTOSELECT) ? 0x30 : 0xbb; + val = msp_read_dem(client, reg); + msp_write_dem(client, reg, (val & ~0x100) | (tuner << 8)); + /* wake thread when a new input is chosen */ + msp_wake_thread(client); + return 0; +} + +static int msp_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (vt->type != V4L2_TUNER_ANALOG_TV) + return 0; + if (!state->radio) { + if (state->opmode == OPMODE_AUTOSELECT) + msp_detect_stereo(client); + vt->rxsubchans = state->rxsubchans; + } + vt->audmode = state->audmode; + vt->capability |= V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; + return 0; +} + +static int msp_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (state->radio) /* TODO: add mono/stereo support for radio */ + return 0; + if (state->audmode == vt->audmode) + return 0; + state->audmode = vt->audmode; + /* only set audmode */ + msp_set_audmode(client); + return 0; +} + +static int msp_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + dev_dbg_lvl(&client->dev, 1, msp_debug, "Setting I2S speed to %d\n", freq); + + switch (freq) { + case 1024000: + state->i2s_mode = 0; + break; + case 2048000: + state->i2s_mode = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static int msp_log_status(struct v4l2_subdev *sd) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + const char *p; + char prefix[V4L2_SUBDEV_NAME_SIZE + 20]; + + if (state->opmode == OPMODE_AUTOSELECT) + msp_detect_stereo(client); + dev_info(&client->dev, "%s rev1 = 0x%04x rev2 = 0x%04x\n", + client->name, state->rev1, state->rev2); + snprintf(prefix, sizeof(prefix), "%s: Audio: ", sd->name); + v4l2_ctrl_handler_log_status(&state->hdl, prefix); + switch (state->mode) { + case MSP_MODE_AM_DETECT: p = "AM (for carrier detect)"; break; + case MSP_MODE_FM_RADIO: p = "FM Radio"; break; + case MSP_MODE_FM_TERRA: p = "Terrestrial FM-mono/stereo"; break; + case MSP_MODE_FM_SAT: p = "Satellite FM-mono"; break; + case MSP_MODE_FM_NICAM1: p = "NICAM/FM (B/G, D/K)"; break; + case MSP_MODE_FM_NICAM2: p = "NICAM/FM (I)"; break; + case MSP_MODE_AM_NICAM: p = "NICAM/AM (L)"; break; + case MSP_MODE_BTSC: p = "BTSC"; break; + case MSP_MODE_EXTERN: p = "External input"; break; + default: p = "unknown"; break; + } + if (state->mode == MSP_MODE_EXTERN) { + dev_info(&client->dev, "Mode: %s\n", p); + } else if (state->opmode == OPMODE_MANUAL) { + dev_info(&client->dev, "Mode: %s (%s%s)\n", p, + (state->rxsubchans & V4L2_TUNER_SUB_STEREO) ? "stereo" : "mono", + (state->rxsubchans & V4L2_TUNER_SUB_LANG2) ? ", dual" : ""); + } else { + if (state->opmode == OPMODE_AUTODETECT) + dev_info(&client->dev, "Mode: %s\n", p); + dev_info(&client->dev, "Standard: %s (%s%s)\n", + msp_standard_std_name(state->std), + (state->rxsubchans & V4L2_TUNER_SUB_STEREO) ? "stereo" : "mono", + (state->rxsubchans & V4L2_TUNER_SUB_LANG2) ? ", dual" : ""); + } + dev_info(&client->dev, "Audmode: 0x%04x\n", state->audmode); + dev_info(&client->dev, "Routing: 0x%08x (input) 0x%08x (output)\n", + state->route_in, state->route_out); + dev_info(&client->dev, "ACB: 0x%04x\n", state->acb); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int msp_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + dev_dbg_lvl(&client->dev, 1, msp_debug, "suspend\n"); + msp_reset(client); + return 0; +} + +static int msp_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + dev_dbg_lvl(&client->dev, 1, msp_debug, "resume\n"); + msp_wake_thread(client); + return 0; +} +#endif + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops msp_ctrl_ops = { + .s_ctrl = msp_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops msp_core_ops = { + .log_status = msp_log_status, +}; + +static const struct v4l2_subdev_video_ops msp_video_ops = { + .s_std = msp_s_std, + .querystd = msp_querystd, +}; + +static const struct v4l2_subdev_tuner_ops msp_tuner_ops = { + .s_frequency = msp_s_frequency, + .g_tuner = msp_g_tuner, + .s_tuner = msp_s_tuner, + .s_radio = msp_s_radio, +}; + +static const struct v4l2_subdev_audio_ops msp_audio_ops = { + .s_routing = msp_s_routing, + .s_i2s_clock_freq = msp_s_i2s_clock_freq, +}; + +static const struct v4l2_subdev_ops msp_ops = { + .core = &msp_core_ops, + .video = &msp_video_ops, + .tuner = &msp_tuner_ops, + .audio = &msp_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ + + +static const char * const opmode_str[] = { + [OPMODE_MANUAL] = "manual", + [OPMODE_AUTODETECT] = "autodetect", + [OPMODE_AUTOSELECT] = "autodetect and autoselect", +}; + +static int msp_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct msp_state *state; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + int (*thread_func)(void *data) = NULL; + int msp_hard; + int msp_family; + int msp_revision; + int msp_product, msp_prod_hi, msp_prod_lo; + int msp_rom; +#if defined(CONFIG_MEDIA_CONTROLLER) + int ret; +#endif + + if (!id) + strscpy(client->name, "msp3400", sizeof(client->name)); + + if (msp_reset(client) == -1) { + dev_dbg_lvl(&client->dev, 1, msp_debug, "msp3400 not found\n"); + return -ENODEV; + } + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &msp_ops); + +#if defined(CONFIG_MEDIA_CONTROLLER) + state->pads[MSP3400_PAD_IF_INPUT].flags = MEDIA_PAD_FL_SINK; + state->pads[MSP3400_PAD_IF_INPUT].sig_type = PAD_SIGNAL_AUDIO; + state->pads[MSP3400_PAD_OUT].flags = MEDIA_PAD_FL_SOURCE; + state->pads[MSP3400_PAD_OUT].sig_type = PAD_SIGNAL_AUDIO; + + sd->entity.function = MEDIA_ENT_F_IF_AUD_DECODER; + + ret = media_entity_pads_init(&sd->entity, 2, state->pads); + if (ret < 0) + return ret; +#endif + + state->v4l2_std = V4L2_STD_NTSC; + state->detected_std = V4L2_STD_ALL; + state->audmode = V4L2_TUNER_MODE_STEREO; + state->input = -1; + state->i2s_mode = 0; + init_waitqueue_head(&state->wq); + /* These are the reset input/output positions */ + state->route_in = MSP_INPUT_DEFAULT; + state->route_out = MSP_OUTPUT_DEFAULT; + + state->rev1 = msp_read_dsp(client, 0x1e); + if (state->rev1 != -1) + state->rev2 = msp_read_dsp(client, 0x1f); + dev_dbg_lvl(&client->dev, 1, msp_debug, "rev1=0x%04x, rev2=0x%04x\n", + state->rev1, state->rev2); + if (state->rev1 == -1 || (state->rev1 == 0 && state->rev2 == 0)) { + dev_dbg_lvl(&client->dev, 1, msp_debug, + "not an msp3400 (cannot read chip version)\n"); + return -ENODEV; + } + + msp_family = ((state->rev1 >> 4) & 0x0f) + 3; + msp_product = (state->rev2 >> 8) & 0xff; + msp_prod_hi = msp_product / 10; + msp_prod_lo = msp_product % 10; + msp_revision = (state->rev1 & 0x0f) + '@'; + msp_hard = ((state->rev1 >> 8) & 0xff) + '@'; + msp_rom = state->rev2 & 0x1f; + /* Rev B=2, C=3, D=4, G=7 */ + state->ident = msp_family * 10000 + 4000 + msp_product * 10 + + msp_revision - '@'; + + /* Has NICAM support: all mspx41x and mspx45x products have NICAM */ + state->has_nicam = + msp_prod_hi == 1 || msp_prod_hi == 5; + /* Has radio support: was added with revision G */ + state->has_radio = + msp_revision >= 'G'; + /* Has headphones output: not for stripped down products */ + state->has_headphones = + msp_prod_lo < 5; + /* Has scart2 input: not in stripped down products of the '3' family */ + state->has_scart2 = + msp_family >= 4 || msp_prod_lo < 7; + /* Has scart3 input: not in stripped down products of the '3' family */ + state->has_scart3 = + msp_family >= 4 || msp_prod_lo < 5; + /* Has scart4 input: not in pre D revisions, not in stripped D revs */ + state->has_scart4 = + msp_family >= 4 || (msp_revision >= 'D' && msp_prod_lo < 5); + /* Has scart2 output: not in stripped down products of + * the '3' family */ + state->has_scart2_out = + msp_family >= 4 || msp_prod_lo < 5; + /* Has scart2 a volume control? Not in pre-D revisions. */ + state->has_scart2_out_volume = + msp_revision > 'C' && state->has_scart2_out; + /* Has a configurable i2s out? */ + state->has_i2s_conf = + msp_revision >= 'G' && msp_prod_lo < 7; + /* Has subwoofer output: not in pre-D revs and not in stripped down + * products */ + state->has_subwoofer = + msp_revision >= 'D' && msp_prod_lo < 5; + /* Has soundprocessing (bass/treble/balance/loudness/equalizer): + * not in stripped down products */ + state->has_sound_processing = + msp_prod_lo < 7; + /* Has Virtual Dolby Surround: only in msp34x1 */ + state->has_virtual_dolby_surround = + msp_revision == 'G' && msp_prod_lo == 1; + /* Has Virtual Dolby Surround & Dolby Pro Logic: only in msp34x2 */ + state->has_dolby_pro_logic = + msp_revision == 'G' && msp_prod_lo == 2; + /* The msp343xG supports BTSC only and cannot do Automatic Standard + * Detection. */ + state->force_btsc = + msp_family == 3 && msp_revision == 'G' && msp_prod_hi == 3; + + state->opmode = opmode; + if (state->opmode < OPMODE_MANUAL + || state->opmode > OPMODE_AUTOSELECT) { + /* MSP revision G and up have both autodetect and autoselect */ + if (msp_revision >= 'G') + state->opmode = OPMODE_AUTOSELECT; + /* MSP revision D and up have autodetect */ + else if (msp_revision >= 'D') + state->opmode = OPMODE_AUTODETECT; + else + state->opmode = OPMODE_MANUAL; + } + + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 6); + if (state->has_sound_processing) { + v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_BASS, 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_TREBLE, 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_LOUDNESS, 0, 1, 1, 0); + } + state->volume = v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, 65535, 65535 / 100, 58880); + v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_BALANCE, 0, 65535, 65535 / 100, 32768); + state->muted = v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + sd->ctrl_handler = hdl; + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return err; + } + + v4l2_ctrl_cluster(2, &state->volume); + v4l2_ctrl_handler_setup(hdl); + + dev_info(&client->dev, + "MSP%d4%02d%c-%c%d found on %s: supports %s%s%s, mode is %s\n", + msp_family, msp_product, + msp_revision, msp_hard, msp_rom, + client->adapter->name, + (state->has_nicam) ? "nicam" : "", + (state->has_nicam && state->has_radio) ? " and " : "", + (state->has_radio) ? "radio" : "", + opmode_str[state->opmode]); + + /* version-specific initialization */ + switch (state->opmode) { + case OPMODE_MANUAL: + thread_func = msp3400c_thread; + break; + case OPMODE_AUTODETECT: + thread_func = msp3410d_thread; + break; + case OPMODE_AUTOSELECT: + thread_func = msp34xxg_thread; + break; + } + + /* startup control thread if needed */ + if (thread_func) { + state->kthread = kthread_run(thread_func, client, "msp34xx"); + + if (IS_ERR(state->kthread)) + dev_warn(&client->dev, "kernel_thread() failed\n"); + msp_wake_thread(client); + } + return 0; +} + +static int msp_remove(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + v4l2_device_unregister_subdev(&state->sd); + /* shutdown control thread */ + if (state->kthread) { + state->restart = 1; + kthread_stop(state->kthread); + } + msp_reset(client); + + v4l2_ctrl_handler_free(&state->hdl); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct dev_pm_ops msp3400_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(msp_suspend, msp_resume) +}; + +static const struct i2c_device_id msp_id[] = { + { "msp3400", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, msp_id); + +static struct i2c_driver msp_driver = { + .driver = { + .name = "msp3400", + .pm = &msp3400_pm_ops, + }, + .probe = msp_probe, + .remove = msp_remove, + .id_table = msp_id, +}; + +module_i2c_driver(msp_driver); diff --git a/drivers/media/i2c/msp3400-driver.h b/drivers/media/i2c/msp3400-driver.h new file mode 100644 index 000000000..2bb9d5ff1 --- /dev/null +++ b/drivers/media/i2c/msp3400-driver.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + */ + +#ifndef MSP3400_DRIVER_H +#define MSP3400_DRIVER_H + +#include <media/drv-intf/msp3400.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-mc.h> + +/* ---------------------------------------------------------------------- */ + +/* This macro is allowed for *constants* only, gcc must calculate it + at compile time. Remember -- no floats in kernel mode */ +#define MSP_CARRIER(freq) ((int)((float)(freq / 18.432) * (1 << 24))) + +#define MSP_MODE_AM_DETECT 0 +#define MSP_MODE_FM_RADIO 2 +#define MSP_MODE_FM_TERRA 3 +#define MSP_MODE_FM_SAT 4 +#define MSP_MODE_FM_NICAM1 5 +#define MSP_MODE_FM_NICAM2 6 +#define MSP_MODE_AM_NICAM 7 +#define MSP_MODE_BTSC 8 +#define MSP_MODE_EXTERN 9 + +#define SCART_IN1 0 +#define SCART_IN2 1 +#define SCART_IN3 2 +#define SCART_IN4 3 +#define SCART_IN1_DA 4 +#define SCART_IN2_DA 5 +#define SCART_MONO 6 +#define SCART_MUTE 7 + +#define SCART_DSP_IN 0 +#define SCART1_OUT 1 +#define SCART2_OUT 2 + +#define OPMODE_AUTO -1 +#define OPMODE_MANUAL 0 +#define OPMODE_AUTODETECT 1 /* use autodetect (>= msp3410 only) */ +#define OPMODE_AUTOSELECT 2 /* use autodetect & autoselect (>= msp34xxG) */ + +/* module parameters */ +extern int msp_debug; +extern bool msp_once; +extern bool msp_amsound; +extern int msp_standard; +extern bool msp_dolby; +extern int msp_stereo_thresh; + +enum msp3400_pads { + MSP3400_PAD_IF_INPUT, + MSP3400_PAD_OUT, + MSP3400_NUM_PADS +}; + +struct msp_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + int rev1, rev2; + int ident; + u8 has_nicam; + u8 has_radio; + u8 has_headphones; + u8 has_ntsc_jp_d_k3; + u8 has_scart2; + u8 has_scart3; + u8 has_scart4; + u8 has_scart2_out; + u8 has_scart2_out_volume; + u8 has_i2s_conf; + u8 has_subwoofer; + u8 has_sound_processing; + u8 has_virtual_dolby_surround; + u8 has_dolby_pro_logic; + u8 force_btsc; + + int radio; + int opmode; + int std; + int mode; + v4l2_std_id v4l2_std, detected_std; + int nicam_on; + int acb; + int in_scart; + int i2s_mode; + int main, second; /* sound carrier */ + int input; + u32 route_in; + u32 route_out; + + /* v4l2 */ + int audmode; + int rxsubchans; + + struct { + /* volume cluster */ + struct v4l2_ctrl *volume; + struct v4l2_ctrl *muted; + }; + + int scan_in_progress; + + /* thread */ + struct task_struct *kthread; + wait_queue_head_t wq; + unsigned int restart:1; + unsigned int watch_stereo:1; + +#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER) + struct media_pad pads[MSP3400_NUM_PADS]; +#endif +}; + +static inline struct msp_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct msp_state, sd); +} + +static inline struct msp_state *ctrl_to_state(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct msp_state, hdl); +} + +/* msp3400-driver.c */ +int msp_write_dem(struct i2c_client *client, int addr, int val); +int msp_write_dsp(struct i2c_client *client, int addr, int val); +int msp_read_dem(struct i2c_client *client, int addr); +int msp_read_dsp(struct i2c_client *client, int addr); +int msp_reset(struct i2c_client *client); +void msp_set_scart(struct i2c_client *client, int in, int out); +void msp_update_volume(struct msp_state *state); +int msp_sleep(struct msp_state *state, int timeout); + +/* msp3400-kthreads.c */ +const char *msp_standard_std_name(int std); +void msp_set_audmode(struct i2c_client *client); +int msp_detect_stereo(struct i2c_client *client); +int msp3400c_thread(void *data); +int msp3410d_thread(void *data); +int msp34xxg_thread(void *data); +void msp3400c_set_mode(struct i2c_client *client, int mode); +void msp3400c_set_carrier(struct i2c_client *client, int cdo1, int cdo2); + +#endif /* MSP3400_DRIVER_H */ diff --git a/drivers/media/i2c/msp3400-kthreads.c b/drivers/media/i2c/msp3400-kthreads.c new file mode 100644 index 000000000..52e506f86 --- /dev/null +++ b/drivers/media/i2c/msp3400-kthreads.c @@ -0,0 +1,1151 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Programming the mspx4xx sound processor family + * + * (c) 1997-2001 Gerd Knorr <kraxel@bytesex.org> + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/freezer.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/drv-intf/msp3400.h> +#include <linux/kthread.h> +#include <linux/suspend.h> +#include "msp3400-driver.h" + +/* this one uses the automatic sound standard detection of newer msp34xx + chip versions */ +static struct { + int retval; + int main, second; + char *name; + v4l2_std_id std; +} msp_stdlist[] = { + { 0x0000, 0, 0, "could not detect sound standard", V4L2_STD_ALL }, + { 0x0001, 0, 0, "autodetect start", V4L2_STD_ALL }, + { 0x0002, MSP_CARRIER(4.5), MSP_CARRIER(4.72), + "4.5/4.72 M Dual FM-Stereo", V4L2_STD_MN }, + { 0x0003, MSP_CARRIER(5.5), MSP_CARRIER(5.7421875), + "5.5/5.74 B/G Dual FM-Stereo", V4L2_STD_BG }, + { 0x0004, MSP_CARRIER(6.5), MSP_CARRIER(6.2578125), + "6.5/6.25 D/K1 Dual FM-Stereo", V4L2_STD_DK }, + { 0x0005, MSP_CARRIER(6.5), MSP_CARRIER(6.7421875), + "6.5/6.74 D/K2 Dual FM-Stereo", V4L2_STD_DK }, + { 0x0006, MSP_CARRIER(6.5), MSP_CARRIER(6.5), + "6.5 D/K FM-Mono (HDEV3)", V4L2_STD_DK }, + { 0x0007, MSP_CARRIER(6.5), MSP_CARRIER(5.7421875), + "6.5/5.74 D/K3 Dual FM-Stereo", V4L2_STD_DK }, + { 0x0008, MSP_CARRIER(5.5), MSP_CARRIER(5.85), + "5.5/5.85 B/G NICAM FM", V4L2_STD_BG }, + { 0x0009, MSP_CARRIER(6.5), MSP_CARRIER(5.85), + "6.5/5.85 L NICAM AM", V4L2_STD_L }, + { 0x000a, MSP_CARRIER(6.0), MSP_CARRIER(6.55), + "6.0/6.55 I NICAM FM", V4L2_STD_PAL_I }, + { 0x000b, MSP_CARRIER(6.5), MSP_CARRIER(5.85), + "6.5/5.85 D/K NICAM FM", V4L2_STD_DK }, + { 0x000c, MSP_CARRIER(6.5), MSP_CARRIER(5.85), + "6.5/5.85 D/K NICAM FM (HDEV2)", V4L2_STD_DK }, + { 0x000d, MSP_CARRIER(6.5), MSP_CARRIER(5.85), + "6.5/5.85 D/K NICAM FM (HDEV3)", V4L2_STD_DK }, + { 0x0020, MSP_CARRIER(4.5), MSP_CARRIER(4.5), + "4.5 M BTSC-Stereo", V4L2_STD_MTS }, + { 0x0021, MSP_CARRIER(4.5), MSP_CARRIER(4.5), + "4.5 M BTSC-Mono + SAP", V4L2_STD_MTS }, + { 0x0030, MSP_CARRIER(4.5), MSP_CARRIER(4.5), + "4.5 M EIA-J Japan Stereo", V4L2_STD_NTSC_M_JP }, + { 0x0040, MSP_CARRIER(10.7), MSP_CARRIER(10.7), + "10.7 FM-Stereo Radio", V4L2_STD_ALL }, + { 0x0050, MSP_CARRIER(6.5), MSP_CARRIER(6.5), + "6.5 SAT-Mono", V4L2_STD_ALL }, + { 0x0051, MSP_CARRIER(7.02), MSP_CARRIER(7.20), + "7.02/7.20 SAT-Stereo", V4L2_STD_ALL }, + { 0x0060, MSP_CARRIER(7.2), MSP_CARRIER(7.2), + "7.2 SAT ADR", V4L2_STD_ALL }, + { -1, 0, 0, NULL, 0 }, /* EOF */ +}; + +static struct msp3400c_init_data_dem { + int fir1[6]; + int fir2[6]; + int cdo1; + int cdo2; + int ad_cv; + int mode_reg; + int dsp_src; + int dsp_matrix; +} msp3400c_init_data[] = { + { /* AM (for carrier detect / msp3400) */ + {75, 19, 36, 35, 39, 40}, + {75, 19, 36, 35, 39, 40}, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0500, 0x0020, 0x3000 + }, { /* AM (for carrier detect / msp3410) */ + {-1, -1, -8, 2, 59, 126}, + {-1, -1, -8, 2, 59, 126}, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0100, 0x0020, 0x3000 + }, { /* FM Radio */ + {-8, -8, 4, 6, 78, 107}, + {-8, -8, 4, 6, 78, 107}, + MSP_CARRIER(10.7), MSP_CARRIER(10.7), + 0x00d0, 0x0480, 0x0020, 0x3000 + }, { /* Terrestrial FM-mono + FM-stereo */ + {3, 18, 27, 48, 66, 72}, + {3, 18, 27, 48, 66, 72}, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0480, 0x0030, 0x3000 + }, { /* Sat FM-mono */ + { 1, 9, 14, 24, 33, 37}, + { 3, 18, 27, 48, 66, 72}, + MSP_CARRIER(6.5), MSP_CARRIER(6.5), + 0x00c6, 0x0480, 0x0000, 0x3000 + }, { /* NICAM/FM -- B/G (5.5/5.85), D/K (6.5/5.85) */ + {-2, -8, -10, 10, 50, 86}, + {3, 18, 27, 48, 66, 72}, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0040, 0x0120, 0x3000 + }, { /* NICAM/FM -- I (6.0/6.552) */ + {2, 4, -6, -4, 40, 94}, + {3, 18, 27, 48, 66, 72}, + MSP_CARRIER(6.0), MSP_CARRIER(6.0), + 0x00d0, 0x0040, 0x0120, 0x3000 + }, { /* NICAM/AM -- L (6.5/5.85) */ + {-2, -8, -10, 10, 50, 86}, + {-4, -12, -9, 23, 79, 126}, + MSP_CARRIER(6.5), MSP_CARRIER(6.5), + 0x00c6, 0x0140, 0x0120, 0x7c00 + }, +}; + +struct msp3400c_carrier_detect { + int cdo; + char *name; +}; + +static struct msp3400c_carrier_detect msp3400c_carrier_detect_main[] = { + /* main carrier */ + { MSP_CARRIER(4.5), "4.5 NTSC" }, + { MSP_CARRIER(5.5), "5.5 PAL B/G" }, + { MSP_CARRIER(6.0), "6.0 PAL I" }, + { MSP_CARRIER(6.5), "6.5 PAL D/K + SAT + SECAM" } +}; + +static struct msp3400c_carrier_detect msp3400c_carrier_detect_55[] = { + /* PAL B/G */ + { MSP_CARRIER(5.7421875), "5.742 PAL B/G FM-stereo" }, + { MSP_CARRIER(5.85), "5.85 PAL B/G NICAM" } +}; + +static struct msp3400c_carrier_detect msp3400c_carrier_detect_65[] = { + /* PAL SAT / SECAM */ + { MSP_CARRIER(5.85), "5.85 PAL D/K + SECAM NICAM" }, + { MSP_CARRIER(6.2578125), "6.25 PAL D/K1 FM-stereo" }, + { MSP_CARRIER(6.7421875), "6.74 PAL D/K2 FM-stereo" }, + { MSP_CARRIER(7.02), "7.02 PAL SAT FM-stereo s/b" }, + { MSP_CARRIER(7.20), "7.20 PAL SAT FM-stereo s" }, + { MSP_CARRIER(7.38), "7.38 PAL SAT FM-stereo b" }, +}; + +/* ------------------------------------------------------------------------ */ + +const char *msp_standard_std_name(int std) +{ + int i; + + for (i = 0; msp_stdlist[i].name != NULL; i++) + if (msp_stdlist[i].retval == std) + return msp_stdlist[i].name; + return "unknown"; +} + +static v4l2_std_id msp_standard_std(int std) +{ + int i; + + for (i = 0; msp_stdlist[i].name != NULL; i++) + if (msp_stdlist[i].retval == std) + return msp_stdlist[i].std; + return V4L2_STD_ALL; +} + +static void msp_set_source(struct i2c_client *client, u16 src) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + if (msp_dolby) { + msp_write_dsp(client, 0x0008, 0x0520); /* I2S1 */ + msp_write_dsp(client, 0x0009, 0x0620); /* I2S2 */ + } else { + msp_write_dsp(client, 0x0008, src); + msp_write_dsp(client, 0x0009, src); + } + msp_write_dsp(client, 0x000a, src); + msp_write_dsp(client, 0x000b, src); + msp_write_dsp(client, 0x000c, src); + if (state->has_scart2_out) + msp_write_dsp(client, 0x0041, src); +} + +void msp3400c_set_carrier(struct i2c_client *client, int cdo1, int cdo2) +{ + msp_write_dem(client, 0x0093, cdo1 & 0xfff); + msp_write_dem(client, 0x009b, cdo1 >> 12); + msp_write_dem(client, 0x00a3, cdo2 & 0xfff); + msp_write_dem(client, 0x00ab, cdo2 >> 12); + msp_write_dem(client, 0x0056, 0); /* LOAD_REG_1/2 */ +} + +void msp3400c_set_mode(struct i2c_client *client, int mode) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + struct msp3400c_init_data_dem *data = &msp3400c_init_data[mode]; + int tuner = (state->route_in >> 3) & 1; + int i; + + dev_dbg_lvl(&client->dev, 1, msp_debug, "set_mode: %d\n", mode); + state->mode = mode; + state->rxsubchans = V4L2_TUNER_SUB_MONO; + + msp_write_dem(client, 0x00bb, data->ad_cv | (tuner ? 0x100 : 0)); + + for (i = 5; i >= 0; i--) /* fir 1 */ + msp_write_dem(client, 0x0001, data->fir1[i]); + + msp_write_dem(client, 0x0005, 0x0004); /* fir 2 */ + msp_write_dem(client, 0x0005, 0x0040); + msp_write_dem(client, 0x0005, 0x0000); + for (i = 5; i >= 0; i--) + msp_write_dem(client, 0x0005, data->fir2[i]); + + msp_write_dem(client, 0x0083, data->mode_reg); + + msp3400c_set_carrier(client, data->cdo1, data->cdo2); + + msp_set_source(client, data->dsp_src); + /* set prescales */ + + /* volume prescale for SCART (AM mono input) */ + msp_write_dsp(client, 0x000d, 0x1900); + msp_write_dsp(client, 0x000e, data->dsp_matrix); + if (state->has_nicam) /* nicam prescale */ + msp_write_dsp(client, 0x0010, 0x5a00); +} + +/* Set audio mode. Note that the pre-'G' models do not support BTSC+SAP, + nor do they support stereo BTSC. */ +static void msp3400c_set_audmode(struct i2c_client *client) +{ + static char *strmode[] = { + "mono", "stereo", "lang2", "lang1", "lang1+lang2" + }; + struct msp_state *state = to_state(i2c_get_clientdata(client)); + char *modestr = (state->audmode >= 0 && state->audmode < 5) ? + strmode[state->audmode] : "unknown"; + int src = 0; /* channel source: FM/AM, nicam or SCART */ + int audmode = state->audmode; + + if (state->opmode == OPMODE_AUTOSELECT) { + /* this method would break everything, let's make sure + * it's never called + */ + dev_dbg_lvl(&client->dev, 1, msp_debug, + "set_audmode called with mode=%d instead of set_source (ignored)\n", + state->audmode); + return; + } + + /* Note: for the C and D revs no NTSC stereo + SAP is possible as + the hardware does not support SAP. So the rxsubchans combination + of STEREO | LANG2 does not occur. */ + + if (state->mode != MSP_MODE_EXTERN) { + /* switch to mono if only mono is available */ + if (state->rxsubchans == V4L2_TUNER_SUB_MONO) + audmode = V4L2_TUNER_MODE_MONO; + /* if bilingual */ + else if (state->rxsubchans & V4L2_TUNER_SUB_LANG2) { + /* and mono or stereo, then fallback to lang1 */ + if (audmode == V4L2_TUNER_MODE_MONO || + audmode == V4L2_TUNER_MODE_STEREO) + audmode = V4L2_TUNER_MODE_LANG1; + } + /* if stereo, and audmode is not mono, then switch to stereo */ + else if (audmode != V4L2_TUNER_MODE_MONO) + audmode = V4L2_TUNER_MODE_STEREO; + } + + /* switch demodulator */ + switch (state->mode) { + case MSP_MODE_FM_TERRA: + dev_dbg_lvl(&client->dev, 1, msp_debug, "FM set_audmode: %s\n", modestr); + switch (audmode) { + case V4L2_TUNER_MODE_STEREO: + msp_write_dsp(client, 0x000e, 0x3001); + break; + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + case V4L2_TUNER_MODE_LANG2: + case V4L2_TUNER_MODE_LANG1_LANG2: + msp_write_dsp(client, 0x000e, 0x3000); + break; + } + break; + case MSP_MODE_FM_SAT: + dev_dbg_lvl(&client->dev, 1, msp_debug, "SAT set_audmode: %s\n", modestr); + switch (audmode) { + case V4L2_TUNER_MODE_MONO: + msp3400c_set_carrier(client, MSP_CARRIER(6.5), MSP_CARRIER(6.5)); + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1_LANG2: + msp3400c_set_carrier(client, MSP_CARRIER(7.2), MSP_CARRIER(7.02)); + break; + case V4L2_TUNER_MODE_LANG1: + msp3400c_set_carrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02)); + break; + case V4L2_TUNER_MODE_LANG2: + msp3400c_set_carrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02)); + break; + } + break; + case MSP_MODE_FM_NICAM1: + case MSP_MODE_FM_NICAM2: + case MSP_MODE_AM_NICAM: + dev_dbg_lvl(&client->dev, 1, msp_debug, + "NICAM set_audmode: %s\n", modestr); + if (state->nicam_on) + src = 0x0100; /* NICAM */ + break; + case MSP_MODE_BTSC: + dev_dbg_lvl(&client->dev, 1, msp_debug, + "BTSC set_audmode: %s\n", modestr); + break; + case MSP_MODE_EXTERN: + dev_dbg_lvl(&client->dev, 1, msp_debug, + "extern set_audmode: %s\n", modestr); + src = 0x0200; /* SCART */ + break; + case MSP_MODE_FM_RADIO: + dev_dbg_lvl(&client->dev, 1, msp_debug, + "FM-Radio set_audmode: %s\n", modestr); + break; + default: + dev_dbg_lvl(&client->dev, 1, msp_debug, "mono set_audmode\n"); + return; + } + + /* switch audio */ + dev_dbg_lvl(&client->dev, 1, msp_debug, "set audmode %d\n", audmode); + switch (audmode) { + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1_LANG2: + src |= 0x0020; + break; + case V4L2_TUNER_MODE_MONO: + if (state->mode == MSP_MODE_AM_NICAM) { + dev_dbg_lvl(&client->dev, 1, msp_debug, "switching to AM mono\n"); + /* AM mono decoding is handled by tuner, not MSP chip */ + /* SCART switching control register */ + msp_set_scart(client, SCART_MONO, 0); + src = 0x0200; + break; + } + if (state->rxsubchans & V4L2_TUNER_SUB_STEREO) + src = 0x0030; + break; + case V4L2_TUNER_MODE_LANG1: + break; + case V4L2_TUNER_MODE_LANG2: + src |= 0x0010; + break; + } + dev_dbg_lvl(&client->dev, 1, msp_debug, + "set_audmode final source/matrix = 0x%x\n", src); + + msp_set_source(client, src); +} + +static void msp3400c_print_mode(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + if (state->main == state->second) + dev_dbg_lvl(&client->dev, 1, msp_debug, + "mono sound carrier: %d.%03d MHz\n", + state->main / 910000, (state->main / 910) % 1000); + else + dev_dbg_lvl(&client->dev, 1, msp_debug, + "main sound carrier: %d.%03d MHz\n", + state->main / 910000, (state->main / 910) % 1000); + if (state->mode == MSP_MODE_FM_NICAM1 || state->mode == MSP_MODE_FM_NICAM2) + dev_dbg_lvl(&client->dev, 1, msp_debug, + "NICAM/FM carrier : %d.%03d MHz\n", + state->second / 910000, (state->second/910) % 1000); + if (state->mode == MSP_MODE_AM_NICAM) + dev_dbg_lvl(&client->dev, 1, msp_debug, + "NICAM/AM carrier : %d.%03d MHz\n", + state->second / 910000, (state->second / 910) % 1000); + if (state->mode == MSP_MODE_FM_TERRA && state->main != state->second) { + dev_dbg_lvl(&client->dev, 1, msp_debug, + "FM-stereo carrier : %d.%03d MHz\n", + state->second / 910000, (state->second / 910) % 1000); + } +} + +/* ----------------------------------------------------------------------- */ + +static int msp3400c_detect_stereo(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + int val; + int rxsubchans = state->rxsubchans; + int newnicam = state->nicam_on; + int update = 0; + + switch (state->mode) { + case MSP_MODE_FM_TERRA: + val = msp_read_dsp(client, 0x18); + if (val > 32767) + val -= 65536; + dev_dbg_lvl(&client->dev, 2, msp_debug, + "stereo detect register: %d\n", val); + if (val > 8192) { + rxsubchans = V4L2_TUNER_SUB_STEREO; + } else if (val < -4096) { + rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + } else { + rxsubchans = V4L2_TUNER_SUB_MONO; + } + newnicam = 0; + break; + case MSP_MODE_FM_NICAM1: + case MSP_MODE_FM_NICAM2: + case MSP_MODE_AM_NICAM: + val = msp_read_dem(client, 0x23); + dev_dbg_lvl(&client->dev, 2, msp_debug, "nicam sync=%d, mode=%d\n", + val & 1, (val & 0x1e) >> 1); + + if (val & 1) { + /* nicam synced */ + switch ((val & 0x1e) >> 1) { + case 0: + case 8: + rxsubchans = V4L2_TUNER_SUB_STEREO; + break; + case 1: + case 9: + rxsubchans = V4L2_TUNER_SUB_MONO; + break; + case 2: + case 10: + rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + break; + default: + rxsubchans = V4L2_TUNER_SUB_MONO; + break; + } + newnicam = 1; + } else { + newnicam = 0; + rxsubchans = V4L2_TUNER_SUB_MONO; + } + break; + } + if (rxsubchans != state->rxsubchans) { + update = 1; + dev_dbg_lvl(&client->dev, 1, msp_debug, + "watch: rxsubchans %02x => %02x\n", + state->rxsubchans, rxsubchans); + state->rxsubchans = rxsubchans; + } + if (newnicam != state->nicam_on) { + update = 1; + dev_dbg_lvl(&client->dev, 1, msp_debug, "watch: nicam %d => %d\n", + state->nicam_on, newnicam); + state->nicam_on = newnicam; + } + return update; +} + +/* + * A kernel thread for msp3400 control -- we don't want to block the + * in the ioctl while doing the sound carrier & stereo detect + */ +/* stereo/multilang monitoring */ +static void watch_stereo(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + if (msp_detect_stereo(client)) + msp_set_audmode(client); + + if (msp_once) + state->watch_stereo = 0; +} + +int msp3400c_thread(void *data) +{ + struct i2c_client *client = data; + struct msp_state *state = to_state(i2c_get_clientdata(client)); + struct msp3400c_carrier_detect *cd; + int count, max1, max2, val1, val2, val, i; + + dev_dbg_lvl(&client->dev, 1, msp_debug, "msp3400 daemon started\n"); + state->detected_std = V4L2_STD_ALL; + set_freezable(); + for (;;) { + dev_dbg_lvl(&client->dev, 2, msp_debug, "msp3400 thread: sleep\n"); + msp_sleep(state, -1); + dev_dbg_lvl(&client->dev, 2, msp_debug, "msp3400 thread: wakeup\n"); + +restart: + dev_dbg_lvl(&client->dev, 2, msp_debug, "thread: restart scan\n"); + state->restart = 0; + if (kthread_should_stop()) + break; + + if (state->radio || MSP_MODE_EXTERN == state->mode) { + /* no carrier scan, just unmute */ + dev_dbg_lvl(&client->dev, 1, msp_debug, + "thread: no carrier scan\n"); + state->scan_in_progress = 0; + msp_update_volume(state); + continue; + } + + /* mute audio */ + state->scan_in_progress = 1; + msp_update_volume(state); + + msp3400c_set_mode(client, MSP_MODE_AM_DETECT); + val1 = val2 = 0; + max1 = max2 = -1; + state->watch_stereo = 0; + state->nicam_on = 0; + + /* wait for tuner to settle down after a channel change */ + if (msp_sleep(state, 200)) + goto restart; + + /* carrier detect pass #1 -- main carrier */ + cd = msp3400c_carrier_detect_main; + count = ARRAY_SIZE(msp3400c_carrier_detect_main); + + if (msp_amsound && (state->v4l2_std & V4L2_STD_SECAM)) { + /* autodetect doesn't work well with AM ... */ + max1 = 3; + count = 0; + dev_dbg_lvl(&client->dev, 1, msp_debug, "AM sound override\n"); + } + + for (i = 0; i < count; i++) { + msp3400c_set_carrier(client, cd[i].cdo, cd[i].cdo); + if (msp_sleep(state, 100)) + goto restart; + val = msp_read_dsp(client, 0x1b); + if (val > 32767) + val -= 65536; + if (val1 < val) + val1 = val, max1 = i; + dev_dbg_lvl(&client->dev, 1, msp_debug, + "carrier1 val: %5d / %s\n", val, cd[i].name); + } + + /* carrier detect pass #2 -- second (stereo) carrier */ + switch (max1) { + case 1: /* 5.5 */ + cd = msp3400c_carrier_detect_55; + count = ARRAY_SIZE(msp3400c_carrier_detect_55); + break; + case 3: /* 6.5 */ + cd = msp3400c_carrier_detect_65; + count = ARRAY_SIZE(msp3400c_carrier_detect_65); + break; + case 0: /* 4.5 */ + case 2: /* 6.0 */ + default: + cd = NULL; + count = 0; + break; + } + + if (msp_amsound && (state->v4l2_std & V4L2_STD_SECAM)) { + /* autodetect doesn't work well with AM ... */ + cd = NULL; + count = 0; + max2 = 0; + } + for (i = 0; i < count; i++) { + msp3400c_set_carrier(client, cd[i].cdo, cd[i].cdo); + if (msp_sleep(state, 100)) + goto restart; + val = msp_read_dsp(client, 0x1b); + if (val > 32767) + val -= 65536; + if (val2 < val) + val2 = val, max2 = i; + dev_dbg_lvl(&client->dev, 1, msp_debug, + "carrier2 val: %5d / %s\n", val, cd[i].name); + } + + /* program the msp3400 according to the results */ + state->main = msp3400c_carrier_detect_main[max1].cdo; + switch (max1) { + case 1: /* 5.5 */ + state->detected_std = V4L2_STD_BG | V4L2_STD_PAL_H; + if (max2 == 0) { + /* B/G FM-stereo */ + state->second = msp3400c_carrier_detect_55[max2].cdo; + msp3400c_set_mode(client, MSP_MODE_FM_TERRA); + state->watch_stereo = 1; + } else if (max2 == 1 && state->has_nicam) { + /* B/G NICAM */ + state->second = msp3400c_carrier_detect_55[max2].cdo; + msp3400c_set_mode(client, MSP_MODE_FM_NICAM1); + state->nicam_on = 1; + state->watch_stereo = 1; + } else { + goto no_second; + } + break; + case 2: /* 6.0 */ + /* PAL I NICAM */ + state->detected_std = V4L2_STD_PAL_I; + state->second = MSP_CARRIER(6.552); + msp3400c_set_mode(client, MSP_MODE_FM_NICAM2); + state->nicam_on = 1; + state->watch_stereo = 1; + break; + case 3: /* 6.5 */ + if (max2 == 1 || max2 == 2) { + /* D/K FM-stereo */ + state->second = msp3400c_carrier_detect_65[max2].cdo; + msp3400c_set_mode(client, MSP_MODE_FM_TERRA); + state->watch_stereo = 1; + state->detected_std = V4L2_STD_DK; + } else if (max2 == 0 && (state->v4l2_std & V4L2_STD_SECAM)) { + /* L NICAM or AM-mono */ + state->second = msp3400c_carrier_detect_65[max2].cdo; + msp3400c_set_mode(client, MSP_MODE_AM_NICAM); + state->watch_stereo = 1; + state->detected_std = V4L2_STD_L; + } else if (max2 == 0 && state->has_nicam) { + /* D/K NICAM */ + state->second = msp3400c_carrier_detect_65[max2].cdo; + msp3400c_set_mode(client, MSP_MODE_FM_NICAM1); + state->nicam_on = 1; + state->watch_stereo = 1; + state->detected_std = V4L2_STD_DK; + } else { + goto no_second; + } + break; + case 0: /* 4.5 */ + state->detected_std = V4L2_STD_MN; + fallthrough; + default: +no_second: + state->second = msp3400c_carrier_detect_main[max1].cdo; + msp3400c_set_mode(client, MSP_MODE_FM_TERRA); + break; + } + msp3400c_set_carrier(client, state->second, state->main); + + /* unmute */ + state->scan_in_progress = 0; + msp3400c_set_audmode(client); + msp_update_volume(state); + + if (msp_debug) + msp3400c_print_mode(client); + + /* monitor tv audio mode, the first time don't wait + so long to get a quick stereo/bilingual result */ + count = 3; + while (state->watch_stereo) { + if (msp_sleep(state, count ? 1000 : 5000)) + goto restart; + if (count) + count--; + watch_stereo(client); + } + } + dev_dbg_lvl(&client->dev, 1, msp_debug, "thread: exit\n"); + return 0; +} + + +int msp3410d_thread(void *data) +{ + struct i2c_client *client = data; + struct msp_state *state = to_state(i2c_get_clientdata(client)); + int val, i, std, count; + + dev_dbg_lvl(&client->dev, 1, msp_debug, "msp3410 daemon started\n"); + state->detected_std = V4L2_STD_ALL; + set_freezable(); + for (;;) { + dev_dbg_lvl(&client->dev, 2, msp_debug, "msp3410 thread: sleep\n"); + msp_sleep(state, -1); + dev_dbg_lvl(&client->dev, 2, msp_debug, "msp3410 thread: wakeup\n"); + +restart: + dev_dbg_lvl(&client->dev, 2, msp_debug, "thread: restart scan\n"); + state->restart = 0; + if (kthread_should_stop()) + break; + + if (state->mode == MSP_MODE_EXTERN) { + /* no carrier scan needed, just unmute */ + dev_dbg_lvl(&client->dev, 1, msp_debug, + "thread: no carrier scan\n"); + state->scan_in_progress = 0; + msp_update_volume(state); + continue; + } + + /* mute audio */ + state->scan_in_progress = 1; + msp_update_volume(state); + + /* start autodetect. Note: autodetect is not supported for + NTSC-M and radio, hence we force the standard in those + cases. */ + if (state->radio) + std = 0x40; + else + std = (state->v4l2_std & V4L2_STD_NTSC) ? 0x20 : 1; + state->watch_stereo = 0; + state->nicam_on = 0; + + /* wait for tuner to settle down after a channel change */ + if (msp_sleep(state, 200)) + goto restart; + + if (msp_debug) + dev_dbg_lvl(&client->dev, 2, msp_debug, + "setting standard: %s (0x%04x)\n", + msp_standard_std_name(std), std); + + if (std != 1) { + /* programmed some specific mode */ + val = std; + } else { + /* triggered autodetect */ + msp_write_dem(client, 0x20, std); + for (;;) { + if (msp_sleep(state, 100)) + goto restart; + + /* check results */ + val = msp_read_dem(client, 0x7e); + if (val < 0x07ff) + break; + dev_dbg_lvl(&client->dev, 2, msp_debug, + "detection still in progress\n"); + } + } + for (i = 0; msp_stdlist[i].name != NULL; i++) + if (msp_stdlist[i].retval == val) + break; + dev_dbg_lvl(&client->dev, 1, msp_debug, "current standard: %s (0x%04x)\n", + msp_standard_std_name(val), val); + state->main = msp_stdlist[i].main; + state->second = msp_stdlist[i].second; + state->std = val; + state->rxsubchans = V4L2_TUNER_SUB_MONO; + + if (msp_amsound && !state->radio && + (state->v4l2_std & V4L2_STD_SECAM) && (val != 0x0009)) { + /* autodetection has failed, let backup */ + dev_dbg_lvl(&client->dev, 1, msp_debug, "autodetection failed, switching to backup standard: %s (0x%04x)\n", + msp_stdlist[8].name ? + msp_stdlist[8].name : "unknown", val); + state->std = val = 0x0009; + msp_write_dem(client, 0x20, val); + } else { + state->detected_std = msp_standard_std(state->std); + } + + /* set stereo */ + switch (val) { + case 0x0008: /* B/G NICAM */ + case 0x000a: /* I NICAM */ + case 0x000b: /* D/K NICAM */ + if (val == 0x000a) + state->mode = MSP_MODE_FM_NICAM2; + else + state->mode = MSP_MODE_FM_NICAM1; + /* just turn on stereo */ + state->nicam_on = 1; + state->watch_stereo = 1; + break; + case 0x0009: + state->mode = MSP_MODE_AM_NICAM; + state->nicam_on = 1; + state->watch_stereo = 1; + break; + case 0x0020: /* BTSC */ + /* The pre-'G' models only have BTSC-mono */ + state->mode = MSP_MODE_BTSC; + break; + case 0x0040: /* FM radio */ + state->mode = MSP_MODE_FM_RADIO; + state->rxsubchans = V4L2_TUNER_SUB_STEREO; + /* not needed in theory if we have radio, but + short programming enables carrier mute */ + msp3400c_set_mode(client, MSP_MODE_FM_RADIO); + msp3400c_set_carrier(client, MSP_CARRIER(10.7), + MSP_CARRIER(10.7)); + break; + case 0x0002: + case 0x0003: + case 0x0004: + case 0x0005: + state->mode = MSP_MODE_FM_TERRA; + state->watch_stereo = 1; + break; + } + + /* set various prescales */ + msp_write_dsp(client, 0x0d, 0x1900); /* scart */ + msp_write_dsp(client, 0x0e, 0x3000); /* FM */ + if (state->has_nicam) + msp_write_dsp(client, 0x10, 0x5a00); /* nicam */ + + if (state->has_i2s_conf) + msp_write_dem(client, 0x40, state->i2s_mode); + + /* unmute */ + msp3400c_set_audmode(client); + state->scan_in_progress = 0; + msp_update_volume(state); + + /* monitor tv audio mode, the first time don't wait + so long to get a quick stereo/bilingual result */ + count = 3; + while (state->watch_stereo) { + if (msp_sleep(state, count ? 1000 : 5000)) + goto restart; + if (count) + count--; + watch_stereo(client); + } + } + dev_dbg_lvl(&client->dev, 1, msp_debug, "thread: exit\n"); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* msp34xxG + (autoselect no-thread) + * this one uses both automatic standard detection and automatic sound + * select which are available in the newer G versions + * struct msp: only norm, acb and source are really used in this mode + */ + +static int msp34xxg_modus(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + if (state->radio) { + dev_dbg_lvl(&client->dev, 1, msp_debug, "selected radio modus\n"); + return 0x0001; + } + if (state->v4l2_std == V4L2_STD_NTSC_M_JP) { + dev_dbg_lvl(&client->dev, 1, msp_debug, "selected M (EIA-J) modus\n"); + return 0x4001; + } + if (state->v4l2_std == V4L2_STD_NTSC_M_KR) { + dev_dbg_lvl(&client->dev, 1, msp_debug, "selected M (A2) modus\n"); + return 0x0001; + } + if (state->v4l2_std == V4L2_STD_SECAM_L) { + dev_dbg_lvl(&client->dev, 1, msp_debug, "selected SECAM-L modus\n"); + return 0x6001; + } + if (state->v4l2_std & V4L2_STD_MN) { + dev_dbg_lvl(&client->dev, 1, msp_debug, "selected M (BTSC) modus\n"); + return 0x2001; + } + return 0x7001; +} + +static void msp34xxg_set_source(struct i2c_client *client, u16 reg, int in) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + int source, matrix; + + switch (state->audmode) { + case V4L2_TUNER_MODE_MONO: + source = 0; /* mono only */ + matrix = 0x30; + break; + case V4L2_TUNER_MODE_LANG2: + source = 4; /* stereo or B */ + matrix = 0x10; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + source = 1; /* stereo or A|B */ + matrix = 0x20; + break; + case V4L2_TUNER_MODE_LANG1: + source = 3; /* stereo or A */ + matrix = 0x00; + break; + case V4L2_TUNER_MODE_STEREO: + default: + source = 3; /* stereo or A */ + matrix = 0x20; + break; + } + + if (in == MSP_DSP_IN_TUNER) + source = (source << 8) | 0x20; + /* the msp34x2g puts the MAIN_AVC, MAIN and AUX sources in 12, 13, 14 + instead of 11, 12, 13. So we add one for that msp version. */ + else if (in >= MSP_DSP_IN_MAIN_AVC && state->has_dolby_pro_logic) + source = ((in + 1) << 8) | matrix; + else + source = (in << 8) | matrix; + + dev_dbg_lvl(&client->dev, 1, msp_debug, + "set source to %d (0x%x) for output %02x\n", in, source, reg); + msp_write_dsp(client, reg, source); +} + +static void msp34xxg_set_sources(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + u32 in = state->route_in; + + msp34xxg_set_source(client, 0x0008, (in >> 4) & 0xf); + /* quasi-peak detector is set to same input as the loudspeaker (MAIN) */ + msp34xxg_set_source(client, 0x000c, (in >> 4) & 0xf); + msp34xxg_set_source(client, 0x0009, (in >> 8) & 0xf); + msp34xxg_set_source(client, 0x000a, (in >> 12) & 0xf); + if (state->has_scart2_out) + msp34xxg_set_source(client, 0x0041, (in >> 16) & 0xf); + msp34xxg_set_source(client, 0x000b, (in >> 20) & 0xf); +} + +/* (re-)initialize the msp34xxg */ +static void msp34xxg_reset(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + int tuner = (state->route_in >> 3) & 1; + int modus; + + /* initialize std to 1 (autodetect) to signal that no standard is + selected yet. */ + state->std = 1; + + msp_reset(client); + + if (state->has_i2s_conf) + msp_write_dem(client, 0x40, state->i2s_mode); + + /* step-by-step initialisation, as described in the manual */ + modus = msp34xxg_modus(client); + modus |= tuner ? 0x100 : 0; + msp_write_dem(client, 0x30, modus); + + /* write the dsps that may have an influence on + standard/audio autodetection right now */ + msp34xxg_set_sources(client); + + msp_write_dsp(client, 0x0d, 0x1900); /* scart */ + msp_write_dsp(client, 0x0e, 0x3000); /* FM */ + if (state->has_nicam) + msp_write_dsp(client, 0x10, 0x5a00); /* nicam */ + + /* set identification threshold. Personally, I + * I set it to a higher value than the default + * of 0x190 to ignore noisy stereo signals. + * this needs tuning. (recommended range 0x00a0-0x03c0) + * 0x7f0 = forced mono mode + * + * a2 threshold for stereo/bilingual. + * Note: this register is part of the Manual/Compatibility mode. + * It is supported by all 'G'-family chips. + */ + msp_write_dem(client, 0x22, msp_stereo_thresh); +} + +int msp34xxg_thread(void *data) +{ + struct i2c_client *client = data; + struct msp_state *state = to_state(i2c_get_clientdata(client)); + int val, i; + + dev_dbg_lvl(&client->dev, 1, msp_debug, "msp34xxg daemon started\n"); + state->detected_std = V4L2_STD_ALL; + set_freezable(); + for (;;) { + dev_dbg_lvl(&client->dev, 2, msp_debug, "msp34xxg thread: sleep\n"); + msp_sleep(state, -1); + dev_dbg_lvl(&client->dev, 2, msp_debug, "msp34xxg thread: wakeup\n"); + +restart: + dev_dbg_lvl(&client->dev, 1, msp_debug, "thread: restart scan\n"); + state->restart = 0; + if (kthread_should_stop()) + break; + + if (state->mode == MSP_MODE_EXTERN) { + /* no carrier scan needed, just unmute */ + dev_dbg_lvl(&client->dev, 1, msp_debug, + "thread: no carrier scan\n"); + state->scan_in_progress = 0; + msp_update_volume(state); + continue; + } + + /* setup the chip*/ + msp34xxg_reset(client); + state->std = state->radio ? 0x40 : + (state->force_btsc && msp_standard == 1) ? 32 : msp_standard; + msp_write_dem(client, 0x20, state->std); + /* start autodetect */ + if (state->std != 1) + goto unmute; + + /* watch autodetect */ + dev_dbg_lvl(&client->dev, 1, msp_debug, + "started autodetect, waiting for result\n"); + for (i = 0; i < 10; i++) { + if (msp_sleep(state, 100)) + goto restart; + + /* check results */ + val = msp_read_dem(client, 0x7e); + if (val < 0x07ff) { + state->std = val; + break; + } + dev_dbg_lvl(&client->dev, 2, msp_debug, + "detection still in progress\n"); + } + if (state->std == 1) { + dev_dbg_lvl(&client->dev, 1, msp_debug, + "detection still in progress after 10 tries. giving up.\n"); + continue; + } + +unmute: + dev_dbg_lvl(&client->dev, 1, msp_debug, + "detected standard: %s (0x%04x)\n", + msp_standard_std_name(state->std), state->std); + state->detected_std = msp_standard_std(state->std); + + if (state->std == 9) { + /* AM NICAM mode */ + msp_write_dsp(client, 0x0e, 0x7c00); + } + + /* unmute: dispatch sound to scart output, set scart volume */ + msp_update_volume(state); + + /* restore ACB */ + if (msp_write_dsp(client, 0x13, state->acb)) + return -1; + + /* the periodic stereo/SAP check is only relevant for + the 0x20 standard (BTSC) */ + if (state->std != 0x20) + continue; + + state->watch_stereo = 1; + + /* monitor tv audio mode, the first time don't wait + in order to get a quick stereo/SAP update */ + watch_stereo(client); + while (state->watch_stereo) { + watch_stereo(client); + if (msp_sleep(state, 5000)) + goto restart; + } + } + dev_dbg_lvl(&client->dev, 1, msp_debug, "thread: exit\n"); + return 0; +} + +static int msp34xxg_detect_stereo(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + int status = msp_read_dem(client, 0x0200); + int is_bilingual = status & 0x100; + int is_stereo = status & 0x40; + int oldrx = state->rxsubchans; + + if (state->mode == MSP_MODE_EXTERN) + return 0; + + state->rxsubchans = 0; + if (is_stereo) + state->rxsubchans = V4L2_TUNER_SUB_STEREO; + else + state->rxsubchans = V4L2_TUNER_SUB_MONO; + if (is_bilingual) { + if (state->std == 0x20) + state->rxsubchans |= V4L2_TUNER_SUB_SAP; + else + state->rxsubchans = + V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + } + dev_dbg_lvl(&client->dev, 1, msp_debug, + "status=0x%x, stereo=%d, bilingual=%d -> rxsubchans=%d\n", + status, is_stereo, is_bilingual, state->rxsubchans); + return (oldrx != state->rxsubchans); +} + +static void msp34xxg_set_audmode(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + if (state->std == 0x20) { + if ((state->rxsubchans & V4L2_TUNER_SUB_SAP) && + (state->audmode == V4L2_TUNER_MODE_LANG1_LANG2 || + state->audmode == V4L2_TUNER_MODE_LANG2)) { + msp_write_dem(client, 0x20, 0x21); + } else { + msp_write_dem(client, 0x20, 0x20); + } + } + + msp34xxg_set_sources(client); +} + +void msp_set_audmode(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + switch (state->opmode) { + case OPMODE_MANUAL: + case OPMODE_AUTODETECT: + msp3400c_set_audmode(client); + break; + case OPMODE_AUTOSELECT: + msp34xxg_set_audmode(client); + break; + } +} + +int msp_detect_stereo(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + switch (state->opmode) { + case OPMODE_MANUAL: + case OPMODE_AUTODETECT: + return msp3400c_detect_stereo(client); + case OPMODE_AUTOSELECT: + return msp34xxg_detect_stereo(client); + } + return 0; +} + diff --git a/drivers/media/i2c/mt9m001.c b/drivers/media/i2c/mt9m001.c new file mode 100644 index 000000000..3b0ba8ed5 --- /dev/null +++ b/drivers/media/i2c/mt9m001.c @@ -0,0 +1,884 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for MT9M001 CMOS Image Sensor from Micron + * + * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/log2.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-subdev.h> + +/* + * mt9m001 i2c address 0x5d + */ + +/* mt9m001 selected register addresses */ +#define MT9M001_CHIP_VERSION 0x00 +#define MT9M001_ROW_START 0x01 +#define MT9M001_COLUMN_START 0x02 +#define MT9M001_WINDOW_HEIGHT 0x03 +#define MT9M001_WINDOW_WIDTH 0x04 +#define MT9M001_HORIZONTAL_BLANKING 0x05 +#define MT9M001_VERTICAL_BLANKING 0x06 +#define MT9M001_OUTPUT_CONTROL 0x07 +#define MT9M001_SHUTTER_WIDTH 0x09 +#define MT9M001_FRAME_RESTART 0x0b +#define MT9M001_SHUTTER_DELAY 0x0c +#define MT9M001_RESET 0x0d +#define MT9M001_READ_OPTIONS1 0x1e +#define MT9M001_READ_OPTIONS2 0x20 +#define MT9M001_GLOBAL_GAIN 0x35 +#define MT9M001_CHIP_ENABLE 0xF1 + +#define MT9M001_MAX_WIDTH 1280 +#define MT9M001_MAX_HEIGHT 1024 +#define MT9M001_MIN_WIDTH 48 +#define MT9M001_MIN_HEIGHT 32 +#define MT9M001_COLUMN_SKIP 20 +#define MT9M001_ROW_SKIP 12 +#define MT9M001_DEFAULT_HBLANK 9 +#define MT9M001_DEFAULT_VBLANK 25 + +/* MT9M001 has only one fixed colorspace per pixelcode */ +struct mt9m001_datafmt { + u32 code; + enum v4l2_colorspace colorspace; +}; + +/* Find a data format by a pixel code in an array */ +static const struct mt9m001_datafmt *mt9m001_find_datafmt( + u32 code, const struct mt9m001_datafmt *fmt, + int n) +{ + int i; + for (i = 0; i < n; i++) + if (fmt[i].code == code) + return fmt + i; + + return NULL; +} + +static const struct mt9m001_datafmt mt9m001_colour_fmts[] = { + /* + * Order important: first natively supported, + * second supported with a GPIO extender + */ + {MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB}, +}; + +static const struct mt9m001_datafmt mt9m001_monochrome_fmts[] = { + /* Order important - see above */ + {MEDIA_BUS_FMT_Y10_1X10, V4L2_COLORSPACE_JPEG}, + {MEDIA_BUS_FMT_Y8_1X8, V4L2_COLORSPACE_JPEG}, +}; + +struct mt9m001 { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct { + /* exposure/auto-exposure cluster */ + struct v4l2_ctrl *autoexposure; + struct v4l2_ctrl *exposure; + }; + bool streaming; + struct mutex mutex; + struct v4l2_rect rect; /* Sensor window */ + struct clk *clk; + struct gpio_desc *standby_gpio; + struct gpio_desc *reset_gpio; + const struct mt9m001_datafmt *fmt; + const struct mt9m001_datafmt *fmts; + int num_fmts; + unsigned int total_h; + unsigned short y_skip_top; /* Lines to skip at the top */ + struct media_pad pad; +}; + +static struct mt9m001 *to_mt9m001(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct mt9m001, subdev); +} + +static int reg_read(struct i2c_client *client, const u8 reg) +{ + return i2c_smbus_read_word_swapped(client, reg); +} + +static int reg_write(struct i2c_client *client, const u8 reg, + const u16 data) +{ + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static int reg_set(struct i2c_client *client, const u8 reg, + const u16 data) +{ + int ret; + + ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, ret | data); +} + +static int reg_clear(struct i2c_client *client, const u8 reg, + const u16 data) +{ + int ret; + + ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, ret & ~data); +} + +struct mt9m001_reg { + u8 reg; + u16 data; +}; + +static int multi_reg_write(struct i2c_client *client, + const struct mt9m001_reg *regs, int num) +{ + int i; + + for (i = 0; i < num; i++) { + int ret = reg_write(client, regs[i].reg, regs[i].data); + + if (ret) + return ret; + } + + return 0; +} + +static int mt9m001_init(struct i2c_client *client) +{ + static const struct mt9m001_reg init_regs[] = { + /* + * Issue a soft reset. This returns all registers to their + * default values. + */ + { MT9M001_RESET, 1 }, + { MT9M001_RESET, 0 }, + /* Disable chip, synchronous option update */ + { MT9M001_OUTPUT_CONTROL, 0 } + }; + + dev_dbg(&client->dev, "%s\n", __func__); + + return multi_reg_write(client, init_regs, ARRAY_SIZE(init_regs)); +} + +static int mt9m001_apply_selection(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + const struct mt9m001_reg regs[] = { + /* Blanking and start values - default... */ + { MT9M001_HORIZONTAL_BLANKING, MT9M001_DEFAULT_HBLANK }, + { MT9M001_VERTICAL_BLANKING, MT9M001_DEFAULT_VBLANK }, + /* + * The caller provides a supported format, as verified per + * call to .set_fmt(FORMAT_TRY). + */ + { MT9M001_COLUMN_START, mt9m001->rect.left }, + { MT9M001_ROW_START, mt9m001->rect.top }, + { MT9M001_WINDOW_WIDTH, mt9m001->rect.width - 1 }, + { MT9M001_WINDOW_HEIGHT, + mt9m001->rect.height + mt9m001->y_skip_top - 1 }, + }; + + return multi_reg_write(client, regs, ARRAY_SIZE(regs)); +} + +static int mt9m001_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + int ret = 0; + + mutex_lock(&mt9m001->mutex); + + if (mt9m001->streaming == enable) + goto done; + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) + goto put_unlock; + + ret = mt9m001_apply_selection(sd); + if (ret) + goto put_unlock; + + ret = __v4l2_ctrl_handler_setup(&mt9m001->hdl); + if (ret) + goto put_unlock; + + /* Switch to master "normal" mode */ + ret = reg_write(client, MT9M001_OUTPUT_CONTROL, 2); + if (ret < 0) + goto put_unlock; + } else { + /* Switch to master stop sensor readout */ + reg_write(client, MT9M001_OUTPUT_CONTROL, 0); + pm_runtime_put(&client->dev); + } + + mt9m001->streaming = enable; +done: + mutex_unlock(&mt9m001->mutex); + + return 0; + +put_unlock: + pm_runtime_put(&client->dev); + mutex_unlock(&mt9m001->mutex); + + return ret; +} + +static int mt9m001_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + struct v4l2_rect rect = sel->r; + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + if (mt9m001->fmts == mt9m001_colour_fmts) + /* + * Bayer format - even number of rows for simplicity, + * but let the user play with the top row. + */ + rect.height = ALIGN(rect.height, 2); + + /* Datasheet requirement: see register description */ + rect.width = ALIGN(rect.width, 2); + rect.left = ALIGN(rect.left, 2); + + rect.width = clamp_t(u32, rect.width, MT9M001_MIN_WIDTH, + MT9M001_MAX_WIDTH); + rect.left = clamp_t(u32, rect.left, MT9M001_COLUMN_SKIP, + MT9M001_COLUMN_SKIP + MT9M001_MAX_WIDTH - rect.width); + + rect.height = clamp_t(u32, rect.height, MT9M001_MIN_HEIGHT, + MT9M001_MAX_HEIGHT); + rect.top = clamp_t(u32, rect.top, MT9M001_ROW_SKIP, + MT9M001_ROW_SKIP + MT9M001_MAX_HEIGHT - rect.height); + + mt9m001->total_h = rect.height + mt9m001->y_skip_top + + MT9M001_DEFAULT_VBLANK; + + mt9m001->rect = rect; + + return 0; +} + +static int mt9m001_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = MT9M001_COLUMN_SKIP; + sel->r.top = MT9M001_ROW_SKIP; + sel->r.width = MT9M001_MAX_WIDTH; + sel->r.height = MT9M001_MAX_HEIGHT; + return 0; + case V4L2_SEL_TGT_CROP: + sel->r = mt9m001->rect; + return 0; + default: + return -EINVAL; + } +} + +static int mt9m001_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + struct v4l2_mbus_framefmt *mf = &format->format; + + if (format->pad) + return -EINVAL; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + format->format = *mf; + return 0; + } + + mf->width = mt9m001->rect.width; + mf->height = mt9m001->rect.height; + mf->code = mt9m001->fmt->code; + mf->colorspace = mt9m001->fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + mf->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + mf->quantization = V4L2_QUANTIZATION_DEFAULT; + mf->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return 0; +} + +static int mt9m001_s_fmt(struct v4l2_subdev *sd, + const struct mt9m001_datafmt *fmt, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + struct v4l2_subdev_selection sel = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .target = V4L2_SEL_TGT_CROP, + .r.left = mt9m001->rect.left, + .r.top = mt9m001->rect.top, + .r.width = mf->width, + .r.height = mf->height, + }; + int ret; + + /* No support for scaling so far, just crop. TODO: use skipping */ + ret = mt9m001_set_selection(sd, NULL, &sel); + if (!ret) { + mf->width = mt9m001->rect.width; + mf->height = mt9m001->rect.height; + mt9m001->fmt = fmt; + mf->colorspace = fmt->colorspace; + } + + return ret; +} + +static int mt9m001_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + const struct mt9m001_datafmt *fmt; + + if (format->pad) + return -EINVAL; + + v4l_bound_align_image(&mf->width, MT9M001_MIN_WIDTH, + MT9M001_MAX_WIDTH, 1, + &mf->height, MT9M001_MIN_HEIGHT + mt9m001->y_skip_top, + MT9M001_MAX_HEIGHT + mt9m001->y_skip_top, 0, 0); + + if (mt9m001->fmts == mt9m001_colour_fmts) + mf->height = ALIGN(mf->height - 1, 2); + + fmt = mt9m001_find_datafmt(mf->code, mt9m001->fmts, + mt9m001->num_fmts); + if (!fmt) { + fmt = mt9m001->fmt; + mf->code = fmt->code; + } + + mf->colorspace = fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + mf->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + mf->quantization = V4L2_QUANTIZATION_DEFAULT; + mf->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return mt9m001_s_fmt(sd, fmt, mf); + cfg->try_fmt = *mf; + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9m001_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0xff) + return -EINVAL; + + reg->size = 2; + reg->val = reg_read(client, reg->reg); + + if (reg->val > 0xffff) + return -EIO; + + return 0; +} + +static int mt9m001_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0xff) + return -EINVAL; + + if (reg_write(client, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static int mt9m001_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mt9m001 *mt9m001 = to_mt9m001(client); + int ret; + + ret = clk_prepare_enable(mt9m001->clk); + if (ret) + return ret; + + if (mt9m001->standby_gpio) { + gpiod_set_value_cansleep(mt9m001->standby_gpio, 0); + usleep_range(1000, 2000); + } + + if (mt9m001->reset_gpio) { + gpiod_set_value_cansleep(mt9m001->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(mt9m001->reset_gpio, 0); + usleep_range(1000, 2000); + } + + return 0; +} + +static int mt9m001_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mt9m001 *mt9m001 = to_mt9m001(client); + + gpiod_set_value_cansleep(mt9m001->standby_gpio, 1); + clk_disable_unprepare(mt9m001->clk); + + return 0; +} + +static int mt9m001_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m001 *mt9m001 = container_of(ctrl->handler, + struct mt9m001, hdl); + s32 min, max; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE_AUTO: + min = mt9m001->exposure->minimum; + max = mt9m001->exposure->maximum; + mt9m001->exposure->val = + (524 + (mt9m001->total_h - 1) * (max - min)) / 1048 + min; + break; + } + return 0; +} + +static int mt9m001_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m001 *mt9m001 = container_of(ctrl->handler, + struct mt9m001, hdl); + struct v4l2_subdev *sd = &mt9m001->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct v4l2_ctrl *exp = mt9m001->exposure; + int data; + int ret; + + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (ctrl->val) + ret = reg_set(client, MT9M001_READ_OPTIONS2, 0x8000); + else + ret = reg_clear(client, MT9M001_READ_OPTIONS2, 0x8000); + break; + + case V4L2_CID_GAIN: + /* See Datasheet Table 7, Gain settings. */ + if (ctrl->val <= ctrl->default_value) { + /* Pack it into 0..1 step 0.125, register values 0..8 */ + unsigned long range = ctrl->default_value - ctrl->minimum; + data = ((ctrl->val - (s32)ctrl->minimum) * 8 + range / 2) / range; + + dev_dbg(&client->dev, "Setting gain %d\n", data); + ret = reg_write(client, MT9M001_GLOBAL_GAIN, data); + } else { + /* Pack it into 1.125..15 variable step, register values 9..67 */ + /* We assume qctrl->maximum - qctrl->default_value - 1 > 0 */ + unsigned long range = ctrl->maximum - ctrl->default_value - 1; + unsigned long gain = ((ctrl->val - (s32)ctrl->default_value - 1) * + 111 + range / 2) / range + 9; + + if (gain <= 32) + data = gain; + else if (gain <= 64) + data = ((gain - 32) * 16 + 16) / 32 + 80; + else + data = ((gain - 64) * 7 + 28) / 56 + 96; + + dev_dbg(&client->dev, "Setting gain from %d to %d\n", + reg_read(client, MT9M001_GLOBAL_GAIN), data); + ret = reg_write(client, MT9M001_GLOBAL_GAIN, data); + } + break; + + case V4L2_CID_EXPOSURE_AUTO: + if (ctrl->val == V4L2_EXPOSURE_MANUAL) { + unsigned long range = exp->maximum - exp->minimum; + unsigned long shutter = ((exp->val - (s32)exp->minimum) * 1048 + + range / 2) / range + 1; + + dev_dbg(&client->dev, + "Setting shutter width from %d to %lu\n", + reg_read(client, MT9M001_SHUTTER_WIDTH), shutter); + ret = reg_write(client, MT9M001_SHUTTER_WIDTH, shutter); + } else { + mt9m001->total_h = mt9m001->rect.height + + mt9m001->y_skip_top + MT9M001_DEFAULT_VBLANK; + ret = reg_write(client, MT9M001_SHUTTER_WIDTH, + mt9m001->total_h); + } + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +/* + * Interface active, can use i2c. If it fails, it can indeed mean, that + * this wasn't our capture interface, so, we wait for the right one + */ +static int mt9m001_video_probe(struct i2c_client *client) +{ + struct mt9m001 *mt9m001 = to_mt9m001(client); + s32 data; + int ret; + + /* Enable the chip */ + data = reg_write(client, MT9M001_CHIP_ENABLE, 1); + dev_dbg(&client->dev, "write: %d\n", data); + + /* Read out the chip version register */ + data = reg_read(client, MT9M001_CHIP_VERSION); + + /* must be 0x8411 or 0x8421 for colour sensor and 8431 for bw */ + switch (data) { + case 0x8411: + case 0x8421: + mt9m001->fmts = mt9m001_colour_fmts; + mt9m001->num_fmts = ARRAY_SIZE(mt9m001_colour_fmts); + break; + case 0x8431: + mt9m001->fmts = mt9m001_monochrome_fmts; + mt9m001->num_fmts = ARRAY_SIZE(mt9m001_monochrome_fmts); + break; + default: + dev_err(&client->dev, + "No MT9M001 chip detected, register read %x\n", data); + ret = -ENODEV; + goto done; + } + + mt9m001->fmt = &mt9m001->fmts[0]; + + dev_info(&client->dev, "Detected a MT9M001 chip ID %x (%s)\n", data, + data == 0x8431 ? "C12STM" : "C12ST"); + + ret = mt9m001_init(client); + if (ret < 0) { + dev_err(&client->dev, "Failed to initialise the camera\n"); + goto done; + } + + /* mt9m001_init() has reset the chip, returning registers to defaults */ + ret = v4l2_ctrl_handler_setup(&mt9m001->hdl); + +done: + return ret; +} + +static int mt9m001_g_skip_top_lines(struct v4l2_subdev *sd, u32 *lines) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + + *lines = mt9m001->y_skip_top; + + return 0; +} + +static const struct v4l2_ctrl_ops mt9m001_ctrl_ops = { + .g_volatile_ctrl = mt9m001_g_volatile_ctrl, + .s_ctrl = mt9m001_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops mt9m001_subdev_core_ops = { + .log_status = v4l2_ctrl_subdev_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9m001_g_register, + .s_register = mt9m001_s_register, +#endif +}; + +static int mt9m001_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, cfg, 0); + + try_fmt->width = MT9M001_MAX_WIDTH; + try_fmt->height = MT9M001_MAX_HEIGHT; + try_fmt->code = mt9m001->fmts[0].code; + try_fmt->colorspace = mt9m001->fmts[0].colorspace; + try_fmt->field = V4L2_FIELD_NONE; + try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT; + try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return 0; +} + +static int mt9m001_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + + if (code->pad || code->index >= mt9m001->num_fmts) + return -EINVAL; + + code->code = mt9m001->fmts[code->index].code; + return 0; +} + +static int mt9m001_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + /* MT9M001 has all capture_format parameters fixed */ + cfg->flags = V4L2_MBUS_PCLK_SAMPLE_FALLING | + V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH | + V4L2_MBUS_DATA_ACTIVE_HIGH | V4L2_MBUS_MASTER; + cfg->type = V4L2_MBUS_PARALLEL; + + return 0; +} + +static const struct v4l2_subdev_video_ops mt9m001_subdev_video_ops = { + .s_stream = mt9m001_s_stream, +}; + +static const struct v4l2_subdev_sensor_ops mt9m001_subdev_sensor_ops = { + .g_skip_top_lines = mt9m001_g_skip_top_lines, +}; + +static const struct v4l2_subdev_pad_ops mt9m001_subdev_pad_ops = { + .init_cfg = mt9m001_init_cfg, + .enum_mbus_code = mt9m001_enum_mbus_code, + .get_selection = mt9m001_get_selection, + .set_selection = mt9m001_set_selection, + .get_fmt = mt9m001_get_fmt, + .set_fmt = mt9m001_set_fmt, + .get_mbus_config = mt9m001_get_mbus_config, +}; + +static const struct v4l2_subdev_ops mt9m001_subdev_ops = { + .core = &mt9m001_subdev_core_ops, + .video = &mt9m001_subdev_video_ops, + .sensor = &mt9m001_subdev_sensor_ops, + .pad = &mt9m001_subdev_pad_ops, +}; + +static int mt9m001_probe(struct i2c_client *client) +{ + struct mt9m001 *mt9m001; + struct i2c_adapter *adapter = client->adapter; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9m001 = devm_kzalloc(&client->dev, sizeof(*mt9m001), GFP_KERNEL); + if (!mt9m001) + return -ENOMEM; + + mt9m001->clk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(mt9m001->clk)) + return PTR_ERR(mt9m001->clk); + + mt9m001->standby_gpio = devm_gpiod_get_optional(&client->dev, "standby", + GPIOD_OUT_LOW); + if (IS_ERR(mt9m001->standby_gpio)) + return PTR_ERR(mt9m001->standby_gpio); + + mt9m001->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(mt9m001->reset_gpio)) + return PTR_ERR(mt9m001->reset_gpio); + + v4l2_i2c_subdev_init(&mt9m001->subdev, client, &mt9m001_subdev_ops); + mt9m001->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + v4l2_ctrl_handler_init(&mt9m001->hdl, 4); + v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops, + V4L2_CID_GAIN, 0, 127, 1, 64); + mt9m001->exposure = v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 255, 1, 255); + /* + * Simulated autoexposure. If enabled, we calculate shutter width + * ourselves in the driver based on vertical blanking and frame width + */ + mt9m001->autoexposure = v4l2_ctrl_new_std_menu(&mt9m001->hdl, + &mt9m001_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0, + V4L2_EXPOSURE_AUTO); + mt9m001->subdev.ctrl_handler = &mt9m001->hdl; + if (mt9m001->hdl.error) + return mt9m001->hdl.error; + + v4l2_ctrl_auto_cluster(2, &mt9m001->autoexposure, + V4L2_EXPOSURE_MANUAL, true); + + mutex_init(&mt9m001->mutex); + mt9m001->hdl.lock = &mt9m001->mutex; + + /* Second stage probe - when a capture adapter is there */ + mt9m001->y_skip_top = 0; + mt9m001->rect.left = MT9M001_COLUMN_SKIP; + mt9m001->rect.top = MT9M001_ROW_SKIP; + mt9m001->rect.width = MT9M001_MAX_WIDTH; + mt9m001->rect.height = MT9M001_MAX_HEIGHT; + + ret = mt9m001_power_on(&client->dev); + if (ret) + goto error_hdl_free; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + + ret = mt9m001_video_probe(client); + if (ret) + goto error_power_off; + + mt9m001->pad.flags = MEDIA_PAD_FL_SOURCE; + mt9m001->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&mt9m001->subdev.entity, 1, &mt9m001->pad); + if (ret) + goto error_power_off; + + ret = v4l2_async_register_subdev(&mt9m001->subdev); + if (ret) + goto error_entity_cleanup; + + pm_runtime_idle(&client->dev); + + return 0; + +error_entity_cleanup: + media_entity_cleanup(&mt9m001->subdev.entity); +error_power_off: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + mt9m001_power_off(&client->dev); + +error_hdl_free: + v4l2_ctrl_handler_free(&mt9m001->hdl); + mutex_destroy(&mt9m001->mutex); + + return ret; +} + +static int mt9m001_remove(struct i2c_client *client) +{ + struct mt9m001 *mt9m001 = to_mt9m001(client); + + pm_runtime_get_sync(&client->dev); + + v4l2_async_unregister_subdev(&mt9m001->subdev); + media_entity_cleanup(&mt9m001->subdev.entity); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + mt9m001_power_off(&client->dev); + + v4l2_ctrl_handler_free(&mt9m001->hdl); + mutex_destroy(&mt9m001->mutex); + + return 0; +} + +static const struct i2c_device_id mt9m001_id[] = { + { "mt9m001", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9m001_id); + +static const struct dev_pm_ops mt9m001_pm_ops = { + SET_RUNTIME_PM_OPS(mt9m001_power_off, mt9m001_power_on, NULL) +}; + +static const struct of_device_id mt9m001_of_match[] = { + { .compatible = "onnn,mt9m001", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mt9m001_of_match); + +static struct i2c_driver mt9m001_i2c_driver = { + .driver = { + .name = "mt9m001", + .pm = &mt9m001_pm_ops, + .of_match_table = mt9m001_of_match, + }, + .probe_new = mt9m001_probe, + .remove = mt9m001_remove, + .id_table = mt9m001_id, +}; + +module_i2c_driver(mt9m001_i2c_driver); + +MODULE_DESCRIPTION("Micron MT9M001 Camera driver"); +MODULE_AUTHOR("Guennadi Liakhovetski <kernel@pengutronix.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/mt9m032.c b/drivers/media/i2c/mt9m032.c new file mode 100644 index 000000000..5a4c0f9d1 --- /dev/null +++ b/drivers/media/i2c/mt9m032.c @@ -0,0 +1,889 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MT9M032 CMOS Image Sensor from Micron + * + * Copyright (C) 2010-2011 Lund Engineering + * Contact: Gil Lund <gwlund@lundeng.com> + * Author: Martin Hostettler <martin@neutronstar.dyndns.org> + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/v4l2-mediabus.h> + +#include <media/media-entity.h> +#include <media/i2c/mt9m032.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#include "aptina-pll.h" + +/* + * width and height include active boundary and black parts + * + * column 0- 15 active boundary + * column 16-1455 image + * column 1456-1471 active boundary + * column 1472-1599 black + * + * row 0- 51 black + * row 53- 59 active boundary + * row 60-1139 image + * row 1140-1147 active boundary + * row 1148-1151 black + */ + +#define MT9M032_PIXEL_ARRAY_WIDTH 1600 +#define MT9M032_PIXEL_ARRAY_HEIGHT 1152 + +#define MT9M032_CHIP_VERSION 0x00 +#define MT9M032_CHIP_VERSION_VALUE 0x1402 +#define MT9M032_ROW_START 0x01 +#define MT9M032_ROW_START_MIN 0 +#define MT9M032_ROW_START_MAX 1152 +#define MT9M032_ROW_START_DEF 60 +#define MT9M032_COLUMN_START 0x02 +#define MT9M032_COLUMN_START_MIN 0 +#define MT9M032_COLUMN_START_MAX 1600 +#define MT9M032_COLUMN_START_DEF 16 +#define MT9M032_ROW_SIZE 0x03 +#define MT9M032_ROW_SIZE_MIN 32 +#define MT9M032_ROW_SIZE_MAX 1152 +#define MT9M032_ROW_SIZE_DEF 1080 +#define MT9M032_COLUMN_SIZE 0x04 +#define MT9M032_COLUMN_SIZE_MIN 32 +#define MT9M032_COLUMN_SIZE_MAX 1600 +#define MT9M032_COLUMN_SIZE_DEF 1440 +#define MT9M032_HBLANK 0x05 +#define MT9M032_VBLANK 0x06 +#define MT9M032_VBLANK_MAX 0x7ff +#define MT9M032_SHUTTER_WIDTH_HIGH 0x08 +#define MT9M032_SHUTTER_WIDTH_LOW 0x09 +#define MT9M032_SHUTTER_WIDTH_MIN 1 +#define MT9M032_SHUTTER_WIDTH_MAX 1048575 +#define MT9M032_SHUTTER_WIDTH_DEF 1943 +#define MT9M032_PIX_CLK_CTRL 0x0a +#define MT9M032_PIX_CLK_CTRL_INV_PIXCLK 0x8000 +#define MT9M032_RESTART 0x0b +#define MT9M032_RESET 0x0d +#define MT9M032_PLL_CONFIG1 0x11 +#define MT9M032_PLL_CONFIG1_PREDIV_MASK 0x3f +#define MT9M032_PLL_CONFIG1_MUL_SHIFT 8 +#define MT9M032_READ_MODE1 0x1e +#define MT9M032_READ_MODE1_OUTPUT_BAD_FRAMES (1 << 13) +#define MT9M032_READ_MODE1_MAINTAIN_FRAME_RATE (1 << 12) +#define MT9M032_READ_MODE1_XOR_LINE_VALID (1 << 11) +#define MT9M032_READ_MODE1_CONT_LINE_VALID (1 << 10) +#define MT9M032_READ_MODE1_INVERT_TRIGGER (1 << 9) +#define MT9M032_READ_MODE1_SNAPSHOT (1 << 8) +#define MT9M032_READ_MODE1_GLOBAL_RESET (1 << 7) +#define MT9M032_READ_MODE1_BULB_EXPOSURE (1 << 6) +#define MT9M032_READ_MODE1_INVERT_STROBE (1 << 5) +#define MT9M032_READ_MODE1_STROBE_ENABLE (1 << 4) +#define MT9M032_READ_MODE1_STROBE_START_TRIG1 (0 << 2) +#define MT9M032_READ_MODE1_STROBE_START_EXP (1 << 2) +#define MT9M032_READ_MODE1_STROBE_START_SHUTTER (2 << 2) +#define MT9M032_READ_MODE1_STROBE_START_TRIG2 (3 << 2) +#define MT9M032_READ_MODE1_STROBE_END_TRIG1 (0 << 0) +#define MT9M032_READ_MODE1_STROBE_END_EXP (1 << 0) +#define MT9M032_READ_MODE1_STROBE_END_SHUTTER (2 << 0) +#define MT9M032_READ_MODE1_STROBE_END_TRIG2 (3 << 0) +#define MT9M032_READ_MODE2 0x20 +#define MT9M032_READ_MODE2_VFLIP_SHIFT 15 +#define MT9M032_READ_MODE2_HFLIP_SHIFT 14 +#define MT9M032_READ_MODE2_ROW_BLC 0x40 +#define MT9M032_GAIN_GREEN1 0x2b +#define MT9M032_GAIN_BLUE 0x2c +#define MT9M032_GAIN_RED 0x2d +#define MT9M032_GAIN_GREEN2 0x2e + +/* write only */ +#define MT9M032_GAIN_ALL 0x35 +#define MT9M032_GAIN_DIGITAL_MASK 0x7f +#define MT9M032_GAIN_DIGITAL_SHIFT 8 +#define MT9M032_GAIN_AMUL_SHIFT 6 +#define MT9M032_GAIN_ANALOG_MASK 0x3f +#define MT9M032_FORMATTER1 0x9e +#define MT9M032_FORMATTER1_PLL_P1_6 (1 << 8) +#define MT9M032_FORMATTER1_PARALLEL (1 << 12) +#define MT9M032_FORMATTER2 0x9f +#define MT9M032_FORMATTER2_DOUT_EN 0x1000 +#define MT9M032_FORMATTER2_PIXCLK_EN 0x2000 + +/* + * The available MT9M032 datasheet is missing documentation for register 0x10 + * MT9P031 seems to be close enough, so use constants from that datasheet for + * now. + * But keep the name MT9P031 to remind us, that this isn't really confirmed + * for this sensor. + */ +#define MT9P031_PLL_CONTROL 0x10 +#define MT9P031_PLL_CONTROL_PWROFF 0x0050 +#define MT9P031_PLL_CONTROL_PWRON 0x0051 +#define MT9P031_PLL_CONTROL_USEPLL 0x0052 + +struct mt9m032 { + struct v4l2_subdev subdev; + struct media_pad pad; + struct mt9m032_platform_data *pdata; + + unsigned int pix_clock; + + struct v4l2_ctrl_handler ctrls; + struct { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + + struct mutex lock; /* Protects streaming, format, interval and crop */ + + bool streaming; + + struct v4l2_mbus_framefmt format; + struct v4l2_rect crop; + struct v4l2_fract frame_interval; +}; + +#define to_mt9m032(sd) container_of(sd, struct mt9m032, subdev) +#define to_dev(sensor) \ + (&((struct i2c_client *)v4l2_get_subdevdata(&(sensor)->subdev))->dev) + +static int mt9m032_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_word_swapped(client, reg); +} + +static int mt9m032_write(struct i2c_client *client, u8 reg, const u16 data) +{ + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static u32 mt9m032_row_time(struct mt9m032 *sensor, unsigned int width) +{ + unsigned int effective_width; + u32 ns; + + effective_width = width + 716; /* empirical value */ + ns = div_u64(1000000000ULL * effective_width, sensor->pix_clock); + dev_dbg(to_dev(sensor), "MT9M032 line time: %u ns\n", ns); + return ns; +} + +static int mt9m032_update_timing(struct mt9m032 *sensor, + struct v4l2_fract *interval) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + struct v4l2_rect *crop = &sensor->crop; + unsigned int min_vblank; + unsigned int vblank; + u32 row_time; + + if (!interval) + interval = &sensor->frame_interval; + + row_time = mt9m032_row_time(sensor, crop->width); + + vblank = div_u64(1000000000ULL * interval->numerator, + (u64)row_time * interval->denominator) + - crop->height; + + if (vblank > MT9M032_VBLANK_MAX) { + /* hardware limits to 11 bit values */ + interval->denominator = 1000; + interval->numerator = + div_u64((crop->height + MT9M032_VBLANK_MAX) * + (u64)row_time * interval->denominator, + 1000000000ULL); + vblank = div_u64(1000000000ULL * interval->numerator, + (u64)row_time * interval->denominator) + - crop->height; + } + /* enforce minimal 1.6ms blanking time. */ + min_vblank = 1600000 / row_time; + vblank = clamp_t(unsigned int, vblank, min_vblank, MT9M032_VBLANK_MAX); + + return mt9m032_write(client, MT9M032_VBLANK, vblank); +} + +static int mt9m032_update_geom_timing(struct mt9m032 *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int ret; + + ret = mt9m032_write(client, MT9M032_COLUMN_SIZE, + sensor->crop.width - 1); + if (!ret) + ret = mt9m032_write(client, MT9M032_ROW_SIZE, + sensor->crop.height - 1); + if (!ret) + ret = mt9m032_write(client, MT9M032_COLUMN_START, + sensor->crop.left); + if (!ret) + ret = mt9m032_write(client, MT9M032_ROW_START, + sensor->crop.top); + if (!ret) + ret = mt9m032_update_timing(sensor, NULL); + return ret; +} + +static int update_formatter2(struct mt9m032 *sensor, bool streaming) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + u16 reg_val = MT9M032_FORMATTER2_DOUT_EN + | 0x0070; /* parts reserved! */ + /* possibly for changing to 14-bit mode */ + + if (streaming) + reg_val |= MT9M032_FORMATTER2_PIXCLK_EN; /* pixclock enable */ + + return mt9m032_write(client, MT9M032_FORMATTER2, reg_val); +} + +static int mt9m032_setup_pll(struct mt9m032 *sensor) +{ + static const struct aptina_pll_limits limits = { + .ext_clock_min = 8000000, + .ext_clock_max = 16500000, + .int_clock_min = 2000000, + .int_clock_max = 24000000, + .out_clock_min = 322000000, + .out_clock_max = 693000000, + .pix_clock_max = 99000000, + .n_min = 1, + .n_max = 64, + .m_min = 16, + .m_max = 255, + .p1_min = 6, + .p1_max = 7, + }; + + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + struct mt9m032_platform_data *pdata = sensor->pdata; + struct aptina_pll pll; + u16 reg_val; + int ret; + + pll.ext_clock = pdata->ext_clock; + pll.pix_clock = pdata->pix_clock; + + ret = aptina_pll_calculate(&client->dev, &limits, &pll); + if (ret < 0) + return ret; + + sensor->pix_clock = pdata->pix_clock; + + ret = mt9m032_write(client, MT9M032_PLL_CONFIG1, + (pll.m << MT9M032_PLL_CONFIG1_MUL_SHIFT) | + ((pll.n - 1) & MT9M032_PLL_CONFIG1_PREDIV_MASK)); + if (!ret) + ret = mt9m032_write(client, MT9P031_PLL_CONTROL, + MT9P031_PLL_CONTROL_PWRON | + MT9P031_PLL_CONTROL_USEPLL); + if (!ret) /* more reserved, Continuous, Master Mode */ + ret = mt9m032_write(client, MT9M032_READ_MODE1, 0x8000 | + MT9M032_READ_MODE1_STROBE_START_EXP | + MT9M032_READ_MODE1_STROBE_END_SHUTTER); + if (!ret) { + reg_val = (pll.p1 == 6 ? MT9M032_FORMATTER1_PLL_P1_6 : 0) + | MT9M032_FORMATTER1_PARALLEL | 0x001e; /* 14-bit */ + ret = mt9m032_write(client, MT9M032_FORMATTER1, reg_val); + } + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Subdev pad operations + */ + +static int mt9m032_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_Y8_1X8; + return 0; +} + +static int mt9m032_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index != 0 || fse->code != MEDIA_BUS_FMT_Y8_1X8) + return -EINVAL; + + fse->min_width = MT9M032_COLUMN_SIZE_DEF; + fse->max_width = MT9M032_COLUMN_SIZE_DEF; + fse->min_height = MT9M032_ROW_SIZE_DEF; + fse->max_height = MT9M032_ROW_SIZE_DEF; + + return 0; +} + +/** + * __mt9m032_get_pad_crop() - get crop rect + * @sensor: pointer to the sensor struct + * @cfg: v4l2_subdev_pad_config for getting the try crop rect from + * @which: select try or active crop rect + * + * Returns a pointer the current active or fh relative try crop rect + */ +static struct v4l2_rect * +__mt9m032_get_pad_crop(struct mt9m032 *sensor, struct v4l2_subdev_pad_config *cfg, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&sensor->subdev, cfg, 0); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &sensor->crop; + default: + return NULL; + } +} + +/** + * __mt9m032_get_pad_format() - get format + * @sensor: pointer to the sensor struct + * @cfg: v4l2_subdev_pad_config for getting the try format from + * @which: select try or active format + * + * Returns a pointer the current active or fh relative try format + */ +static struct v4l2_mbus_framefmt * +__mt9m032_get_pad_format(struct mt9m032 *sensor, struct v4l2_subdev_pad_config *cfg, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&sensor->subdev, cfg, 0); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &sensor->format; + default: + return NULL; + } +} + +static int mt9m032_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + + mutex_lock(&sensor->lock); + fmt->format = *__mt9m032_get_pad_format(sensor, cfg, fmt->which); + mutex_unlock(&sensor->lock); + + return 0; +} + +static int mt9m032_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + int ret; + + mutex_lock(&sensor->lock); + + if (sensor->streaming && fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ret = -EBUSY; + goto done; + } + + /* Scaling is not supported, the format is thus fixed. */ + fmt->format = *__mt9m032_get_pad_format(sensor, cfg, fmt->which); + ret = 0; + +done: + mutex_unlock(&sensor->lock); + return ret; +} + +static int mt9m032_get_pad_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + mutex_lock(&sensor->lock); + sel->r = *__mt9m032_get_pad_crop(sensor, cfg, sel->which); + mutex_unlock(&sensor->lock); + + return 0; +} + +static int mt9m032_set_pad_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *__crop; + struct v4l2_rect rect; + int ret = 0; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + mutex_lock(&sensor->lock); + + if (sensor->streaming && sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ret = -EBUSY; + goto done; + } + + /* Clamp the crop rectangle boundaries and align them to a multiple of 2 + * pixels to ensure a GRBG Bayer pattern. + */ + rect.left = clamp(ALIGN(sel->r.left, 2), MT9M032_COLUMN_START_MIN, + MT9M032_COLUMN_START_MAX); + rect.top = clamp(ALIGN(sel->r.top, 2), MT9M032_ROW_START_MIN, + MT9M032_ROW_START_MAX); + rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), + MT9M032_COLUMN_SIZE_MIN, MT9M032_COLUMN_SIZE_MAX); + rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), + MT9M032_ROW_SIZE_MIN, MT9M032_ROW_SIZE_MAX); + + rect.width = min_t(unsigned int, rect.width, + MT9M032_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min_t(unsigned int, rect.height, + MT9M032_PIXEL_ARRAY_HEIGHT - rect.top); + + __crop = __mt9m032_get_pad_crop(sensor, cfg, sel->which); + + if (rect.width != __crop->width || rect.height != __crop->height) { + /* Reset the output image size if the crop rectangle size has + * been modified. + */ + format = __mt9m032_get_pad_format(sensor, cfg, sel->which); + format->width = rect.width; + format->height = rect.height; + } + + *__crop = rect; + sel->r = rect; + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) + ret = mt9m032_update_geom_timing(sensor); + +done: + mutex_unlock(&sensor->lock); + return ret; +} + +static int mt9m032_get_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fi) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + + mutex_lock(&sensor->lock); + memset(fi, 0, sizeof(*fi)); + fi->interval = sensor->frame_interval; + mutex_unlock(&sensor->lock); + + return 0; +} + +static int mt9m032_set_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fi) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + int ret; + + mutex_lock(&sensor->lock); + + if (sensor->streaming) { + ret = -EBUSY; + goto done; + } + + /* Avoid divisions by 0. */ + if (fi->interval.denominator == 0) + fi->interval.denominator = 1; + + ret = mt9m032_update_timing(sensor, &fi->interval); + if (!ret) + sensor->frame_interval = fi->interval; + +done: + mutex_unlock(&sensor->lock); + return ret; +} + +static int mt9m032_s_stream(struct v4l2_subdev *subdev, int streaming) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + int ret; + + mutex_lock(&sensor->lock); + ret = update_formatter2(sensor, streaming); + if (!ret) + sensor->streaming = streaming; + mutex_unlock(&sensor->lock); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9m032_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct mt9m032 *sensor = to_mt9m032(sd); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int val; + + if (reg->reg > 0xff) + return -EINVAL; + + val = mt9m032_read(client, reg->reg); + if (val < 0) + return -EIO; + + reg->size = 2; + reg->val = val; + + return 0; +} + +static int mt9m032_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct mt9m032 *sensor = to_mt9m032(sd); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + + if (reg->reg > 0xff) + return -EINVAL; + + return mt9m032_write(client, reg->reg, reg->val); +} +#endif + +/* ----------------------------------------------------------------------------- + * V4L2 subdev control operations + */ + +static int update_read_mode2(struct mt9m032 *sensor, bool vflip, bool hflip) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int reg_val = (vflip << MT9M032_READ_MODE2_VFLIP_SHIFT) + | (hflip << MT9M032_READ_MODE2_HFLIP_SHIFT) + | MT9M032_READ_MODE2_ROW_BLC + | 0x0007; + + return mt9m032_write(client, MT9M032_READ_MODE2, reg_val); +} + +static int mt9m032_set_gain(struct mt9m032 *sensor, s32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int digital_gain_val; /* in 1/8th (0..127) */ + int analog_mul; /* 0 or 1 */ + int analog_gain_val; /* in 1/16th. (0..63) */ + u16 reg_val; + + digital_gain_val = 51; /* from setup example */ + + if (val < 63) { + analog_mul = 0; + analog_gain_val = val; + } else { + analog_mul = 1; + analog_gain_val = val / 2; + } + + /* a_gain = (1 + analog_mul) + (analog_gain_val + 1) / 16 */ + /* overall_gain = a_gain * (1 + digital_gain_val / 8) */ + + reg_val = ((digital_gain_val & MT9M032_GAIN_DIGITAL_MASK) + << MT9M032_GAIN_DIGITAL_SHIFT) + | ((analog_mul & 1) << MT9M032_GAIN_AMUL_SHIFT) + | (analog_gain_val & MT9M032_GAIN_ANALOG_MASK); + + return mt9m032_write(client, MT9M032_GAIN_ALL, reg_val); +} + +static int mt9m032_try_ctrl(struct v4l2_ctrl *ctrl) +{ + if (ctrl->id == V4L2_CID_GAIN && ctrl->val >= 63) { + /* round because of multiplier used for values >= 63 */ + ctrl->val &= ~1; + } + + return 0; +} + +static int mt9m032_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m032 *sensor = + container_of(ctrl->handler, struct mt9m032, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int ret; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + return mt9m032_set_gain(sensor, ctrl->val); + + case V4L2_CID_HFLIP: + /* case V4L2_CID_VFLIP: -- In the same cluster */ + return update_read_mode2(sensor, sensor->vflip->val, + sensor->hflip->val); + + case V4L2_CID_EXPOSURE: + ret = mt9m032_write(client, MT9M032_SHUTTER_WIDTH_HIGH, + (ctrl->val >> 16) & 0xffff); + if (ret < 0) + return ret; + + return mt9m032_write(client, MT9M032_SHUTTER_WIDTH_LOW, + ctrl->val & 0xffff); + } + + return 0; +} + +static const struct v4l2_ctrl_ops mt9m032_ctrl_ops = { + .s_ctrl = mt9m032_set_ctrl, + .try_ctrl = mt9m032_try_ctrl, +}; + +/* -------------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops mt9m032_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9m032_g_register, + .s_register = mt9m032_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops mt9m032_video_ops = { + .s_stream = mt9m032_s_stream, + .g_frame_interval = mt9m032_get_frame_interval, + .s_frame_interval = mt9m032_set_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops mt9m032_pad_ops = { + .enum_mbus_code = mt9m032_enum_mbus_code, + .enum_frame_size = mt9m032_enum_frame_size, + .get_fmt = mt9m032_get_pad_format, + .set_fmt = mt9m032_set_pad_format, + .set_selection = mt9m032_set_pad_selection, + .get_selection = mt9m032_get_pad_selection, +}; + +static const struct v4l2_subdev_ops mt9m032_ops = { + .core = &mt9m032_core_ops, + .video = &mt9m032_video_ops, + .pad = &mt9m032_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Driver initialization and probing + */ + +static int mt9m032_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct mt9m032_platform_data *pdata = client->dev.platform_data; + struct i2c_adapter *adapter = client->adapter; + struct mt9m032 *sensor; + int chip_version; + int ret; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&client->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + if (!client->dev.platform_data) + return -ENODEV; + + sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); + if (sensor == NULL) + return -ENOMEM; + + mutex_init(&sensor->lock); + + sensor->pdata = pdata; + + v4l2_i2c_subdev_init(&sensor->subdev, client, &mt9m032_ops); + sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + chip_version = mt9m032_read(client, MT9M032_CHIP_VERSION); + if (chip_version != MT9M032_CHIP_VERSION_VALUE) { + dev_err(&client->dev, "MT9M032 not detected, wrong version " + "0x%04x\n", chip_version); + ret = -ENODEV; + goto error_sensor; + } + + dev_info(&client->dev, "MT9M032 detected at address 0x%02x\n", + client->addr); + + sensor->frame_interval.numerator = 1; + sensor->frame_interval.denominator = 30; + + sensor->crop.left = MT9M032_COLUMN_START_DEF; + sensor->crop.top = MT9M032_ROW_START_DEF; + sensor->crop.width = MT9M032_COLUMN_SIZE_DEF; + sensor->crop.height = MT9M032_ROW_SIZE_DEF; + + sensor->format.width = sensor->crop.width; + sensor->format.height = sensor->crop.height; + sensor->format.code = MEDIA_BUS_FMT_Y8_1X8; + sensor->format.field = V4L2_FIELD_NONE; + sensor->format.colorspace = V4L2_COLORSPACE_SRGB; + + v4l2_ctrl_handler_init(&sensor->ctrls, 5); + + v4l2_ctrl_new_std(&sensor->ctrls, &mt9m032_ctrl_ops, + V4L2_CID_GAIN, 0, 127, 1, 64); + + sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, + &mt9m032_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, + &mt9m032_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(&sensor->ctrls, &mt9m032_ctrl_ops, + V4L2_CID_EXPOSURE, MT9M032_SHUTTER_WIDTH_MIN, + MT9M032_SHUTTER_WIDTH_MAX, 1, + MT9M032_SHUTTER_WIDTH_DEF); + v4l2_ctrl_new_std(&sensor->ctrls, &mt9m032_ctrl_ops, + V4L2_CID_PIXEL_RATE, pdata->pix_clock, + pdata->pix_clock, 1, pdata->pix_clock); + + if (sensor->ctrls.error) { + ret = sensor->ctrls.error; + dev_err(&client->dev, "control initialization error %d\n", ret); + goto error_ctrl; + } + + v4l2_ctrl_cluster(2, &sensor->hflip); + + sensor->subdev.ctrl_handler = &sensor->ctrls; + sensor->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad); + if (ret < 0) + goto error_ctrl; + + ret = mt9m032_write(client, MT9M032_RESET, 1); /* reset on */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, MT9M032_RESET, 0); /* reset off */ + if (ret < 0) + goto error_entity; + + ret = mt9m032_setup_pll(sensor); + if (ret < 0) + goto error_entity; + usleep_range(10000, 11000); + + ret = v4l2_ctrl_handler_setup(&sensor->ctrls); + if (ret < 0) + goto error_entity; + + /* SIZE */ + ret = mt9m032_update_geom_timing(sensor); + if (ret < 0) + goto error_entity; + + ret = mt9m032_write(client, 0x41, 0x0000); /* reserved !!! */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, 0x42, 0x0003); /* reserved !!! */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, 0x43, 0x0003); /* reserved !!! */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, 0x7f, 0x0000); /* reserved !!! */ + if (ret < 0) + goto error_entity; + if (sensor->pdata->invert_pixclock) { + ret = mt9m032_write(client, MT9M032_PIX_CLK_CTRL, + MT9M032_PIX_CLK_CTRL_INV_PIXCLK); + if (ret < 0) + goto error_entity; + } + + ret = mt9m032_write(client, MT9M032_RESTART, 1); /* Restart on */ + if (ret < 0) + goto error_entity; + msleep(100); + ret = mt9m032_write(client, MT9M032_RESTART, 0); /* Restart off */ + if (ret < 0) + goto error_entity; + msleep(100); + ret = update_formatter2(sensor, false); + if (ret < 0) + goto error_entity; + + return ret; + +error_entity: + media_entity_cleanup(&sensor->subdev.entity); +error_ctrl: + v4l2_ctrl_handler_free(&sensor->ctrls); +error_sensor: + mutex_destroy(&sensor->lock); + return ret; +} + +static int mt9m032_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct mt9m032 *sensor = to_mt9m032(subdev); + + v4l2_device_unregister_subdev(subdev); + v4l2_ctrl_handler_free(&sensor->ctrls); + media_entity_cleanup(&subdev->entity); + mutex_destroy(&sensor->lock); + return 0; +} + +static const struct i2c_device_id mt9m032_id_table[] = { + { MT9M032_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, mt9m032_id_table); + +static struct i2c_driver mt9m032_i2c_driver = { + .driver = { + .name = MT9M032_NAME, + }, + .probe = mt9m032_probe, + .remove = mt9m032_remove, + .id_table = mt9m032_id_table, +}; + +module_i2c_driver(mt9m032_i2c_driver); + +MODULE_AUTHOR("Martin Hostettler <martin@neutronstar.dyndns.org>"); +MODULE_DESCRIPTION("MT9M032 camera sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/mt9m111.c b/drivers/media/i2c/mt9m111.c new file mode 100644 index 000000000..69697386f --- /dev/null +++ b/drivers/media/i2c/mt9m111.c @@ -0,0 +1,1400 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MT9M111/MT9M112/MT9M131 CMOS Image Sensor from Micron/Aptina + * + * Copyright (C) 2008, Robert Jarzmik <robert.jarzmik@free.fr> + */ +#include <linux/videodev2.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/log2.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include <linux/v4l2-mediabus.h> +#include <linux/module.h> +#include <linux/property.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-clk.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> + +/* + * MT9M111, MT9M112 and MT9M131: + * i2c address is 0x48 or 0x5d (depending on SADDR pin) + * The platform has to define struct i2c_board_info objects and link to them + * from struct soc_camera_host_desc + */ + +/* + * Sensor core register addresses (0x000..0x0ff) + */ +#define MT9M111_CHIP_VERSION 0x000 +#define MT9M111_ROW_START 0x001 +#define MT9M111_COLUMN_START 0x002 +#define MT9M111_WINDOW_HEIGHT 0x003 +#define MT9M111_WINDOW_WIDTH 0x004 +#define MT9M111_HORIZONTAL_BLANKING_B 0x005 +#define MT9M111_VERTICAL_BLANKING_B 0x006 +#define MT9M111_HORIZONTAL_BLANKING_A 0x007 +#define MT9M111_VERTICAL_BLANKING_A 0x008 +#define MT9M111_SHUTTER_WIDTH 0x009 +#define MT9M111_ROW_SPEED 0x00a +#define MT9M111_EXTRA_DELAY 0x00b +#define MT9M111_SHUTTER_DELAY 0x00c +#define MT9M111_RESET 0x00d +#define MT9M111_READ_MODE_B 0x020 +#define MT9M111_READ_MODE_A 0x021 +#define MT9M111_FLASH_CONTROL 0x023 +#define MT9M111_GREEN1_GAIN 0x02b +#define MT9M111_BLUE_GAIN 0x02c +#define MT9M111_RED_GAIN 0x02d +#define MT9M111_GREEN2_GAIN 0x02e +#define MT9M111_GLOBAL_GAIN 0x02f +#define MT9M111_CONTEXT_CONTROL 0x0c8 +#define MT9M111_PAGE_MAP 0x0f0 +#define MT9M111_BYTE_WISE_ADDR 0x0f1 + +#define MT9M111_RESET_SYNC_CHANGES (1 << 15) +#define MT9M111_RESET_RESTART_BAD_FRAME (1 << 9) +#define MT9M111_RESET_SHOW_BAD_FRAMES (1 << 8) +#define MT9M111_RESET_RESET_SOC (1 << 5) +#define MT9M111_RESET_OUTPUT_DISABLE (1 << 4) +#define MT9M111_RESET_CHIP_ENABLE (1 << 3) +#define MT9M111_RESET_ANALOG_STANDBY (1 << 2) +#define MT9M111_RESET_RESTART_FRAME (1 << 1) +#define MT9M111_RESET_RESET_MODE (1 << 0) + +#define MT9M111_RM_FULL_POWER_RD (0 << 10) +#define MT9M111_RM_LOW_POWER_RD (1 << 10) +#define MT9M111_RM_COL_SKIP_4X (1 << 5) +#define MT9M111_RM_ROW_SKIP_4X (1 << 4) +#define MT9M111_RM_COL_SKIP_2X (1 << 3) +#define MT9M111_RM_ROW_SKIP_2X (1 << 2) +#define MT9M111_RMB_MIRROR_COLS (1 << 1) +#define MT9M111_RMB_MIRROR_ROWS (1 << 0) +#define MT9M111_CTXT_CTRL_RESTART (1 << 15) +#define MT9M111_CTXT_CTRL_DEFECTCOR_B (1 << 12) +#define MT9M111_CTXT_CTRL_RESIZE_B (1 << 10) +#define MT9M111_CTXT_CTRL_CTRL2_B (1 << 9) +#define MT9M111_CTXT_CTRL_GAMMA_B (1 << 8) +#define MT9M111_CTXT_CTRL_XENON_EN (1 << 7) +#define MT9M111_CTXT_CTRL_READ_MODE_B (1 << 3) +#define MT9M111_CTXT_CTRL_LED_FLASH_EN (1 << 2) +#define MT9M111_CTXT_CTRL_VBLANK_SEL_B (1 << 1) +#define MT9M111_CTXT_CTRL_HBLANK_SEL_B (1 << 0) + +/* + * Colorpipe register addresses (0x100..0x1ff) + */ +#define MT9M111_OPER_MODE_CTRL 0x106 +#define MT9M111_OUTPUT_FORMAT_CTRL 0x108 +#define MT9M111_TPG_CTRL 0x148 +#define MT9M111_REDUCER_XZOOM_B 0x1a0 +#define MT9M111_REDUCER_XSIZE_B 0x1a1 +#define MT9M111_REDUCER_YZOOM_B 0x1a3 +#define MT9M111_REDUCER_YSIZE_B 0x1a4 +#define MT9M111_REDUCER_XZOOM_A 0x1a6 +#define MT9M111_REDUCER_XSIZE_A 0x1a7 +#define MT9M111_REDUCER_YZOOM_A 0x1a9 +#define MT9M111_REDUCER_YSIZE_A 0x1aa +#define MT9M111_EFFECTS_MODE 0x1e2 + +#define MT9M111_OUTPUT_FORMAT_CTRL2_A 0x13a +#define MT9M111_OUTPUT_FORMAT_CTRL2_B 0x19b + +#define MT9M111_OPMODE_AUTOEXPO_EN (1 << 14) +#define MT9M111_OPMODE_AUTOWHITEBAL_EN (1 << 1) +#define MT9M111_OUTFMT_FLIP_BAYER_COL (1 << 9) +#define MT9M111_OUTFMT_FLIP_BAYER_ROW (1 << 8) +#define MT9M111_OUTFMT_PROCESSED_BAYER (1 << 14) +#define MT9M111_OUTFMT_BYPASS_IFP (1 << 10) +#define MT9M111_OUTFMT_INV_PIX_CLOCK (1 << 9) +#define MT9M111_OUTFMT_RGB (1 << 8) +#define MT9M111_OUTFMT_RGB565 (0 << 6) +#define MT9M111_OUTFMT_RGB555 (1 << 6) +#define MT9M111_OUTFMT_RGB444x (2 << 6) +#define MT9M111_OUTFMT_RGBx444 (3 << 6) +#define MT9M111_OUTFMT_TST_RAMP_OFF (0 << 4) +#define MT9M111_OUTFMT_TST_RAMP_COL (1 << 4) +#define MT9M111_OUTFMT_TST_RAMP_ROW (2 << 4) +#define MT9M111_OUTFMT_TST_RAMP_FRAME (3 << 4) +#define MT9M111_OUTFMT_SHIFT_3_UP (1 << 3) +#define MT9M111_OUTFMT_AVG_CHROMA (1 << 2) +#define MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN (1 << 1) +#define MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B (1 << 0) +#define MT9M111_TPG_SEL_MASK GENMASK(2, 0) +#define MT9M111_EFFECTS_MODE_MASK GENMASK(2, 0) +#define MT9M111_RM_PWR_MASK BIT(10) +#define MT9M111_RM_SKIP2_MASK GENMASK(3, 2) + +/* + * Camera control register addresses (0x200..0x2ff not implemented) + */ + +#define reg_read(reg) mt9m111_reg_read(client, MT9M111_##reg) +#define reg_write(reg, val) mt9m111_reg_write(client, MT9M111_##reg, (val)) +#define reg_set(reg, val) mt9m111_reg_set(client, MT9M111_##reg, (val)) +#define reg_clear(reg, val) mt9m111_reg_clear(client, MT9M111_##reg, (val)) +#define reg_mask(reg, val, mask) mt9m111_reg_mask(client, MT9M111_##reg, \ + (val), (mask)) + +#define MT9M111_MIN_DARK_ROWS 8 +#define MT9M111_MIN_DARK_COLS 26 +#define MT9M111_MAX_HEIGHT 1024 +#define MT9M111_MAX_WIDTH 1280 + +struct mt9m111_context { + u16 read_mode; + u16 blanking_h; + u16 blanking_v; + u16 reducer_xzoom; + u16 reducer_yzoom; + u16 reducer_xsize; + u16 reducer_ysize; + u16 output_fmt_ctrl2; + u16 control; +}; + +static struct mt9m111_context context_a = { + .read_mode = MT9M111_READ_MODE_A, + .blanking_h = MT9M111_HORIZONTAL_BLANKING_A, + .blanking_v = MT9M111_VERTICAL_BLANKING_A, + .reducer_xzoom = MT9M111_REDUCER_XZOOM_A, + .reducer_yzoom = MT9M111_REDUCER_YZOOM_A, + .reducer_xsize = MT9M111_REDUCER_XSIZE_A, + .reducer_ysize = MT9M111_REDUCER_YSIZE_A, + .output_fmt_ctrl2 = MT9M111_OUTPUT_FORMAT_CTRL2_A, + .control = MT9M111_CTXT_CTRL_RESTART, +}; + +static struct mt9m111_context context_b = { + .read_mode = MT9M111_READ_MODE_B, + .blanking_h = MT9M111_HORIZONTAL_BLANKING_B, + .blanking_v = MT9M111_VERTICAL_BLANKING_B, + .reducer_xzoom = MT9M111_REDUCER_XZOOM_B, + .reducer_yzoom = MT9M111_REDUCER_YZOOM_B, + .reducer_xsize = MT9M111_REDUCER_XSIZE_B, + .reducer_ysize = MT9M111_REDUCER_YSIZE_B, + .output_fmt_ctrl2 = MT9M111_OUTPUT_FORMAT_CTRL2_B, + .control = MT9M111_CTXT_CTRL_RESTART | + MT9M111_CTXT_CTRL_DEFECTCOR_B | MT9M111_CTXT_CTRL_RESIZE_B | + MT9M111_CTXT_CTRL_CTRL2_B | MT9M111_CTXT_CTRL_GAMMA_B | + MT9M111_CTXT_CTRL_READ_MODE_B | MT9M111_CTXT_CTRL_VBLANK_SEL_B | + MT9M111_CTXT_CTRL_HBLANK_SEL_B, +}; + +/* MT9M111 has only one fixed colorspace per pixelcode */ +struct mt9m111_datafmt { + u32 code; + enum v4l2_colorspace colorspace; +}; + +static const struct mt9m111_datafmt mt9m111_colour_fmts[] = { + {MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_YVYU8_2X8, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_UYVY8_2X8, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_VYUY8_2X8, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_RGB565_2X8_LE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_BGR565_2X8_LE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_BGR565_2X8_BE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, V4L2_COLORSPACE_SRGB}, +}; + +enum mt9m111_mode_id { + MT9M111_MODE_SXGA_8FPS, + MT9M111_MODE_SXGA_15FPS, + MT9M111_MODE_QSXGA_30FPS, + MT9M111_NUM_MODES, +}; + +struct mt9m111_mode_info { + unsigned int sensor_w; + unsigned int sensor_h; + unsigned int max_image_w; + unsigned int max_image_h; + unsigned int max_fps; + unsigned int reg_val; + unsigned int reg_mask; +}; + +struct mt9m111 { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *gain; + struct mt9m111_context *ctx; + struct v4l2_rect rect; /* cropping rectangle */ + struct v4l2_clk *clk; + unsigned int width; /* output */ + unsigned int height; /* sizes */ + struct v4l2_fract frame_interval; + const struct mt9m111_mode_info *current_mode; + struct mutex power_lock; /* lock to protect power_count */ + int power_count; + const struct mt9m111_datafmt *fmt; + int lastpage; /* PageMap cache value */ + struct regulator *regulator; + bool is_streaming; + /* user point of view - 0: falling 1: rising edge */ + unsigned int pclk_sample:1; +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_pad pad; +#endif +}; + +static const struct mt9m111_mode_info mt9m111_mode_data[MT9M111_NUM_MODES] = { + [MT9M111_MODE_SXGA_8FPS] = { + .sensor_w = 1280, + .sensor_h = 1024, + .max_image_w = 1280, + .max_image_h = 1024, + .max_fps = 8, + .reg_val = MT9M111_RM_LOW_POWER_RD, + .reg_mask = MT9M111_RM_PWR_MASK | MT9M111_RM_SKIP2_MASK, + }, + [MT9M111_MODE_SXGA_15FPS] = { + .sensor_w = 1280, + .sensor_h = 1024, + .max_image_w = 1280, + .max_image_h = 1024, + .max_fps = 15, + .reg_val = MT9M111_RM_FULL_POWER_RD, + .reg_mask = MT9M111_RM_PWR_MASK | MT9M111_RM_SKIP2_MASK, + }, + [MT9M111_MODE_QSXGA_30FPS] = { + .sensor_w = 1280, + .sensor_h = 1024, + .max_image_w = 640, + .max_image_h = 512, + .max_fps = 30, + .reg_val = MT9M111_RM_LOW_POWER_RD | MT9M111_RM_COL_SKIP_2X | + MT9M111_RM_ROW_SKIP_2X, + .reg_mask = MT9M111_RM_PWR_MASK | MT9M111_RM_SKIP2_MASK, + }, +}; + +/* Find a data format by a pixel code */ +static const struct mt9m111_datafmt *mt9m111_find_datafmt(struct mt9m111 *mt9m111, + u32 code) +{ + int i; + for (i = 0; i < ARRAY_SIZE(mt9m111_colour_fmts); i++) + if (mt9m111_colour_fmts[i].code == code) + return mt9m111_colour_fmts + i; + + return mt9m111->fmt; +} + +static struct mt9m111 *to_mt9m111(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct mt9m111, subdev); +} + +static int reg_page_map_set(struct i2c_client *client, const u16 reg) +{ + int ret; + u16 page; + struct mt9m111 *mt9m111 = to_mt9m111(client); + + page = (reg >> 8); + if (page == mt9m111->lastpage) + return 0; + if (page > 2) + return -EINVAL; + + ret = i2c_smbus_write_word_swapped(client, MT9M111_PAGE_MAP, page); + if (!ret) + mt9m111->lastpage = page; + return ret; +} + +static int mt9m111_reg_read(struct i2c_client *client, const u16 reg) +{ + int ret; + + ret = reg_page_map_set(client, reg); + if (!ret) + ret = i2c_smbus_read_word_swapped(client, reg & 0xff); + + dev_dbg(&client->dev, "read reg.%03x -> %04x\n", reg, ret); + return ret; +} + +static int mt9m111_reg_write(struct i2c_client *client, const u16 reg, + const u16 data) +{ + int ret; + + ret = reg_page_map_set(client, reg); + if (!ret) + ret = i2c_smbus_write_word_swapped(client, reg & 0xff, data); + dev_dbg(&client->dev, "write reg.%03x = %04x -> %d\n", reg, data, ret); + return ret; +} + +static int mt9m111_reg_set(struct i2c_client *client, const u16 reg, + const u16 data) +{ + int ret; + + ret = mt9m111_reg_read(client, reg); + if (ret >= 0) + ret = mt9m111_reg_write(client, reg, ret | data); + return ret; +} + +static int mt9m111_reg_clear(struct i2c_client *client, const u16 reg, + const u16 data) +{ + int ret; + + ret = mt9m111_reg_read(client, reg); + if (ret >= 0) + ret = mt9m111_reg_write(client, reg, ret & ~data); + return ret; +} + +static int mt9m111_reg_mask(struct i2c_client *client, const u16 reg, + const u16 data, const u16 mask) +{ + int ret; + + ret = mt9m111_reg_read(client, reg); + if (ret >= 0) + ret = mt9m111_reg_write(client, reg, (ret & ~mask) | data); + return ret; +} + +static int mt9m111_set_context(struct mt9m111 *mt9m111, + struct mt9m111_context *ctx) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + return reg_write(CONTEXT_CONTROL, ctx->control); +} + +static int mt9m111_setup_rect_ctx(struct mt9m111 *mt9m111, + struct mt9m111_context *ctx, struct v4l2_rect *rect, + unsigned int width, unsigned int height) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret = mt9m111_reg_write(client, ctx->reducer_xzoom, rect->width); + if (!ret) + ret = mt9m111_reg_write(client, ctx->reducer_yzoom, rect->height); + if (!ret) + ret = mt9m111_reg_write(client, ctx->reducer_xsize, width); + if (!ret) + ret = mt9m111_reg_write(client, ctx->reducer_ysize, height); + return ret; +} + +static int mt9m111_setup_geometry(struct mt9m111 *mt9m111, struct v4l2_rect *rect, + int width, int height, u32 code) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + ret = reg_write(COLUMN_START, rect->left); + if (!ret) + ret = reg_write(ROW_START, rect->top); + + if (!ret) + ret = reg_write(WINDOW_WIDTH, rect->width); + if (!ret) + ret = reg_write(WINDOW_HEIGHT, rect->height); + + if (code != MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE) { + /* IFP in use, down-scaling possible */ + if (!ret) + ret = mt9m111_setup_rect_ctx(mt9m111, &context_b, + rect, width, height); + if (!ret) + ret = mt9m111_setup_rect_ctx(mt9m111, &context_a, + rect, width, height); + } + + dev_dbg(&client->dev, "%s(%x): %ux%u@%u:%u -> %ux%u = %d\n", + __func__, code, rect->width, rect->height, rect->left, rect->top, + width, height, ret); + + return ret; +} + +static int mt9m111_enable(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + return reg_write(RESET, MT9M111_RESET_CHIP_ENABLE); +} + +static int mt9m111_reset(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + ret = reg_set(RESET, MT9M111_RESET_RESET_MODE); + if (!ret) + ret = reg_set(RESET, MT9M111_RESET_RESET_SOC); + if (!ret) + ret = reg_clear(RESET, MT9M111_RESET_RESET_MODE + | MT9M111_RESET_RESET_SOC); + + return ret; +} + +static int mt9m111_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m111 *mt9m111 = to_mt9m111(client); + struct v4l2_rect rect = sel->r; + int width, height; + int ret, align = 0; + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + if (mt9m111->fmt->code == MEDIA_BUS_FMT_SBGGR8_1X8 || + mt9m111->fmt->code == MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE) { + /* Bayer format - even size lengths */ + align = 1; + /* Let the user play with the starting pixel */ + } + + /* FIXME: the datasheet doesn't specify minimum sizes */ + v4l_bound_align_image(&rect.width, 2, MT9M111_MAX_WIDTH, align, + &rect.height, 2, MT9M111_MAX_HEIGHT, align, 0); + rect.left = clamp(rect.left, MT9M111_MIN_DARK_COLS, + MT9M111_MIN_DARK_COLS + MT9M111_MAX_WIDTH - + (__s32)rect.width); + rect.top = clamp(rect.top, MT9M111_MIN_DARK_ROWS, + MT9M111_MIN_DARK_ROWS + MT9M111_MAX_HEIGHT - + (__s32)rect.height); + + width = min(mt9m111->width, rect.width); + height = min(mt9m111->height, rect.height); + + ret = mt9m111_setup_geometry(mt9m111, &rect, width, height, mt9m111->fmt->code); + if (!ret) { + mt9m111->rect = rect; + mt9m111->width = width; + mt9m111->height = height; + } + + return ret; +} + +static int mt9m111_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m111 *mt9m111 = to_mt9m111(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = MT9M111_MIN_DARK_COLS; + sel->r.top = MT9M111_MIN_DARK_ROWS; + sel->r.width = MT9M111_MAX_WIDTH; + sel->r.height = MT9M111_MAX_HEIGHT; + return 0; + case V4L2_SEL_TGT_CROP: + sel->r = mt9m111->rect; + return 0; + default: + return -EINVAL; + } +} + +static int mt9m111_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + + if (format->pad) + return -EINVAL; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + mf = v4l2_subdev_get_try_format(sd, cfg, format->pad); + format->format = *mf; + return 0; +#else + return -EINVAL; +#endif + } + + mf->width = mt9m111->width; + mf->height = mt9m111->height; + mf->code = mt9m111->fmt->code; + mf->colorspace = mt9m111->fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + mf->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + mf->quantization = V4L2_QUANTIZATION_DEFAULT; + mf->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return 0; +} + +static int mt9m111_set_pixfmt(struct mt9m111 *mt9m111, + u32 code) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + u16 data_outfmt2, mask_outfmt2 = MT9M111_OUTFMT_PROCESSED_BAYER | + MT9M111_OUTFMT_BYPASS_IFP | MT9M111_OUTFMT_RGB | + MT9M111_OUTFMT_RGB565 | MT9M111_OUTFMT_RGB555 | + MT9M111_OUTFMT_RGB444x | MT9M111_OUTFMT_RGBx444 | + MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN | + MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B; + int ret; + + switch (code) { + case MEDIA_BUS_FMT_SBGGR8_1X8: + data_outfmt2 = MT9M111_OUTFMT_PROCESSED_BAYER | + MT9M111_OUTFMT_RGB; + break; + case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE: + data_outfmt2 = MT9M111_OUTFMT_BYPASS_IFP | MT9M111_OUTFMT_RGB; + break; + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB555 | + MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN; + break; + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB555; + break; + case MEDIA_BUS_FMT_RGB565_2X8_LE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB565 | + MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN; + break; + case MEDIA_BUS_FMT_RGB565_2X8_BE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB565; + break; + case MEDIA_BUS_FMT_BGR565_2X8_BE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB565 | + MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B; + break; + case MEDIA_BUS_FMT_BGR565_2X8_LE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB565 | + MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN | + MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + data_outfmt2 = 0; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + data_outfmt2 = MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B; + break; + case MEDIA_BUS_FMT_YUYV8_2X8: + data_outfmt2 = MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + data_outfmt2 = MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN | + MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B; + break; + default: + dev_err(&client->dev, "Pixel format not handled: %x\n", code); + return -EINVAL; + } + + /* receiver samples on falling edge, chip-hw default is rising */ + if (mt9m111->pclk_sample == 0) + mask_outfmt2 |= MT9M111_OUTFMT_INV_PIX_CLOCK; + + ret = mt9m111_reg_mask(client, context_a.output_fmt_ctrl2, + data_outfmt2, mask_outfmt2); + if (!ret) + ret = mt9m111_reg_mask(client, context_b.output_fmt_ctrl2, + data_outfmt2, mask_outfmt2); + + return ret; +} + +static int mt9m111_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + const struct mt9m111_datafmt *fmt; + struct v4l2_rect *rect = &mt9m111->rect; + bool bayer; + int ret; + + if (mt9m111->is_streaming) + return -EBUSY; + + if (format->pad) + return -EINVAL; + + fmt = mt9m111_find_datafmt(mt9m111, mf->code); + + bayer = fmt->code == MEDIA_BUS_FMT_SBGGR8_1X8 || + fmt->code == MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE; + + /* + * With Bayer format enforce even side lengths, but let the user play + * with the starting pixel + */ + if (bayer) { + rect->width = ALIGN(rect->width, 2); + rect->height = ALIGN(rect->height, 2); + } + + if (fmt->code == MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE) { + /* IFP bypass mode, no scaling */ + mf->width = rect->width; + mf->height = rect->height; + } else { + /* No upscaling */ + if (mf->width > rect->width) + mf->width = rect->width; + if (mf->height > rect->height) + mf->height = rect->height; + } + + dev_dbg(&client->dev, "%s(): %ux%u, code=%x\n", __func__, + mf->width, mf->height, fmt->code); + + mf->code = fmt->code; + mf->colorspace = fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + mf->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + mf->quantization = V4L2_QUANTIZATION_DEFAULT; + mf->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + cfg->try_fmt = *mf; + return 0; + } + + ret = mt9m111_setup_geometry(mt9m111, rect, mf->width, mf->height, mf->code); + if (!ret) + ret = mt9m111_set_pixfmt(mt9m111, mf->code); + if (!ret) { + mt9m111->width = mf->width; + mt9m111->height = mf->height; + mt9m111->fmt = fmt; + } + + return ret; +} + +static const struct mt9m111_mode_info * +mt9m111_find_mode(struct mt9m111 *mt9m111, unsigned int req_fps, + unsigned int width, unsigned int height) +{ + const struct mt9m111_mode_info *mode; + struct v4l2_rect *sensor_rect = &mt9m111->rect; + unsigned int gap, gap_best = (unsigned int) -1; + int i, best_gap_idx = MT9M111_MODE_SXGA_15FPS; + bool skip_30fps = false; + + /* + * The fps selection is based on the row, column skipping mechanism. + * So ensure that the sensor window is set to default else the fps + * aren't calculated correctly within the sensor hw. + */ + if (sensor_rect->width != MT9M111_MAX_WIDTH || + sensor_rect->height != MT9M111_MAX_HEIGHT) { + dev_info(mt9m111->subdev.dev, + "Framerate selection is not supported for cropped " + "images\n"); + return NULL; + } + + /* 30fps only supported for images not exceeding 640x512 */ + if (width > MT9M111_MAX_WIDTH / 2 || height > MT9M111_MAX_HEIGHT / 2) { + dev_dbg(mt9m111->subdev.dev, + "Framerates > 15fps are supported only for images " + "not exceeding 640x512\n"); + skip_30fps = true; + } + + /* find best matched fps */ + for (i = 0; i < MT9M111_NUM_MODES; i++) { + unsigned int fps = mt9m111_mode_data[i].max_fps; + + if (fps == 30 && skip_30fps) + continue; + + gap = abs(fps - req_fps); + if (gap < gap_best) { + best_gap_idx = i; + gap_best = gap; + } + } + + /* + * Use context a/b default timing values instead of calculate blanking + * timing values. + */ + mode = &mt9m111_mode_data[best_gap_idx]; + mt9m111->ctx = (best_gap_idx == MT9M111_MODE_QSXGA_30FPS) ? &context_a : + &context_b; + return mode; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9m111_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int val; + + if (reg->reg > 0x2ff) + return -EINVAL; + + val = mt9m111_reg_read(client, reg->reg); + reg->size = 2; + reg->val = (u64)val; + + if (reg->val > 0xffff) + return -EIO; + + return 0; +} + +static int mt9m111_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0x2ff) + return -EINVAL; + + if (mt9m111_reg_write(client, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static int mt9m111_set_flip(struct mt9m111 *mt9m111, int flip, int mask) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + if (flip) + ret = mt9m111_reg_set(client, mt9m111->ctx->read_mode, mask); + else + ret = mt9m111_reg_clear(client, mt9m111->ctx->read_mode, mask); + + return ret; +} + +static int mt9m111_get_global_gain(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int data; + + data = reg_read(GLOBAL_GAIN); + if (data >= 0) + return (data & 0x2f) * (1 << ((data >> 10) & 1)) * + (1 << ((data >> 9) & 1)); + return data; +} + +static int mt9m111_set_global_gain(struct mt9m111 *mt9m111, int gain) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + u16 val; + + if (gain > 63 * 2 * 2) + return -EINVAL; + + if ((gain >= 64 * 2) && (gain < 63 * 2 * 2)) + val = (1 << 10) | (1 << 9) | (gain / 4); + else if ((gain >= 64) && (gain < 64 * 2)) + val = (1 << 9) | (gain / 2); + else + val = gain; + + return reg_write(GLOBAL_GAIN, val); +} + +static int mt9m111_set_autoexposure(struct mt9m111 *mt9m111, int val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + + if (val == V4L2_EXPOSURE_AUTO) + return reg_set(OPER_MODE_CTRL, MT9M111_OPMODE_AUTOEXPO_EN); + return reg_clear(OPER_MODE_CTRL, MT9M111_OPMODE_AUTOEXPO_EN); +} + +static int mt9m111_set_autowhitebalance(struct mt9m111 *mt9m111, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + + if (on) + return reg_set(OPER_MODE_CTRL, MT9M111_OPMODE_AUTOWHITEBAL_EN); + return reg_clear(OPER_MODE_CTRL, MT9M111_OPMODE_AUTOWHITEBAL_EN); +} + +static const char * const mt9m111_test_pattern_menu[] = { + "Disabled", + "Vertical monochrome gradient", + "Flat color type 1", + "Flat color type 2", + "Flat color type 3", + "Flat color type 4", + "Flat color type 5", + "Color bar", +}; + +static int mt9m111_set_test_pattern(struct mt9m111 *mt9m111, int val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + + return mt9m111_reg_mask(client, MT9M111_TPG_CTRL, val, + MT9M111_TPG_SEL_MASK); +} + +static int mt9m111_set_colorfx(struct mt9m111 *mt9m111, int val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + static const struct v4l2_control colorfx[] = { + { V4L2_COLORFX_NONE, 0 }, + { V4L2_COLORFX_BW, 1 }, + { V4L2_COLORFX_SEPIA, 2 }, + { V4L2_COLORFX_NEGATIVE, 3 }, + { V4L2_COLORFX_SOLARIZATION, 4 }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(colorfx); i++) { + if (colorfx[i].id == val) { + return mt9m111_reg_mask(client, MT9M111_EFFECTS_MODE, + colorfx[i].value, + MT9M111_EFFECTS_MODE_MASK); + } + } + + return -EINVAL; +} + +static int mt9m111_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m111 *mt9m111 = container_of(ctrl->handler, + struct mt9m111, hdl); + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + return mt9m111_set_flip(mt9m111, ctrl->val, + MT9M111_RMB_MIRROR_ROWS); + case V4L2_CID_HFLIP: + return mt9m111_set_flip(mt9m111, ctrl->val, + MT9M111_RMB_MIRROR_COLS); + case V4L2_CID_GAIN: + return mt9m111_set_global_gain(mt9m111, ctrl->val); + case V4L2_CID_EXPOSURE_AUTO: + return mt9m111_set_autoexposure(mt9m111, ctrl->val); + case V4L2_CID_AUTO_WHITE_BALANCE: + return mt9m111_set_autowhitebalance(mt9m111, ctrl->val); + case V4L2_CID_TEST_PATTERN: + return mt9m111_set_test_pattern(mt9m111, ctrl->val); + case V4L2_CID_COLORFX: + return mt9m111_set_colorfx(mt9m111, ctrl->val); + } + + return -EINVAL; +} + +static int mt9m111_suspend(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + v4l2_ctrl_s_ctrl(mt9m111->gain, mt9m111_get_global_gain(mt9m111)); + + ret = reg_set(RESET, MT9M111_RESET_RESET_MODE); + if (!ret) + ret = reg_set(RESET, MT9M111_RESET_RESET_SOC | + MT9M111_RESET_OUTPUT_DISABLE | + MT9M111_RESET_ANALOG_STANDBY); + if (!ret) + ret = reg_clear(RESET, MT9M111_RESET_CHIP_ENABLE); + + return ret; +} + +static void mt9m111_restore_state(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + + mt9m111_set_context(mt9m111, mt9m111->ctx); + mt9m111_set_pixfmt(mt9m111, mt9m111->fmt->code); + mt9m111_setup_geometry(mt9m111, &mt9m111->rect, + mt9m111->width, mt9m111->height, mt9m111->fmt->code); + v4l2_ctrl_handler_setup(&mt9m111->hdl); + mt9m111_reg_mask(client, mt9m111->ctx->read_mode, + mt9m111->current_mode->reg_val, + mt9m111->current_mode->reg_mask); +} + +static int mt9m111_resume(struct mt9m111 *mt9m111) +{ + int ret = mt9m111_enable(mt9m111); + if (!ret) + ret = mt9m111_reset(mt9m111); + if (!ret) + mt9m111_restore_state(mt9m111); + + return ret; +} + +static int mt9m111_init(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + ret = mt9m111_enable(mt9m111); + if (!ret) + ret = mt9m111_reset(mt9m111); + if (!ret) + ret = mt9m111_set_context(mt9m111, mt9m111->ctx); + if (ret) + dev_err(&client->dev, "mt9m111 init failed: %d\n", ret); + return ret; +} + +static int mt9m111_power_on(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + ret = v4l2_clk_enable(mt9m111->clk); + if (ret < 0) + return ret; + + ret = regulator_enable(mt9m111->regulator); + if (ret < 0) + goto out_clk_disable; + + ret = mt9m111_resume(mt9m111); + if (ret < 0) + goto out_regulator_disable; + + return 0; + +out_regulator_disable: + regulator_disable(mt9m111->regulator); + +out_clk_disable: + v4l2_clk_disable(mt9m111->clk); + + dev_err(&client->dev, "Failed to resume the sensor: %d\n", ret); + + return ret; +} + +static void mt9m111_power_off(struct mt9m111 *mt9m111) +{ + mt9m111_suspend(mt9m111); + regulator_disable(mt9m111->regulator); + v4l2_clk_disable(mt9m111->clk); +} + +static int mt9m111_s_power(struct v4l2_subdev *sd, int on) +{ + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + int ret = 0; + + mutex_lock(&mt9m111->power_lock); + + /* + * If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (mt9m111->power_count == !on) { + if (on) + ret = mt9m111_power_on(mt9m111); + else + mt9m111_power_off(mt9m111); + } + + if (!ret) { + /* Update the power count. */ + mt9m111->power_count += on ? 1 : -1; + WARN_ON(mt9m111->power_count < 0); + } + + mutex_unlock(&mt9m111->power_lock); + return ret; +} + +static const struct v4l2_ctrl_ops mt9m111_ctrl_ops = { + .s_ctrl = mt9m111_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops mt9m111_subdev_core_ops = { + .s_power = mt9m111_s_power, + .log_status = v4l2_ctrl_subdev_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9m111_g_register, + .s_register = mt9m111_s_register, +#endif +}; + +static int mt9m111_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + + fi->interval = mt9m111->frame_interval; + + return 0; +} + +static int mt9m111_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + const struct mt9m111_mode_info *mode; + struct v4l2_fract *fract = &fi->interval; + int fps; + + if (mt9m111->is_streaming) + return -EBUSY; + + if (fi->pad != 0) + return -EINVAL; + + if (fract->numerator == 0) { + fract->denominator = 30; + fract->numerator = 1; + } + + fps = DIV_ROUND_CLOSEST(fract->denominator, fract->numerator); + + /* Find best fitting mode. Do not update the mode if no one was found. */ + mode = mt9m111_find_mode(mt9m111, fps, mt9m111->width, mt9m111->height); + if (!mode) + return 0; + + if (mode->max_fps != fps) { + fract->denominator = mode->max_fps; + fract->numerator = 1; + } + + mt9m111->current_mode = mode; + mt9m111->frame_interval = fi->interval; + + return 0; +} + +static int mt9m111_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(mt9m111_colour_fmts)) + return -EINVAL; + + code->code = mt9m111_colour_fmts[code->index].code; + return 0; +} + +static int mt9m111_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + + mt9m111->is_streaming = !!enable; + return 0; +} + +static int mt9m111_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + struct v4l2_mbus_framefmt *format = + v4l2_subdev_get_try_format(sd, cfg, 0); + + format->width = MT9M111_MAX_WIDTH; + format->height = MT9M111_MAX_HEIGHT; + format->code = mt9m111_colour_fmts[0].code; + format->colorspace = mt9m111_colour_fmts[0].colorspace; + format->field = V4L2_FIELD_NONE; + format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + format->quantization = V4L2_QUANTIZATION_DEFAULT; + format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +#endif + return 0; +} + +static int mt9m111_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + + cfg->flags = V4L2_MBUS_MASTER | + V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH | + V4L2_MBUS_DATA_ACTIVE_HIGH; + + cfg->flags |= mt9m111->pclk_sample ? V4L2_MBUS_PCLK_SAMPLE_RISING : + V4L2_MBUS_PCLK_SAMPLE_FALLING; + + cfg->type = V4L2_MBUS_PARALLEL; + + return 0; +} + +static const struct v4l2_subdev_video_ops mt9m111_subdev_video_ops = { + .s_stream = mt9m111_s_stream, + .g_frame_interval = mt9m111_g_frame_interval, + .s_frame_interval = mt9m111_s_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops mt9m111_subdev_pad_ops = { + .init_cfg = mt9m111_init_cfg, + .enum_mbus_code = mt9m111_enum_mbus_code, + .get_selection = mt9m111_get_selection, + .set_selection = mt9m111_set_selection, + .get_fmt = mt9m111_get_fmt, + .set_fmt = mt9m111_set_fmt, + .get_mbus_config = mt9m111_get_mbus_config, +}; + +static const struct v4l2_subdev_ops mt9m111_subdev_ops = { + .core = &mt9m111_subdev_core_ops, + .video = &mt9m111_subdev_video_ops, + .pad = &mt9m111_subdev_pad_ops, +}; + +/* + * Interface active, can use i2c. If it fails, it can indeed mean, that + * this wasn't our capture interface, so, we wait for the right one + */ +static int mt9m111_video_probe(struct i2c_client *client) +{ + struct mt9m111 *mt9m111 = to_mt9m111(client); + s32 data; + int ret; + + ret = mt9m111_s_power(&mt9m111->subdev, 1); + if (ret < 0) + return ret; + + data = reg_read(CHIP_VERSION); + + switch (data) { + case 0x143a: /* MT9M111 or MT9M131 */ + dev_info(&client->dev, + "Detected a MT9M111/MT9M131 chip ID %x\n", data); + break; + case 0x148c: /* MT9M112 */ + dev_info(&client->dev, "Detected a MT9M112 chip ID %x\n", data); + break; + default: + dev_err(&client->dev, + "No MT9M111/MT9M112/MT9M131 chip detected register read %x\n", + data); + ret = -ENODEV; + goto done; + } + + ret = mt9m111_init(mt9m111); + if (ret) + goto done; + + ret = v4l2_ctrl_handler_setup(&mt9m111->hdl); + +done: + mt9m111_s_power(&mt9m111->subdev, 0); + return ret; +} + +static int mt9m111_probe_fw(struct i2c_client *client, struct mt9m111 *mt9m111) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_PARALLEL + }; + struct fwnode_handle *np; + int ret; + + np = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL); + if (!np) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(np, &bus_cfg); + if (ret) + goto out_put_fw; + + mt9m111->pclk_sample = !!(bus_cfg.bus.parallel.flags & + V4L2_MBUS_PCLK_SAMPLE_RISING); + +out_put_fw: + fwnode_handle_put(np); + return ret; +} + +static int mt9m111_probe(struct i2c_client *client) +{ + struct mt9m111 *mt9m111; + struct i2c_adapter *adapter = client->adapter; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9m111 = devm_kzalloc(&client->dev, sizeof(struct mt9m111), GFP_KERNEL); + if (!mt9m111) + return -ENOMEM; + + if (dev_fwnode(&client->dev)) { + ret = mt9m111_probe_fw(client, mt9m111); + if (ret) + return ret; + } + + mt9m111->clk = v4l2_clk_get(&client->dev, "mclk"); + if (IS_ERR(mt9m111->clk)) + return PTR_ERR(mt9m111->clk); + + mt9m111->regulator = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(mt9m111->regulator)) { + dev_err(&client->dev, "regulator not found: %ld\n", + PTR_ERR(mt9m111->regulator)); + return PTR_ERR(mt9m111->regulator); + } + + /* Default HIGHPOWER context */ + mt9m111->ctx = &context_b; + + v4l2_i2c_subdev_init(&mt9m111->subdev, client, &mt9m111_subdev_ops); + mt9m111->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + + v4l2_ctrl_handler_init(&mt9m111->hdl, 7); + v4l2_ctrl_new_std(&mt9m111->hdl, &mt9m111_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9m111->hdl, &mt9m111_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9m111->hdl, &mt9m111_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + mt9m111->gain = v4l2_ctrl_new_std(&mt9m111->hdl, &mt9m111_ctrl_ops, + V4L2_CID_GAIN, 0, 63 * 2 * 2, 1, 32); + v4l2_ctrl_new_std_menu(&mt9m111->hdl, + &mt9m111_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0, + V4L2_EXPOSURE_AUTO); + v4l2_ctrl_new_std_menu_items(&mt9m111->hdl, + &mt9m111_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(mt9m111_test_pattern_menu) - 1, 0, 0, + mt9m111_test_pattern_menu); + v4l2_ctrl_new_std_menu(&mt9m111->hdl, &mt9m111_ctrl_ops, + V4L2_CID_COLORFX, V4L2_COLORFX_SOLARIZATION, + ~(BIT(V4L2_COLORFX_NONE) | + BIT(V4L2_COLORFX_BW) | + BIT(V4L2_COLORFX_SEPIA) | + BIT(V4L2_COLORFX_NEGATIVE) | + BIT(V4L2_COLORFX_SOLARIZATION)), + V4L2_COLORFX_NONE); + mt9m111->subdev.ctrl_handler = &mt9m111->hdl; + if (mt9m111->hdl.error) { + ret = mt9m111->hdl.error; + goto out_clkput; + } + +#ifdef CONFIG_MEDIA_CONTROLLER + mt9m111->pad.flags = MEDIA_PAD_FL_SOURCE; + mt9m111->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&mt9m111->subdev.entity, 1, &mt9m111->pad); + if (ret < 0) + goto out_hdlfree; +#endif + + mt9m111->current_mode = &mt9m111_mode_data[MT9M111_MODE_SXGA_15FPS]; + mt9m111->frame_interval.numerator = 1; + mt9m111->frame_interval.denominator = mt9m111->current_mode->max_fps; + + /* Second stage probe - when a capture adapter is there */ + mt9m111->rect.left = MT9M111_MIN_DARK_COLS; + mt9m111->rect.top = MT9M111_MIN_DARK_ROWS; + mt9m111->rect.width = MT9M111_MAX_WIDTH; + mt9m111->rect.height = MT9M111_MAX_HEIGHT; + mt9m111->width = mt9m111->rect.width; + mt9m111->height = mt9m111->rect.height; + mt9m111->fmt = &mt9m111_colour_fmts[0]; + mt9m111->lastpage = -1; + mutex_init(&mt9m111->power_lock); + + ret = mt9m111_video_probe(client); + if (ret < 0) + goto out_entityclean; + + mt9m111->subdev.dev = &client->dev; + ret = v4l2_async_register_subdev(&mt9m111->subdev); + if (ret < 0) + goto out_entityclean; + + return 0; + +out_entityclean: +#ifdef CONFIG_MEDIA_CONTROLLER + media_entity_cleanup(&mt9m111->subdev.entity); +out_hdlfree: +#endif + v4l2_ctrl_handler_free(&mt9m111->hdl); +out_clkput: + v4l2_clk_put(mt9m111->clk); + + return ret; +} + +static int mt9m111_remove(struct i2c_client *client) +{ + struct mt9m111 *mt9m111 = to_mt9m111(client); + + v4l2_async_unregister_subdev(&mt9m111->subdev); + media_entity_cleanup(&mt9m111->subdev.entity); + v4l2_clk_put(mt9m111->clk); + v4l2_ctrl_handler_free(&mt9m111->hdl); + + return 0; +} +static const struct of_device_id mt9m111_of_match[] = { + { .compatible = "micron,mt9m111", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mt9m111_of_match); + +static const struct i2c_device_id mt9m111_id[] = { + { "mt9m111", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9m111_id); + +static struct i2c_driver mt9m111_i2c_driver = { + .driver = { + .name = "mt9m111", + .of_match_table = of_match_ptr(mt9m111_of_match), + }, + .probe_new = mt9m111_probe, + .remove = mt9m111_remove, + .id_table = mt9m111_id, +}; + +module_i2c_driver(mt9m111_i2c_driver); + +MODULE_DESCRIPTION("Micron/Aptina MT9M111/MT9M112/MT9M131 Camera driver"); +MODULE_AUTHOR("Robert Jarzmik"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/mt9p031.c b/drivers/media/i2c/mt9p031.c new file mode 100644 index 000000000..18440c510 --- /dev/null +++ b/drivers/media/i2c/mt9p031.c @@ -0,0 +1,1222 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MT9P031 CMOS Image Sensor from Aptina + * + * Copyright (C) 2011, Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * Copyright (C) 2011, Javier Martin <javier.martin@vista-silicon.com> + * Copyright (C) 2011, Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * Based on the MT9V032 driver and Bastian Hecht's code. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/log2.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/pm.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include <media/i2c/mt9p031.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#include "aptina-pll.h" + +#define MT9P031_PIXEL_ARRAY_WIDTH 2752 +#define MT9P031_PIXEL_ARRAY_HEIGHT 2004 + +#define MT9P031_CHIP_VERSION 0x00 +#define MT9P031_CHIP_VERSION_VALUE 0x1801 +#define MT9P031_ROW_START 0x01 +#define MT9P031_ROW_START_MIN 0 +#define MT9P031_ROW_START_MAX 2004 +#define MT9P031_ROW_START_DEF 54 +#define MT9P031_COLUMN_START 0x02 +#define MT9P031_COLUMN_START_MIN 0 +#define MT9P031_COLUMN_START_MAX 2750 +#define MT9P031_COLUMN_START_DEF 16 +#define MT9P031_WINDOW_HEIGHT 0x03 +#define MT9P031_WINDOW_HEIGHT_MIN 2 +#define MT9P031_WINDOW_HEIGHT_MAX 2006 +#define MT9P031_WINDOW_HEIGHT_DEF 1944 +#define MT9P031_WINDOW_WIDTH 0x04 +#define MT9P031_WINDOW_WIDTH_MIN 2 +#define MT9P031_WINDOW_WIDTH_MAX 2752 +#define MT9P031_WINDOW_WIDTH_DEF 2592 +#define MT9P031_HORIZONTAL_BLANK 0x05 +#define MT9P031_HORIZONTAL_BLANK_MIN 0 +#define MT9P031_HORIZONTAL_BLANK_MAX 4095 +#define MT9P031_VERTICAL_BLANK 0x06 +#define MT9P031_VERTICAL_BLANK_MIN 1 +#define MT9P031_VERTICAL_BLANK_MAX 4096 +#define MT9P031_VERTICAL_BLANK_DEF 26 +#define MT9P031_OUTPUT_CONTROL 0x07 +#define MT9P031_OUTPUT_CONTROL_CEN 2 +#define MT9P031_OUTPUT_CONTROL_SYN 1 +#define MT9P031_OUTPUT_CONTROL_DEF 0x1f82 +#define MT9P031_SHUTTER_WIDTH_UPPER 0x08 +#define MT9P031_SHUTTER_WIDTH_LOWER 0x09 +#define MT9P031_SHUTTER_WIDTH_MIN 1 +#define MT9P031_SHUTTER_WIDTH_MAX 1048575 +#define MT9P031_SHUTTER_WIDTH_DEF 1943 +#define MT9P031_PLL_CONTROL 0x10 +#define MT9P031_PLL_CONTROL_PWROFF 0x0050 +#define MT9P031_PLL_CONTROL_PWRON 0x0051 +#define MT9P031_PLL_CONTROL_USEPLL 0x0052 +#define MT9P031_PLL_CONFIG_1 0x11 +#define MT9P031_PLL_CONFIG_2 0x12 +#define MT9P031_PIXEL_CLOCK_CONTROL 0x0a +#define MT9P031_PIXEL_CLOCK_INVERT (1 << 15) +#define MT9P031_PIXEL_CLOCK_SHIFT(n) ((n) << 8) +#define MT9P031_PIXEL_CLOCK_DIVIDE(n) ((n) << 0) +#define MT9P031_RESTART 0x0b +#define MT9P031_FRAME_PAUSE_RESTART (1 << 1) +#define MT9P031_FRAME_RESTART (1 << 0) +#define MT9P031_SHUTTER_DELAY 0x0c +#define MT9P031_RST 0x0d +#define MT9P031_RST_ENABLE 1 +#define MT9P031_RST_DISABLE 0 +#define MT9P031_READ_MODE_1 0x1e +#define MT9P031_READ_MODE_2 0x20 +#define MT9P031_READ_MODE_2_ROW_MIR (1 << 15) +#define MT9P031_READ_MODE_2_COL_MIR (1 << 14) +#define MT9P031_READ_MODE_2_ROW_BLC (1 << 6) +#define MT9P031_ROW_ADDRESS_MODE 0x22 +#define MT9P031_COLUMN_ADDRESS_MODE 0x23 +#define MT9P031_GLOBAL_GAIN 0x35 +#define MT9P031_GLOBAL_GAIN_MIN 8 +#define MT9P031_GLOBAL_GAIN_MAX 1024 +#define MT9P031_GLOBAL_GAIN_DEF 8 +#define MT9P031_GLOBAL_GAIN_MULT (1 << 6) +#define MT9P031_ROW_BLACK_TARGET 0x49 +#define MT9P031_ROW_BLACK_DEF_OFFSET 0x4b +#define MT9P031_GREEN1_OFFSET 0x60 +#define MT9P031_GREEN2_OFFSET 0x61 +#define MT9P031_BLACK_LEVEL_CALIBRATION 0x62 +#define MT9P031_BLC_MANUAL_BLC (1 << 0) +#define MT9P031_RED_OFFSET 0x63 +#define MT9P031_BLUE_OFFSET 0x64 +#define MT9P031_TEST_PATTERN 0xa0 +#define MT9P031_TEST_PATTERN_SHIFT 3 +#define MT9P031_TEST_PATTERN_ENABLE (1 << 0) +#define MT9P031_TEST_PATTERN_DISABLE (0 << 0) +#define MT9P031_TEST_PATTERN_GREEN 0xa1 +#define MT9P031_TEST_PATTERN_RED 0xa2 +#define MT9P031_TEST_PATTERN_BLUE 0xa3 + +enum mt9p031_model { + MT9P031_MODEL_COLOR, + MT9P031_MODEL_MONOCHROME, +}; + +struct mt9p031 { + struct v4l2_subdev subdev; + struct media_pad pad; + struct v4l2_rect crop; /* Sensor window */ + struct v4l2_mbus_framefmt format; + struct mt9p031_platform_data *pdata; + struct mutex power_lock; /* lock to protect power_count */ + int power_count; + + struct clk *clk; + struct regulator_bulk_data regulators[3]; + + enum mt9p031_model model; + struct aptina_pll pll; + unsigned int clk_div; + bool use_pll; + struct gpio_desc *reset; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *blc_auto; + struct v4l2_ctrl *blc_offset; + + /* Registers cache */ + u16 output_control; + u16 mode2; +}; + +static struct mt9p031 *to_mt9p031(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mt9p031, subdev); +} + +static int mt9p031_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_word_swapped(client, reg); +} + +static int mt9p031_write(struct i2c_client *client, u8 reg, u16 data) +{ + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static int mt9p031_set_output_control(struct mt9p031 *mt9p031, u16 clear, + u16 set) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + u16 value = (mt9p031->output_control & ~clear) | set; + int ret; + + ret = mt9p031_write(client, MT9P031_OUTPUT_CONTROL, value); + if (ret < 0) + return ret; + + mt9p031->output_control = value; + return 0; +} + +static int mt9p031_set_mode2(struct mt9p031 *mt9p031, u16 clear, u16 set) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + u16 value = (mt9p031->mode2 & ~clear) | set; + int ret; + + ret = mt9p031_write(client, MT9P031_READ_MODE_2, value); + if (ret < 0) + return ret; + + mt9p031->mode2 = value; + return 0; +} + +static int mt9p031_reset(struct mt9p031 *mt9p031) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + int ret; + + /* Disable chip output, synchronous option update */ + ret = mt9p031_write(client, MT9P031_RST, MT9P031_RST_ENABLE); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_RST, MT9P031_RST_DISABLE); + if (ret < 0) + return ret; + + ret = mt9p031_write(client, MT9P031_PIXEL_CLOCK_CONTROL, + MT9P031_PIXEL_CLOCK_DIVIDE(mt9p031->clk_div)); + if (ret < 0) + return ret; + + return mt9p031_set_output_control(mt9p031, MT9P031_OUTPUT_CONTROL_CEN, + 0); +} + +static int mt9p031_clk_setup(struct mt9p031 *mt9p031) +{ + static const struct aptina_pll_limits limits = { + .ext_clock_min = 6000000, + .ext_clock_max = 27000000, + .int_clock_min = 2000000, + .int_clock_max = 13500000, + .out_clock_min = 180000000, + .out_clock_max = 360000000, + .pix_clock_max = 96000000, + .n_min = 1, + .n_max = 64, + .m_min = 16, + .m_max = 255, + .p1_min = 1, + .p1_max = 128, + }; + + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + struct mt9p031_platform_data *pdata = mt9p031->pdata; + int ret; + + mt9p031->clk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(mt9p031->clk)) + return PTR_ERR(mt9p031->clk); + + ret = clk_set_rate(mt9p031->clk, pdata->ext_freq); + if (ret < 0) + return ret; + + /* If the external clock frequency is out of bounds for the PLL use the + * pixel clock divider only and disable the PLL. + */ + if (pdata->ext_freq > limits.ext_clock_max) { + unsigned int div; + + div = DIV_ROUND_UP(pdata->ext_freq, pdata->target_freq); + div = roundup_pow_of_two(div) / 2; + + mt9p031->clk_div = min_t(unsigned int, div, 64); + mt9p031->use_pll = false; + + return 0; + } + + mt9p031->pll.ext_clock = pdata->ext_freq; + mt9p031->pll.pix_clock = pdata->target_freq; + mt9p031->use_pll = true; + + return aptina_pll_calculate(&client->dev, &limits, &mt9p031->pll); +} + +static int mt9p031_pll_enable(struct mt9p031 *mt9p031) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + int ret; + + if (!mt9p031->use_pll) + return 0; + + ret = mt9p031_write(client, MT9P031_PLL_CONTROL, + MT9P031_PLL_CONTROL_PWRON); + if (ret < 0) + return ret; + + ret = mt9p031_write(client, MT9P031_PLL_CONFIG_1, + (mt9p031->pll.m << 8) | (mt9p031->pll.n - 1)); + if (ret < 0) + return ret; + + ret = mt9p031_write(client, MT9P031_PLL_CONFIG_2, mt9p031->pll.p1 - 1); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + ret = mt9p031_write(client, MT9P031_PLL_CONTROL, + MT9P031_PLL_CONTROL_PWRON | + MT9P031_PLL_CONTROL_USEPLL); + return ret; +} + +static inline int mt9p031_pll_disable(struct mt9p031 *mt9p031) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + + if (!mt9p031->use_pll) + return 0; + + return mt9p031_write(client, MT9P031_PLL_CONTROL, + MT9P031_PLL_CONTROL_PWROFF); +} + +static int mt9p031_power_on(struct mt9p031 *mt9p031) +{ + int ret; + + /* Ensure RESET_BAR is active */ + if (mt9p031->reset) { + gpiod_set_value(mt9p031->reset, 1); + usleep_range(1000, 2000); + } + + /* Bring up the supplies */ + ret = regulator_bulk_enable(ARRAY_SIZE(mt9p031->regulators), + mt9p031->regulators); + if (ret < 0) + return ret; + + /* Enable clock */ + if (mt9p031->clk) { + ret = clk_prepare_enable(mt9p031->clk); + if (ret) { + regulator_bulk_disable(ARRAY_SIZE(mt9p031->regulators), + mt9p031->regulators); + return ret; + } + } + + /* Now RESET_BAR must be high */ + if (mt9p031->reset) { + gpiod_set_value(mt9p031->reset, 0); + usleep_range(1000, 2000); + } + + return 0; +} + +static void mt9p031_power_off(struct mt9p031 *mt9p031) +{ + if (mt9p031->reset) { + gpiod_set_value(mt9p031->reset, 1); + usleep_range(1000, 2000); + } + + regulator_bulk_disable(ARRAY_SIZE(mt9p031->regulators), + mt9p031->regulators); + + if (mt9p031->clk) + clk_disable_unprepare(mt9p031->clk); +} + +static int __mt9p031_set_power(struct mt9p031 *mt9p031, bool on) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + int ret; + + if (!on) { + mt9p031_power_off(mt9p031); + return 0; + } + + ret = mt9p031_power_on(mt9p031); + if (ret < 0) + return ret; + + ret = mt9p031_reset(mt9p031); + if (ret < 0) { + dev_err(&client->dev, "Failed to reset the camera\n"); + return ret; + } + + return v4l2_ctrl_handler_setup(&mt9p031->ctrls); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev video operations + */ + +static int mt9p031_set_params(struct mt9p031 *mt9p031) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + struct v4l2_mbus_framefmt *format = &mt9p031->format; + const struct v4l2_rect *crop = &mt9p031->crop; + unsigned int hblank; + unsigned int vblank; + unsigned int xskip; + unsigned int yskip; + unsigned int xbin; + unsigned int ybin; + int ret; + + /* Windows position and size. + * + * TODO: Make sure the start coordinates and window size match the + * skipping, binning and mirroring (see description of registers 2 and 4 + * in table 13, and Binning section on page 41). + */ + ret = mt9p031_write(client, MT9P031_COLUMN_START, crop->left); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_ROW_START, crop->top); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_WINDOW_WIDTH, crop->width - 1); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_WINDOW_HEIGHT, crop->height - 1); + if (ret < 0) + return ret; + + /* Row and column binning and skipping. Use the maximum binning value + * compatible with the skipping settings. + */ + xskip = DIV_ROUND_CLOSEST(crop->width, format->width); + yskip = DIV_ROUND_CLOSEST(crop->height, format->height); + xbin = 1 << (ffs(xskip) - 1); + ybin = 1 << (ffs(yskip) - 1); + + ret = mt9p031_write(client, MT9P031_COLUMN_ADDRESS_MODE, + ((xbin - 1) << 4) | (xskip - 1)); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_ROW_ADDRESS_MODE, + ((ybin - 1) << 4) | (yskip - 1)); + if (ret < 0) + return ret; + + /* Blanking - use minimum value for horizontal blanking and default + * value for vertical blanking. + */ + hblank = 346 * ybin + 64 + (80 >> min_t(unsigned int, xbin, 3)); + vblank = MT9P031_VERTICAL_BLANK_DEF; + + ret = mt9p031_write(client, MT9P031_HORIZONTAL_BLANK, hblank - 1); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_VERTICAL_BLANK, vblank - 1); + if (ret < 0) + return ret; + + return ret; +} + +static int mt9p031_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int val; + int ret; + + if (!enable) { + /* enable pause restart */ + val = MT9P031_FRAME_PAUSE_RESTART; + ret = mt9p031_write(client, MT9P031_RESTART, val); + if (ret < 0) + return ret; + + /* enable restart + keep pause restart set */ + val |= MT9P031_FRAME_RESTART; + ret = mt9p031_write(client, MT9P031_RESTART, val); + if (ret < 0) + return ret; + + /* Stop sensor readout */ + ret = mt9p031_set_output_control(mt9p031, + MT9P031_OUTPUT_CONTROL_CEN, 0); + if (ret < 0) + return ret; + + return mt9p031_pll_disable(mt9p031); + } + + ret = mt9p031_set_params(mt9p031); + if (ret < 0) + return ret; + + /* Switch to master "normal" mode */ + ret = mt9p031_set_output_control(mt9p031, 0, + MT9P031_OUTPUT_CONTROL_CEN); + if (ret < 0) + return ret; + + /* + * - clear pause restart + * - don't clear restart as clearing restart manually can cause + * undefined behavior + */ + val = MT9P031_FRAME_RESTART; + ret = mt9p031_write(client, MT9P031_RESTART, val); + if (ret < 0) + return ret; + + return mt9p031_pll_enable(mt9p031); +} + +static int mt9p031_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + + if (code->pad || code->index) + return -EINVAL; + + code->code = mt9p031->format.code; + return 0; +} + +static int mt9p031_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + + if (fse->index >= 8 || fse->code != mt9p031->format.code) + return -EINVAL; + + fse->min_width = MT9P031_WINDOW_WIDTH_DEF + / min_t(unsigned int, 7, fse->index + 1); + fse->max_width = fse->min_width; + fse->min_height = MT9P031_WINDOW_HEIGHT_DEF / (fse->index + 1); + fse->max_height = fse->min_height; + + return 0; +} + +static struct v4l2_mbus_framefmt * +__mt9p031_get_pad_format(struct mt9p031 *mt9p031, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&mt9p031->subdev, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9p031->format; + default: + return NULL; + } +} + +static struct v4l2_rect * +__mt9p031_get_pad_crop(struct mt9p031 *mt9p031, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&mt9p031->subdev, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9p031->crop; + default: + return NULL; + } +} + +static int mt9p031_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + + fmt->format = *__mt9p031_get_pad_format(mt9p031, cfg, fmt->pad, + fmt->which); + return 0; +} + +static int mt9p031_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + unsigned int width; + unsigned int height; + unsigned int hratio; + unsigned int vratio; + + __crop = __mt9p031_get_pad_crop(mt9p031, cfg, format->pad, + format->which); + + /* Clamp the width and height to avoid dividing by zero. */ + width = clamp_t(unsigned int, ALIGN(format->format.width, 2), + max_t(unsigned int, __crop->width / 7, + MT9P031_WINDOW_WIDTH_MIN), + __crop->width); + height = clamp_t(unsigned int, ALIGN(format->format.height, 2), + max_t(unsigned int, __crop->height / 8, + MT9P031_WINDOW_HEIGHT_MIN), + __crop->height); + + hratio = DIV_ROUND_CLOSEST(__crop->width, width); + vratio = DIV_ROUND_CLOSEST(__crop->height, height); + + __format = __mt9p031_get_pad_format(mt9p031, cfg, format->pad, + format->which); + __format->width = __crop->width / hratio; + __format->height = __crop->height / vratio; + + format->format = *__format; + + return 0; +} + +static int mt9p031_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + sel->r = *__mt9p031_get_pad_crop(mt9p031, cfg, sel->pad, sel->which); + return 0; +} + +static int mt9p031_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + struct v4l2_rect rect; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + /* Clamp the crop rectangle boundaries and align them to a multiple of 2 + * pixels to ensure a GRBG Bayer pattern. + */ + rect.left = clamp(ALIGN(sel->r.left, 2), MT9P031_COLUMN_START_MIN, + MT9P031_COLUMN_START_MAX); + rect.top = clamp(ALIGN(sel->r.top, 2), MT9P031_ROW_START_MIN, + MT9P031_ROW_START_MAX); + rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), + MT9P031_WINDOW_WIDTH_MIN, + MT9P031_WINDOW_WIDTH_MAX); + rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), + MT9P031_WINDOW_HEIGHT_MIN, + MT9P031_WINDOW_HEIGHT_MAX); + + rect.width = min_t(unsigned int, rect.width, + MT9P031_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min_t(unsigned int, rect.height, + MT9P031_PIXEL_ARRAY_HEIGHT - rect.top); + + __crop = __mt9p031_get_pad_crop(mt9p031, cfg, sel->pad, sel->which); + + if (rect.width != __crop->width || rect.height != __crop->height) { + /* Reset the output image size if the crop rectangle size has + * been modified. + */ + __format = __mt9p031_get_pad_format(mt9p031, cfg, sel->pad, + sel->which); + __format->width = rect.width; + __format->height = rect.height; + } + + *__crop = rect; + sel->r = rect; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev control operations + */ + +#define V4L2_CID_BLC_AUTO (V4L2_CID_USER_BASE | 0x1002) +#define V4L2_CID_BLC_TARGET_LEVEL (V4L2_CID_USER_BASE | 0x1003) +#define V4L2_CID_BLC_ANALOG_OFFSET (V4L2_CID_USER_BASE | 0x1004) +#define V4L2_CID_BLC_DIGITAL_OFFSET (V4L2_CID_USER_BASE | 0x1005) + +static int mt9p031_restore_blc(struct mt9p031 *mt9p031) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + int ret; + + if (mt9p031->blc_auto->cur.val != 0) { + ret = mt9p031_set_mode2(mt9p031, 0, + MT9P031_READ_MODE_2_ROW_BLC); + if (ret < 0) + return ret; + } + + if (mt9p031->blc_offset->cur.val != 0) { + ret = mt9p031_write(client, MT9P031_ROW_BLACK_TARGET, + mt9p031->blc_offset->cur.val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int mt9p031_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9p031 *mt9p031 = + container_of(ctrl->handler, struct mt9p031, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + u16 data; + int ret; + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = mt9p031_write(client, MT9P031_SHUTTER_WIDTH_UPPER, + (ctrl->val >> 16) & 0xffff); + if (ret < 0) + return ret; + + return mt9p031_write(client, MT9P031_SHUTTER_WIDTH_LOWER, + ctrl->val & 0xffff); + + case V4L2_CID_GAIN: + /* Gain is controlled by 2 analog stages and a digital stage. + * Valid values for the 3 stages are + * + * Stage Min Max Step + * ------------------------------------------ + * First analog stage x1 x2 1 + * Second analog stage x1 x4 0.125 + * Digital stage x1 x16 0.125 + * + * To minimize noise, the gain stages should be used in the + * second analog stage, first analog stage, digital stage order. + * Gain from a previous stage should be pushed to its maximum + * value before the next stage is used. + */ + if (ctrl->val <= 32) { + data = ctrl->val; + } else if (ctrl->val <= 64) { + ctrl->val &= ~1; + data = (1 << 6) | (ctrl->val >> 1); + } else { + ctrl->val &= ~7; + data = ((ctrl->val - 64) << 5) | (1 << 6) | 32; + } + + return mt9p031_write(client, MT9P031_GLOBAL_GAIN, data); + + case V4L2_CID_HFLIP: + if (ctrl->val) + return mt9p031_set_mode2(mt9p031, + 0, MT9P031_READ_MODE_2_COL_MIR); + else + return mt9p031_set_mode2(mt9p031, + MT9P031_READ_MODE_2_COL_MIR, 0); + + case V4L2_CID_VFLIP: + if (ctrl->val) + return mt9p031_set_mode2(mt9p031, + 0, MT9P031_READ_MODE_2_ROW_MIR); + else + return mt9p031_set_mode2(mt9p031, + MT9P031_READ_MODE_2_ROW_MIR, 0); + + case V4L2_CID_TEST_PATTERN: + /* The digital side of the Black Level Calibration function must + * be disabled when generating a test pattern to avoid artifacts + * in the image. Activate (deactivate) the BLC-related controls + * when the test pattern is enabled (disabled). + */ + v4l2_ctrl_activate(mt9p031->blc_auto, ctrl->val == 0); + v4l2_ctrl_activate(mt9p031->blc_offset, ctrl->val == 0); + + if (!ctrl->val) { + /* Restore the BLC settings. */ + ret = mt9p031_restore_blc(mt9p031); + if (ret < 0) + return ret; + + return mt9p031_write(client, MT9P031_TEST_PATTERN, + MT9P031_TEST_PATTERN_DISABLE); + } + + ret = mt9p031_write(client, MT9P031_TEST_PATTERN_GREEN, 0x05a0); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_TEST_PATTERN_RED, 0x0a50); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_TEST_PATTERN_BLUE, 0x0aa0); + if (ret < 0) + return ret; + + /* Disable digital BLC when generating a test pattern. */ + ret = mt9p031_set_mode2(mt9p031, MT9P031_READ_MODE_2_ROW_BLC, + 0); + if (ret < 0) + return ret; + + ret = mt9p031_write(client, MT9P031_ROW_BLACK_DEF_OFFSET, 0); + if (ret < 0) + return ret; + + return mt9p031_write(client, MT9P031_TEST_PATTERN, + ((ctrl->val - 1) << MT9P031_TEST_PATTERN_SHIFT) + | MT9P031_TEST_PATTERN_ENABLE); + + case V4L2_CID_BLC_AUTO: + ret = mt9p031_set_mode2(mt9p031, + ctrl->val ? 0 : MT9P031_READ_MODE_2_ROW_BLC, + ctrl->val ? MT9P031_READ_MODE_2_ROW_BLC : 0); + if (ret < 0) + return ret; + + return mt9p031_write(client, MT9P031_BLACK_LEVEL_CALIBRATION, + ctrl->val ? 0 : MT9P031_BLC_MANUAL_BLC); + + case V4L2_CID_BLC_TARGET_LEVEL: + return mt9p031_write(client, MT9P031_ROW_BLACK_TARGET, + ctrl->val); + + case V4L2_CID_BLC_ANALOG_OFFSET: + data = ctrl->val & ((1 << 9) - 1); + + ret = mt9p031_write(client, MT9P031_GREEN1_OFFSET, data); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_GREEN2_OFFSET, data); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_RED_OFFSET, data); + if (ret < 0) + return ret; + return mt9p031_write(client, MT9P031_BLUE_OFFSET, data); + + case V4L2_CID_BLC_DIGITAL_OFFSET: + return mt9p031_write(client, MT9P031_ROW_BLACK_DEF_OFFSET, + ctrl->val & ((1 << 12) - 1)); + } + + return 0; +} + +static const struct v4l2_ctrl_ops mt9p031_ctrl_ops = { + .s_ctrl = mt9p031_s_ctrl, +}; + +static const char * const mt9p031_test_pattern_menu[] = { + "Disabled", + "Color Field", + "Horizontal Gradient", + "Vertical Gradient", + "Diagonal Gradient", + "Classic Test Pattern", + "Walking 1s", + "Monochrome Horizontal Bars", + "Monochrome Vertical Bars", + "Vertical Color Bars", +}; + +static const struct v4l2_ctrl_config mt9p031_ctrls[] = { + { + .ops = &mt9p031_ctrl_ops, + .id = V4L2_CID_BLC_AUTO, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "BLC, Auto", + .min = 0, + .max = 1, + .step = 1, + .def = 1, + .flags = 0, + }, { + .ops = &mt9p031_ctrl_ops, + .id = V4L2_CID_BLC_TARGET_LEVEL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "BLC Target Level", + .min = 0, + .max = 4095, + .step = 1, + .def = 168, + .flags = 0, + }, { + .ops = &mt9p031_ctrl_ops, + .id = V4L2_CID_BLC_ANALOG_OFFSET, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "BLC Analog Offset", + .min = -255, + .max = 255, + .step = 1, + .def = 32, + .flags = 0, + }, { + .ops = &mt9p031_ctrl_ops, + .id = V4L2_CID_BLC_DIGITAL_OFFSET, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "BLC Digital Offset", + .min = -2048, + .max = 2047, + .step = 1, + .def = 40, + .flags = 0, + } +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +static int mt9p031_set_power(struct v4l2_subdev *subdev, int on) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + int ret = 0; + + mutex_lock(&mt9p031->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (mt9p031->power_count == !on) { + ret = __mt9p031_set_power(mt9p031, !!on); + if (ret < 0) + goto out; + } + + /* Update the power count. */ + mt9p031->power_count += on ? 1 : -1; + WARN_ON(mt9p031->power_count < 0); + +out: + mutex_unlock(&mt9p031->power_lock); + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev internal operations + */ + +static int mt9p031_registered(struct v4l2_subdev *subdev) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + s32 data; + int ret; + + ret = mt9p031_power_on(mt9p031); + if (ret < 0) { + dev_err(&client->dev, "MT9P031 power up failed\n"); + return ret; + } + + /* Read out the chip version register */ + data = mt9p031_read(client, MT9P031_CHIP_VERSION); + mt9p031_power_off(mt9p031); + + if (data != MT9P031_CHIP_VERSION_VALUE) { + dev_err(&client->dev, "MT9P031 not detected, wrong version " + "0x%04x\n", data); + return -ENODEV; + } + + dev_info(&client->dev, "MT9P031 detected at address 0x%02x\n", + client->addr); + + return 0; +} + +static int mt9p031_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + crop = v4l2_subdev_get_try_crop(subdev, fh->pad, 0); + crop->left = MT9P031_COLUMN_START_DEF; + crop->top = MT9P031_ROW_START_DEF; + crop->width = MT9P031_WINDOW_WIDTH_DEF; + crop->height = MT9P031_WINDOW_HEIGHT_DEF; + + format = v4l2_subdev_get_try_format(subdev, fh->pad, 0); + + if (mt9p031->model == MT9P031_MODEL_MONOCHROME) + format->code = MEDIA_BUS_FMT_Y12_1X12; + else + format->code = MEDIA_BUS_FMT_SGRBG12_1X12; + + format->width = MT9P031_WINDOW_WIDTH_DEF; + format->height = MT9P031_WINDOW_HEIGHT_DEF; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + return mt9p031_set_power(subdev, 1); +} + +static int mt9p031_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + return mt9p031_set_power(subdev, 0); +} + +static const struct v4l2_subdev_core_ops mt9p031_subdev_core_ops = { + .s_power = mt9p031_set_power, +}; + +static const struct v4l2_subdev_video_ops mt9p031_subdev_video_ops = { + .s_stream = mt9p031_s_stream, +}; + +static const struct v4l2_subdev_pad_ops mt9p031_subdev_pad_ops = { + .enum_mbus_code = mt9p031_enum_mbus_code, + .enum_frame_size = mt9p031_enum_frame_size, + .get_fmt = mt9p031_get_format, + .set_fmt = mt9p031_set_format, + .get_selection = mt9p031_get_selection, + .set_selection = mt9p031_set_selection, +}; + +static const struct v4l2_subdev_ops mt9p031_subdev_ops = { + .core = &mt9p031_subdev_core_ops, + .video = &mt9p031_subdev_video_ops, + .pad = &mt9p031_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops mt9p031_subdev_internal_ops = { + .registered = mt9p031_registered, + .open = mt9p031_open, + .close = mt9p031_close, +}; + +/* ----------------------------------------------------------------------------- + * Driver initialization and probing + */ + +static struct mt9p031_platform_data * +mt9p031_get_pdata(struct i2c_client *client) +{ + struct mt9p031_platform_data *pdata; + struct device_node *np; + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return client->dev.platform_data; + + np = of_graph_get_next_endpoint(client->dev.of_node, NULL); + if (!np) + return NULL; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto done; + + of_property_read_u32(np, "input-clock-frequency", &pdata->ext_freq); + of_property_read_u32(np, "pixel-clock-frequency", &pdata->target_freq); + +done: + of_node_put(np); + return pdata; +} + +static int mt9p031_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9p031_platform_data *pdata = mt9p031_get_pdata(client); + struct i2c_adapter *adapter = client->adapter; + struct mt9p031 *mt9p031; + unsigned int i; + int ret; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&client->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9p031 = devm_kzalloc(&client->dev, sizeof(*mt9p031), GFP_KERNEL); + if (mt9p031 == NULL) + return -ENOMEM; + + mt9p031->pdata = pdata; + mt9p031->output_control = MT9P031_OUTPUT_CONTROL_DEF; + mt9p031->mode2 = MT9P031_READ_MODE_2_ROW_BLC; + mt9p031->model = did->driver_data; + + mt9p031->regulators[0].supply = "vdd"; + mt9p031->regulators[1].supply = "vdd_io"; + mt9p031->regulators[2].supply = "vaa"; + + ret = devm_regulator_bulk_get(&client->dev, 3, mt9p031->regulators); + if (ret < 0) { + dev_err(&client->dev, "Unable to get regulators\n"); + return ret; + } + + mutex_init(&mt9p031->power_lock); + + v4l2_ctrl_handler_init(&mt9p031->ctrls, ARRAY_SIZE(mt9p031_ctrls) + 6); + + v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_EXPOSURE, MT9P031_SHUTTER_WIDTH_MIN, + MT9P031_SHUTTER_WIDTH_MAX, 1, + MT9P031_SHUTTER_WIDTH_DEF); + v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_GAIN, MT9P031_GLOBAL_GAIN_MIN, + MT9P031_GLOBAL_GAIN_MAX, 1, MT9P031_GLOBAL_GAIN_DEF); + v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_PIXEL_RATE, pdata->target_freq, + pdata->target_freq, 1, pdata->target_freq); + v4l2_ctrl_new_std_menu_items(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(mt9p031_test_pattern_menu) - 1, 0, + 0, mt9p031_test_pattern_menu); + + for (i = 0; i < ARRAY_SIZE(mt9p031_ctrls); ++i) + v4l2_ctrl_new_custom(&mt9p031->ctrls, &mt9p031_ctrls[i], NULL); + + mt9p031->subdev.ctrl_handler = &mt9p031->ctrls; + + if (mt9p031->ctrls.error) { + printk(KERN_INFO "%s: control initialization error %d\n", + __func__, mt9p031->ctrls.error); + ret = mt9p031->ctrls.error; + goto done; + } + + mt9p031->blc_auto = v4l2_ctrl_find(&mt9p031->ctrls, V4L2_CID_BLC_AUTO); + mt9p031->blc_offset = v4l2_ctrl_find(&mt9p031->ctrls, + V4L2_CID_BLC_DIGITAL_OFFSET); + + v4l2_i2c_subdev_init(&mt9p031->subdev, client, &mt9p031_subdev_ops); + mt9p031->subdev.internal_ops = &mt9p031_subdev_internal_ops; + + mt9p031->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + mt9p031->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&mt9p031->subdev.entity, 1, &mt9p031->pad); + if (ret < 0) + goto done; + + mt9p031->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + mt9p031->crop.width = MT9P031_WINDOW_WIDTH_DEF; + mt9p031->crop.height = MT9P031_WINDOW_HEIGHT_DEF; + mt9p031->crop.left = MT9P031_COLUMN_START_DEF; + mt9p031->crop.top = MT9P031_ROW_START_DEF; + + if (mt9p031->model == MT9P031_MODEL_MONOCHROME) + mt9p031->format.code = MEDIA_BUS_FMT_Y12_1X12; + else + mt9p031->format.code = MEDIA_BUS_FMT_SGRBG12_1X12; + + mt9p031->format.width = MT9P031_WINDOW_WIDTH_DEF; + mt9p031->format.height = MT9P031_WINDOW_HEIGHT_DEF; + mt9p031->format.field = V4L2_FIELD_NONE; + mt9p031->format.colorspace = V4L2_COLORSPACE_SRGB; + + mt9p031->reset = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + + ret = mt9p031_clk_setup(mt9p031); + if (ret) + goto done; + + ret = v4l2_async_register_subdev(&mt9p031->subdev); + +done: + if (ret < 0) { + v4l2_ctrl_handler_free(&mt9p031->ctrls); + media_entity_cleanup(&mt9p031->subdev.entity); + mutex_destroy(&mt9p031->power_lock); + } + + return ret; +} + +static int mt9p031_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + + v4l2_ctrl_handler_free(&mt9p031->ctrls); + v4l2_async_unregister_subdev(subdev); + media_entity_cleanup(&subdev->entity); + mutex_destroy(&mt9p031->power_lock); + + return 0; +} + +static const struct i2c_device_id mt9p031_id[] = { + { "mt9p031", MT9P031_MODEL_COLOR }, + { "mt9p031m", MT9P031_MODEL_MONOCHROME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9p031_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id mt9p031_of_match[] = { + { .compatible = "aptina,mt9p031", }, + { .compatible = "aptina,mt9p031m", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mt9p031_of_match); +#endif + +static struct i2c_driver mt9p031_i2c_driver = { + .driver = { + .of_match_table = of_match_ptr(mt9p031_of_match), + .name = "mt9p031", + }, + .probe = mt9p031_probe, + .remove = mt9p031_remove, + .id_table = mt9p031_id, +}; + +module_i2c_driver(mt9p031_i2c_driver); + +MODULE_DESCRIPTION("Aptina MT9P031 Camera driver"); +MODULE_AUTHOR("Bastian Hecht <hechtb@gmail.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/mt9t001.c b/drivers/media/i2c/mt9t001.c new file mode 100644 index 000000000..2e96ff523 --- /dev/null +++ b/drivers/media/i2c/mt9t001.c @@ -0,0 +1,986 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MT9T001 CMOS Image Sensor from Aptina (Micron) + * + * Copyright (C) 2010-2011, Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * + * Based on the MT9M001 driver, + * + * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> + */ + +#include <linux/clk.h> +#include <linux/i2c.h> +#include <linux/log2.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/v4l2-mediabus.h> + +#include <media/i2c/mt9t001.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#define MT9T001_PIXEL_ARRAY_HEIGHT 1568 +#define MT9T001_PIXEL_ARRAY_WIDTH 2112 + +#define MT9T001_CHIP_VERSION 0x00 +#define MT9T001_CHIP_ID 0x1621 +#define MT9T001_ROW_START 0x01 +#define MT9T001_ROW_START_MIN 0 +#define MT9T001_ROW_START_DEF 20 +#define MT9T001_ROW_START_MAX 1534 +#define MT9T001_COLUMN_START 0x02 +#define MT9T001_COLUMN_START_MIN 0 +#define MT9T001_COLUMN_START_DEF 32 +#define MT9T001_COLUMN_START_MAX 2046 +#define MT9T001_WINDOW_HEIGHT 0x03 +#define MT9T001_WINDOW_HEIGHT_MIN 1 +#define MT9T001_WINDOW_HEIGHT_DEF 1535 +#define MT9T001_WINDOW_HEIGHT_MAX 1567 +#define MT9T001_WINDOW_WIDTH 0x04 +#define MT9T001_WINDOW_WIDTH_MIN 1 +#define MT9T001_WINDOW_WIDTH_DEF 2047 +#define MT9T001_WINDOW_WIDTH_MAX 2111 +#define MT9T001_HORIZONTAL_BLANKING 0x05 +#define MT9T001_HORIZONTAL_BLANKING_MIN 21 +#define MT9T001_HORIZONTAL_BLANKING_MAX 1023 +#define MT9T001_VERTICAL_BLANKING 0x06 +#define MT9T001_VERTICAL_BLANKING_MIN 3 +#define MT9T001_VERTICAL_BLANKING_MAX 1023 +#define MT9T001_OUTPUT_CONTROL 0x07 +#define MT9T001_OUTPUT_CONTROL_SYNC (1 << 0) +#define MT9T001_OUTPUT_CONTROL_CHIP_ENABLE (1 << 1) +#define MT9T001_OUTPUT_CONTROL_TEST_DATA (1 << 6) +#define MT9T001_OUTPUT_CONTROL_DEF 0x0002 +#define MT9T001_SHUTTER_WIDTH_HIGH 0x08 +#define MT9T001_SHUTTER_WIDTH_LOW 0x09 +#define MT9T001_SHUTTER_WIDTH_MIN 1 +#define MT9T001_SHUTTER_WIDTH_DEF 1561 +#define MT9T001_SHUTTER_WIDTH_MAX (1024 * 1024) +#define MT9T001_PIXEL_CLOCK 0x0a +#define MT9T001_PIXEL_CLOCK_INVERT (1 << 15) +#define MT9T001_PIXEL_CLOCK_SHIFT_MASK (7 << 8) +#define MT9T001_PIXEL_CLOCK_SHIFT_SHIFT 8 +#define MT9T001_PIXEL_CLOCK_DIVIDE_MASK (0x7f << 0) +#define MT9T001_FRAME_RESTART 0x0b +#define MT9T001_SHUTTER_DELAY 0x0c +#define MT9T001_SHUTTER_DELAY_MAX 2047 +#define MT9T001_RESET 0x0d +#define MT9T001_READ_MODE1 0x1e +#define MT9T001_READ_MODE_SNAPSHOT (1 << 8) +#define MT9T001_READ_MODE_STROBE_ENABLE (1 << 9) +#define MT9T001_READ_MODE_STROBE_WIDTH (1 << 10) +#define MT9T001_READ_MODE_STROBE_OVERRIDE (1 << 11) +#define MT9T001_READ_MODE2 0x20 +#define MT9T001_READ_MODE_BAD_FRAMES (1 << 0) +#define MT9T001_READ_MODE_LINE_VALID_CONTINUOUS (1 << 9) +#define MT9T001_READ_MODE_LINE_VALID_FRAME (1 << 10) +#define MT9T001_READ_MODE3 0x21 +#define MT9T001_READ_MODE_GLOBAL_RESET (1 << 0) +#define MT9T001_READ_MODE_GHST_CTL (1 << 1) +#define MT9T001_ROW_ADDRESS_MODE 0x22 +#define MT9T001_ROW_SKIP_MASK (7 << 0) +#define MT9T001_ROW_BIN_MASK (3 << 3) +#define MT9T001_ROW_BIN_SHIFT 3 +#define MT9T001_COLUMN_ADDRESS_MODE 0x23 +#define MT9T001_COLUMN_SKIP_MASK (7 << 0) +#define MT9T001_COLUMN_BIN_MASK (3 << 3) +#define MT9T001_COLUMN_BIN_SHIFT 3 +#define MT9T001_GREEN1_GAIN 0x2b +#define MT9T001_BLUE_GAIN 0x2c +#define MT9T001_RED_GAIN 0x2d +#define MT9T001_GREEN2_GAIN 0x2e +#define MT9T001_TEST_DATA 0x32 +#define MT9T001_GLOBAL_GAIN 0x35 +#define MT9T001_GLOBAL_GAIN_MIN 8 +#define MT9T001_GLOBAL_GAIN_MAX 1024 +#define MT9T001_BLACK_LEVEL 0x49 +#define MT9T001_ROW_BLACK_DEFAULT_OFFSET 0x4b +#define MT9T001_BLC_DELTA_THRESHOLDS 0x5d +#define MT9T001_CAL_THRESHOLDS 0x5f +#define MT9T001_GREEN1_OFFSET 0x60 +#define MT9T001_GREEN2_OFFSET 0x61 +#define MT9T001_BLACK_LEVEL_CALIBRATION 0x62 +#define MT9T001_BLACK_LEVEL_OVERRIDE (1 << 0) +#define MT9T001_BLACK_LEVEL_DISABLE_OFFSET (1 << 1) +#define MT9T001_BLACK_LEVEL_RECALCULATE (1 << 12) +#define MT9T001_BLACK_LEVEL_LOCK_RED_BLUE (1 << 13) +#define MT9T001_BLACK_LEVEL_LOCK_GREEN (1 << 14) +#define MT9T001_RED_OFFSET 0x63 +#define MT9T001_BLUE_OFFSET 0x64 + +struct mt9t001 { + struct v4l2_subdev subdev; + struct media_pad pad; + + struct clk *clk; + struct regulator_bulk_data regulators[2]; + + struct mutex power_lock; /* lock to protect power_count */ + int power_count; + + struct v4l2_mbus_framefmt format; + struct v4l2_rect crop; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *gains[4]; + + u16 output_control; + u16 black_level; +}; + +static inline struct mt9t001 *to_mt9t001(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mt9t001, subdev); +} + +static int mt9t001_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_word_swapped(client, reg); +} + +static int mt9t001_write(struct i2c_client *client, u8 reg, u16 data) +{ + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static int mt9t001_set_output_control(struct mt9t001 *mt9t001, u16 clear, + u16 set) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9t001->subdev); + u16 value = (mt9t001->output_control & ~clear) | set; + int ret; + + if (value == mt9t001->output_control) + return 0; + + ret = mt9t001_write(client, MT9T001_OUTPUT_CONTROL, value); + if (ret < 0) + return ret; + + mt9t001->output_control = value; + return 0; +} + +static int mt9t001_reset(struct mt9t001 *mt9t001) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9t001->subdev); + int ret; + + /* Reset the chip and stop data read out */ + ret = mt9t001_write(client, MT9T001_RESET, 1); + if (ret < 0) + return ret; + + ret = mt9t001_write(client, MT9T001_RESET, 0); + if (ret < 0) + return ret; + + mt9t001->output_control = MT9T001_OUTPUT_CONTROL_DEF; + + return mt9t001_set_output_control(mt9t001, + MT9T001_OUTPUT_CONTROL_CHIP_ENABLE, + 0); +} + +static int mt9t001_power_on(struct mt9t001 *mt9t001) +{ + int ret; + + /* Bring up the supplies */ + ret = regulator_bulk_enable(ARRAY_SIZE(mt9t001->regulators), + mt9t001->regulators); + if (ret < 0) + return ret; + + /* Enable clock */ + ret = clk_prepare_enable(mt9t001->clk); + if (ret < 0) + regulator_bulk_disable(ARRAY_SIZE(mt9t001->regulators), + mt9t001->regulators); + + return ret; +} + +static void mt9t001_power_off(struct mt9t001 *mt9t001) +{ + regulator_bulk_disable(ARRAY_SIZE(mt9t001->regulators), + mt9t001->regulators); + + clk_disable_unprepare(mt9t001->clk); +} + +static int __mt9t001_set_power(struct mt9t001 *mt9t001, bool on) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9t001->subdev); + int ret; + + if (!on) { + mt9t001_power_off(mt9t001); + return 0; + } + + ret = mt9t001_power_on(mt9t001); + if (ret < 0) + return ret; + + ret = mt9t001_reset(mt9t001); + if (ret < 0) { + dev_err(&client->dev, "Failed to reset the camera\n"); + goto e_power; + } + + ret = v4l2_ctrl_handler_setup(&mt9t001->ctrls); + if (ret < 0) { + dev_err(&client->dev, "Failed to set up control handlers\n"); + goto e_power; + } + + return 0; + +e_power: + mt9t001_power_off(mt9t001); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev video operations + */ + +static struct v4l2_mbus_framefmt * +__mt9t001_get_pad_format(struct mt9t001 *mt9t001, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&mt9t001->subdev, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9t001->format; + default: + return NULL; + } +} + +static struct v4l2_rect * +__mt9t001_get_pad_crop(struct mt9t001 *mt9t001, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&mt9t001->subdev, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9t001->crop; + default: + return NULL; + } +} + +static int mt9t001_s_stream(struct v4l2_subdev *subdev, int enable) +{ + const u16 mode = MT9T001_OUTPUT_CONTROL_CHIP_ENABLE; + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct mt9t001_platform_data *pdata = client->dev.platform_data; + struct mt9t001 *mt9t001 = to_mt9t001(subdev); + struct v4l2_mbus_framefmt *format = &mt9t001->format; + struct v4l2_rect *crop = &mt9t001->crop; + unsigned int hratio; + unsigned int vratio; + int ret; + + if (!enable) + return mt9t001_set_output_control(mt9t001, mode, 0); + + /* Configure the pixel clock polarity */ + if (pdata->clk_pol) { + ret = mt9t001_write(client, MT9T001_PIXEL_CLOCK, + MT9T001_PIXEL_CLOCK_INVERT); + if (ret < 0) + return ret; + } + + /* Configure the window size and row/column bin */ + hratio = DIV_ROUND_CLOSEST(crop->width, format->width); + vratio = DIV_ROUND_CLOSEST(crop->height, format->height); + + ret = mt9t001_write(client, MT9T001_ROW_ADDRESS_MODE, hratio - 1); + if (ret < 0) + return ret; + + ret = mt9t001_write(client, MT9T001_COLUMN_ADDRESS_MODE, vratio - 1); + if (ret < 0) + return ret; + + ret = mt9t001_write(client, MT9T001_COLUMN_START, crop->left); + if (ret < 0) + return ret; + + ret = mt9t001_write(client, MT9T001_ROW_START, crop->top); + if (ret < 0) + return ret; + + ret = mt9t001_write(client, MT9T001_WINDOW_WIDTH, crop->width - 1); + if (ret < 0) + return ret; + + ret = mt9t001_write(client, MT9T001_WINDOW_HEIGHT, crop->height - 1); + if (ret < 0) + return ret; + + /* Switch to master "normal" mode */ + return mt9t001_set_output_control(mt9t001, 0, mode); +} + +static int mt9t001_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + return 0; +} + +static int mt9t001_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= 8 || fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = (MT9T001_WINDOW_WIDTH_DEF + 1) / fse->index; + fse->max_width = fse->min_width; + fse->min_height = (MT9T001_WINDOW_HEIGHT_DEF + 1) / fse->index; + fse->max_height = fse->min_height; + + return 0; +} + +static int mt9t001_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct mt9t001 *mt9t001 = to_mt9t001(subdev); + + format->format = *__mt9t001_get_pad_format(mt9t001, cfg, format->pad, + format->which); + return 0; +} + +static int mt9t001_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct mt9t001 *mt9t001 = to_mt9t001(subdev); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + unsigned int width; + unsigned int height; + unsigned int hratio; + unsigned int vratio; + + __crop = __mt9t001_get_pad_crop(mt9t001, cfg, format->pad, + format->which); + + /* Clamp the width and height to avoid dividing by zero. */ + width = clamp_t(unsigned int, ALIGN(format->format.width, 2), + max_t(unsigned int, __crop->width / 8, + MT9T001_WINDOW_HEIGHT_MIN + 1), + __crop->width); + height = clamp_t(unsigned int, ALIGN(format->format.height, 2), + max_t(unsigned int, __crop->height / 8, + MT9T001_WINDOW_HEIGHT_MIN + 1), + __crop->height); + + hratio = DIV_ROUND_CLOSEST(__crop->width, width); + vratio = DIV_ROUND_CLOSEST(__crop->height, height); + + __format = __mt9t001_get_pad_format(mt9t001, cfg, format->pad, + format->which); + __format->width = __crop->width / hratio; + __format->height = __crop->height / vratio; + + format->format = *__format; + + return 0; +} + +static int mt9t001_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct mt9t001 *mt9t001 = to_mt9t001(subdev); + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + sel->r = *__mt9t001_get_pad_crop(mt9t001, cfg, sel->pad, sel->which); + return 0; +} + +static int mt9t001_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct mt9t001 *mt9t001 = to_mt9t001(subdev); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + struct v4l2_rect rect; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + /* Clamp the crop rectangle boundaries and align them to a multiple of 2 + * pixels. + */ + rect.left = clamp(ALIGN(sel->r.left, 2), + MT9T001_COLUMN_START_MIN, + MT9T001_COLUMN_START_MAX); + rect.top = clamp(ALIGN(sel->r.top, 2), + MT9T001_ROW_START_MIN, + MT9T001_ROW_START_MAX); + rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), + MT9T001_WINDOW_WIDTH_MIN + 1, + MT9T001_WINDOW_WIDTH_MAX + 1); + rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), + MT9T001_WINDOW_HEIGHT_MIN + 1, + MT9T001_WINDOW_HEIGHT_MAX + 1); + + rect.width = min_t(unsigned int, rect.width, + MT9T001_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min_t(unsigned int, rect.height, + MT9T001_PIXEL_ARRAY_HEIGHT - rect.top); + + __crop = __mt9t001_get_pad_crop(mt9t001, cfg, sel->pad, sel->which); + + if (rect.width != __crop->width || rect.height != __crop->height) { + /* Reset the output image size if the crop rectangle size has + * been modified. + */ + __format = __mt9t001_get_pad_format(mt9t001, cfg, sel->pad, + sel->which); + __format->width = rect.width; + __format->height = rect.height; + } + + *__crop = rect; + sel->r = rect; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev control operations + */ + +#define V4L2_CID_TEST_PATTERN_COLOR (V4L2_CID_USER_BASE | 0x1001) +#define V4L2_CID_BLACK_LEVEL_AUTO (V4L2_CID_USER_BASE | 0x1002) +#define V4L2_CID_BLACK_LEVEL_OFFSET (V4L2_CID_USER_BASE | 0x1003) +#define V4L2_CID_BLACK_LEVEL_CALIBRATE (V4L2_CID_USER_BASE | 0x1004) + +#define V4L2_CID_GAIN_RED (V4L2_CTRL_CLASS_CAMERA | 0x1001) +#define V4L2_CID_GAIN_GREEN_RED (V4L2_CTRL_CLASS_CAMERA | 0x1002) +#define V4L2_CID_GAIN_GREEN_BLUE (V4L2_CTRL_CLASS_CAMERA | 0x1003) +#define V4L2_CID_GAIN_BLUE (V4L2_CTRL_CLASS_CAMERA | 0x1004) + +static u16 mt9t001_gain_value(s32 *gain) +{ + /* Gain is controlled by 2 analog stages and a digital stage. Valid + * values for the 3 stages are + * + * Stage Min Max Step + * ------------------------------------------ + * First analog stage x1 x2 1 + * Second analog stage x1 x4 0.125 + * Digital stage x1 x16 0.125 + * + * To minimize noise, the gain stages should be used in the second + * analog stage, first analog stage, digital stage order. Gain from a + * previous stage should be pushed to its maximum value before the next + * stage is used. + */ + if (*gain <= 32) + return *gain; + + if (*gain <= 64) { + *gain &= ~1; + return (1 << 6) | (*gain >> 1); + } + + *gain &= ~7; + return ((*gain - 64) << 5) | (1 << 6) | 32; +} + +static int mt9t001_ctrl_freeze(struct mt9t001 *mt9t001, bool freeze) +{ + return mt9t001_set_output_control(mt9t001, + freeze ? 0 : MT9T001_OUTPUT_CONTROL_SYNC, + freeze ? MT9T001_OUTPUT_CONTROL_SYNC : 0); +} + +static int mt9t001_s_ctrl(struct v4l2_ctrl *ctrl) +{ + static const u8 gains[4] = { + MT9T001_RED_GAIN, MT9T001_GREEN1_GAIN, + MT9T001_GREEN2_GAIN, MT9T001_BLUE_GAIN + }; + + struct mt9t001 *mt9t001 = + container_of(ctrl->handler, struct mt9t001, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&mt9t001->subdev); + unsigned int count; + unsigned int i; + u16 value; + int ret; + + switch (ctrl->id) { + case V4L2_CID_GAIN_RED: + case V4L2_CID_GAIN_GREEN_RED: + case V4L2_CID_GAIN_GREEN_BLUE: + case V4L2_CID_GAIN_BLUE: + + /* Disable control updates if more than one control has changed + * in the cluster. + */ + for (i = 0, count = 0; i < 4; ++i) { + struct v4l2_ctrl *gain = mt9t001->gains[i]; + + if (gain->val != gain->cur.val) + count++; + } + + if (count > 1) { + ret = mt9t001_ctrl_freeze(mt9t001, true); + if (ret < 0) + return ret; + } + + /* Update the gain controls. */ + for (i = 0; i < 4; ++i) { + struct v4l2_ctrl *gain = mt9t001->gains[i]; + + if (gain->val == gain->cur.val) + continue; + + value = mt9t001_gain_value(&gain->val); + ret = mt9t001_write(client, gains[i], value); + if (ret < 0) { + mt9t001_ctrl_freeze(mt9t001, false); + return ret; + } + } + + /* Enable control updates. */ + if (count > 1) { + ret = mt9t001_ctrl_freeze(mt9t001, false); + if (ret < 0) + return ret; + } + + break; + + case V4L2_CID_EXPOSURE: + ret = mt9t001_write(client, MT9T001_SHUTTER_WIDTH_LOW, + ctrl->val & 0xffff); + if (ret < 0) + return ret; + + return mt9t001_write(client, MT9T001_SHUTTER_WIDTH_HIGH, + ctrl->val >> 16); + + case V4L2_CID_TEST_PATTERN: + return mt9t001_set_output_control(mt9t001, + ctrl->val ? 0 : MT9T001_OUTPUT_CONTROL_TEST_DATA, + ctrl->val ? MT9T001_OUTPUT_CONTROL_TEST_DATA : 0); + + case V4L2_CID_TEST_PATTERN_COLOR: + return mt9t001_write(client, MT9T001_TEST_DATA, ctrl->val << 2); + + case V4L2_CID_BLACK_LEVEL_AUTO: + value = ctrl->val ? 0 : MT9T001_BLACK_LEVEL_OVERRIDE; + ret = mt9t001_write(client, MT9T001_BLACK_LEVEL_CALIBRATION, + value); + if (ret < 0) + return ret; + + mt9t001->black_level = value; + break; + + case V4L2_CID_BLACK_LEVEL_OFFSET: + ret = mt9t001_write(client, MT9T001_GREEN1_OFFSET, ctrl->val); + if (ret < 0) + return ret; + + ret = mt9t001_write(client, MT9T001_GREEN2_OFFSET, ctrl->val); + if (ret < 0) + return ret; + + ret = mt9t001_write(client, MT9T001_RED_OFFSET, ctrl->val); + if (ret < 0) + return ret; + + return mt9t001_write(client, MT9T001_BLUE_OFFSET, ctrl->val); + + case V4L2_CID_BLACK_LEVEL_CALIBRATE: + return mt9t001_write(client, MT9T001_BLACK_LEVEL_CALIBRATION, + MT9T001_BLACK_LEVEL_RECALCULATE | + mt9t001->black_level); + } + + return 0; +} + +static const struct v4l2_ctrl_ops mt9t001_ctrl_ops = { + .s_ctrl = mt9t001_s_ctrl, +}; + +static const char * const mt9t001_test_pattern_menu[] = { + "Disabled", + "Enabled", +}; + +static const struct v4l2_ctrl_config mt9t001_ctrls[] = { + { + .ops = &mt9t001_ctrl_ops, + .id = V4L2_CID_TEST_PATTERN_COLOR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Test Pattern Color", + .min = 0, + .max = 1023, + .step = 1, + .def = 0, + .flags = 0, + }, { + .ops = &mt9t001_ctrl_ops, + .id = V4L2_CID_BLACK_LEVEL_AUTO, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Black Level, Auto", + .min = 0, + .max = 1, + .step = 1, + .def = 1, + .flags = 0, + }, { + .ops = &mt9t001_ctrl_ops, + .id = V4L2_CID_BLACK_LEVEL_OFFSET, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Black Level, Offset", + .min = -256, + .max = 255, + .step = 1, + .def = 32, + .flags = 0, + }, { + .ops = &mt9t001_ctrl_ops, + .id = V4L2_CID_BLACK_LEVEL_CALIBRATE, + .type = V4L2_CTRL_TYPE_BUTTON, + .name = "Black Level, Calibrate", + .min = 0, + .max = 0, + .step = 0, + .def = 0, + .flags = V4L2_CTRL_FLAG_WRITE_ONLY, + }, +}; + +static const struct v4l2_ctrl_config mt9t001_gains[] = { + { + .ops = &mt9t001_ctrl_ops, + .id = V4L2_CID_GAIN_RED, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain, Red", + .min = MT9T001_GLOBAL_GAIN_MIN, + .max = MT9T001_GLOBAL_GAIN_MAX, + .step = 1, + .def = MT9T001_GLOBAL_GAIN_MIN, + .flags = 0, + }, { + .ops = &mt9t001_ctrl_ops, + .id = V4L2_CID_GAIN_GREEN_RED, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain, Green (R)", + .min = MT9T001_GLOBAL_GAIN_MIN, + .max = MT9T001_GLOBAL_GAIN_MAX, + .step = 1, + .def = MT9T001_GLOBAL_GAIN_MIN, + .flags = 0, + }, { + .ops = &mt9t001_ctrl_ops, + .id = V4L2_CID_GAIN_GREEN_BLUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain, Green (B)", + .min = MT9T001_GLOBAL_GAIN_MIN, + .max = MT9T001_GLOBAL_GAIN_MAX, + .step = 1, + .def = MT9T001_GLOBAL_GAIN_MIN, + .flags = 0, + }, { + .ops = &mt9t001_ctrl_ops, + .id = V4L2_CID_GAIN_BLUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain, Blue", + .min = MT9T001_GLOBAL_GAIN_MIN, + .max = MT9T001_GLOBAL_GAIN_MAX, + .step = 1, + .def = MT9T001_GLOBAL_GAIN_MIN, + .flags = 0, + }, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +static int mt9t001_set_power(struct v4l2_subdev *subdev, int on) +{ + struct mt9t001 *mt9t001 = to_mt9t001(subdev); + int ret = 0; + + mutex_lock(&mt9t001->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (mt9t001->power_count == !on) { + ret = __mt9t001_set_power(mt9t001, !!on); + if (ret < 0) + goto out; + } + + /* Update the power count. */ + mt9t001->power_count += on ? 1 : -1; + WARN_ON(mt9t001->power_count < 0); + +out: + mutex_unlock(&mt9t001->power_lock); + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev internal operations + */ + +static int mt9t001_registered(struct v4l2_subdev *subdev) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct mt9t001 *mt9t001 = to_mt9t001(subdev); + s32 data; + int ret; + + ret = mt9t001_power_on(mt9t001); + if (ret < 0) { + dev_err(&client->dev, "MT9T001 power up failed\n"); + return ret; + } + + /* Read out the chip version register */ + data = mt9t001_read(client, MT9T001_CHIP_VERSION); + mt9t001_power_off(mt9t001); + + if (data != MT9T001_CHIP_ID) { + dev_err(&client->dev, + "MT9T001 not detected, wrong version 0x%04x\n", data); + return -ENODEV; + } + + dev_info(&client->dev, "MT9T001 detected at address 0x%02x\n", + client->addr); + + return 0; +} + +static int mt9t001_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + crop = v4l2_subdev_get_try_crop(subdev, fh->pad, 0); + crop->left = MT9T001_COLUMN_START_DEF; + crop->top = MT9T001_ROW_START_DEF; + crop->width = MT9T001_WINDOW_WIDTH_DEF + 1; + crop->height = MT9T001_WINDOW_HEIGHT_DEF + 1; + + format = v4l2_subdev_get_try_format(subdev, fh->pad, 0); + format->code = MEDIA_BUS_FMT_SGRBG10_1X10; + format->width = MT9T001_WINDOW_WIDTH_DEF + 1; + format->height = MT9T001_WINDOW_HEIGHT_DEF + 1; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + return mt9t001_set_power(subdev, 1); +} + +static int mt9t001_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + return mt9t001_set_power(subdev, 0); +} + +static const struct v4l2_subdev_core_ops mt9t001_subdev_core_ops = { + .s_power = mt9t001_set_power, +}; + +static const struct v4l2_subdev_video_ops mt9t001_subdev_video_ops = { + .s_stream = mt9t001_s_stream, +}; + +static const struct v4l2_subdev_pad_ops mt9t001_subdev_pad_ops = { + .enum_mbus_code = mt9t001_enum_mbus_code, + .enum_frame_size = mt9t001_enum_frame_size, + .get_fmt = mt9t001_get_format, + .set_fmt = mt9t001_set_format, + .get_selection = mt9t001_get_selection, + .set_selection = mt9t001_set_selection, +}; + +static const struct v4l2_subdev_ops mt9t001_subdev_ops = { + .core = &mt9t001_subdev_core_ops, + .video = &mt9t001_subdev_video_ops, + .pad = &mt9t001_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops mt9t001_subdev_internal_ops = { + .registered = mt9t001_registered, + .open = mt9t001_open, + .close = mt9t001_close, +}; + +static int mt9t001_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9t001_platform_data *pdata = client->dev.platform_data; + struct mt9t001 *mt9t001; + unsigned int i; + int ret; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&client->adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9t001 = devm_kzalloc(&client->dev, sizeof(*mt9t001), GFP_KERNEL); + if (!mt9t001) + return -ENOMEM; + + mutex_init(&mt9t001->power_lock); + mt9t001->output_control = MT9T001_OUTPUT_CONTROL_DEF; + + mt9t001->regulators[0].supply = "vdd"; + mt9t001->regulators[1].supply = "vaa"; + + ret = devm_regulator_bulk_get(&client->dev, 2, mt9t001->regulators); + if (ret < 0) { + dev_err(&client->dev, "Unable to get regulators\n"); + return ret; + } + + mt9t001->clk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(mt9t001->clk)) { + dev_err(&client->dev, "Unable to get clock\n"); + return PTR_ERR(mt9t001->clk); + } + + v4l2_ctrl_handler_init(&mt9t001->ctrls, ARRAY_SIZE(mt9t001_ctrls) + + ARRAY_SIZE(mt9t001_gains) + 4); + + v4l2_ctrl_new_std(&mt9t001->ctrls, &mt9t001_ctrl_ops, + V4L2_CID_EXPOSURE, MT9T001_SHUTTER_WIDTH_MIN, + MT9T001_SHUTTER_WIDTH_MAX, 1, + MT9T001_SHUTTER_WIDTH_DEF); + v4l2_ctrl_new_std(&mt9t001->ctrls, &mt9t001_ctrl_ops, + V4L2_CID_BLACK_LEVEL, 1, 1, 1, 1); + v4l2_ctrl_new_std(&mt9t001->ctrls, &mt9t001_ctrl_ops, + V4L2_CID_PIXEL_RATE, pdata->ext_clk, pdata->ext_clk, + 1, pdata->ext_clk); + v4l2_ctrl_new_std_menu_items(&mt9t001->ctrls, &mt9t001_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(mt9t001_test_pattern_menu) - 1, 0, + 0, mt9t001_test_pattern_menu); + + for (i = 0; i < ARRAY_SIZE(mt9t001_ctrls); ++i) + v4l2_ctrl_new_custom(&mt9t001->ctrls, &mt9t001_ctrls[i], NULL); + + for (i = 0; i < ARRAY_SIZE(mt9t001_gains); ++i) + mt9t001->gains[i] = v4l2_ctrl_new_custom(&mt9t001->ctrls, + &mt9t001_gains[i], NULL); + + v4l2_ctrl_cluster(ARRAY_SIZE(mt9t001_gains), mt9t001->gains); + + mt9t001->subdev.ctrl_handler = &mt9t001->ctrls; + + if (mt9t001->ctrls.error) { + printk(KERN_INFO "%s: control initialization error %d\n", + __func__, mt9t001->ctrls.error); + ret = -EINVAL; + goto done; + } + + mt9t001->crop.left = MT9T001_COLUMN_START_DEF; + mt9t001->crop.top = MT9T001_ROW_START_DEF; + mt9t001->crop.width = MT9T001_WINDOW_WIDTH_DEF + 1; + mt9t001->crop.height = MT9T001_WINDOW_HEIGHT_DEF + 1; + + mt9t001->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + mt9t001->format.width = MT9T001_WINDOW_WIDTH_DEF + 1; + mt9t001->format.height = MT9T001_WINDOW_HEIGHT_DEF + 1; + mt9t001->format.field = V4L2_FIELD_NONE; + mt9t001->format.colorspace = V4L2_COLORSPACE_SRGB; + + v4l2_i2c_subdev_init(&mt9t001->subdev, client, &mt9t001_subdev_ops); + mt9t001->subdev.internal_ops = &mt9t001_subdev_internal_ops; + mt9t001->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + mt9t001->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + mt9t001->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&mt9t001->subdev.entity, 1, &mt9t001->pad); + +done: + if (ret < 0) { + v4l2_ctrl_handler_free(&mt9t001->ctrls); + media_entity_cleanup(&mt9t001->subdev.entity); + } + + return ret; +} + +static int mt9t001_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct mt9t001 *mt9t001 = to_mt9t001(subdev); + + v4l2_ctrl_handler_free(&mt9t001->ctrls); + v4l2_device_unregister_subdev(subdev); + media_entity_cleanup(&subdev->entity); + return 0; +} + +static const struct i2c_device_id mt9t001_id[] = { + { "mt9t001", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9t001_id); + +static struct i2c_driver mt9t001_driver = { + .driver = { + .name = "mt9t001", + }, + .probe = mt9t001_probe, + .remove = mt9t001_remove, + .id_table = mt9t001_id, +}; + +module_i2c_driver(mt9t001_driver); + +MODULE_DESCRIPTION("Aptina (Micron) MT9T001 Camera driver"); +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/mt9t112.c b/drivers/media/i2c/mt9t112.c new file mode 100644 index 000000000..ae3c336ea --- /dev/null +++ b/drivers/media/i2c/mt9t112.c @@ -0,0 +1,1134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mt9t112 Camera Driver + * + * Copyright (C) 2018 Jacopo Mondi <jacopo+renesas@jmondi.org> + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * Based on ov772x driver, mt9m111 driver, + * + * Copyright (C) 2008 Kuninori Morimoto <morimoto.kuninori@renesas.com> + * Copyright (C) 2008, Robert Jarzmik <robert.jarzmik@free.fr> + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> + * + * TODO: This driver lacks support for frame rate control due to missing + * register level documentation and suitable hardware for testing. + * v4l-utils compliance tools will report errors. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/v4l2-mediabus.h> +#include <linux/videodev2.h> + +#include <media/i2c/mt9t112.h> +#include <media/v4l2-common.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-subdev.h> + +/* you can check PLL/clock info */ +/* #define EXT_CLOCK 24000000 */ + +/************************************************************************ + * macro + ***********************************************************************/ +/* + * frame size + */ +#define MAX_WIDTH 2048 +#define MAX_HEIGHT 1536 + +/* + * macro of read/write + */ +#define ECHECKER(ret, x) \ + do { \ + (ret) = (x); \ + if ((ret) < 0) \ + return (ret); \ + } while (0) + +#define mt9t112_reg_write(ret, client, a, b) \ + ECHECKER(ret, __mt9t112_reg_write(client, a, b)) +#define mt9t112_mcu_write(ret, client, a, b) \ + ECHECKER(ret, __mt9t112_mcu_write(client, a, b)) + +#define mt9t112_reg_mask_set(ret, client, a, b, c) \ + ECHECKER(ret, __mt9t112_reg_mask_set(client, a, b, c)) +#define mt9t112_mcu_mask_set(ret, client, a, b, c) \ + ECHECKER(ret, __mt9t112_mcu_mask_set(client, a, b, c)) + +#define mt9t112_reg_read(ret, client, a) \ + ECHECKER(ret, __mt9t112_reg_read(client, a)) + +/* + * Logical address + */ +#define _VAR(id, offset, base) (base | (id & 0x1f) << 10 | (offset & 0x3ff)) +#define VAR(id, offset) _VAR(id, offset, 0x0000) +#define VAR8(id, offset) _VAR(id, offset, 0x8000) + +/************************************************************************ + * struct + ***********************************************************************/ +struct mt9t112_format { + u32 code; + enum v4l2_colorspace colorspace; + u16 fmt; + u16 order; +}; + +struct mt9t112_priv { + struct v4l2_subdev subdev; + struct mt9t112_platform_data *info; + struct i2c_client *client; + struct v4l2_rect frame; + struct clk *clk; + struct gpio_desc *standby_gpio; + const struct mt9t112_format *format; + int num_formats; + bool init_done; +}; + +/************************************************************************ + * supported format + ***********************************************************************/ + +static const struct mt9t112_format mt9t112_cfmts[] = { + { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt = 1, + .order = 0, + }, { + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt = 1, + .order = 1, + }, { + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt = 1, + .order = 2, + }, { + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt = 1, + .order = 3, + }, { + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt = 8, + .order = 2, + }, { + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt = 4, + .order = 2, + }, +}; + +/************************************************************************ + * general function + ***********************************************************************/ +static struct mt9t112_priv *to_mt9t112(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), + struct mt9t112_priv, + subdev); +} + +static int __mt9t112_reg_read(const struct i2c_client *client, u16 command) +{ + struct i2c_msg msg[2]; + u8 buf[2]; + int ret; + + command = swab16(command); + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 2; + msg[0].buf = (u8 *)&command; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = buf; + + /* + * If return value of this function is < 0, it means error, else, + * below 16bit is valid data. + */ + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + + memcpy(&ret, buf, 2); + + return swab16(ret); +} + +static int __mt9t112_reg_write(const struct i2c_client *client, + u16 command, u16 data) +{ + struct i2c_msg msg; + u8 buf[4]; + int ret; + + command = swab16(command); + data = swab16(data); + + memcpy(buf + 0, &command, 2); + memcpy(buf + 2, &data, 2); + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 4; + msg.buf = buf; + + /* + * i2c_transfer return message length, but this function should + * return 0 if correct case. + */ + ret = i2c_transfer(client->adapter, &msg, 1); + + return ret >= 0 ? 0 : ret; +} + +static int __mt9t112_reg_mask_set(const struct i2c_client *client, + u16 command, u16 mask, u16 set) +{ + int val = __mt9t112_reg_read(client, command); + + if (val < 0) + return val; + + val &= ~mask; + val |= set & mask; + + return __mt9t112_reg_write(client, command, val); +} + +/* mcu access */ +static int __mt9t112_mcu_read(const struct i2c_client *client, u16 command) +{ + int ret; + + ret = __mt9t112_reg_write(client, 0x098E, command); + if (ret < 0) + return ret; + + return __mt9t112_reg_read(client, 0x0990); +} + +static int __mt9t112_mcu_write(const struct i2c_client *client, + u16 command, u16 data) +{ + int ret; + + ret = __mt9t112_reg_write(client, 0x098E, command); + if (ret < 0) + return ret; + + return __mt9t112_reg_write(client, 0x0990, data); +} + +static int __mt9t112_mcu_mask_set(const struct i2c_client *client, + u16 command, u16 mask, u16 set) +{ + int val = __mt9t112_mcu_read(client, command); + + if (val < 0) + return val; + + val &= ~mask; + val |= set & mask; + + return __mt9t112_mcu_write(client, command, val); +} + +static int mt9t112_reset(const struct i2c_client *client) +{ + int ret; + + mt9t112_reg_mask_set(ret, client, 0x001a, 0x0001, 0x0001); + usleep_range(1000, 5000); + mt9t112_reg_mask_set(ret, client, 0x001a, 0x0001, 0x0000); + + return ret; +} + +#ifndef EXT_CLOCK +#define CLOCK_INFO(a, b) +#else +#define CLOCK_INFO(a, b) mt9t112_clock_info(a, b) +static int mt9t112_clock_info(const struct i2c_client *client, u32 ext) +{ + int m, n, p1, p2, p3, p4, p5, p6, p7; + u32 vco, clk; + char *enable; + + ext /= 1000; /* kbyte order */ + + mt9t112_reg_read(n, client, 0x0012); + p1 = n & 0x000f; + n = n >> 4; + p2 = n & 0x000f; + n = n >> 4; + p3 = n & 0x000f; + + mt9t112_reg_read(n, client, 0x002a); + p4 = n & 0x000f; + n = n >> 4; + p5 = n & 0x000f; + n = n >> 4; + p6 = n & 0x000f; + + mt9t112_reg_read(n, client, 0x002c); + p7 = n & 0x000f; + + mt9t112_reg_read(n, client, 0x0010); + m = n & 0x00ff; + n = (n >> 8) & 0x003f; + + enable = ((ext < 6000) || (ext > 54000)) ? "X" : ""; + dev_dbg(&client->dev, "EXTCLK : %10u K %s\n", ext, enable); + + vco = 2 * m * ext / (n + 1); + enable = ((vco < 384000) || (vco > 768000)) ? "X" : ""; + dev_dbg(&client->dev, "VCO : %10u K %s\n", vco, enable); + + clk = vco / (p1 + 1) / (p2 + 1); + enable = (clk > 96000) ? "X" : ""; + dev_dbg(&client->dev, "PIXCLK : %10u K %s\n", clk, enable); + + clk = vco / (p3 + 1); + enable = (clk > 768000) ? "X" : ""; + dev_dbg(&client->dev, "MIPICLK : %10u K %s\n", clk, enable); + + clk = vco / (p6 + 1); + enable = (clk > 96000) ? "X" : ""; + dev_dbg(&client->dev, "MCU CLK : %10u K %s\n", clk, enable); + + clk = vco / (p5 + 1); + enable = (clk > 54000) ? "X" : ""; + dev_dbg(&client->dev, "SOC CLK : %10u K %s\n", clk, enable); + + clk = vco / (p4 + 1); + enable = (clk > 70000) ? "X" : ""; + dev_dbg(&client->dev, "Sensor CLK : %10u K %s\n", clk, enable); + + clk = vco / (p7 + 1); + dev_dbg(&client->dev, "External sensor : %10u K\n", clk); + + clk = ext / (n + 1); + enable = ((clk < 2000) || (clk > 24000)) ? "X" : ""; + dev_dbg(&client->dev, "PFD : %10u K %s\n", clk, enable); + + return 0; +} +#endif + +static int mt9t112_set_a_frame_size(const struct i2c_client *client, + u16 width, u16 height) +{ + int ret; + u16 wstart = (MAX_WIDTH - width) / 2; + u16 hstart = (MAX_HEIGHT - height) / 2; + + /* (Context A) Image Width/Height. */ + mt9t112_mcu_write(ret, client, VAR(26, 0), width); + mt9t112_mcu_write(ret, client, VAR(26, 2), height); + + /* (Context A) Output Width/Height. */ + mt9t112_mcu_write(ret, client, VAR(18, 43), 8 + width); + mt9t112_mcu_write(ret, client, VAR(18, 45), 8 + height); + + /* (Context A) Start Row/Column. */ + mt9t112_mcu_write(ret, client, VAR(18, 2), 4 + hstart); + mt9t112_mcu_write(ret, client, VAR(18, 4), 4 + wstart); + + /* (Context A) End Row/Column. */ + mt9t112_mcu_write(ret, client, VAR(18, 6), 11 + height + hstart); + mt9t112_mcu_write(ret, client, VAR(18, 8), 11 + width + wstart); + + mt9t112_mcu_write(ret, client, VAR8(1, 0), 0x06); + + return ret; +} + +static int mt9t112_set_pll_dividers(const struct i2c_client *client, + u8 m, u8 n, u8 p1, u8 p2, u8 p3, u8 p4, + u8 p5, u8 p6, u8 p7) +{ + int ret; + u16 val; + + /* N/M */ + val = (n << 8) | (m << 0); + mt9t112_reg_mask_set(ret, client, 0x0010, 0x3fff, val); + + /* P1/P2/P3 */ + val = ((p3 & 0x0F) << 8) | ((p2 & 0x0F) << 4) | ((p1 & 0x0F) << 0); + mt9t112_reg_mask_set(ret, client, 0x0012, 0x0fff, val); + + /* P4/P5/P6 */ + val = (0x7 << 12) | ((p6 & 0x0F) << 8) | ((p5 & 0x0F) << 4) | + ((p4 & 0x0F) << 0); + mt9t112_reg_mask_set(ret, client, 0x002A, 0x7fff, val); + + /* P7 */ + val = (0x1 << 12) | ((p7 & 0x0F) << 0); + mt9t112_reg_mask_set(ret, client, 0x002C, 0x100f, val); + + return ret; +} + +static int mt9t112_init_pll(const struct i2c_client *client) +{ + struct mt9t112_priv *priv = to_mt9t112(client); + int data, i, ret; + + mt9t112_reg_mask_set(ret, client, 0x0014, 0x003, 0x0001); + + /* PLL control: BYPASS PLL = 8517. */ + mt9t112_reg_write(ret, client, 0x0014, 0x2145); + + /* Replace these registers when new timing parameters are generated. */ + mt9t112_set_pll_dividers(client, + priv->info->divider.m, priv->info->divider.n, + priv->info->divider.p1, priv->info->divider.p2, + priv->info->divider.p3, priv->info->divider.p4, + priv->info->divider.p5, priv->info->divider.p6, + priv->info->divider.p7); + + /* + * TEST_BYPASS on + * PLL_ENABLE on + * SEL_LOCK_DET on + * TEST_BYPASS off + */ + mt9t112_reg_write(ret, client, 0x0014, 0x2525); + mt9t112_reg_write(ret, client, 0x0014, 0x2527); + mt9t112_reg_write(ret, client, 0x0014, 0x3427); + mt9t112_reg_write(ret, client, 0x0014, 0x3027); + + mdelay(10); + + /* + * PLL_BYPASS off + * Reference clock count + * I2C Master Clock Divider + */ + mt9t112_reg_write(ret, client, 0x0014, 0x3046); + /* JPEG initialization workaround */ + mt9t112_reg_write(ret, client, 0x0016, 0x0400); + mt9t112_reg_write(ret, client, 0x0022, 0x0190); + mt9t112_reg_write(ret, client, 0x3B84, 0x0212); + + /* External sensor clock is PLL bypass. */ + mt9t112_reg_write(ret, client, 0x002E, 0x0500); + + mt9t112_reg_mask_set(ret, client, 0x0018, 0x0002, 0x0002); + mt9t112_reg_mask_set(ret, client, 0x3B82, 0x0004, 0x0004); + + /* MCU disabled. */ + mt9t112_reg_mask_set(ret, client, 0x0018, 0x0004, 0x0004); + + /* Out of standby. */ + mt9t112_reg_mask_set(ret, client, 0x0018, 0x0001, 0); + + mdelay(50); + + /* + * Standby Workaround + * Disable Secondary I2C Pads + */ + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + + /* Poll to verify out of standby. Must Poll this bit. */ + for (i = 0; i < 100; i++) { + mt9t112_reg_read(data, client, 0x0018); + if (!(data & 0x4000)) + break; + + mdelay(10); + } + + return ret; +} + +static int mt9t112_init_setting(const struct i2c_client *client) +{ + int ret; + + /* Adaptive Output Clock (A) */ + mt9t112_mcu_mask_set(ret, client, VAR(26, 160), 0x0040, 0x0000); + + /* Read Mode (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 12), 0x0024); + + /* Fine Correction (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 15), 0x00CC); + + /* Fine IT Min (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 17), 0x01f1); + + /* Fine IT Max Margin (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 19), 0x00fF); + + /* Base Frame Lines (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 29), 0x032D); + + /* Min Line Length (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 31), 0x073a); + + /* Line Length (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 37), 0x07d0); + + /* Adaptive Output Clock (B) */ + mt9t112_mcu_mask_set(ret, client, VAR(27, 160), 0x0040, 0x0000); + + /* Row Start (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 74), 0x004); + + /* Column Start (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 76), 0x004); + + /* Row End (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 78), 0x60B); + + /* Column End (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 80), 0x80B); + + /* Fine Correction (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 87), 0x008C); + + /* Fine IT Min (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 89), 0x01F1); + + /* Fine IT Max Margin (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 91), 0x00FF); + + /* Base Frame Lines (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 101), 0x0668); + + /* Min Line Length (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 103), 0x0AF0); + + /* Line Length (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 109), 0x0AF0); + + /* + * Flicker Detection registers. + * This section should be replaced whenever new timing file is + * generated. All the following registers need to be replaced. + * Following registers are generated from Register Wizard but user can + * modify them. For detail see auto flicker detection tuning. + */ + + /* FD_FDPERIOD_SELECT */ + mt9t112_mcu_write(ret, client, VAR8(8, 5), 0x01); + + /* PRI_B_CONFIG_FD_ALGO_RUN */ + mt9t112_mcu_write(ret, client, VAR(27, 17), 0x0003); + + /* PRI_A_CONFIG_FD_ALGO_RUN */ + mt9t112_mcu_write(ret, client, VAR(26, 17), 0x0003); + + /* + * AFD range detection tuning registers. + */ + + /* Search_f1_50 */ + mt9t112_mcu_write(ret, client, VAR8(18, 165), 0x25); + + /* Search_f2_50 */ + mt9t112_mcu_write(ret, client, VAR8(18, 166), 0x28); + + /* Search_f1_60 */ + mt9t112_mcu_write(ret, client, VAR8(18, 167), 0x2C); + + /* Search_f2_60 */ + mt9t112_mcu_write(ret, client, VAR8(18, 168), 0x2F); + + /* Period_50Hz (A) */ + mt9t112_mcu_write(ret, client, VAR8(18, 68), 0xBA); + + /* Secret register by Aptina. */ + /* Period_50Hz (A MSB) */ + mt9t112_mcu_write(ret, client, VAR8(18, 303), 0x00); + + /* Period_60Hz (A) */ + mt9t112_mcu_write(ret, client, VAR8(18, 69), 0x9B); + + /* Secret register by Aptina. */ + /* Period_60Hz (A MSB) */ + mt9t112_mcu_write(ret, client, VAR8(18, 301), 0x00); + + /* Period_50Hz (B) */ + mt9t112_mcu_write(ret, client, VAR8(18, 140), 0x82); + + /* Secret register by Aptina. */ + /* Period_50Hz (B) MSB */ + mt9t112_mcu_write(ret, client, VAR8(18, 304), 0x00); + + /* Period_60Hz (B) */ + mt9t112_mcu_write(ret, client, VAR8(18, 141), 0x6D); + + /* Secret register by Aptina. */ + /* Period_60Hz (B) MSB */ + mt9t112_mcu_write(ret, client, VAR8(18, 302), 0x00); + + /* FD Mode */ + mt9t112_mcu_write(ret, client, VAR8(8, 2), 0x10); + + /* Stat_min */ + mt9t112_mcu_write(ret, client, VAR8(8, 9), 0x02); + + /* Stat_max */ + mt9t112_mcu_write(ret, client, VAR8(8, 10), 0x03); + + /* Min_amplitude */ + mt9t112_mcu_write(ret, client, VAR8(8, 12), 0x0A); + + /* RX FIFO Watermark (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 70), 0x0014); + + /* RX FIFO Watermark (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 142), 0x0014); + + /* MCLK: 16MHz + * PCLK: 73MHz + * CorePixCLK: 36.5 MHz + */ + mt9t112_mcu_write(ret, client, VAR8(18, 0x0044), 133); + mt9t112_mcu_write(ret, client, VAR8(18, 0x0045), 110); + mt9t112_mcu_write(ret, client, VAR8(18, 0x008c), 130); + mt9t112_mcu_write(ret, client, VAR8(18, 0x008d), 108); + + mt9t112_mcu_write(ret, client, VAR8(18, 0x00A5), 27); + mt9t112_mcu_write(ret, client, VAR8(18, 0x00a6), 30); + mt9t112_mcu_write(ret, client, VAR8(18, 0x00a7), 32); + mt9t112_mcu_write(ret, client, VAR8(18, 0x00a8), 35); + + return ret; +} + +static int mt9t112_auto_focus_setting(const struct i2c_client *client) +{ + int ret; + + mt9t112_mcu_write(ret, client, VAR(12, 13), 0x000F); + mt9t112_mcu_write(ret, client, VAR(12, 23), 0x0F0F); + mt9t112_mcu_write(ret, client, VAR8(1, 0), 0x06); + + mt9t112_reg_write(ret, client, 0x0614, 0x0000); + + mt9t112_mcu_write(ret, client, VAR8(1, 0), 0x05); + mt9t112_mcu_write(ret, client, VAR8(12, 2), 0x02); + mt9t112_mcu_write(ret, client, VAR(12, 3), 0x0002); + mt9t112_mcu_write(ret, client, VAR(17, 3), 0x8001); + mt9t112_mcu_write(ret, client, VAR(17, 11), 0x0025); + mt9t112_mcu_write(ret, client, VAR(17, 13), 0x0193); + mt9t112_mcu_write(ret, client, VAR8(17, 33), 0x18); + mt9t112_mcu_write(ret, client, VAR8(1, 0), 0x05); + + return ret; +} + +static int mt9t112_auto_focus_trigger(const struct i2c_client *client) +{ + int ret; + + mt9t112_mcu_write(ret, client, VAR8(12, 25), 0x01); + + return ret; +} + +static int mt9t112_init_camera(const struct i2c_client *client) +{ + int ret; + + ECHECKER(ret, mt9t112_reset(client)); + ECHECKER(ret, mt9t112_init_pll(client)); + ECHECKER(ret, mt9t112_init_setting(client)); + ECHECKER(ret, mt9t112_auto_focus_setting(client)); + + mt9t112_reg_mask_set(ret, client, 0x0018, 0x0004, 0); + + /* Analog setting B.*/ + mt9t112_reg_write(ret, client, 0x3084, 0x2409); + mt9t112_reg_write(ret, client, 0x3092, 0x0A49); + mt9t112_reg_write(ret, client, 0x3094, 0x4949); + mt9t112_reg_write(ret, client, 0x3096, 0x4950); + + /* + * Disable adaptive clock. + * PRI_A_CONFIG_JPEG_OB_TX_CONTROL_VAR + * PRI_B_CONFIG_JPEG_OB_TX_CONTROL_VAR + */ + mt9t112_mcu_write(ret, client, VAR(26, 160), 0x0A2E); + mt9t112_mcu_write(ret, client, VAR(27, 160), 0x0A2E); + + /* + * Configure Status in Status_before_length Format and enable header. + * PRI_B_CONFIG_JPEG_OB_TX_CONTROL_VAR + */ + mt9t112_mcu_write(ret, client, VAR(27, 144), 0x0CB4); + + /* + * Enable JPEG in context B. + * PRI_B_CONFIG_JPEG_OB_TX_CONTROL_VAR + */ + mt9t112_mcu_write(ret, client, VAR8(27, 142), 0x01); + + /* Disable Dac_TXLO. */ + mt9t112_reg_write(ret, client, 0x316C, 0x350F); + + /* Set max slew rates. */ + mt9t112_reg_write(ret, client, 0x1E, 0x777); + + return ret; +} + +/************************************************************************ + * v4l2_subdev_core_ops + ***********************************************************************/ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9t112_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + reg->size = 2; + mt9t112_reg_read(ret, client, reg->reg); + + reg->val = (__u64)ret; + + return 0; +} + +static int mt9t112_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + mt9t112_reg_write(ret, client, reg->reg, reg->val); + + return ret; +} +#endif + +static int mt9t112_power_on(struct mt9t112_priv *priv) +{ + int ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + if (priv->standby_gpio) { + gpiod_set_value(priv->standby_gpio, 0); + msleep(100); + } + + return 0; +} + +static int mt9t112_power_off(struct mt9t112_priv *priv) +{ + clk_disable_unprepare(priv->clk); + if (priv->standby_gpio) { + gpiod_set_value(priv->standby_gpio, 1); + msleep(100); + } + + return 0; +} + +static int mt9t112_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + + return on ? mt9t112_power_on(priv) : + mt9t112_power_off(priv); +} + +static const struct v4l2_subdev_core_ops mt9t112_subdev_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9t112_g_register, + .s_register = mt9t112_s_register, +#endif + .s_power = mt9t112_s_power, +}; + +/************************************************************************ + * v4l2_subdev_video_ops + **********************************************************************/ +static int mt9t112_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + int ret = 0; + + if (!enable) { + /* FIXME + * + * If user selected large output size, and used it long time, + * mt9t112 camera will be very warm. + * + * But current driver can not stop mt9t112 camera. + * So, set small size here to solve this problem. + */ + mt9t112_set_a_frame_size(client, VGA_WIDTH, VGA_HEIGHT); + return ret; + } + + if (!priv->init_done) { + u16 param = MT9T112_FLAG_PCLK_RISING_EDGE & priv->info->flags ? + 0x0001 : 0x0000; + + ECHECKER(ret, mt9t112_init_camera(client)); + + /* Invert PCLK (Data sampled on falling edge of pixclk). */ + mt9t112_reg_write(ret, client, 0x3C20, param); + + mdelay(5); + + priv->init_done = true; + } + + mt9t112_mcu_write(ret, client, VAR(26, 7), priv->format->fmt); + mt9t112_mcu_write(ret, client, VAR(26, 9), priv->format->order); + mt9t112_mcu_write(ret, client, VAR8(1, 0), 0x06); + + mt9t112_set_a_frame_size(client, priv->frame.width, priv->frame.height); + + ECHECKER(ret, mt9t112_auto_focus_trigger(client)); + + dev_dbg(&client->dev, "format : %d\n", priv->format->code); + dev_dbg(&client->dev, "size : %d x %d\n", + priv->frame.width, + priv->frame.height); + + CLOCK_INFO(client, EXT_CLOCK); + + return ret; +} + +static int mt9t112_set_params(struct mt9t112_priv *priv, + const struct v4l2_rect *rect, + u32 code) +{ + int i; + + /* + * get color format + */ + for (i = 0; i < priv->num_formats; i++) + if (mt9t112_cfmts[i].code == code) + break; + + if (i == priv->num_formats) + return -EINVAL; + + priv->frame = *rect; + + /* + * frame size check + */ + v4l_bound_align_image(&priv->frame.width, 0, MAX_WIDTH, 0, + &priv->frame.height, 0, MAX_HEIGHT, 0, 0); + + priv->format = mt9t112_cfmts + i; + + return 0; +} + +static int mt9t112_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = MAX_WIDTH; + sel->r.height = MAX_HEIGHT; + return 0; + case V4L2_SEL_TGT_CROP: + sel->r = priv->frame; + return 0; + default: + return -EINVAL; + } +} + +static int mt9t112_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + const struct v4l2_rect *rect = &sel->r; + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + return mt9t112_set_params(priv, rect, priv->format->code); +} + +static int mt9t112_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + + if (format->pad) + return -EINVAL; + + mf->width = priv->frame.width; + mf->height = priv->frame.height; + mf->colorspace = priv->format->colorspace; + mf->code = priv->format->code; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int mt9t112_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + struct v4l2_rect rect = { + .width = mf->width, + .height = mf->height, + .left = priv->frame.left, + .top = priv->frame.top, + }; + int ret; + + ret = mt9t112_set_params(priv, &rect, mf->code); + + if (!ret) + mf->colorspace = priv->format->colorspace; + + return ret; +} + +static int mt9t112_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf = &format->format; + struct mt9t112_priv *priv = to_mt9t112(client); + int i; + + if (format->pad) + return -EINVAL; + + for (i = 0; i < priv->num_formats; i++) + if (mt9t112_cfmts[i].code == mf->code) + break; + + if (i == priv->num_formats) { + mf->code = MEDIA_BUS_FMT_UYVY8_2X8; + mf->colorspace = V4L2_COLORSPACE_JPEG; + } else { + mf->colorspace = mt9t112_cfmts[i].colorspace; + } + + v4l_bound_align_image(&mf->width, 0, MAX_WIDTH, 0, + &mf->height, 0, MAX_HEIGHT, 0, 0); + + mf->field = V4L2_FIELD_NONE; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return mt9t112_s_fmt(sd, mf); + cfg->try_fmt = *mf; + + return 0; +} + +static int mt9t112_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + + if (code->pad || code->index >= priv->num_formats) + return -EINVAL; + + code->code = mt9t112_cfmts[code->index].code; + + return 0; +} + +static const struct v4l2_subdev_video_ops mt9t112_subdev_video_ops = { + .s_stream = mt9t112_s_stream, +}; + +static const struct v4l2_subdev_pad_ops mt9t112_subdev_pad_ops = { + .enum_mbus_code = mt9t112_enum_mbus_code, + .get_selection = mt9t112_get_selection, + .set_selection = mt9t112_set_selection, + .get_fmt = mt9t112_get_fmt, + .set_fmt = mt9t112_set_fmt, +}; + +/************************************************************************ + * i2c driver + ***********************************************************************/ +static const struct v4l2_subdev_ops mt9t112_subdev_ops = { + .core = &mt9t112_subdev_core_ops, + .video = &mt9t112_subdev_video_ops, + .pad = &mt9t112_subdev_pad_ops, +}; + +static int mt9t112_camera_probe(struct i2c_client *client) +{ + struct mt9t112_priv *priv = to_mt9t112(client); + const char *devname; + int chipid; + int ret; + + ret = mt9t112_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* Check and show chip ID. */ + mt9t112_reg_read(chipid, client, 0x0000); + + switch (chipid) { + case 0x2680: + devname = "mt9t111"; + priv->num_formats = 1; + break; + case 0x2682: + devname = "mt9t112"; + priv->num_formats = ARRAY_SIZE(mt9t112_cfmts); + break; + default: + dev_err(&client->dev, "Product ID error %04x\n", chipid); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, "%s chip ID %04x\n", devname, chipid); + +done: + mt9t112_s_power(&priv->subdev, 0); + + return ret; +} + +static int mt9t112_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9t112_priv *priv; + int ret; + + if (!client->dev.platform_data) { + dev_err(&client->dev, "mt9t112: missing platform data!\n"); + return -EINVAL; + } + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = client->dev.platform_data; + priv->init_done = false; + + v4l2_i2c_subdev_init(&priv->subdev, client, &mt9t112_subdev_ops); + + priv->clk = devm_clk_get(&client->dev, "extclk"); + if (PTR_ERR(priv->clk) == -ENOENT) { + priv->clk = NULL; + } else if (IS_ERR(priv->clk)) { + dev_err(&client->dev, "Unable to get clock \"extclk\"\n"); + return PTR_ERR(priv->clk); + } + + priv->standby_gpio = devm_gpiod_get_optional(&client->dev, "standby", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->standby_gpio)) { + dev_err(&client->dev, "Unable to get gpio \"standby\"\n"); + return PTR_ERR(priv->standby_gpio); + } + + ret = mt9t112_camera_probe(client); + if (ret) + return ret; + + return v4l2_async_register_subdev(&priv->subdev); +} + +static int mt9t112_remove(struct i2c_client *client) +{ + struct mt9t112_priv *priv = to_mt9t112(client); + + clk_disable_unprepare(priv->clk); + v4l2_async_unregister_subdev(&priv->subdev); + + return 0; +} + +static const struct i2c_device_id mt9t112_id[] = { + { "mt9t112", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9t112_id); + +static struct i2c_driver mt9t112_i2c_driver = { + .driver = { + .name = "mt9t112", + }, + .probe = mt9t112_probe, + .remove = mt9t112_remove, + .id_table = mt9t112_id, +}; + +module_i2c_driver(mt9t112_i2c_driver); + +MODULE_DESCRIPTION("V4L2 driver for MT9T111/MT9T112 camera sensor"); +MODULE_AUTHOR("Kuninori Morimoto"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/mt9v011.c b/drivers/media/i2c/mt9v011.c new file mode 100644 index 000000000..46ef74a2c --- /dev/null +++ b/drivers/media/i2c/mt9v011.c @@ -0,0 +1,596 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mt9v011 -Micron 1/4-Inch VGA Digital Image Sensor +// +// Copyright (c) 2009 Mauro Carvalho Chehab <mchehab@kernel.org> + +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <asm/div64.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/i2c/mt9v011.h> + +MODULE_DESCRIPTION("Micron mt9v011 sensor driver"); +MODULE_AUTHOR("Mauro Carvalho Chehab"); +MODULE_LICENSE("GPL v2"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +#define R00_MT9V011_CHIP_VERSION 0x00 +#define R01_MT9V011_ROWSTART 0x01 +#define R02_MT9V011_COLSTART 0x02 +#define R03_MT9V011_HEIGHT 0x03 +#define R04_MT9V011_WIDTH 0x04 +#define R05_MT9V011_HBLANK 0x05 +#define R06_MT9V011_VBLANK 0x06 +#define R07_MT9V011_OUT_CTRL 0x07 +#define R09_MT9V011_SHUTTER_WIDTH 0x09 +#define R0A_MT9V011_CLK_SPEED 0x0a +#define R0B_MT9V011_RESTART 0x0b +#define R0C_MT9V011_SHUTTER_DELAY 0x0c +#define R0D_MT9V011_RESET 0x0d +#define R1E_MT9V011_DIGITAL_ZOOM 0x1e +#define R20_MT9V011_READ_MODE 0x20 +#define R2B_MT9V011_GREEN_1_GAIN 0x2b +#define R2C_MT9V011_BLUE_GAIN 0x2c +#define R2D_MT9V011_RED_GAIN 0x2d +#define R2E_MT9V011_GREEN_2_GAIN 0x2e +#define R35_MT9V011_GLOBAL_GAIN 0x35 +#define RF1_MT9V011_CHIP_ENABLE 0xf1 + +#define MT9V011_VERSION 0x8232 +#define MT9V011_REV_B_VERSION 0x8243 + +struct mt9v011 { + struct v4l2_subdev sd; +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_pad pad; +#endif + struct v4l2_ctrl_handler ctrls; + unsigned width, height; + unsigned xtal; + unsigned hflip:1; + unsigned vflip:1; + + u16 global_gain, exposure; + s16 red_bal, blue_bal; +}; + +static inline struct mt9v011 *to_mt9v011(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mt9v011, sd); +} + +static int mt9v011_read(struct v4l2_subdev *sd, unsigned char addr) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + __be16 buffer; + int rc, val; + + rc = i2c_master_send(c, &addr, 1); + if (rc != 1) + v4l2_dbg(0, debug, sd, + "i2c i/o error: rc == %d (should be 1)\n", rc); + + msleep(10); + + rc = i2c_master_recv(c, (char *)&buffer, 2); + if (rc != 2) + v4l2_dbg(0, debug, sd, + "i2c i/o error: rc == %d (should be 2)\n", rc); + + val = be16_to_cpu(buffer); + + v4l2_dbg(2, debug, sd, "mt9v011: read 0x%02x = 0x%04x\n", addr, val); + + return val; +} + +static void mt9v011_write(struct v4l2_subdev *sd, unsigned char addr, + u16 value) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + unsigned char buffer[3]; + int rc; + + buffer[0] = addr; + buffer[1] = value >> 8; + buffer[2] = value & 0xff; + + v4l2_dbg(2, debug, sd, + "mt9v011: writing 0x%02x 0x%04x\n", buffer[0], value); + rc = i2c_master_send(c, buffer, 3); + if (rc != 3) + v4l2_dbg(0, debug, sd, + "i2c i/o error: rc == %d (should be 3)\n", rc); +} + + +struct i2c_reg_value { + unsigned char reg; + u16 value; +}; + +/* + * Values used at the original driver + * Some values are marked as Reserved at the datasheet + */ +static const struct i2c_reg_value mt9v011_init_default[] = { + { R0D_MT9V011_RESET, 0x0001 }, + { R0D_MT9V011_RESET, 0x0000 }, + + { R0C_MT9V011_SHUTTER_DELAY, 0x0000 }, + { R09_MT9V011_SHUTTER_WIDTH, 0x1fc }, + + { R0A_MT9V011_CLK_SPEED, 0x0000 }, + { R1E_MT9V011_DIGITAL_ZOOM, 0x0000 }, + + { R07_MT9V011_OUT_CTRL, 0x0002 }, /* chip enable */ +}; + + +static u16 calc_mt9v011_gain(s16 lineargain) +{ + + u16 digitalgain = 0; + u16 analogmult = 0; + u16 analoginit = 0; + + if (lineargain < 0) + lineargain = 0; + + /* recommended minimum */ + lineargain += 0x0020; + + if (lineargain > 2047) + lineargain = 2047; + + if (lineargain > 1023) { + digitalgain = 3; + analogmult = 3; + analoginit = lineargain / 16; + } else if (lineargain > 511) { + digitalgain = 1; + analogmult = 3; + analoginit = lineargain / 8; + } else if (lineargain > 255) { + analogmult = 3; + analoginit = lineargain / 4; + } else if (lineargain > 127) { + analogmult = 1; + analoginit = lineargain / 2; + } else + analoginit = lineargain; + + return analoginit + (analogmult << 7) + (digitalgain << 9); + +} + +static void set_balance(struct v4l2_subdev *sd) +{ + struct mt9v011 *core = to_mt9v011(sd); + u16 green_gain, blue_gain, red_gain; + u16 exposure; + s16 bal; + + exposure = core->exposure; + + green_gain = calc_mt9v011_gain(core->global_gain); + + bal = core->global_gain; + bal += (core->blue_bal * core->global_gain / (1 << 7)); + blue_gain = calc_mt9v011_gain(bal); + + bal = core->global_gain; + bal += (core->red_bal * core->global_gain / (1 << 7)); + red_gain = calc_mt9v011_gain(bal); + + mt9v011_write(sd, R2B_MT9V011_GREEN_1_GAIN, green_gain); + mt9v011_write(sd, R2E_MT9V011_GREEN_2_GAIN, green_gain); + mt9v011_write(sd, R2C_MT9V011_BLUE_GAIN, blue_gain); + mt9v011_write(sd, R2D_MT9V011_RED_GAIN, red_gain); + mt9v011_write(sd, R09_MT9V011_SHUTTER_WIDTH, exposure); +} + +static void calc_fps(struct v4l2_subdev *sd, u32 *numerator, u32 *denominator) +{ + struct mt9v011 *core = to_mt9v011(sd); + unsigned height, width, hblank, vblank, speed; + unsigned row_time, t_time; + u64 frames_per_ms; + unsigned tmp; + + height = mt9v011_read(sd, R03_MT9V011_HEIGHT); + width = mt9v011_read(sd, R04_MT9V011_WIDTH); + hblank = mt9v011_read(sd, R05_MT9V011_HBLANK); + vblank = mt9v011_read(sd, R06_MT9V011_VBLANK); + speed = mt9v011_read(sd, R0A_MT9V011_CLK_SPEED); + + row_time = (width + 113 + hblank) * (speed + 2); + t_time = row_time * (height + vblank + 1); + + frames_per_ms = core->xtal * 1000l; + do_div(frames_per_ms, t_time); + tmp = frames_per_ms; + + v4l2_dbg(1, debug, sd, "Programmed to %u.%03u fps (%d pixel clcks)\n", + tmp / 1000, tmp % 1000, t_time); + + if (numerator && denominator) { + *numerator = 1000; + *denominator = (u32)frames_per_ms; + } +} + +static u16 calc_speed(struct v4l2_subdev *sd, u32 numerator, u32 denominator) +{ + struct mt9v011 *core = to_mt9v011(sd); + unsigned height, width, hblank, vblank; + unsigned row_time, line_time; + u64 t_time, speed; + + /* Avoid bogus calculus */ + if (!numerator || !denominator) + return 0; + + height = mt9v011_read(sd, R03_MT9V011_HEIGHT); + width = mt9v011_read(sd, R04_MT9V011_WIDTH); + hblank = mt9v011_read(sd, R05_MT9V011_HBLANK); + vblank = mt9v011_read(sd, R06_MT9V011_VBLANK); + + row_time = width + 113 + hblank; + line_time = height + vblank + 1; + + t_time = core->xtal * ((u64)numerator); + /* round to the closest value */ + t_time += denominator / 2; + do_div(t_time, denominator); + + speed = t_time; + do_div(speed, row_time * line_time); + + /* Avoid having a negative value for speed */ + if (speed < 2) + speed = 0; + else + speed -= 2; + + /* Avoid speed overflow */ + if (speed > 15) + return 15; + + return (u16)speed; +} + +static void set_res(struct v4l2_subdev *sd) +{ + struct mt9v011 *core = to_mt9v011(sd); + unsigned vstart, hstart; + + /* + * The mt9v011 doesn't have scaling. So, in order to select the desired + * resolution, we're cropping at the middle of the sensor. + * hblank and vblank should be adjusted, in order to warrant that + * we'll preserve the line timings for 30 fps, no matter what resolution + * is selected. + * NOTE: datasheet says that width (and height) should be filled with + * width-1. However, this doesn't work, since one pixel per line will + * be missing. + */ + + hstart = 20 + (640 - core->width) / 2; + mt9v011_write(sd, R02_MT9V011_COLSTART, hstart); + mt9v011_write(sd, R04_MT9V011_WIDTH, core->width); + mt9v011_write(sd, R05_MT9V011_HBLANK, 771 - core->width); + + vstart = 8 + (480 - core->height) / 2; + mt9v011_write(sd, R01_MT9V011_ROWSTART, vstart); + mt9v011_write(sd, R03_MT9V011_HEIGHT, core->height); + mt9v011_write(sd, R06_MT9V011_VBLANK, 508 - core->height); + + calc_fps(sd, NULL, NULL); +}; + +static void set_read_mode(struct v4l2_subdev *sd) +{ + struct mt9v011 *core = to_mt9v011(sd); + unsigned mode = 0x1000; + + if (core->hflip) + mode |= 0x4000; + + if (core->vflip) + mode |= 0x8000; + + mt9v011_write(sd, R20_MT9V011_READ_MODE, mode); +} + +static int mt9v011_reset(struct v4l2_subdev *sd, u32 val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mt9v011_init_default); i++) + mt9v011_write(sd, mt9v011_init_default[i].reg, + mt9v011_init_default[i].value); + + set_balance(sd); + set_res(sd); + set_read_mode(sd); + + return 0; +} + +static int mt9v011_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG8_1X8; + return 0; +} + +static int mt9v011_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + struct mt9v011 *core = to_mt9v011(sd); + + if (format->pad || fmt->code != MEDIA_BUS_FMT_SGRBG8_1X8) + return -EINVAL; + + v4l_bound_align_image(&fmt->width, 48, 639, 1, + &fmt->height, 32, 480, 1, 0); + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + core->width = fmt->width; + core->height = fmt->height; + + set_res(sd); + } else { + cfg->try_fmt = *fmt; + } + + return 0; +} + +static int mt9v011_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + calc_fps(sd, + &ival->interval.numerator, + &ival->interval.denominator); + + return 0; +} + +static int mt9v011_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct v4l2_fract *tpf = &ival->interval; + u16 speed; + + speed = calc_speed(sd, tpf->numerator, tpf->denominator); + + mt9v011_write(sd, R0A_MT9V011_CLK_SPEED, speed); + v4l2_dbg(1, debug, sd, "Setting speed to %d\n", speed); + + /* Recalculate and update fps info */ + calc_fps(sd, &tpf->numerator, &tpf->denominator); + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9v011_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + reg->val = mt9v011_read(sd, reg->reg & 0xff); + reg->size = 2; + + return 0; +} + +static int mt9v011_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + mt9v011_write(sd, reg->reg & 0xff, reg->val & 0xffff); + + return 0; +} +#endif + +static int mt9v011_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9v011 *core = + container_of(ctrl->handler, struct mt9v011, ctrls); + struct v4l2_subdev *sd = &core->sd; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + core->global_gain = ctrl->val; + break; + case V4L2_CID_EXPOSURE: + core->exposure = ctrl->val; + break; + case V4L2_CID_RED_BALANCE: + core->red_bal = ctrl->val; + break; + case V4L2_CID_BLUE_BALANCE: + core->blue_bal = ctrl->val; + break; + case V4L2_CID_HFLIP: + core->hflip = ctrl->val; + set_read_mode(sd); + return 0; + case V4L2_CID_VFLIP: + core->vflip = ctrl->val; + set_read_mode(sd); + return 0; + default: + return -EINVAL; + } + + set_balance(sd); + return 0; +} + +static const struct v4l2_ctrl_ops mt9v011_ctrl_ops = { + .s_ctrl = mt9v011_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops mt9v011_core_ops = { + .reset = mt9v011_reset, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9v011_g_register, + .s_register = mt9v011_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops mt9v011_video_ops = { + .g_frame_interval = mt9v011_g_frame_interval, + .s_frame_interval = mt9v011_s_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops mt9v011_pad_ops = { + .enum_mbus_code = mt9v011_enum_mbus_code, + .set_fmt = mt9v011_set_fmt, +}; + +static const struct v4l2_subdev_ops mt9v011_ops = { + .core = &mt9v011_core_ops, + .video = &mt9v011_video_ops, + .pad = &mt9v011_pad_ops, +}; + + +/**************************************************************************** + I2C Client & Driver + ****************************************************************************/ + +static int mt9v011_probe(struct i2c_client *c, + const struct i2c_device_id *id) +{ + u16 version; + struct mt9v011 *core; + struct v4l2_subdev *sd; +#ifdef CONFIG_MEDIA_CONTROLLER + int ret; +#endif + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(c->adapter, + I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -EIO; + + core = devm_kzalloc(&c->dev, sizeof(struct mt9v011), GFP_KERNEL); + if (!core) + return -ENOMEM; + + sd = &core->sd; + v4l2_i2c_subdev_init(sd, c, &mt9v011_ops); + +#ifdef CONFIG_MEDIA_CONTROLLER + core->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&sd->entity, 1, &core->pad); + if (ret < 0) + return ret; +#endif + + /* Check if the sensor is really a MT9V011 */ + version = mt9v011_read(sd, R00_MT9V011_CHIP_VERSION); + if ((version != MT9V011_VERSION) && + (version != MT9V011_REV_B_VERSION)) { + v4l2_info(sd, "*** unknown micron chip detected (0x%04x).\n", + version); + return -EINVAL; + } + + v4l2_ctrl_handler_init(&core->ctrls, 5); + v4l2_ctrl_new_std(&core->ctrls, &mt9v011_ctrl_ops, + V4L2_CID_GAIN, 0, (1 << 12) - 1 - 0x20, 1, 0x20); + v4l2_ctrl_new_std(&core->ctrls, &mt9v011_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 2047, 1, 0x01fc); + v4l2_ctrl_new_std(&core->ctrls, &mt9v011_ctrl_ops, + V4L2_CID_RED_BALANCE, -(1 << 9), (1 << 9) - 1, 1, 0); + v4l2_ctrl_new_std(&core->ctrls, &mt9v011_ctrl_ops, + V4L2_CID_BLUE_BALANCE, -(1 << 9), (1 << 9) - 1, 1, 0); + v4l2_ctrl_new_std(&core->ctrls, &mt9v011_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&core->ctrls, &mt9v011_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (core->ctrls.error) { + int ret = core->ctrls.error; + + v4l2_err(sd, "control initialization error %d\n", ret); + v4l2_ctrl_handler_free(&core->ctrls); + return ret; + } + core->sd.ctrl_handler = &core->ctrls; + + core->global_gain = 0x0024; + core->exposure = 0x01fc; + core->width = 640; + core->height = 480; + core->xtal = 27000000; /* Hz */ + + if (c->dev.platform_data) { + struct mt9v011_platform_data *pdata = c->dev.platform_data; + + core->xtal = pdata->xtal; + v4l2_dbg(1, debug, sd, "xtal set to %d.%03d MHz\n", + core->xtal / 1000000, (core->xtal / 1000) % 1000); + } + + v4l_info(c, "chip found @ 0x%02x (%s - chip version 0x%04x)\n", + c->addr << 1, c->adapter->name, version); + + return 0; +} + +static int mt9v011_remove(struct i2c_client *c) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct mt9v011 *core = to_mt9v011(sd); + + v4l2_dbg(1, debug, sd, + "mt9v011.c: removing mt9v011 adapter on address 0x%x\n", + c->addr << 1); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&core->ctrls); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id mt9v011_id[] = { + { "mt9v011", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9v011_id); + +static struct i2c_driver mt9v011_driver = { + .driver = { + .name = "mt9v011", + }, + .probe = mt9v011_probe, + .remove = mt9v011_remove, + .id_table = mt9v011_id, +}; + +module_i2c_driver(mt9v011_driver); diff --git a/drivers/media/i2c/mt9v032.c b/drivers/media/i2c/mt9v032.c new file mode 100644 index 000000000..5bd3ae829 --- /dev/null +++ b/drivers/media/i2c/mt9v032.c @@ -0,0 +1,1302 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MT9V022, MT9V024, MT9V032, and MT9V034 CMOS Image Sensors + * + * Copyright (C) 2010, Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * + * Based on the MT9M001 driver, + * + * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/log2.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/v4l2-mediabus.h> +#include <linux/module.h> + +#include <media/i2c/mt9v032.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +/* The first four rows are black rows. The active area spans 753x481 pixels. */ +#define MT9V032_PIXEL_ARRAY_HEIGHT 485 +#define MT9V032_PIXEL_ARRAY_WIDTH 753 + +#define MT9V032_SYSCLK_FREQ_DEF 26600000 + +#define MT9V032_CHIP_VERSION 0x00 +#define MT9V032_CHIP_ID_REV1 0x1311 +#define MT9V032_CHIP_ID_REV3 0x1313 +#define MT9V034_CHIP_ID_REV1 0X1324 +#define MT9V032_COLUMN_START 0x01 +#define MT9V032_COLUMN_START_MIN 1 +#define MT9V032_COLUMN_START_DEF 1 +#define MT9V032_COLUMN_START_MAX 752 +#define MT9V032_ROW_START 0x02 +#define MT9V032_ROW_START_MIN 4 +#define MT9V032_ROW_START_DEF 5 +#define MT9V032_ROW_START_MAX 482 +#define MT9V032_WINDOW_HEIGHT 0x03 +#define MT9V032_WINDOW_HEIGHT_MIN 1 +#define MT9V032_WINDOW_HEIGHT_DEF 480 +#define MT9V032_WINDOW_HEIGHT_MAX 480 +#define MT9V032_WINDOW_WIDTH 0x04 +#define MT9V032_WINDOW_WIDTH_MIN 1 +#define MT9V032_WINDOW_WIDTH_DEF 752 +#define MT9V032_WINDOW_WIDTH_MAX 752 +#define MT9V032_HORIZONTAL_BLANKING 0x05 +#define MT9V032_HORIZONTAL_BLANKING_MIN 43 +#define MT9V034_HORIZONTAL_BLANKING_MIN 61 +#define MT9V032_HORIZONTAL_BLANKING_DEF 94 +#define MT9V032_HORIZONTAL_BLANKING_MAX 1023 +#define MT9V032_VERTICAL_BLANKING 0x06 +#define MT9V032_VERTICAL_BLANKING_MIN 4 +#define MT9V034_VERTICAL_BLANKING_MIN 2 +#define MT9V032_VERTICAL_BLANKING_DEF 45 +#define MT9V032_VERTICAL_BLANKING_MAX 3000 +#define MT9V034_VERTICAL_BLANKING_MAX 32288 +#define MT9V032_CHIP_CONTROL 0x07 +#define MT9V032_CHIP_CONTROL_MASTER_MODE (1 << 3) +#define MT9V032_CHIP_CONTROL_DOUT_ENABLE (1 << 7) +#define MT9V032_CHIP_CONTROL_SEQUENTIAL (1 << 8) +#define MT9V032_SHUTTER_WIDTH1 0x08 +#define MT9V032_SHUTTER_WIDTH2 0x09 +#define MT9V032_SHUTTER_WIDTH_CONTROL 0x0a +#define MT9V032_TOTAL_SHUTTER_WIDTH 0x0b +#define MT9V032_TOTAL_SHUTTER_WIDTH_MIN 1 +#define MT9V034_TOTAL_SHUTTER_WIDTH_MIN 0 +#define MT9V032_TOTAL_SHUTTER_WIDTH_DEF 480 +#define MT9V032_TOTAL_SHUTTER_WIDTH_MAX 32767 +#define MT9V034_TOTAL_SHUTTER_WIDTH_MAX 32765 +#define MT9V032_RESET 0x0c +#define MT9V032_READ_MODE 0x0d +#define MT9V032_READ_MODE_ROW_BIN_MASK (3 << 0) +#define MT9V032_READ_MODE_ROW_BIN_SHIFT 0 +#define MT9V032_READ_MODE_COLUMN_BIN_MASK (3 << 2) +#define MT9V032_READ_MODE_COLUMN_BIN_SHIFT 2 +#define MT9V032_READ_MODE_ROW_FLIP (1 << 4) +#define MT9V032_READ_MODE_COLUMN_FLIP (1 << 5) +#define MT9V032_READ_MODE_DARK_COLUMNS (1 << 6) +#define MT9V032_READ_MODE_DARK_ROWS (1 << 7) +#define MT9V032_READ_MODE_RESERVED 0x0300 +#define MT9V032_PIXEL_OPERATION_MODE 0x0f +#define MT9V034_PIXEL_OPERATION_MODE_HDR (1 << 0) +#define MT9V034_PIXEL_OPERATION_MODE_COLOR (1 << 1) +#define MT9V032_PIXEL_OPERATION_MODE_COLOR (1 << 2) +#define MT9V032_PIXEL_OPERATION_MODE_HDR (1 << 6) +#define MT9V032_ANALOG_GAIN 0x35 +#define MT9V032_ANALOG_GAIN_MIN 16 +#define MT9V032_ANALOG_GAIN_DEF 16 +#define MT9V032_ANALOG_GAIN_MAX 64 +#define MT9V032_MAX_ANALOG_GAIN 0x36 +#define MT9V032_MAX_ANALOG_GAIN_MAX 127 +#define MT9V032_FRAME_DARK_AVERAGE 0x42 +#define MT9V032_DARK_AVG_THRESH 0x46 +#define MT9V032_DARK_AVG_LOW_THRESH_MASK (255 << 0) +#define MT9V032_DARK_AVG_LOW_THRESH_SHIFT 0 +#define MT9V032_DARK_AVG_HIGH_THRESH_MASK (255 << 8) +#define MT9V032_DARK_AVG_HIGH_THRESH_SHIFT 8 +#define MT9V032_ROW_NOISE_CORR_CONTROL 0x70 +#define MT9V034_ROW_NOISE_CORR_ENABLE (1 << 0) +#define MT9V034_ROW_NOISE_CORR_USE_BLK_AVG (1 << 1) +#define MT9V032_ROW_NOISE_CORR_ENABLE (1 << 5) +#define MT9V032_ROW_NOISE_CORR_USE_BLK_AVG (1 << 7) +#define MT9V032_PIXEL_CLOCK 0x74 +#define MT9V034_PIXEL_CLOCK 0x72 +#define MT9V032_PIXEL_CLOCK_INV_LINE (1 << 0) +#define MT9V032_PIXEL_CLOCK_INV_FRAME (1 << 1) +#define MT9V032_PIXEL_CLOCK_XOR_LINE (1 << 2) +#define MT9V032_PIXEL_CLOCK_CONT_LINE (1 << 3) +#define MT9V032_PIXEL_CLOCK_INV_PXL_CLK (1 << 4) +#define MT9V032_TEST_PATTERN 0x7f +#define MT9V032_TEST_PATTERN_DATA_MASK (1023 << 0) +#define MT9V032_TEST_PATTERN_DATA_SHIFT 0 +#define MT9V032_TEST_PATTERN_USE_DATA (1 << 10) +#define MT9V032_TEST_PATTERN_GRAY_MASK (3 << 11) +#define MT9V032_TEST_PATTERN_GRAY_NONE (0 << 11) +#define MT9V032_TEST_PATTERN_GRAY_VERTICAL (1 << 11) +#define MT9V032_TEST_PATTERN_GRAY_HORIZONTAL (2 << 11) +#define MT9V032_TEST_PATTERN_GRAY_DIAGONAL (3 << 11) +#define MT9V032_TEST_PATTERN_ENABLE (1 << 13) +#define MT9V032_TEST_PATTERN_FLIP (1 << 14) +#define MT9V032_AEGC_DESIRED_BIN 0xa5 +#define MT9V032_AEC_UPDATE_FREQUENCY 0xa6 +#define MT9V032_AEC_LPF 0xa8 +#define MT9V032_AGC_UPDATE_FREQUENCY 0xa9 +#define MT9V032_AGC_LPF 0xaa +#define MT9V032_AEC_AGC_ENABLE 0xaf +#define MT9V032_AEC_ENABLE (1 << 0) +#define MT9V032_AGC_ENABLE (1 << 1) +#define MT9V034_AEC_MAX_SHUTTER_WIDTH 0xad +#define MT9V032_AEC_MAX_SHUTTER_WIDTH 0xbd +#define MT9V032_THERMAL_INFO 0xc1 + +enum mt9v032_model { + MT9V032_MODEL_V022_COLOR, /* MT9V022IX7ATC */ + MT9V032_MODEL_V022_MONO, /* MT9V022IX7ATM */ + MT9V032_MODEL_V024_COLOR, /* MT9V024IA7XTC */ + MT9V032_MODEL_V024_MONO, /* MT9V024IA7XTM */ + MT9V032_MODEL_V032_COLOR, /* MT9V032C12STM */ + MT9V032_MODEL_V032_MONO, /* MT9V032C12STC */ + MT9V032_MODEL_V034_COLOR, + MT9V032_MODEL_V034_MONO, +}; + +struct mt9v032_model_version { + unsigned int version; + const char *name; +}; + +struct mt9v032_model_data { + unsigned int min_row_time; + unsigned int min_hblank; + unsigned int min_vblank; + unsigned int max_vblank; + unsigned int min_shutter; + unsigned int max_shutter; + unsigned int pclk_reg; + unsigned int aec_max_shutter_reg; + const struct v4l2_ctrl_config * const aec_max_shutter_v4l2_ctrl; +}; + +struct mt9v032_model_info { + const struct mt9v032_model_data *data; + bool color; +}; + +static const struct mt9v032_model_version mt9v032_versions[] = { + { MT9V032_CHIP_ID_REV1, "MT9V022/MT9V032 rev1/2" }, + { MT9V032_CHIP_ID_REV3, "MT9V022/MT9V032 rev3" }, + { MT9V034_CHIP_ID_REV1, "MT9V024/MT9V034 rev1" }, +}; + +struct mt9v032 { + struct v4l2_subdev subdev; + struct media_pad pad; + + struct v4l2_mbus_framefmt format; + struct v4l2_rect crop; + unsigned int hratio; + unsigned int vratio; + + struct v4l2_ctrl_handler ctrls; + struct { + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + }; + + struct mutex power_lock; + int power_count; + + struct regmap *regmap; + struct clk *clk; + struct gpio_desc *reset_gpio; + struct gpio_desc *standby_gpio; + + struct mt9v032_platform_data *pdata; + const struct mt9v032_model_info *model; + const struct mt9v032_model_version *version; + + u32 sysclk; + u16 aec_agc; + u16 hblank; + struct { + struct v4l2_ctrl *test_pattern; + struct v4l2_ctrl *test_pattern_color; + }; +}; + +static struct mt9v032 *to_mt9v032(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mt9v032, subdev); +} + +static int +mt9v032_update_aec_agc(struct mt9v032 *mt9v032, u16 which, int enable) +{ + struct regmap *map = mt9v032->regmap; + u16 value = mt9v032->aec_agc; + int ret; + + if (enable) + value |= which; + else + value &= ~which; + + ret = regmap_write(map, MT9V032_AEC_AGC_ENABLE, value); + if (ret < 0) + return ret; + + mt9v032->aec_agc = value; + return 0; +} + +static int +mt9v032_update_hblank(struct mt9v032 *mt9v032) +{ + struct v4l2_rect *crop = &mt9v032->crop; + unsigned int min_hblank = mt9v032->model->data->min_hblank; + unsigned int hblank; + + if (mt9v032->version->version == MT9V034_CHIP_ID_REV1) + min_hblank += (mt9v032->hratio - 1) * 10; + min_hblank = max_t(int, mt9v032->model->data->min_row_time - crop->width, + min_hblank); + hblank = max_t(unsigned int, mt9v032->hblank, min_hblank); + + return regmap_write(mt9v032->regmap, MT9V032_HORIZONTAL_BLANKING, + hblank); +} + +static int mt9v032_power_on(struct mt9v032 *mt9v032) +{ + struct regmap *map = mt9v032->regmap; + int ret; + + gpiod_set_value_cansleep(mt9v032->reset_gpio, 1); + + ret = clk_set_rate(mt9v032->clk, mt9v032->sysclk); + if (ret < 0) + return ret; + + /* System clock has to be enabled before releasing the reset */ + ret = clk_prepare_enable(mt9v032->clk); + if (ret) + return ret; + + udelay(1); + + if (mt9v032->reset_gpio) { + gpiod_set_value_cansleep(mt9v032->reset_gpio, 0); + + /* After releasing reset we need to wait 10 clock cycles + * before accessing the sensor over I2C. As the minimum SYSCLK + * frequency is 13MHz, waiting 1µs will be enough in the worst + * case. + */ + udelay(1); + } + + /* Reset the chip and stop data read out */ + ret = regmap_write(map, MT9V032_RESET, 1); + if (ret < 0) + goto err; + + ret = regmap_write(map, MT9V032_RESET, 0); + if (ret < 0) + goto err; + + ret = regmap_write(map, MT9V032_CHIP_CONTROL, + MT9V032_CHIP_CONTROL_MASTER_MODE); + if (ret < 0) + goto err; + + return 0; + +err: + clk_disable_unprepare(mt9v032->clk); + return ret; +} + +static void mt9v032_power_off(struct mt9v032 *mt9v032) +{ + clk_disable_unprepare(mt9v032->clk); +} + +static int __mt9v032_set_power(struct mt9v032 *mt9v032, bool on) +{ + struct regmap *map = mt9v032->regmap; + int ret; + + if (!on) { + mt9v032_power_off(mt9v032); + return 0; + } + + ret = mt9v032_power_on(mt9v032); + if (ret < 0) + return ret; + + /* Configure the pixel clock polarity */ + if (mt9v032->pdata && mt9v032->pdata->clk_pol) { + ret = regmap_write(map, mt9v032->model->data->pclk_reg, + MT9V032_PIXEL_CLOCK_INV_PXL_CLK); + if (ret < 0) + return ret; + } + + /* Disable the noise correction algorithm and restore the controls. */ + ret = regmap_write(map, MT9V032_ROW_NOISE_CORR_CONTROL, 0); + if (ret < 0) + return ret; + + return v4l2_ctrl_handler_setup(&mt9v032->ctrls); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev video operations + */ + +static struct v4l2_mbus_framefmt * +__mt9v032_get_pad_format(struct mt9v032 *mt9v032, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&mt9v032->subdev, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9v032->format; + default: + return NULL; + } +} + +static struct v4l2_rect * +__mt9v032_get_pad_crop(struct mt9v032 *mt9v032, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&mt9v032->subdev, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9v032->crop; + default: + return NULL; + } +} + +static int mt9v032_s_stream(struct v4l2_subdev *subdev, int enable) +{ + const u16 mode = MT9V032_CHIP_CONTROL_DOUT_ENABLE + | MT9V032_CHIP_CONTROL_SEQUENTIAL; + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + struct v4l2_rect *crop = &mt9v032->crop; + struct regmap *map = mt9v032->regmap; + unsigned int hbin; + unsigned int vbin; + int ret; + + if (!enable) + return regmap_update_bits(map, MT9V032_CHIP_CONTROL, mode, 0); + + /* Configure the window size and row/column bin */ + hbin = fls(mt9v032->hratio) - 1; + vbin = fls(mt9v032->vratio) - 1; + ret = regmap_update_bits(map, MT9V032_READ_MODE, + ~MT9V032_READ_MODE_RESERVED, + hbin << MT9V032_READ_MODE_COLUMN_BIN_SHIFT | + vbin << MT9V032_READ_MODE_ROW_BIN_SHIFT); + if (ret < 0) + return ret; + + ret = regmap_write(map, MT9V032_COLUMN_START, crop->left); + if (ret < 0) + return ret; + + ret = regmap_write(map, MT9V032_ROW_START, crop->top); + if (ret < 0) + return ret; + + ret = regmap_write(map, MT9V032_WINDOW_WIDTH, crop->width); + if (ret < 0) + return ret; + + ret = regmap_write(map, MT9V032_WINDOW_HEIGHT, crop->height); + if (ret < 0) + return ret; + + ret = mt9v032_update_hblank(mt9v032); + if (ret < 0) + return ret; + + /* Switch to master "normal" mode */ + return regmap_update_bits(map, MT9V032_CHIP_CONTROL, mode, mode); +} + +static int mt9v032_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + + if (code->index > 0) + return -EINVAL; + + code->code = mt9v032->format.code; + return 0; +} + +static int mt9v032_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + + if (fse->index >= 3) + return -EINVAL; + if (mt9v032->format.code != fse->code) + return -EINVAL; + + fse->min_width = MT9V032_WINDOW_WIDTH_DEF / (1 << fse->index); + fse->max_width = fse->min_width; + fse->min_height = MT9V032_WINDOW_HEIGHT_DEF / (1 << fse->index); + fse->max_height = fse->min_height; + + return 0; +} + +static int mt9v032_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + + format->format = *__mt9v032_get_pad_format(mt9v032, cfg, format->pad, + format->which); + return 0; +} + +static void mt9v032_configure_pixel_rate(struct mt9v032 *mt9v032) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); + int ret; + + ret = v4l2_ctrl_s_ctrl_int64(mt9v032->pixel_rate, + mt9v032->sysclk / mt9v032->hratio); + if (ret < 0) + dev_warn(&client->dev, "failed to set pixel rate (%d)\n", ret); +} + +static unsigned int mt9v032_calc_ratio(unsigned int input, unsigned int output) +{ + /* Compute the power-of-two binning factor closest to the input size to + * output size ratio. Given that the output size is bounded by input/4 + * and input, a generic implementation would be an ineffective luxury. + */ + if (output * 3 > input * 2) + return 1; + if (output * 3 > input) + return 2; + return 4; +} + +static int mt9v032_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + unsigned int width; + unsigned int height; + unsigned int hratio; + unsigned int vratio; + + __crop = __mt9v032_get_pad_crop(mt9v032, cfg, format->pad, + format->which); + + /* Clamp the width and height to avoid dividing by zero. */ + width = clamp(ALIGN(format->format.width, 2), + max_t(unsigned int, __crop->width / 4, + MT9V032_WINDOW_WIDTH_MIN), + __crop->width); + height = clamp(ALIGN(format->format.height, 2), + max_t(unsigned int, __crop->height / 4, + MT9V032_WINDOW_HEIGHT_MIN), + __crop->height); + + hratio = mt9v032_calc_ratio(__crop->width, width); + vratio = mt9v032_calc_ratio(__crop->height, height); + + __format = __mt9v032_get_pad_format(mt9v032, cfg, format->pad, + format->which); + __format->width = __crop->width / hratio; + __format->height = __crop->height / vratio; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + mt9v032->hratio = hratio; + mt9v032->vratio = vratio; + mt9v032_configure_pixel_rate(mt9v032); + } + + format->format = *__format; + + return 0; +} + +static int mt9v032_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + sel->r = *__mt9v032_get_pad_crop(mt9v032, cfg, sel->pad, sel->which); + return 0; +} + +static int mt9v032_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + struct v4l2_rect rect; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + /* Clamp the crop rectangle boundaries and align them to a non multiple + * of 2 pixels to ensure a GRBG Bayer pattern. + */ + rect.left = clamp(ALIGN(sel->r.left + 1, 2) - 1, + MT9V032_COLUMN_START_MIN, + MT9V032_COLUMN_START_MAX); + rect.top = clamp(ALIGN(sel->r.top + 1, 2) - 1, + MT9V032_ROW_START_MIN, + MT9V032_ROW_START_MAX); + rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), + MT9V032_WINDOW_WIDTH_MIN, + MT9V032_WINDOW_WIDTH_MAX); + rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), + MT9V032_WINDOW_HEIGHT_MIN, + MT9V032_WINDOW_HEIGHT_MAX); + + rect.width = min_t(unsigned int, + rect.width, MT9V032_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min_t(unsigned int, + rect.height, MT9V032_PIXEL_ARRAY_HEIGHT - rect.top); + + __crop = __mt9v032_get_pad_crop(mt9v032, cfg, sel->pad, sel->which); + + if (rect.width != __crop->width || rect.height != __crop->height) { + /* Reset the output image size if the crop rectangle size has + * been modified. + */ + __format = __mt9v032_get_pad_format(mt9v032, cfg, sel->pad, + sel->which); + __format->width = rect.width; + __format->height = rect.height; + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + mt9v032->hratio = 1; + mt9v032->vratio = 1; + mt9v032_configure_pixel_rate(mt9v032); + } + } + + *__crop = rect; + sel->r = rect; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev control operations + */ + +#define V4L2_CID_TEST_PATTERN_COLOR (V4L2_CID_USER_BASE | 0x1001) +/* + * Value between 1 and 64 to set the desired bin. This is effectively a measure + * of how bright the image is supposed to be. Both AGC and AEC try to reach + * this. + */ +#define V4L2_CID_AEGC_DESIRED_BIN (V4L2_CID_USER_BASE | 0x1002) +/* + * LPF is the low pass filter capability of the chip. Both AEC and AGC have + * this setting. This limits the speed in which AGC/AEC adjust their settings. + * Possible values are 0-2. 0 means no LPF. For 1 and 2 this equation is used: + * + * if |(calculated new exp - current exp)| > (current exp / 4) + * next exp = calculated new exp + * else + * next exp = current exp + ((calculated new exp - current exp) / 2^LPF) + */ +#define V4L2_CID_AEC_LPF (V4L2_CID_USER_BASE | 0x1003) +#define V4L2_CID_AGC_LPF (V4L2_CID_USER_BASE | 0x1004) +/* + * Value between 0 and 15. This is the number of frames being skipped before + * updating the auto exposure/gain. + */ +#define V4L2_CID_AEC_UPDATE_INTERVAL (V4L2_CID_USER_BASE | 0x1005) +#define V4L2_CID_AGC_UPDATE_INTERVAL (V4L2_CID_USER_BASE | 0x1006) +/* + * Maximum shutter width used for AEC. + */ +#define V4L2_CID_AEC_MAX_SHUTTER_WIDTH (V4L2_CID_USER_BASE | 0x1007) + +static int mt9v032_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9v032 *mt9v032 = + container_of(ctrl->handler, struct mt9v032, ctrls); + struct regmap *map = mt9v032->regmap; + u32 freq; + u16 data; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + return mt9v032_update_aec_agc(mt9v032, MT9V032_AGC_ENABLE, + ctrl->val); + + case V4L2_CID_GAIN: + return regmap_write(map, MT9V032_ANALOG_GAIN, ctrl->val); + + case V4L2_CID_EXPOSURE_AUTO: + return mt9v032_update_aec_agc(mt9v032, MT9V032_AEC_ENABLE, + !ctrl->val); + + case V4L2_CID_EXPOSURE: + return regmap_write(map, MT9V032_TOTAL_SHUTTER_WIDTH, + ctrl->val); + + case V4L2_CID_HBLANK: + mt9v032->hblank = ctrl->val; + return mt9v032_update_hblank(mt9v032); + + case V4L2_CID_VBLANK: + return regmap_write(map, MT9V032_VERTICAL_BLANKING, + ctrl->val); + + case V4L2_CID_PIXEL_RATE: + case V4L2_CID_LINK_FREQ: + if (mt9v032->link_freq == NULL) + break; + + freq = mt9v032->pdata->link_freqs[mt9v032->link_freq->val]; + *mt9v032->pixel_rate->p_new.p_s64 = freq; + mt9v032->sysclk = freq; + break; + + case V4L2_CID_TEST_PATTERN: + switch (mt9v032->test_pattern->val) { + case 0: + data = 0; + break; + case 1: + data = MT9V032_TEST_PATTERN_GRAY_VERTICAL + | MT9V032_TEST_PATTERN_ENABLE; + break; + case 2: + data = MT9V032_TEST_PATTERN_GRAY_HORIZONTAL + | MT9V032_TEST_PATTERN_ENABLE; + break; + case 3: + data = MT9V032_TEST_PATTERN_GRAY_DIAGONAL + | MT9V032_TEST_PATTERN_ENABLE; + break; + default: + data = (mt9v032->test_pattern_color->val << + MT9V032_TEST_PATTERN_DATA_SHIFT) + | MT9V032_TEST_PATTERN_USE_DATA + | MT9V032_TEST_PATTERN_ENABLE + | MT9V032_TEST_PATTERN_FLIP; + break; + } + return regmap_write(map, MT9V032_TEST_PATTERN, data); + + case V4L2_CID_AEGC_DESIRED_BIN: + return regmap_write(map, MT9V032_AEGC_DESIRED_BIN, ctrl->val); + + case V4L2_CID_AEC_LPF: + return regmap_write(map, MT9V032_AEC_LPF, ctrl->val); + + case V4L2_CID_AGC_LPF: + return regmap_write(map, MT9V032_AGC_LPF, ctrl->val); + + case V4L2_CID_AEC_UPDATE_INTERVAL: + return regmap_write(map, MT9V032_AEC_UPDATE_FREQUENCY, + ctrl->val); + + case V4L2_CID_AGC_UPDATE_INTERVAL: + return regmap_write(map, MT9V032_AGC_UPDATE_FREQUENCY, + ctrl->val); + + case V4L2_CID_AEC_MAX_SHUTTER_WIDTH: + return regmap_write(map, + mt9v032->model->data->aec_max_shutter_reg, + ctrl->val); + } + + return 0; +} + +static const struct v4l2_ctrl_ops mt9v032_ctrl_ops = { + .s_ctrl = mt9v032_s_ctrl, +}; + +static const char * const mt9v032_test_pattern_menu[] = { + "Disabled", + "Gray Vertical Shade", + "Gray Horizontal Shade", + "Gray Diagonal Shade", + "Plain", +}; + +static const struct v4l2_ctrl_config mt9v032_test_pattern_color = { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_TEST_PATTERN_COLOR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Test Pattern Color", + .min = 0, + .max = 1023, + .step = 1, + .def = 0, + .flags = 0, +}; + +static const struct v4l2_ctrl_config mt9v032_aegc_controls[] = { + { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AEGC_DESIRED_BIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC/AGC Desired Bin", + .min = 1, + .max = 64, + .step = 1, + .def = 58, + .flags = 0, + }, { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AEC_LPF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC Low Pass Filter", + .min = 0, + .max = 2, + .step = 1, + .def = 0, + .flags = 0, + }, { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AGC_LPF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AGC Low Pass Filter", + .min = 0, + .max = 2, + .step = 1, + .def = 2, + .flags = 0, + }, { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AEC_UPDATE_INTERVAL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC Update Interval", + .min = 0, + .max = 16, + .step = 1, + .def = 2, + .flags = 0, + }, { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AGC_UPDATE_INTERVAL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AGC Update Interval", + .min = 0, + .max = 16, + .step = 1, + .def = 2, + .flags = 0, + } +}; + +static const struct v4l2_ctrl_config mt9v032_aec_max_shutter_width = { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AEC_MAX_SHUTTER_WIDTH, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC Max Shutter Width", + .min = 1, + .max = 2047, + .step = 1, + .def = 480, + .flags = 0, +}; + +static const struct v4l2_ctrl_config mt9v034_aec_max_shutter_width = { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AEC_MAX_SHUTTER_WIDTH, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC Max Shutter Width", + .min = 1, + .max = 32765, + .step = 1, + .def = 480, + .flags = 0, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +static int mt9v032_set_power(struct v4l2_subdev *subdev, int on) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + int ret = 0; + + mutex_lock(&mt9v032->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (mt9v032->power_count == !on) { + ret = __mt9v032_set_power(mt9v032, !!on); + if (ret < 0) + goto done; + } + + /* Update the power count. */ + mt9v032->power_count += on ? 1 : -1; + WARN_ON(mt9v032->power_count < 0); + +done: + mutex_unlock(&mt9v032->power_lock); + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev internal operations + */ + +static int mt9v032_registered(struct v4l2_subdev *subdev) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + unsigned int i; + u32 version; + int ret; + + dev_info(&client->dev, "Probing MT9V032 at address 0x%02x\n", + client->addr); + + ret = mt9v032_power_on(mt9v032); + if (ret < 0) { + dev_err(&client->dev, "MT9V032 power up failed\n"); + return ret; + } + + /* Read and check the sensor version */ + ret = regmap_read(mt9v032->regmap, MT9V032_CHIP_VERSION, &version); + + mt9v032_power_off(mt9v032); + + if (ret < 0) { + dev_err(&client->dev, "Failed reading chip version\n"); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(mt9v032_versions); ++i) { + if (mt9v032_versions[i].version == version) { + mt9v032->version = &mt9v032_versions[i]; + break; + } + } + + if (mt9v032->version == NULL) { + dev_err(&client->dev, "Unsupported chip version 0x%04x\n", + version); + return -ENODEV; + } + + dev_info(&client->dev, "%s detected at address 0x%02x\n", + mt9v032->version->name, client->addr); + + mt9v032_configure_pixel_rate(mt9v032); + + return ret; +} + +static int mt9v032_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + crop = v4l2_subdev_get_try_crop(subdev, fh->pad, 0); + crop->left = MT9V032_COLUMN_START_DEF; + crop->top = MT9V032_ROW_START_DEF; + crop->width = MT9V032_WINDOW_WIDTH_DEF; + crop->height = MT9V032_WINDOW_HEIGHT_DEF; + + format = v4l2_subdev_get_try_format(subdev, fh->pad, 0); + + if (mt9v032->model->color) + format->code = MEDIA_BUS_FMT_SGRBG10_1X10; + else + format->code = MEDIA_BUS_FMT_Y10_1X10; + + format->width = MT9V032_WINDOW_WIDTH_DEF; + format->height = MT9V032_WINDOW_HEIGHT_DEF; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + return mt9v032_set_power(subdev, 1); +} + +static int mt9v032_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + return mt9v032_set_power(subdev, 0); +} + +static const struct v4l2_subdev_core_ops mt9v032_subdev_core_ops = { + .s_power = mt9v032_set_power, +}; + +static const struct v4l2_subdev_video_ops mt9v032_subdev_video_ops = { + .s_stream = mt9v032_s_stream, +}; + +static const struct v4l2_subdev_pad_ops mt9v032_subdev_pad_ops = { + .enum_mbus_code = mt9v032_enum_mbus_code, + .enum_frame_size = mt9v032_enum_frame_size, + .get_fmt = mt9v032_get_format, + .set_fmt = mt9v032_set_format, + .get_selection = mt9v032_get_selection, + .set_selection = mt9v032_set_selection, +}; + +static const struct v4l2_subdev_ops mt9v032_subdev_ops = { + .core = &mt9v032_subdev_core_ops, + .video = &mt9v032_subdev_video_ops, + .pad = &mt9v032_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops mt9v032_subdev_internal_ops = { + .registered = mt9v032_registered, + .open = mt9v032_open, + .close = mt9v032_close, +}; + +static const struct regmap_config mt9v032_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, +}; + +/* ----------------------------------------------------------------------------- + * Driver initialization and probing + */ + +static struct mt9v032_platform_data * +mt9v032_get_pdata(struct i2c_client *client) +{ + struct mt9v032_platform_data *pdata = NULL; + struct v4l2_fwnode_endpoint endpoint = { .bus_type = 0 }; + struct device_node *np; + struct property *prop; + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return client->dev.platform_data; + + np = of_graph_get_next_endpoint(client->dev.of_node, NULL); + if (!np) + return NULL; + + if (v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &endpoint) < 0) + goto done; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto done; + + prop = of_find_property(np, "link-frequencies", NULL); + if (prop) { + u64 *link_freqs; + size_t size = prop->length / sizeof(*link_freqs); + + link_freqs = devm_kcalloc(&client->dev, size, + sizeof(*link_freqs), GFP_KERNEL); + if (!link_freqs) + goto done; + + if (of_property_read_u64_array(np, "link-frequencies", + link_freqs, size) < 0) + goto done; + + pdata->link_freqs = link_freqs; + pdata->link_def_freq = link_freqs[0]; + } + + pdata->clk_pol = !!(endpoint.bus.parallel.flags & + V4L2_MBUS_PCLK_SAMPLE_RISING); + +done: + of_node_put(np); + return pdata; +} + +static int mt9v032_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9v032_platform_data *pdata = mt9v032_get_pdata(client); + struct mt9v032 *mt9v032; + unsigned int i; + int ret; + + mt9v032 = devm_kzalloc(&client->dev, sizeof(*mt9v032), GFP_KERNEL); + if (!mt9v032) + return -ENOMEM; + + mt9v032->regmap = devm_regmap_init_i2c(client, &mt9v032_regmap_config); + if (IS_ERR(mt9v032->regmap)) + return PTR_ERR(mt9v032->regmap); + + mt9v032->clk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(mt9v032->clk)) + return PTR_ERR(mt9v032->clk); + + mt9v032->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(mt9v032->reset_gpio)) + return PTR_ERR(mt9v032->reset_gpio); + + mt9v032->standby_gpio = devm_gpiod_get_optional(&client->dev, "standby", + GPIOD_OUT_LOW); + if (IS_ERR(mt9v032->standby_gpio)) + return PTR_ERR(mt9v032->standby_gpio); + + mutex_init(&mt9v032->power_lock); + mt9v032->pdata = pdata; + mt9v032->model = (const void *)did->driver_data; + + v4l2_ctrl_handler_init(&mt9v032->ctrls, 11 + + ARRAY_SIZE(mt9v032_aegc_controls)); + + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_GAIN, MT9V032_ANALOG_GAIN_MIN, + MT9V032_ANALOG_GAIN_MAX, 1, MT9V032_ANALOG_GAIN_DEF); + v4l2_ctrl_new_std_menu(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, 0, + V4L2_EXPOSURE_AUTO); + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_EXPOSURE, mt9v032->model->data->min_shutter, + mt9v032->model->data->max_shutter, 1, + MT9V032_TOTAL_SHUTTER_WIDTH_DEF); + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_HBLANK, mt9v032->model->data->min_hblank, + MT9V032_HORIZONTAL_BLANKING_MAX, 1, + MT9V032_HORIZONTAL_BLANKING_DEF); + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_VBLANK, mt9v032->model->data->min_vblank, + mt9v032->model->data->max_vblank, 1, + MT9V032_VERTICAL_BLANKING_DEF); + mt9v032->test_pattern = v4l2_ctrl_new_std_menu_items(&mt9v032->ctrls, + &mt9v032_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(mt9v032_test_pattern_menu) - 1, 0, 0, + mt9v032_test_pattern_menu); + mt9v032->test_pattern_color = v4l2_ctrl_new_custom(&mt9v032->ctrls, + &mt9v032_test_pattern_color, NULL); + + v4l2_ctrl_new_custom(&mt9v032->ctrls, + mt9v032->model->data->aec_max_shutter_v4l2_ctrl, + NULL); + for (i = 0; i < ARRAY_SIZE(mt9v032_aegc_controls); ++i) + v4l2_ctrl_new_custom(&mt9v032->ctrls, &mt9v032_aegc_controls[i], + NULL); + + v4l2_ctrl_cluster(2, &mt9v032->test_pattern); + + mt9v032->pixel_rate = + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1); + + if (pdata && pdata->link_freqs) { + unsigned int def = 0; + + for (i = 0; pdata->link_freqs[i]; ++i) { + if (pdata->link_freqs[i] == pdata->link_def_freq) + def = i; + } + + mt9v032->link_freq = + v4l2_ctrl_new_int_menu(&mt9v032->ctrls, + &mt9v032_ctrl_ops, + V4L2_CID_LINK_FREQ, i - 1, def, + pdata->link_freqs); + v4l2_ctrl_cluster(2, &mt9v032->link_freq); + } + + + mt9v032->subdev.ctrl_handler = &mt9v032->ctrls; + + if (mt9v032->ctrls.error) { + dev_err(&client->dev, "control initialization error %d\n", + mt9v032->ctrls.error); + ret = mt9v032->ctrls.error; + goto err; + } + + mt9v032->crop.left = MT9V032_COLUMN_START_DEF; + mt9v032->crop.top = MT9V032_ROW_START_DEF; + mt9v032->crop.width = MT9V032_WINDOW_WIDTH_DEF; + mt9v032->crop.height = MT9V032_WINDOW_HEIGHT_DEF; + + if (mt9v032->model->color) + mt9v032->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + else + mt9v032->format.code = MEDIA_BUS_FMT_Y10_1X10; + + mt9v032->format.width = MT9V032_WINDOW_WIDTH_DEF; + mt9v032->format.height = MT9V032_WINDOW_HEIGHT_DEF; + mt9v032->format.field = V4L2_FIELD_NONE; + mt9v032->format.colorspace = V4L2_COLORSPACE_SRGB; + + mt9v032->hratio = 1; + mt9v032->vratio = 1; + + mt9v032->aec_agc = MT9V032_AEC_ENABLE | MT9V032_AGC_ENABLE; + mt9v032->hblank = MT9V032_HORIZONTAL_BLANKING_DEF; + mt9v032->sysclk = MT9V032_SYSCLK_FREQ_DEF; + + v4l2_i2c_subdev_init(&mt9v032->subdev, client, &mt9v032_subdev_ops); + mt9v032->subdev.internal_ops = &mt9v032_subdev_internal_ops; + mt9v032->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + mt9v032->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + mt9v032->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&mt9v032->subdev.entity, 1, &mt9v032->pad); + if (ret < 0) + goto err; + + mt9v032->subdev.dev = &client->dev; + ret = v4l2_async_register_subdev(&mt9v032->subdev); + if (ret < 0) + goto err; + + return 0; + +err: + media_entity_cleanup(&mt9v032->subdev.entity); + v4l2_ctrl_handler_free(&mt9v032->ctrls); + return ret; +} + +static int mt9v032_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + + v4l2_async_unregister_subdev(subdev); + v4l2_ctrl_handler_free(&mt9v032->ctrls); + media_entity_cleanup(&subdev->entity); + + return 0; +} + +static const struct mt9v032_model_data mt9v032_model_data[] = { + { + /* MT9V022, MT9V032 revisions 1/2/3 */ + .min_row_time = 660, + .min_hblank = MT9V032_HORIZONTAL_BLANKING_MIN, + .min_vblank = MT9V032_VERTICAL_BLANKING_MIN, + .max_vblank = MT9V032_VERTICAL_BLANKING_MAX, + .min_shutter = MT9V032_TOTAL_SHUTTER_WIDTH_MIN, + .max_shutter = MT9V032_TOTAL_SHUTTER_WIDTH_MAX, + .pclk_reg = MT9V032_PIXEL_CLOCK, + .aec_max_shutter_reg = MT9V032_AEC_MAX_SHUTTER_WIDTH, + .aec_max_shutter_v4l2_ctrl = &mt9v032_aec_max_shutter_width, + }, { + /* MT9V024, MT9V034 */ + .min_row_time = 690, + .min_hblank = MT9V034_HORIZONTAL_BLANKING_MIN, + .min_vblank = MT9V034_VERTICAL_BLANKING_MIN, + .max_vblank = MT9V034_VERTICAL_BLANKING_MAX, + .min_shutter = MT9V034_TOTAL_SHUTTER_WIDTH_MIN, + .max_shutter = MT9V034_TOTAL_SHUTTER_WIDTH_MAX, + .pclk_reg = MT9V034_PIXEL_CLOCK, + .aec_max_shutter_reg = MT9V034_AEC_MAX_SHUTTER_WIDTH, + .aec_max_shutter_v4l2_ctrl = &mt9v034_aec_max_shutter_width, + }, +}; + +static const struct mt9v032_model_info mt9v032_models[] = { + [MT9V032_MODEL_V022_COLOR] = { + .data = &mt9v032_model_data[0], + .color = true, + }, + [MT9V032_MODEL_V022_MONO] = { + .data = &mt9v032_model_data[0], + .color = false, + }, + [MT9V032_MODEL_V024_COLOR] = { + .data = &mt9v032_model_data[1], + .color = true, + }, + [MT9V032_MODEL_V024_MONO] = { + .data = &mt9v032_model_data[1], + .color = false, + }, + [MT9V032_MODEL_V032_COLOR] = { + .data = &mt9v032_model_data[0], + .color = true, + }, + [MT9V032_MODEL_V032_MONO] = { + .data = &mt9v032_model_data[0], + .color = false, + }, + [MT9V032_MODEL_V034_COLOR] = { + .data = &mt9v032_model_data[1], + .color = true, + }, + [MT9V032_MODEL_V034_MONO] = { + .data = &mt9v032_model_data[1], + .color = false, + }, +}; + +static const struct i2c_device_id mt9v032_id[] = { + { "mt9v022", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V022_COLOR] }, + { "mt9v022m", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V022_MONO] }, + { "mt9v024", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V024_COLOR] }, + { "mt9v024m", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V024_MONO] }, + { "mt9v032", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V032_COLOR] }, + { "mt9v032m", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V032_MONO] }, + { "mt9v034", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V034_COLOR] }, + { "mt9v034m", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V034_MONO] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9v032_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id mt9v032_of_match[] = { + { .compatible = "aptina,mt9v022" }, + { .compatible = "aptina,mt9v022m" }, + { .compatible = "aptina,mt9v024" }, + { .compatible = "aptina,mt9v024m" }, + { .compatible = "aptina,mt9v032" }, + { .compatible = "aptina,mt9v032m" }, + { .compatible = "aptina,mt9v034" }, + { .compatible = "aptina,mt9v034m" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mt9v032_of_match); +#endif + +static struct i2c_driver mt9v032_driver = { + .driver = { + .name = "mt9v032", + .of_match_table = of_match_ptr(mt9v032_of_match), + }, + .probe = mt9v032_probe, + .remove = mt9v032_remove, + .id_table = mt9v032_id, +}; + +module_i2c_driver(mt9v032_driver); + +MODULE_DESCRIPTION("Aptina MT9V032 Camera driver"); +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/mt9v111.c b/drivers/media/i2c/mt9v111.c new file mode 100644 index 000000000..61ae6a0d5 --- /dev/null +++ b/drivers/media/i2c/mt9v111.c @@ -0,0 +1,1283 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 sensor driver for Aptina MT9V111 image sensor + * Copyright (C) 2018 Jacopo Mondi <jacopo@jmondi.org> + * + * Based on mt9v032 driver + * Copyright (C) 2010, Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> + * + * Based on mt9v011 driver + * Copyright (c) 2009 Mauro Carvalho Chehab <mchehab@kernel.org> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/v4l2-mediabus.h> +#include <linux/module.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-subdev.h> + +/* + * MT9V111 is a 1/4-Inch CMOS digital image sensor with an integrated + * Image Flow Processing (IFP) engine and a sensor core loosely based on + * MT9V011. + * + * The IFP can produce several output image formats from the sensor core + * output. This driver currently supports only YUYV format permutations. + * + * The driver allows manual frame rate control through s_frame_interval subdev + * operation or V4L2_CID_V/HBLANK controls, but it is known that the + * auto-exposure algorithm might modify the programmed frame rate. While the + * driver initially programs the sensor with auto-exposure and + * auto-white-balancing enabled, it is possible to disable them and more + * precisely control the frame rate. + * + * While it seems possible to instruct the auto-exposure control algorithm to + * respect a programmed frame rate when adjusting the pixel integration time, + * registers controlling this feature are not documented in the public + * available sensor manual used to develop this driver (09005aef80e90084, + * MT9V111_1.fm - Rev. G 1/05 EN). + */ + +#define MT9V111_CHIP_ID_HIGH 0x82 +#define MT9V111_CHIP_ID_LOW 0x3a + +#define MT9V111_R01_ADDR_SPACE 0x01 +#define MT9V111_R01_IFP 0x01 +#define MT9V111_R01_CORE 0x04 + +#define MT9V111_IFP_R06_OPMODE_CTRL 0x06 +#define MT9V111_IFP_R06_OPMODE_CTRL_AWB_EN BIT(1) +#define MT9V111_IFP_R06_OPMODE_CTRL_AE_EN BIT(14) +#define MT9V111_IFP_R07_IFP_RESET 0x07 +#define MT9V111_IFP_R07_IFP_RESET_MASK BIT(0) +#define MT9V111_IFP_R08_OUTFMT_CTRL 0x08 +#define MT9V111_IFP_R08_OUTFMT_CTRL_FLICKER BIT(11) +#define MT9V111_IFP_R08_OUTFMT_CTRL_PCLK BIT(5) +#define MT9V111_IFP_R3A_OUTFMT_CTRL2 0x3a +#define MT9V111_IFP_R3A_OUTFMT_CTRL2_SWAP_CBCR BIT(0) +#define MT9V111_IFP_R3A_OUTFMT_CTRL2_SWAP_YC BIT(1) +#define MT9V111_IFP_R3A_OUTFMT_CTRL2_SWAP_MASK GENMASK(2, 0) +#define MT9V111_IFP_RA5_HPAN 0xa5 +#define MT9V111_IFP_RA6_HZOOM 0xa6 +#define MT9V111_IFP_RA7_HOUT 0xa7 +#define MT9V111_IFP_RA8_VPAN 0xa8 +#define MT9V111_IFP_RA9_VZOOM 0xa9 +#define MT9V111_IFP_RAA_VOUT 0xaa +#define MT9V111_IFP_DECIMATION_MASK GENMASK(9, 0) +#define MT9V111_IFP_DECIMATION_FREEZE BIT(15) + +#define MT9V111_CORE_R03_WIN_HEIGHT 0x03 +#define MT9V111_CORE_R03_WIN_V_OFFS 2 +#define MT9V111_CORE_R04_WIN_WIDTH 0x04 +#define MT9V111_CORE_R04_WIN_H_OFFS 114 +#define MT9V111_CORE_R05_HBLANK 0x05 +#define MT9V111_CORE_R05_MIN_HBLANK 0x09 +#define MT9V111_CORE_R05_MAX_HBLANK GENMASK(9, 0) +#define MT9V111_CORE_R05_DEF_HBLANK 0x26 +#define MT9V111_CORE_R06_VBLANK 0x06 +#define MT9V111_CORE_R06_MIN_VBLANK 0x03 +#define MT9V111_CORE_R06_MAX_VBLANK GENMASK(11, 0) +#define MT9V111_CORE_R06_DEF_VBLANK 0x04 +#define MT9V111_CORE_R07_OUT_CTRL 0x07 +#define MT9V111_CORE_R07_OUT_CTRL_SAMPLE BIT(4) +#define MT9V111_CORE_R09_PIXEL_INT 0x09 +#define MT9V111_CORE_R09_PIXEL_INT_MASK GENMASK(11, 0) +#define MT9V111_CORE_R0D_CORE_RESET 0x0d +#define MT9V111_CORE_R0D_CORE_RESET_MASK BIT(0) +#define MT9V111_CORE_RFF_CHIP_VER 0xff + +#define MT9V111_PIXEL_ARRAY_WIDTH 640 +#define MT9V111_PIXEL_ARRAY_HEIGHT 480 + +#define MT9V111_MAX_CLKIN 27000000 + +/* The default sensor configuration at startup time. */ +static const struct v4l2_mbus_framefmt mt9v111_def_fmt = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, +}; + +struct mt9v111_dev { + struct device *dev; + struct i2c_client *client; + + u8 addr_space; + + struct v4l2_subdev sd; +#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER) + struct media_pad pad; +#endif + + struct v4l2_ctrl *auto_awb; + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl_handler ctrls; + + /* Output image format and sizes. */ + struct v4l2_mbus_framefmt fmt; + unsigned int fps; + + /* Protects power up/down sequences. */ + struct mutex pwr_mutex; + int pwr_count; + + /* Protects stream on/off sequences. */ + struct mutex stream_mutex; + bool streaming; + + /* Flags to mark HW settings as not yet applied. */ + bool pending; + + /* Clock provider and system clock frequency. */ + struct clk *clk; + u32 sysclk; + + struct gpio_desc *oe; + struct gpio_desc *standby; + struct gpio_desc *reset; +}; + +#define sd_to_mt9v111(__sd) container_of((__sd), struct mt9v111_dev, sd) + +/* + * mt9v111_mbus_fmt - List all media bus formats supported by the driver. + * + * Only list the media bus code here. The image sizes are freely configurable + * in the pixel array sizes range. + * + * The desired frame interval, in the supported frame interval range, is + * obtained by configuring blanking as the sensor does not have a PLL but + * only a fixed clock divider that generates the output pixel clock. + */ +static struct mt9v111_mbus_fmt { + u32 code; +} mt9v111_formats[] = { + { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + }, + { + .code = MEDIA_BUS_FMT_YUYV8_2X8, + }, + { + .code = MEDIA_BUS_FMT_VYUY8_2X8, + }, + { + .code = MEDIA_BUS_FMT_YVYU8_2X8, + }, +}; + +static u32 mt9v111_frame_intervals[] = {5, 10, 15, 20, 30}; + +/* + * mt9v111_frame_sizes - List sensor's supported resolutions. + * + * Resolution generated through decimation in the IFP block from the + * full VGA pixel array. + */ +static struct v4l2_rect mt9v111_frame_sizes[] = { + { + .width = 640, + .height = 480, + }, + { + .width = 352, + .height = 288 + }, + { + .width = 320, + .height = 240, + }, + { + .width = 176, + .height = 144, + }, + { + .width = 160, + .height = 120, + }, +}; + +/* --- Device I/O access --- */ + +static int __mt9v111_read(struct i2c_client *c, u8 reg, u16 *val) +{ + struct i2c_msg msg[2]; + __be16 buf; + int ret; + + msg[0].addr = c->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = ® + + msg[1].addr = c->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = (char *)&buf; + + ret = i2c_transfer(c->adapter, msg, 2); + if (ret < 0) { + dev_err(&c->dev, "i2c read transfer error: %d\n", ret); + return ret; + } + + *val = be16_to_cpu(buf); + + dev_dbg(&c->dev, "%s: %x=%x\n", __func__, reg, *val); + + return 0; +} + +static int __mt9v111_write(struct i2c_client *c, u8 reg, u16 val) +{ + struct i2c_msg msg; + u8 buf[3] = { 0 }; + int ret; + + buf[0] = reg; + buf[1] = val >> 8; + buf[2] = val & 0xff; + + msg.addr = c->addr; + msg.flags = 0; + msg.len = 3; + msg.buf = (char *)buf; + + dev_dbg(&c->dev, "%s: %x = %x%x\n", __func__, reg, buf[1], buf[2]); + + ret = i2c_transfer(c->adapter, &msg, 1); + if (ret < 0) { + dev_err(&c->dev, "i2c write transfer error: %d\n", ret); + return ret; + } + + return 0; +} + +static int __mt9v111_addr_space_select(struct i2c_client *c, u16 addr_space) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct mt9v111_dev *mt9v111 = sd_to_mt9v111(sd); + u16 val; + int ret; + + if (mt9v111->addr_space == addr_space) + return 0; + + ret = __mt9v111_write(c, MT9V111_R01_ADDR_SPACE, addr_space); + if (ret) + return ret; + + /* Verify address space has been updated */ + ret = __mt9v111_read(c, MT9V111_R01_ADDR_SPACE, &val); + if (ret) + return ret; + + if (val != addr_space) + return -EINVAL; + + mt9v111->addr_space = addr_space; + + return 0; +} + +static int mt9v111_read(struct i2c_client *c, u8 addr_space, u8 reg, u16 *val) +{ + int ret; + + /* Select register address space first. */ + ret = __mt9v111_addr_space_select(c, addr_space); + if (ret) + return ret; + + ret = __mt9v111_read(c, reg, val); + if (ret) + return ret; + + return 0; +} + +static int mt9v111_write(struct i2c_client *c, u8 addr_space, u8 reg, u16 val) +{ + int ret; + + /* Select register address space first. */ + ret = __mt9v111_addr_space_select(c, addr_space); + if (ret) + return ret; + + ret = __mt9v111_write(c, reg, val); + if (ret) + return ret; + + return 0; +} + +static int mt9v111_update(struct i2c_client *c, u8 addr_space, u8 reg, + u16 mask, u16 val) +{ + u16 current_val; + int ret; + + /* Select register address space first. */ + ret = __mt9v111_addr_space_select(c, addr_space); + if (ret) + return ret; + + /* Read the current register value, then update it. */ + ret = __mt9v111_read(c, reg, ¤t_val); + if (ret) + return ret; + + current_val &= ~mask; + current_val |= (val & mask); + ret = __mt9v111_write(c, reg, current_val); + if (ret) + return ret; + + return 0; +} + +/* --- Sensor HW operations --- */ + +static int __mt9v111_power_on(struct v4l2_subdev *sd) +{ + struct mt9v111_dev *mt9v111 = sd_to_mt9v111(sd); + int ret; + + ret = clk_prepare_enable(mt9v111->clk); + if (ret) + return ret; + + clk_set_rate(mt9v111->clk, mt9v111->sysclk); + + gpiod_set_value(mt9v111->standby, 0); + usleep_range(500, 1000); + + gpiod_set_value(mt9v111->oe, 1); + usleep_range(500, 1000); + + return 0; +} + +static int __mt9v111_power_off(struct v4l2_subdev *sd) +{ + struct mt9v111_dev *mt9v111 = sd_to_mt9v111(sd); + + gpiod_set_value(mt9v111->oe, 0); + usleep_range(500, 1000); + + gpiod_set_value(mt9v111->standby, 1); + usleep_range(500, 1000); + + clk_disable_unprepare(mt9v111->clk); + + return 0; +} + +static int __mt9v111_hw_reset(struct mt9v111_dev *mt9v111) +{ + if (!mt9v111->reset) + return -EINVAL; + + gpiod_set_value(mt9v111->reset, 1); + usleep_range(500, 1000); + + gpiod_set_value(mt9v111->reset, 0); + usleep_range(500, 1000); + + return 0; +} + +static int __mt9v111_sw_reset(struct mt9v111_dev *mt9v111) +{ + struct i2c_client *c = mt9v111->client; + int ret; + + /* Software reset core and IFP blocks. */ + + ret = mt9v111_update(c, MT9V111_R01_CORE, + MT9V111_CORE_R0D_CORE_RESET, + MT9V111_CORE_R0D_CORE_RESET_MASK, 1); + if (ret) + return ret; + usleep_range(500, 1000); + + ret = mt9v111_update(c, MT9V111_R01_CORE, + MT9V111_CORE_R0D_CORE_RESET, + MT9V111_CORE_R0D_CORE_RESET_MASK, 0); + if (ret) + return ret; + usleep_range(500, 1000); + + ret = mt9v111_update(c, MT9V111_R01_IFP, + MT9V111_IFP_R07_IFP_RESET, + MT9V111_IFP_R07_IFP_RESET_MASK, 1); + if (ret) + return ret; + usleep_range(500, 1000); + + ret = mt9v111_update(c, MT9V111_R01_IFP, + MT9V111_IFP_R07_IFP_RESET, + MT9V111_IFP_R07_IFP_RESET_MASK, 0); + if (ret) + return ret; + usleep_range(500, 1000); + + return 0; +} + +static int mt9v111_calc_frame_rate(struct mt9v111_dev *mt9v111, + struct v4l2_fract *tpf) +{ + unsigned int fps = tpf->numerator ? + tpf->denominator / tpf->numerator : + tpf->denominator; + unsigned int best_diff; + unsigned int frm_cols; + unsigned int row_pclk; + unsigned int best_fps; + unsigned int pclk; + unsigned int diff; + unsigned int idx; + unsigned int hb; + unsigned int vb; + unsigned int i; + int ret; + + /* Approximate to the closest supported frame interval. */ + best_diff = ~0L; + for (i = 0, idx = 0; i < ARRAY_SIZE(mt9v111_frame_intervals); i++) { + diff = abs(fps - mt9v111_frame_intervals[i]); + if (diff < best_diff) { + idx = i; + best_diff = diff; + } + } + fps = mt9v111_frame_intervals[idx]; + + /* + * The sensor does not provide a PLL circuitry and pixel clock is + * generated dividing the master clock source by two. + * + * Trow = (W + Hblank + 114) * 2 * (1 / SYSCLK) + * TFrame = Trow * (H + Vblank + 2) + * + * FPS = (SYSCLK / 2) / (Trow * (H + Vblank + 2)) + * + * This boils down to tune H and V blanks to best approximate the + * above equation. + * + * Test all available H/V blank values, until we reach the + * desired frame rate. + */ + best_fps = vb = hb = 0; + pclk = DIV_ROUND_CLOSEST(mt9v111->sysclk, 2); + row_pclk = MT9V111_PIXEL_ARRAY_WIDTH + 7 + MT9V111_CORE_R04_WIN_H_OFFS; + frm_cols = MT9V111_PIXEL_ARRAY_HEIGHT + 7 + MT9V111_CORE_R03_WIN_V_OFFS; + + best_diff = ~0L; + for (vb = MT9V111_CORE_R06_MIN_VBLANK; + vb < MT9V111_CORE_R06_MAX_VBLANK; vb++) { + for (hb = MT9V111_CORE_R05_MIN_HBLANK; + hb < MT9V111_CORE_R05_MAX_HBLANK; hb += 10) { + unsigned int t_frame = (row_pclk + hb) * + (frm_cols + vb); + unsigned int t_fps = DIV_ROUND_CLOSEST(pclk, t_frame); + + diff = abs(fps - t_fps); + if (diff < best_diff) { + best_diff = diff; + best_fps = t_fps; + + if (diff == 0) + break; + } + } + + if (diff == 0) + break; + } + + ret = v4l2_ctrl_s_ctrl_int64(mt9v111->hblank, hb); + if (ret) + return ret; + + ret = v4l2_ctrl_s_ctrl_int64(mt9v111->vblank, vb); + if (ret) + return ret; + + tpf->numerator = 1; + tpf->denominator = best_fps; + + return 0; +} + +static int mt9v111_hw_config(struct mt9v111_dev *mt9v111) +{ + struct i2c_client *c = mt9v111->client; + unsigned int ret; + u16 outfmtctrl2; + + /* Force device reset. */ + ret = __mt9v111_hw_reset(mt9v111); + if (ret == -EINVAL) + ret = __mt9v111_sw_reset(mt9v111); + if (ret) + return ret; + + /* Configure internal clock sample rate. */ + ret = mt9v111->sysclk < DIV_ROUND_CLOSEST(MT9V111_MAX_CLKIN, 2) ? + mt9v111_update(c, MT9V111_R01_CORE, + MT9V111_CORE_R07_OUT_CTRL, + MT9V111_CORE_R07_OUT_CTRL_SAMPLE, 1) : + mt9v111_update(c, MT9V111_R01_CORE, + MT9V111_CORE_R07_OUT_CTRL, + MT9V111_CORE_R07_OUT_CTRL_SAMPLE, 0); + if (ret) + return ret; + + /* + * Configure output image format components ordering. + * + * TODO: IFP block can also output several RGB permutations, we only + * support YUYV permutations at the moment. + */ + switch (mt9v111->fmt.code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + outfmtctrl2 = MT9V111_IFP_R3A_OUTFMT_CTRL2_SWAP_YC; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + outfmtctrl2 = MT9V111_IFP_R3A_OUTFMT_CTRL2_SWAP_CBCR; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + outfmtctrl2 = MT9V111_IFP_R3A_OUTFMT_CTRL2_SWAP_YC | + MT9V111_IFP_R3A_OUTFMT_CTRL2_SWAP_CBCR; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + outfmtctrl2 = 0; + break; + } + + ret = mt9v111_update(c, MT9V111_R01_IFP, MT9V111_IFP_R3A_OUTFMT_CTRL2, + MT9V111_IFP_R3A_OUTFMT_CTRL2_SWAP_MASK, + outfmtctrl2); + if (ret) + return ret; + + /* + * Do not change default sensor's core configuration: + * output the whole 640x480 pixel array, skip 18 columns and 6 rows. + * + * Instead, control the output image size through IFP block. + * + * TODO: No zoom&pan support. Currently we control the output image + * size only through decimation, with no zoom support. + */ + ret = mt9v111_write(c, MT9V111_R01_IFP, MT9V111_IFP_RA5_HPAN, + MT9V111_IFP_DECIMATION_FREEZE); + if (ret) + return ret; + + ret = mt9v111_write(c, MT9V111_R01_IFP, MT9V111_IFP_RA8_VPAN, + MT9V111_IFP_DECIMATION_FREEZE); + if (ret) + return ret; + + ret = mt9v111_write(c, MT9V111_R01_IFP, MT9V111_IFP_RA6_HZOOM, + MT9V111_IFP_DECIMATION_FREEZE | + MT9V111_PIXEL_ARRAY_WIDTH); + if (ret) + return ret; + + ret = mt9v111_write(c, MT9V111_R01_IFP, MT9V111_IFP_RA9_VZOOM, + MT9V111_IFP_DECIMATION_FREEZE | + MT9V111_PIXEL_ARRAY_HEIGHT); + if (ret) + return ret; + + ret = mt9v111_write(c, MT9V111_R01_IFP, MT9V111_IFP_RA7_HOUT, + MT9V111_IFP_DECIMATION_FREEZE | + mt9v111->fmt.width); + if (ret) + return ret; + + ret = mt9v111_write(c, MT9V111_R01_IFP, MT9V111_IFP_RAA_VOUT, + mt9v111->fmt.height); + if (ret) + return ret; + + /* Apply controls to set auto exp, auto awb and timings */ + ret = v4l2_ctrl_handler_setup(&mt9v111->ctrls); + if (ret) + return ret; + + /* + * Set pixel integration time to the whole frame time. + * This value controls the the shutter delay when running with AE + * disabled. If longer than frame time, it affects the output + * frame rate. + */ + return mt9v111_write(c, MT9V111_R01_CORE, MT9V111_CORE_R09_PIXEL_INT, + MT9V111_PIXEL_ARRAY_HEIGHT); +} + +/* --- V4L2 subdev operations --- */ + +static int mt9v111_s_power(struct v4l2_subdev *sd, int on) +{ + struct mt9v111_dev *mt9v111 = sd_to_mt9v111(sd); + int pwr_count; + int ret = 0; + + mutex_lock(&mt9v111->pwr_mutex); + + /* + * Make sure we're transitioning from 0 to 1, or viceversa, + * before actually changing the power state. + */ + pwr_count = mt9v111->pwr_count; + pwr_count += on ? 1 : -1; + if (pwr_count == !!on) { + ret = on ? __mt9v111_power_on(sd) : + __mt9v111_power_off(sd); + if (!ret) + /* All went well, updated power counter. */ + mt9v111->pwr_count = pwr_count; + + mutex_unlock(&mt9v111->pwr_mutex); + + return ret; + } + + /* + * Update power counter to keep track of how many nested calls we + * received. + */ + WARN_ON(pwr_count < 0 || pwr_count > 1); + mt9v111->pwr_count = pwr_count; + + mutex_unlock(&mt9v111->pwr_mutex); + + return ret; +} + +static int mt9v111_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct mt9v111_dev *mt9v111 = sd_to_mt9v111(subdev); + int ret; + + mutex_lock(&mt9v111->stream_mutex); + + if (mt9v111->streaming == enable) { + mutex_unlock(&mt9v111->stream_mutex); + return 0; + } + + ret = mt9v111_s_power(subdev, enable); + if (ret) + goto error_unlock; + + if (enable && mt9v111->pending) { + ret = mt9v111_hw_config(mt9v111); + if (ret) + goto error_unlock; + + /* + * No need to update control here as far as only H/VBLANK are + * supported and immediately programmed to registers in .s_ctrl + */ + + mt9v111->pending = false; + } + + mt9v111->streaming = enable ? true : false; + mutex_unlock(&mt9v111->stream_mutex); + + return 0; + +error_unlock: + mutex_unlock(&mt9v111->stream_mutex); + + return ret; +} + +static int mt9v111_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct mt9v111_dev *mt9v111 = sd_to_mt9v111(sd); + struct v4l2_fract *tpf = &ival->interval; + unsigned int fps = tpf->numerator ? + tpf->denominator / tpf->numerator : + tpf->denominator; + unsigned int max_fps; + + if (!tpf->numerator) + tpf->numerator = 1; + + mutex_lock(&mt9v111->stream_mutex); + + if (mt9v111->streaming) { + mutex_unlock(&mt9v111->stream_mutex); + return -EBUSY; + } + + if (mt9v111->fps == fps) { + mutex_unlock(&mt9v111->stream_mutex); + return 0; + } + + /* Make sure frame rate/image sizes constraints are respected. */ + if (mt9v111->fmt.width < QVGA_WIDTH && + mt9v111->fmt.height < QVGA_HEIGHT) + max_fps = 90; + else if (mt9v111->fmt.width < CIF_WIDTH && + mt9v111->fmt.height < CIF_HEIGHT) + max_fps = 60; + else + max_fps = mt9v111->sysclk < + DIV_ROUND_CLOSEST(MT9V111_MAX_CLKIN, 2) ? 15 : + 30; + + if (fps > max_fps) { + mutex_unlock(&mt9v111->stream_mutex); + return -EINVAL; + } + + mt9v111_calc_frame_rate(mt9v111, tpf); + + mt9v111->fps = fps; + mt9v111->pending = true; + + mutex_unlock(&mt9v111->stream_mutex); + + return 0; +} + +static int mt9v111_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct mt9v111_dev *mt9v111 = sd_to_mt9v111(sd); + struct v4l2_fract *tpf = &ival->interval; + + mutex_lock(&mt9v111->stream_mutex); + + tpf->numerator = 1; + tpf->denominator = mt9v111->fps; + + mutex_unlock(&mt9v111->stream_mutex); + + return 0; +} + +static struct v4l2_mbus_framefmt *__mt9v111_get_pad_format( + struct mt9v111_dev *mt9v111, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: +#if IS_ENABLED(CONFIG_VIDEO_V4L2_SUBDEV_API) + return v4l2_subdev_get_try_format(&mt9v111->sd, cfg, pad); +#else + return &cfg->try_fmt; +#endif + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9v111->fmt; + default: + return NULL; + } +} + +static int mt9v111_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > ARRAY_SIZE(mt9v111_formats) - 1) + return -EINVAL; + + code->code = mt9v111_formats[code->index].code; + + return 0; +} + +static int mt9v111_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + unsigned int i; + + if (fie->pad || fie->index >= ARRAY_SIZE(mt9v111_frame_intervals)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mt9v111_frame_sizes); i++) + if (fie->width == mt9v111_frame_sizes[i].width && + fie->height == mt9v111_frame_sizes[i].height) + break; + + if (i == ARRAY_SIZE(mt9v111_frame_sizes)) + return -EINVAL; + + fie->interval.numerator = 1; + fie->interval.denominator = mt9v111_frame_intervals[fie->index]; + + return 0; +} + +static int mt9v111_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->pad || fse->index >= ARRAY_SIZE(mt9v111_frame_sizes)) + return -EINVAL; + + fse->min_width = mt9v111_frame_sizes[fse->index].width; + fse->max_width = mt9v111_frame_sizes[fse->index].width; + fse->min_height = mt9v111_frame_sizes[fse->index].height; + fse->max_height = mt9v111_frame_sizes[fse->index].height; + + return 0; +} + +static int mt9v111_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct mt9v111_dev *mt9v111 = sd_to_mt9v111(subdev); + + if (format->pad) + return -EINVAL; + + mutex_lock(&mt9v111->stream_mutex); + format->format = *__mt9v111_get_pad_format(mt9v111, cfg, format->pad, + format->which); + mutex_unlock(&mt9v111->stream_mutex); + + return 0; +} + +static int mt9v111_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct mt9v111_dev *mt9v111 = sd_to_mt9v111(subdev); + struct v4l2_mbus_framefmt new_fmt; + struct v4l2_mbus_framefmt *__fmt; + unsigned int best_fit = ~0L; + unsigned int idx = 0; + unsigned int i; + + mutex_lock(&mt9v111->stream_mutex); + if (mt9v111->streaming) { + mutex_unlock(&mt9v111->stream_mutex); + return -EBUSY; + } + + if (format->pad) { + mutex_unlock(&mt9v111->stream_mutex); + return -EINVAL; + } + + /* Update mbus format code and sizes. */ + for (i = 0; i < ARRAY_SIZE(mt9v111_formats); i++) { + if (format->format.code == mt9v111_formats[i].code) { + new_fmt.code = mt9v111_formats[i].code; + break; + } + } + if (i == ARRAY_SIZE(mt9v111_formats)) + new_fmt.code = mt9v111_formats[0].code; + + for (i = 0; i < ARRAY_SIZE(mt9v111_frame_sizes); i++) { + unsigned int fit = abs(mt9v111_frame_sizes[i].width - + format->format.width) + + abs(mt9v111_frame_sizes[i].height - + format->format.height); + if (fit < best_fit) { + best_fit = fit; + idx = i; + + if (fit == 0) + break; + } + } + new_fmt.width = mt9v111_frame_sizes[idx].width; + new_fmt.height = mt9v111_frame_sizes[idx].height; + + /* Update the device (or pad) format if it has changed. */ + __fmt = __mt9v111_get_pad_format(mt9v111, cfg, format->pad, + format->which); + + /* Format hasn't changed, stop here. */ + if (__fmt->code == new_fmt.code && + __fmt->width == new_fmt.width && + __fmt->height == new_fmt.height) + goto done; + + /* Update the format and sizes, then mark changes as pending. */ + __fmt->code = new_fmt.code; + __fmt->width = new_fmt.width; + __fmt->height = new_fmt.height; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + mt9v111->pending = true; + + dev_dbg(mt9v111->dev, "%s: mbus_code: %x - (%ux%u)\n", + __func__, __fmt->code, __fmt->width, __fmt->height); + +done: + format->format = *__fmt; + + mutex_unlock(&mt9v111->stream_mutex); + + return 0; +} + +static int mt9v111_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg) +{ + cfg->try_fmt = mt9v111_def_fmt; + + return 0; +} + +static const struct v4l2_subdev_core_ops mt9v111_core_ops = { + .s_power = mt9v111_s_power, +}; + +static const struct v4l2_subdev_video_ops mt9v111_video_ops = { + .s_stream = mt9v111_s_stream, + .s_frame_interval = mt9v111_s_frame_interval, + .g_frame_interval = mt9v111_g_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops mt9v111_pad_ops = { + .init_cfg = mt9v111_init_cfg, + .enum_mbus_code = mt9v111_enum_mbus_code, + .enum_frame_size = mt9v111_enum_frame_size, + .enum_frame_interval = mt9v111_enum_frame_interval, + .get_fmt = mt9v111_get_format, + .set_fmt = mt9v111_set_format, +}; + +static const struct v4l2_subdev_ops mt9v111_ops = { + .core = &mt9v111_core_ops, + .video = &mt9v111_video_ops, + .pad = &mt9v111_pad_ops, +}; + +#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER) +static const struct media_entity_operations mt9v111_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; +#endif + +/* --- V4L2 ctrl --- */ +static int mt9v111_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9v111_dev *mt9v111 = container_of(ctrl->handler, + struct mt9v111_dev, + ctrls); + int ret; + + mutex_lock(&mt9v111->pwr_mutex); + /* + * If sensor is powered down, just cache new control values, + * no actual register access. + */ + if (!mt9v111->pwr_count) { + mt9v111->pending = true; + mutex_unlock(&mt9v111->pwr_mutex); + return 0; + } + mutex_unlock(&mt9v111->pwr_mutex); + + /* + * Flickering control gets disabled if both auto exp and auto awb + * are disabled too. If any of the two is enabled, enable it. + * + * Disabling flickering when ae and awb are off allows a more precise + * control of the programmed frame rate. + */ + if (mt9v111->auto_exp->is_new || mt9v111->auto_awb->is_new) { + if (mt9v111->auto_exp->val == V4L2_EXPOSURE_MANUAL && + mt9v111->auto_awb->val == V4L2_WHITE_BALANCE_MANUAL) + ret = mt9v111_update(mt9v111->client, MT9V111_R01_IFP, + MT9V111_IFP_R08_OUTFMT_CTRL, + MT9V111_IFP_R08_OUTFMT_CTRL_FLICKER, + 0); + else + ret = mt9v111_update(mt9v111->client, MT9V111_R01_IFP, + MT9V111_IFP_R08_OUTFMT_CTRL, + MT9V111_IFP_R08_OUTFMT_CTRL_FLICKER, + 1); + if (ret) + return ret; + } + + ret = -EINVAL; + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = mt9v111_update(mt9v111->client, MT9V111_R01_IFP, + MT9V111_IFP_R06_OPMODE_CTRL, + MT9V111_IFP_R06_OPMODE_CTRL_AWB_EN, + ctrl->val == V4L2_WHITE_BALANCE_AUTO ? + MT9V111_IFP_R06_OPMODE_CTRL_AWB_EN : 0); + break; + case V4L2_CID_EXPOSURE_AUTO: + ret = mt9v111_update(mt9v111->client, MT9V111_R01_IFP, + MT9V111_IFP_R06_OPMODE_CTRL, + MT9V111_IFP_R06_OPMODE_CTRL_AE_EN, + ctrl->val == V4L2_EXPOSURE_AUTO ? + MT9V111_IFP_R06_OPMODE_CTRL_AE_EN : 0); + break; + case V4L2_CID_HBLANK: + ret = mt9v111_update(mt9v111->client, MT9V111_R01_CORE, + MT9V111_CORE_R05_HBLANK, + MT9V111_CORE_R05_MAX_HBLANK, + mt9v111->hblank->val); + break; + case V4L2_CID_VBLANK: + ret = mt9v111_update(mt9v111->client, MT9V111_R01_CORE, + MT9V111_CORE_R06_VBLANK, + MT9V111_CORE_R06_MAX_VBLANK, + mt9v111->vblank->val); + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops mt9v111_ctrl_ops = { + .s_ctrl = mt9v111_s_ctrl, +}; + +static int mt9v111_chip_probe(struct mt9v111_dev *mt9v111) +{ + int ret; + u16 val; + + ret = __mt9v111_power_on(&mt9v111->sd); + if (ret) + return ret; + + ret = mt9v111_read(mt9v111->client, MT9V111_R01_CORE, + MT9V111_CORE_RFF_CHIP_VER, &val); + if (ret) + goto power_off; + + if ((val >> 8) != MT9V111_CHIP_ID_HIGH && + (val & 0xff) != MT9V111_CHIP_ID_LOW) { + dev_err(mt9v111->dev, + "Unable to identify MT9V111 chip: 0x%2x%2x\n", + val >> 8, val & 0xff); + ret = -EIO; + goto power_off; + } + + dev_dbg(mt9v111->dev, "Chip identified: 0x%2x%2x\n", + val >> 8, val & 0xff); + +power_off: + __mt9v111_power_off(&mt9v111->sd); + + return ret; +} + +static int mt9v111_probe(struct i2c_client *client) +{ + struct mt9v111_dev *mt9v111; + struct v4l2_fract tpf; + int ret; + + mt9v111 = devm_kzalloc(&client->dev, sizeof(*mt9v111), GFP_KERNEL); + if (!mt9v111) + return -ENOMEM; + + mt9v111->dev = &client->dev; + mt9v111->client = client; + + mt9v111->clk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(mt9v111->clk)) + return PTR_ERR(mt9v111->clk); + + mt9v111->sysclk = clk_get_rate(mt9v111->clk); + if (mt9v111->sysclk > MT9V111_MAX_CLKIN) + return -EINVAL; + + mt9v111->oe = devm_gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(mt9v111->oe)) { + dev_err(&client->dev, "Unable to get GPIO \"enable\": %ld\n", + PTR_ERR(mt9v111->oe)); + return PTR_ERR(mt9v111->oe); + } + + mt9v111->standby = devm_gpiod_get_optional(&client->dev, "standby", + GPIOD_OUT_HIGH); + if (IS_ERR(mt9v111->standby)) { + dev_err(&client->dev, "Unable to get GPIO \"standby\": %ld\n", + PTR_ERR(mt9v111->standby)); + return PTR_ERR(mt9v111->standby); + } + + mt9v111->reset = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(mt9v111->reset)) { + dev_err(&client->dev, "Unable to get GPIO \"reset\": %ld\n", + PTR_ERR(mt9v111->reset)); + return PTR_ERR(mt9v111->reset); + } + + mutex_init(&mt9v111->pwr_mutex); + mutex_init(&mt9v111->stream_mutex); + + v4l2_ctrl_handler_init(&mt9v111->ctrls, 5); + + mt9v111->auto_awb = v4l2_ctrl_new_std(&mt9v111->ctrls, + &mt9v111_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, + V4L2_WHITE_BALANCE_AUTO); + mt9v111->auto_exp = v4l2_ctrl_new_std_menu(&mt9v111->ctrls, + &mt9v111_ctrl_ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, + 0, V4L2_EXPOSURE_AUTO); + mt9v111->hblank = v4l2_ctrl_new_std(&mt9v111->ctrls, &mt9v111_ctrl_ops, + V4L2_CID_HBLANK, + MT9V111_CORE_R05_MIN_HBLANK, + MT9V111_CORE_R05_MAX_HBLANK, 1, + MT9V111_CORE_R05_DEF_HBLANK); + mt9v111->vblank = v4l2_ctrl_new_std(&mt9v111->ctrls, &mt9v111_ctrl_ops, + V4L2_CID_VBLANK, + MT9V111_CORE_R06_MIN_VBLANK, + MT9V111_CORE_R06_MAX_VBLANK, 1, + MT9V111_CORE_R06_DEF_VBLANK); + + /* PIXEL_RATE is fixed: just expose it to user space. */ + v4l2_ctrl_new_std(&mt9v111->ctrls, &mt9v111_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + DIV_ROUND_CLOSEST(mt9v111->sysclk, 2), 1, + DIV_ROUND_CLOSEST(mt9v111->sysclk, 2)); + + if (mt9v111->ctrls.error) { + ret = mt9v111->ctrls.error; + goto error_free_ctrls; + } + mt9v111->sd.ctrl_handler = &mt9v111->ctrls; + + /* Start with default configuration: 640x480 UYVY. */ + mt9v111->fmt = mt9v111_def_fmt; + + /* Re-calculate blankings for 640x480@15fps. */ + mt9v111->fps = 15; + tpf.numerator = 1; + tpf.denominator = mt9v111->fps; + mt9v111_calc_frame_rate(mt9v111, &tpf); + + mt9v111->pwr_count = 0; + mt9v111->addr_space = MT9V111_R01_IFP; + mt9v111->pending = true; + + v4l2_i2c_subdev_init(&mt9v111->sd, client, &mt9v111_ops); + +#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER) + mt9v111->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + mt9v111->sd.entity.ops = &mt9v111_subdev_entity_ops; + mt9v111->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + mt9v111->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&mt9v111->sd.entity, 1, &mt9v111->pad); + if (ret) + goto error_free_entity; +#endif + + ret = mt9v111_chip_probe(mt9v111); + if (ret) + goto error_free_entity; + + ret = v4l2_async_register_subdev(&mt9v111->sd); + if (ret) + goto error_free_entity; + + return 0; + +error_free_entity: +#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&mt9v111->sd.entity); +#endif + +error_free_ctrls: + v4l2_ctrl_handler_free(&mt9v111->ctrls); + + mutex_destroy(&mt9v111->pwr_mutex); + mutex_destroy(&mt9v111->stream_mutex); + + return ret; +} + +static int mt9v111_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct mt9v111_dev *mt9v111 = sd_to_mt9v111(sd); + + v4l2_async_unregister_subdev(sd); + +#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif + + v4l2_ctrl_handler_free(&mt9v111->ctrls); + + mutex_destroy(&mt9v111->pwr_mutex); + mutex_destroy(&mt9v111->stream_mutex); + + devm_gpiod_put(mt9v111->dev, mt9v111->oe); + devm_gpiod_put(mt9v111->dev, mt9v111->standby); + devm_gpiod_put(mt9v111->dev, mt9v111->reset); + + devm_clk_put(mt9v111->dev, mt9v111->clk); + + return 0; +} + +static const struct of_device_id mt9v111_of_match[] = { + { .compatible = "aptina,mt9v111", }, + { /* sentinel */ }, +}; + +static struct i2c_driver mt9v111_driver = { + .driver = { + .name = "mt9v111", + .of_match_table = mt9v111_of_match, + }, + .probe_new = mt9v111_probe, + .remove = mt9v111_remove, +}; + +module_i2c_driver(mt9v111_driver); + +MODULE_DESCRIPTION("V4L2 sensor driver for Aptina MT9V111"); +MODULE_AUTHOR("Jacopo Mondi <jacopo@jmondi.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/noon010pc30.c b/drivers/media/i2c/noon010pc30.c new file mode 100644 index 000000000..87d76a7f6 --- /dev/null +++ b/drivers/media/i2c/noon010pc30.c @@ -0,0 +1,826 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for SiliconFile NOON010PC30 CIF (1/11") Image Sensor with ISP + * + * Copyright (C) 2010 - 2011 Samsung Electronics Co., Ltd. + * Contact: Sylwester Nawrocki, <s.nawrocki@samsung.com> + * + * Initial register configuration based on a driver authored by + * HeungJun Kim <riverful.kim@samsung.com>. + */ + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <media/i2c/noon010pc30.h> +#include <linux/videodev2.h> +#include <linux/module.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-mediabus.h> +#include <media/v4l2-subdev.h> + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Enable module debug trace. Set to 1 to enable."); + +#define MODULE_NAME "NOON010PC30" + +/* + * Register offsets within a page + * b15..b8 - page id, b7..b0 - register address + */ +#define POWER_CTRL_REG 0x0001 +#define PAGEMODE_REG 0x03 +#define DEVICE_ID_REG 0x0004 +#define NOON010PC30_ID 0x86 +#define VDO_CTL_REG(n) (0x0010 + (n)) +#define SYNC_CTL_REG 0x0012 +/* Window size and position */ +#define WIN_ROWH_REG 0x0013 +#define WIN_ROWL_REG 0x0014 +#define WIN_COLH_REG 0x0015 +#define WIN_COLL_REG 0x0016 +#define WIN_HEIGHTH_REG 0x0017 +#define WIN_HEIGHTL_REG 0x0018 +#define WIN_WIDTHH_REG 0x0019 +#define WIN_WIDTHL_REG 0x001A +#define HBLANKH_REG 0x001B +#define HBLANKL_REG 0x001C +#define VSYNCH_REG 0x001D +#define VSYNCL_REG 0x001E +/* VSYNC control */ +#define VS_CTL_REG(n) (0x00A1 + (n)) +/* page 1 */ +#define ISP_CTL_REG(n) (0x0110 + (n)) +#define YOFS_REG 0x0119 +#define DARK_YOFS_REG 0x011A +#define SAT_CTL_REG 0x0120 +#define BSAT_REG 0x0121 +#define RSAT_REG 0x0122 +/* Color correction */ +#define CMC_CTL_REG 0x0130 +#define CMC_OFSGH_REG 0x0133 +#define CMC_OFSGL_REG 0x0135 +#define CMC_SIGN_REG 0x0136 +#define CMC_GOFS_REG 0x0137 +#define CMC_COEF_REG(n) (0x0138 + (n)) +#define CMC_OFS_REG(n) (0x0141 + (n)) +/* Gamma correction */ +#define GMA_CTL_REG 0x0160 +#define GMA_COEF_REG(n) (0x0161 + (n)) +/* Lens Shading */ +#define LENS_CTRL_REG 0x01D0 +#define LENS_XCEN_REG 0x01D1 +#define LENS_YCEN_REG 0x01D2 +#define LENS_RC_REG 0x01D3 +#define LENS_GC_REG 0x01D4 +#define LENS_BC_REG 0x01D5 +#define L_AGON_REG 0x01D6 +#define L_AGOFF_REG 0x01D7 +/* Page 3 - Auto Exposure */ +#define AE_CTL_REG(n) (0x0310 + (n)) +#define AE_CTL9_REG 0x032C +#define AE_CTL10_REG 0x032D +#define AE_YLVL_REG 0x031C +#define AE_YTH_REG(n) (0x031D + (n)) +#define AE_WGT_REG 0x0326 +#define EXP_TIMEH_REG 0x0333 +#define EXP_TIMEM_REG 0x0334 +#define EXP_TIMEL_REG 0x0335 +#define EXP_MMINH_REG 0x0336 +#define EXP_MMINL_REG 0x0337 +#define EXP_MMAXH_REG 0x0338 +#define EXP_MMAXM_REG 0x0339 +#define EXP_MMAXL_REG 0x033A +/* Page 4 - Auto White Balance */ +#define AWB_CTL_REG(n) (0x0410 + (n)) +#define AWB_ENABE 0x80 +#define AWB_WGHT_REG 0x0419 +#define BGAIN_PAR_REG(n) (0x044F + (n)) +/* Manual white balance, when AWB_CTL2[0]=1 */ +#define MWB_RGAIN_REG 0x0466 +#define MWB_BGAIN_REG 0x0467 + +/* The token to mark an array end */ +#define REG_TERM 0xFFFF + +struct noon010_format { + u32 code; + enum v4l2_colorspace colorspace; + u16 ispctl1_reg; +}; + +struct noon010_frmsize { + u16 width; + u16 height; + int vid_ctl1; +}; + +static const char * const noon010_supply_name[] = { + "vdd_core", "vddio", "vdda" +}; + +#define NOON010_NUM_SUPPLIES ARRAY_SIZE(noon010_supply_name) + +struct noon010_info { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + struct regulator_bulk_data supply[NOON010_NUM_SUPPLIES]; + u32 gpio_nreset; + u32 gpio_nstby; + + /* Protects the struct members below */ + struct mutex lock; + + const struct noon010_format *curr_fmt; + const struct noon010_frmsize *curr_win; + unsigned int apply_new_cfg:1; + unsigned int streaming:1; + unsigned int hflip:1; + unsigned int vflip:1; + unsigned int power:1; + u8 i2c_reg_page; +}; + +struct i2c_regval { + u16 addr; + u16 val; +}; + +/* Supported resolutions. */ +static const struct noon010_frmsize noon010_sizes[] = { + { + .width = 352, + .height = 288, + .vid_ctl1 = 0, + }, { + .width = 176, + .height = 144, + .vid_ctl1 = 0x10, + }, { + .width = 88, + .height = 72, + .vid_ctl1 = 0x20, + }, +}; + +/* Supported pixel formats. */ +static const struct noon010_format noon010_formats[] = { + { + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x03, + }, { + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x02, + }, { + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0, + }, { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x01, + }, { + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x40, + }, +}; + +static const struct i2c_regval noon010_base_regs[] = { + { WIN_COLL_REG, 0x06 }, { HBLANKL_REG, 0x7C }, + /* Color corection and saturation */ + { ISP_CTL_REG(0), 0x30 }, { ISP_CTL_REG(2), 0x30 }, + { YOFS_REG, 0x80 }, { DARK_YOFS_REG, 0x04 }, + { SAT_CTL_REG, 0x1F }, { BSAT_REG, 0x90 }, + { CMC_CTL_REG, 0x0F }, { CMC_OFSGH_REG, 0x3C }, + { CMC_OFSGL_REG, 0x2C }, { CMC_SIGN_REG, 0x3F }, + { CMC_COEF_REG(0), 0x79 }, { CMC_OFS_REG(0), 0x00 }, + { CMC_COEF_REG(1), 0x39 }, { CMC_OFS_REG(1), 0x00 }, + { CMC_COEF_REG(2), 0x00 }, { CMC_OFS_REG(2), 0x00 }, + { CMC_COEF_REG(3), 0x11 }, { CMC_OFS_REG(3), 0x8B }, + { CMC_COEF_REG(4), 0x65 }, { CMC_OFS_REG(4), 0x07 }, + { CMC_COEF_REG(5), 0x14 }, { CMC_OFS_REG(5), 0x04 }, + { CMC_COEF_REG(6), 0x01 }, { CMC_OFS_REG(6), 0x9C }, + { CMC_COEF_REG(7), 0x33 }, { CMC_OFS_REG(7), 0x89 }, + { CMC_COEF_REG(8), 0x74 }, { CMC_OFS_REG(8), 0x25 }, + /* Automatic white balance */ + { AWB_CTL_REG(0), 0x78 }, { AWB_CTL_REG(1), 0x2E }, + { AWB_CTL_REG(2), 0x20 }, { AWB_CTL_REG(3), 0x85 }, + /* Auto exposure */ + { AE_CTL_REG(0), 0xDC }, { AE_CTL_REG(1), 0x81 }, + { AE_CTL_REG(2), 0x30 }, { AE_CTL_REG(3), 0xA5 }, + { AE_CTL_REG(4), 0x40 }, { AE_CTL_REG(5), 0x51 }, + { AE_CTL_REG(6), 0x33 }, { AE_CTL_REG(7), 0x7E }, + { AE_CTL9_REG, 0x00 }, { AE_CTL10_REG, 0x02 }, + { AE_YLVL_REG, 0x44 }, { AE_YTH_REG(0), 0x34 }, + { AE_YTH_REG(1), 0x30 }, { AE_WGT_REG, 0xD5 }, + /* Lens shading compensation */ + { LENS_CTRL_REG, 0x01 }, { LENS_XCEN_REG, 0x80 }, + { LENS_YCEN_REG, 0x70 }, { LENS_RC_REG, 0x53 }, + { LENS_GC_REG, 0x40 }, { LENS_BC_REG, 0x3E }, + { REG_TERM, 0 }, +}; + +static inline struct noon010_info *to_noon010(struct v4l2_subdev *sd) +{ + return container_of(sd, struct noon010_info, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct noon010_info, hdl)->sd; +} + +static inline int set_i2c_page(struct noon010_info *info, + struct i2c_client *client, unsigned int reg) +{ + u32 page = reg >> 8 & 0xFF; + int ret = 0; + + if (info->i2c_reg_page != page && (reg & 0xFF) != 0x03) { + ret = i2c_smbus_write_byte_data(client, PAGEMODE_REG, page); + if (!ret) + info->i2c_reg_page = page; + } + return ret; +} + +static int cam_i2c_read(struct v4l2_subdev *sd, u32 reg_addr) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct noon010_info *info = to_noon010(sd); + int ret = set_i2c_page(info, client, reg_addr); + + if (ret) + return ret; + return i2c_smbus_read_byte_data(client, reg_addr & 0xFF); +} + +static int cam_i2c_write(struct v4l2_subdev *sd, u32 reg_addr, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct noon010_info *info = to_noon010(sd); + int ret = set_i2c_page(info, client, reg_addr); + + if (ret) + return ret; + return i2c_smbus_write_byte_data(client, reg_addr & 0xFF, val); +} + +static inline int noon010_bulk_write_reg(struct v4l2_subdev *sd, + const struct i2c_regval *msg) +{ + while (msg->addr != REG_TERM) { + int ret = cam_i2c_write(sd, msg->addr, msg->val); + + if (ret) + return ret; + msg++; + } + return 0; +} + +/* Device reset and sleep mode control */ +static int noon010_power_ctrl(struct v4l2_subdev *sd, bool reset, bool sleep) +{ + struct noon010_info *info = to_noon010(sd); + u8 reg = sleep ? 0xF1 : 0xF0; + int ret = 0; + + if (reset) { + ret = cam_i2c_write(sd, POWER_CTRL_REG, reg | 0x02); + udelay(20); + } + if (!ret) { + ret = cam_i2c_write(sd, POWER_CTRL_REG, reg); + if (reset && !ret) + info->i2c_reg_page = -1; + } + return ret; +} + +/* Automatic white balance control */ +static int noon010_enable_autowhitebalance(struct v4l2_subdev *sd, int on) +{ + int ret; + + ret = cam_i2c_write(sd, AWB_CTL_REG(1), on ? 0x2E : 0x2F); + if (!ret) + ret = cam_i2c_write(sd, AWB_CTL_REG(0), on ? 0xFB : 0x7B); + return ret; +} + +/* Called with struct noon010_info.lock mutex held */ +static int noon010_set_flip(struct v4l2_subdev *sd, int hflip, int vflip) +{ + struct noon010_info *info = to_noon010(sd); + int reg, ret; + + reg = cam_i2c_read(sd, VDO_CTL_REG(1)); + if (reg < 0) + return reg; + + reg &= 0x7C; + if (hflip) + reg |= 0x01; + if (vflip) + reg |= 0x02; + + ret = cam_i2c_write(sd, VDO_CTL_REG(1), reg | 0x80); + if (!ret) { + info->hflip = hflip; + info->vflip = vflip; + } + return ret; +} + +/* Configure resolution and color format */ +static int noon010_set_params(struct v4l2_subdev *sd) +{ + struct noon010_info *info = to_noon010(sd); + + int ret = cam_i2c_write(sd, VDO_CTL_REG(0), + info->curr_win->vid_ctl1); + if (ret) + return ret; + return cam_i2c_write(sd, ISP_CTL_REG(0), + info->curr_fmt->ispctl1_reg); +} + +/* Find nearest matching image pixel size. */ +static int noon010_try_frame_size(struct v4l2_mbus_framefmt *mf, + const struct noon010_frmsize **size) +{ + unsigned int min_err = ~0; + int i = ARRAY_SIZE(noon010_sizes); + const struct noon010_frmsize *fsize = &noon010_sizes[0], + *match = NULL; + + while (i--) { + int err = abs(fsize->width - mf->width) + + abs(fsize->height - mf->height); + + if (err < min_err) { + min_err = err; + match = fsize; + } + fsize++; + } + if (match) { + mf->width = match->width; + mf->height = match->height; + if (size) + *size = match; + return 0; + } + return -EINVAL; +} + +/* Called with info.lock mutex held */ +static int power_enable(struct noon010_info *info) +{ + int ret; + + if (info->power) { + v4l2_info(&info->sd, "%s: sensor is already on\n", __func__); + return 0; + } + + if (gpio_is_valid(info->gpio_nstby)) + gpio_set_value(info->gpio_nstby, 0); + + if (gpio_is_valid(info->gpio_nreset)) + gpio_set_value(info->gpio_nreset, 0); + + ret = regulator_bulk_enable(NOON010_NUM_SUPPLIES, info->supply); + if (ret) + return ret; + + if (gpio_is_valid(info->gpio_nreset)) { + msleep(50); + gpio_set_value(info->gpio_nreset, 1); + } + if (gpio_is_valid(info->gpio_nstby)) { + udelay(1000); + gpio_set_value(info->gpio_nstby, 1); + } + if (gpio_is_valid(info->gpio_nreset)) { + udelay(1000); + gpio_set_value(info->gpio_nreset, 0); + msleep(100); + gpio_set_value(info->gpio_nreset, 1); + msleep(20); + } + info->power = 1; + + v4l2_dbg(1, debug, &info->sd, "%s: sensor is on\n", __func__); + return 0; +} + +/* Called with info.lock mutex held */ +static int power_disable(struct noon010_info *info) +{ + int ret; + + if (!info->power) { + v4l2_info(&info->sd, "%s: sensor is already off\n", __func__); + return 0; + } + + ret = regulator_bulk_disable(NOON010_NUM_SUPPLIES, info->supply); + if (ret) + return ret; + + if (gpio_is_valid(info->gpio_nstby)) + gpio_set_value(info->gpio_nstby, 0); + + if (gpio_is_valid(info->gpio_nreset)) + gpio_set_value(info->gpio_nreset, 0); + + info->power = 0; + + v4l2_dbg(1, debug, &info->sd, "%s: sensor is off\n", __func__); + + return 0; +} + +static int noon010_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct noon010_info *info = to_noon010(sd); + int ret = 0; + + v4l2_dbg(1, debug, sd, "%s: ctrl_id: %d, value: %d\n", + __func__, ctrl->id, ctrl->val); + + mutex_lock(&info->lock); + /* + * If the device is not powered up by the host driver do + * not apply any controls to H/W at this time. Instead + * the controls will be restored right after power-up. + */ + if (!info->power) + goto unlock; + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = noon010_enable_autowhitebalance(sd, ctrl->val); + break; + case V4L2_CID_BLUE_BALANCE: + ret = cam_i2c_write(sd, MWB_BGAIN_REG, ctrl->val); + break; + case V4L2_CID_RED_BALANCE: + ret = cam_i2c_write(sd, MWB_RGAIN_REG, ctrl->val); + break; + default: + ret = -EINVAL; + } +unlock: + mutex_unlock(&info->lock); + return ret; +} + +static int noon010_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(noon010_formats)) + return -EINVAL; + + code->code = noon010_formats[code->index].code; + return 0; +} + +static int noon010_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct noon010_info *info = to_noon010(sd); + struct v4l2_mbus_framefmt *mf; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + if (cfg) { + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + fmt->format = *mf; + } + return 0; + } + mf = &fmt->format; + + mutex_lock(&info->lock); + mf->width = info->curr_win->width; + mf->height = info->curr_win->height; + mf->code = info->curr_fmt->code; + mf->colorspace = info->curr_fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + + mutex_unlock(&info->lock); + return 0; +} + +/* Return nearest media bus frame format. */ +static const struct noon010_format *noon010_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + int i = ARRAY_SIZE(noon010_formats); + + while (--i) + if (mf->code == noon010_formats[i].code) + break; + mf->code = noon010_formats[i].code; + + return &noon010_formats[i]; +} + +static int noon010_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct noon010_info *info = to_noon010(sd); + const struct noon010_frmsize *size = NULL; + const struct noon010_format *nf; + struct v4l2_mbus_framefmt *mf; + int ret = 0; + + nf = noon010_try_fmt(sd, &fmt->format); + noon010_try_frame_size(&fmt->format, &size); + fmt->format.colorspace = V4L2_COLORSPACE_JPEG; + fmt->format.field = V4L2_FIELD_NONE; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + if (cfg) { + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + *mf = fmt->format; + } + return 0; + } + mutex_lock(&info->lock); + if (!info->streaming) { + info->apply_new_cfg = 1; + info->curr_fmt = nf; + info->curr_win = size; + } else { + ret = -EBUSY; + } + mutex_unlock(&info->lock); + return ret; +} + +/* Called with struct noon010_info.lock mutex held */ +static int noon010_base_config(struct v4l2_subdev *sd) +{ + int ret = noon010_bulk_write_reg(sd, noon010_base_regs); + if (!ret) + ret = noon010_set_params(sd); + if (!ret) + ret = noon010_set_flip(sd, 1, 0); + + return ret; +} + +static int noon010_s_power(struct v4l2_subdev *sd, int on) +{ + struct noon010_info *info = to_noon010(sd); + int ret; + + mutex_lock(&info->lock); + if (on) { + ret = power_enable(info); + if (!ret) + ret = noon010_base_config(sd); + } else { + noon010_power_ctrl(sd, false, true); + ret = power_disable(info); + } + mutex_unlock(&info->lock); + + /* Restore the controls state */ + if (!ret && on) + ret = v4l2_ctrl_handler_setup(&info->hdl); + + return ret; +} + +static int noon010_s_stream(struct v4l2_subdev *sd, int on) +{ + struct noon010_info *info = to_noon010(sd); + int ret = 0; + + mutex_lock(&info->lock); + if (!info->streaming != !on) { + ret = noon010_power_ctrl(sd, false, !on); + if (!ret) + info->streaming = on; + } + if (!ret && on && info->apply_new_cfg) { + ret = noon010_set_params(sd); + if (!ret) + info->apply_new_cfg = 0; + } + mutex_unlock(&info->lock); + return ret; +} + +static int noon010_log_status(struct v4l2_subdev *sd) +{ + struct noon010_info *info = to_noon010(sd); + + v4l2_ctrl_handler_log_status(&info->hdl, sd->name); + return 0; +} + +static int noon010_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *mf = v4l2_subdev_get_try_format(sd, fh->pad, 0); + + mf->width = noon010_sizes[0].width; + mf->height = noon010_sizes[0].height; + mf->code = noon010_formats[0].code; + mf->colorspace = V4L2_COLORSPACE_JPEG; + mf->field = V4L2_FIELD_NONE; + return 0; +} + +static const struct v4l2_subdev_internal_ops noon010_subdev_internal_ops = { + .open = noon010_open, +}; + +static const struct v4l2_ctrl_ops noon010_ctrl_ops = { + .s_ctrl = noon010_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops noon010_core_ops = { + .s_power = noon010_s_power, + .log_status = noon010_log_status, +}; + +static const struct v4l2_subdev_pad_ops noon010_pad_ops = { + .enum_mbus_code = noon010_enum_mbus_code, + .get_fmt = noon010_get_fmt, + .set_fmt = noon010_set_fmt, +}; + +static const struct v4l2_subdev_video_ops noon010_video_ops = { + .s_stream = noon010_s_stream, +}; + +static const struct v4l2_subdev_ops noon010_ops = { + .core = &noon010_core_ops, + .pad = &noon010_pad_ops, + .video = &noon010_video_ops, +}; + +/* Return 0 if NOON010PC30L sensor type was detected or -ENODEV otherwise. */ +static int noon010_detect(struct i2c_client *client, struct noon010_info *info) +{ + int ret; + + ret = power_enable(info); + if (ret) + return ret; + + ret = i2c_smbus_read_byte_data(client, DEVICE_ID_REG); + if (ret < 0) + dev_err(&client->dev, "I2C read failed: 0x%X\n", ret); + + power_disable(info); + + return ret == NOON010PC30_ID ? 0 : -ENODEV; +} + +static int noon010_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct noon010_info *info; + struct v4l2_subdev *sd; + const struct noon010pc30_platform_data *pdata + = client->dev.platform_data; + int ret; + int i; + + if (!pdata) { + dev_err(&client->dev, "No platform data!\n"); + return -EIO; + } + + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + mutex_init(&info->lock); + sd = &info->sd; + v4l2_i2c_subdev_init(sd, client, &noon010_ops); + /* Static name; NEVER use in new drivers! */ + strscpy(sd->name, MODULE_NAME, sizeof(sd->name)); + + sd->internal_ops = &noon010_subdev_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + v4l2_ctrl_handler_init(&info->hdl, 3); + + v4l2_ctrl_new_std(&info->hdl, &noon010_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + v4l2_ctrl_new_std(&info->hdl, &noon010_ctrl_ops, + V4L2_CID_RED_BALANCE, 0, 127, 1, 64); + v4l2_ctrl_new_std(&info->hdl, &noon010_ctrl_ops, + V4L2_CID_BLUE_BALANCE, 0, 127, 1, 64); + + sd->ctrl_handler = &info->hdl; + + ret = info->hdl.error; + if (ret) + goto np_err; + + info->i2c_reg_page = -1; + info->gpio_nreset = -EINVAL; + info->gpio_nstby = -EINVAL; + info->curr_fmt = &noon010_formats[0]; + info->curr_win = &noon010_sizes[0]; + + if (gpio_is_valid(pdata->gpio_nreset)) { + ret = devm_gpio_request_one(&client->dev, pdata->gpio_nreset, + GPIOF_OUT_INIT_LOW, + "NOON010PC30 NRST"); + if (ret) { + dev_err(&client->dev, "GPIO request error: %d\n", ret); + goto np_err; + } + info->gpio_nreset = pdata->gpio_nreset; + gpio_export(info->gpio_nreset, 0); + } + + if (gpio_is_valid(pdata->gpio_nstby)) { + ret = devm_gpio_request_one(&client->dev, pdata->gpio_nstby, + GPIOF_OUT_INIT_LOW, + "NOON010PC30 NSTBY"); + if (ret) { + dev_err(&client->dev, "GPIO request error: %d\n", ret); + goto np_err; + } + info->gpio_nstby = pdata->gpio_nstby; + gpio_export(info->gpio_nstby, 0); + } + + for (i = 0; i < NOON010_NUM_SUPPLIES; i++) + info->supply[i].supply = noon010_supply_name[i]; + + ret = devm_regulator_bulk_get(&client->dev, NOON010_NUM_SUPPLIES, + info->supply); + if (ret) + goto np_err; + + info->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &info->pad); + if (ret < 0) + goto np_err; + + ret = noon010_detect(client, info); + if (!ret) + return 0; + +np_err: + v4l2_ctrl_handler_free(&info->hdl); + v4l2_device_unregister_subdev(sd); + return ret; +} + +static int noon010_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct noon010_info *info = to_noon010(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&info->hdl); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct i2c_device_id noon010_id[] = { + { MODULE_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, noon010_id); + + +static struct i2c_driver noon010_i2c_driver = { + .driver = { + .name = MODULE_NAME + }, + .probe = noon010_probe, + .remove = noon010_remove, + .id_table = noon010_id, +}; + +module_i2c_driver(noon010_i2c_driver); + +MODULE_DESCRIPTION("Siliconfile NOON010PC30 camera driver"); +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ov13858.c b/drivers/media/i2c/ov13858.c new file mode 100644 index 000000000..236ad2c81 --- /dev/null +++ b/drivers/media/i2c/ov13858.c @@ -0,0 +1,1818 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017 Intel Corporation. + +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define OV13858_REG_VALUE_08BIT 1 +#define OV13858_REG_VALUE_16BIT 2 +#define OV13858_REG_VALUE_24BIT 3 + +#define OV13858_REG_MODE_SELECT 0x0100 +#define OV13858_MODE_STANDBY 0x00 +#define OV13858_MODE_STREAMING 0x01 + +#define OV13858_REG_SOFTWARE_RST 0x0103 +#define OV13858_SOFTWARE_RST 0x01 + +/* PLL1 generates PCLK and MIPI_PHY_CLK */ +#define OV13858_REG_PLL1_CTRL_0 0x0300 +#define OV13858_REG_PLL1_CTRL_1 0x0301 +#define OV13858_REG_PLL1_CTRL_2 0x0302 +#define OV13858_REG_PLL1_CTRL_3 0x0303 +#define OV13858_REG_PLL1_CTRL_4 0x0304 +#define OV13858_REG_PLL1_CTRL_5 0x0305 + +/* PLL2 generates DAC_CLK, SCLK and SRAM_CLK */ +#define OV13858_REG_PLL2_CTRL_B 0x030b +#define OV13858_REG_PLL2_CTRL_C 0x030c +#define OV13858_REG_PLL2_CTRL_D 0x030d +#define OV13858_REG_PLL2_CTRL_E 0x030e +#define OV13858_REG_PLL2_CTRL_F 0x030f +#define OV13858_REG_PLL2_CTRL_12 0x0312 +#define OV13858_REG_MIPI_SC_CTRL0 0x3016 +#define OV13858_REG_MIPI_SC_CTRL1 0x3022 + +/* Chip ID */ +#define OV13858_REG_CHIP_ID 0x300a +#define OV13858_CHIP_ID 0x00d855 + +/* V_TIMING internal */ +#define OV13858_REG_VTS 0x380e +#define OV13858_VTS_30FPS 0x0c8e /* 30 fps */ +#define OV13858_VTS_60FPS 0x0648 /* 60 fps */ +#define OV13858_VTS_MAX 0x7fff + +/* HBLANK control - read only */ +#define OV13858_PPL_270MHZ 2244 +#define OV13858_PPL_540MHZ 4488 + +/* Exposure control */ +#define OV13858_REG_EXPOSURE 0x3500 +#define OV13858_EXPOSURE_MIN 4 +#define OV13858_EXPOSURE_STEP 1 +#define OV13858_EXPOSURE_DEFAULT 0x640 + +/* Analog gain control */ +#define OV13858_REG_ANALOG_GAIN 0x3508 +#define OV13858_ANA_GAIN_MIN 0 +#define OV13858_ANA_GAIN_MAX 0x1fff +#define OV13858_ANA_GAIN_STEP 1 +#define OV13858_ANA_GAIN_DEFAULT 0x80 + +/* Digital gain control */ +#define OV13858_REG_B_MWB_GAIN 0x5100 +#define OV13858_REG_G_MWB_GAIN 0x5102 +#define OV13858_REG_R_MWB_GAIN 0x5104 +#define OV13858_DGTL_GAIN_MIN 0 +#define OV13858_DGTL_GAIN_MAX 16384 /* Max = 16 X */ +#define OV13858_DGTL_GAIN_DEFAULT 1024 /* Default gain = 1 X */ +#define OV13858_DGTL_GAIN_STEP 1 /* Each step = 1/1024 */ + +/* Test Pattern Control */ +#define OV13858_REG_TEST_PATTERN 0x4503 +#define OV13858_TEST_PATTERN_ENABLE BIT(7) +#define OV13858_TEST_PATTERN_MASK 0xfc + +/* Number of frames to skip */ +#define OV13858_NUM_OF_SKIP_FRAMES 2 + +struct ov13858_reg { + u16 address; + u8 val; +}; + +struct ov13858_reg_list { + u32 num_of_regs; + const struct ov13858_reg *regs; +}; + +/* Link frequency config */ +struct ov13858_link_freq_config { + u32 pixels_per_line; + + /* PLL registers for this link frequency */ + struct ov13858_reg_list reg_list; +}; + +/* Mode : resolution and related config&values */ +struct ov13858_mode { + /* Frame width */ + u32 width; + /* Frame height */ + u32 height; + + /* V-timing */ + u32 vts_def; + u32 vts_min; + + /* Index of Link frequency config to be used */ + u32 link_freq_index; + /* Default register values */ + struct ov13858_reg_list reg_list; +}; + +/* 4224x3136 needs 1080Mbps/lane, 4 lanes */ +static const struct ov13858_reg mipi_data_rate_1080mbps[] = { + /* PLL1 registers */ + {OV13858_REG_PLL1_CTRL_0, 0x07}, + {OV13858_REG_PLL1_CTRL_1, 0x01}, + {OV13858_REG_PLL1_CTRL_2, 0xc2}, + {OV13858_REG_PLL1_CTRL_3, 0x00}, + {OV13858_REG_PLL1_CTRL_4, 0x00}, + {OV13858_REG_PLL1_CTRL_5, 0x01}, + + /* PLL2 registers */ + {OV13858_REG_PLL2_CTRL_B, 0x05}, + {OV13858_REG_PLL2_CTRL_C, 0x01}, + {OV13858_REG_PLL2_CTRL_D, 0x0e}, + {OV13858_REG_PLL2_CTRL_E, 0x05}, + {OV13858_REG_PLL2_CTRL_F, 0x01}, + {OV13858_REG_PLL2_CTRL_12, 0x01}, + {OV13858_REG_MIPI_SC_CTRL0, 0x72}, + {OV13858_REG_MIPI_SC_CTRL1, 0x01}, +}; + +/* + * 2112x1568, 2112x1188, 1056x784 need 540Mbps/lane, + * 4 lanes + */ +static const struct ov13858_reg mipi_data_rate_540mbps[] = { + /* PLL1 registers */ + {OV13858_REG_PLL1_CTRL_0, 0x07}, + {OV13858_REG_PLL1_CTRL_1, 0x01}, + {OV13858_REG_PLL1_CTRL_2, 0xc2}, + {OV13858_REG_PLL1_CTRL_3, 0x01}, + {OV13858_REG_PLL1_CTRL_4, 0x00}, + {OV13858_REG_PLL1_CTRL_5, 0x01}, + + /* PLL2 registers */ + {OV13858_REG_PLL2_CTRL_B, 0x05}, + {OV13858_REG_PLL2_CTRL_C, 0x01}, + {OV13858_REG_PLL2_CTRL_D, 0x0e}, + {OV13858_REG_PLL2_CTRL_E, 0x05}, + {OV13858_REG_PLL2_CTRL_F, 0x01}, + {OV13858_REG_PLL2_CTRL_12, 0x01}, + {OV13858_REG_MIPI_SC_CTRL0, 0x72}, + {OV13858_REG_MIPI_SC_CTRL1, 0x01}, +}; + +static const struct ov13858_reg mode_4224x3136_regs[] = { + {0x3013, 0x32}, + {0x301b, 0xf0}, + {0x301f, 0xd0}, + {0x3106, 0x15}, + {0x3107, 0x23}, + {0x350a, 0x00}, + {0x350e, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x2b}, + {0x3601, 0x52}, + {0x3602, 0x60}, + {0x3612, 0x05}, + {0x3613, 0xa4}, + {0x3620, 0x80}, + {0x3621, 0x10}, + {0x3622, 0x30}, + {0x3624, 0x1c}, + {0x3640, 0x10}, + {0x3641, 0x70}, + {0x3660, 0x04}, + {0x3661, 0x80}, + {0x3662, 0x12}, + {0x3664, 0x73}, + {0x3665, 0xa7}, + {0x366e, 0xff}, + {0x366f, 0xf4}, + {0x3674, 0x00}, + {0x3679, 0x0c}, + {0x367f, 0x01}, + {0x3680, 0x0c}, + {0x3681, 0x50}, + {0x3682, 0x50}, + {0x3683, 0xa9}, + {0x3684, 0xa9}, + {0x3709, 0x5f}, + {0x3714, 0x24}, + {0x371a, 0x3e}, + {0x3737, 0x04}, + {0x3738, 0xcc}, + {0x3739, 0x12}, + {0x373d, 0x26}, + {0x3764, 0x20}, + {0x3765, 0x20}, + {0x37a1, 0x36}, + {0x37a8, 0x3b}, + {0x37ab, 0x31}, + {0x37c2, 0x04}, + {0x37c3, 0xf1}, + {0x37c5, 0x00}, + {0x37d8, 0x03}, + {0x37d9, 0x0c}, + {0x37da, 0xc2}, + {0x37dc, 0x02}, + {0x37e0, 0x00}, + {0x37e1, 0x0a}, + {0x37e2, 0x14}, + {0x37e3, 0x04}, + {0x37e4, 0x2a}, + {0x37e5, 0x03}, + {0x37e6, 0x04}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x08}, + {0x3804, 0x10}, + {0x3805, 0x9f}, + {0x3806, 0x0c}, + {0x3807, 0x57}, + {0x3808, 0x10}, + {0x3809, 0x80}, + {0x380a, 0x0c}, + {0x380b, 0x40}, + {0x380c, 0x04}, + {0x380d, 0x62}, + {0x380e, 0x0c}, + {0x380f, 0x8e}, + {0x3811, 0x04}, + {0x3813, 0x05}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x01}, + {0x3817, 0x01}, + {0x3820, 0xa8}, + {0x3821, 0x00}, + {0x3822, 0xc2}, + {0x3823, 0x18}, + {0x3826, 0x11}, + {0x3827, 0x1c}, + {0x3829, 0x03}, + {0x3832, 0x00}, + {0x3c80, 0x00}, + {0x3c87, 0x01}, + {0x3c8c, 0x19}, + {0x3c8d, 0x1c}, + {0x3c90, 0x00}, + {0x3c91, 0x00}, + {0x3c92, 0x00}, + {0x3c93, 0x00}, + {0x3c94, 0x40}, + {0x3c95, 0x54}, + {0x3c96, 0x34}, + {0x3c97, 0x04}, + {0x3c98, 0x00}, + {0x3d8c, 0x73}, + {0x3d8d, 0xc0}, + {0x3f00, 0x0b}, + {0x3f03, 0x00}, + {0x4001, 0xe0}, + {0x4008, 0x00}, + {0x4009, 0x0f}, + {0x4011, 0xf0}, + {0x4017, 0x08}, + {0x4050, 0x04}, + {0x4051, 0x0b}, + {0x4052, 0x00}, + {0x4053, 0x80}, + {0x4054, 0x00}, + {0x4055, 0x80}, + {0x4056, 0x00}, + {0x4057, 0x80}, + {0x4058, 0x00}, + {0x4059, 0x80}, + {0x405e, 0x20}, + {0x4500, 0x07}, + {0x4503, 0x00}, + {0x450a, 0x04}, + {0x4809, 0x04}, + {0x480c, 0x12}, + {0x481f, 0x30}, + {0x4833, 0x10}, + {0x4837, 0x0e}, + {0x4902, 0x01}, + {0x4d00, 0x03}, + {0x4d01, 0xc9}, + {0x4d02, 0xbc}, + {0x4d03, 0xd7}, + {0x4d04, 0xf0}, + {0x4d05, 0xa2}, + {0x5000, 0xfd}, + {0x5001, 0x01}, + {0x5040, 0x39}, + {0x5041, 0x10}, + {0x5042, 0x10}, + {0x5043, 0x84}, + {0x5044, 0x62}, + {0x5180, 0x00}, + {0x5181, 0x10}, + {0x5182, 0x02}, + {0x5183, 0x0f}, + {0x5200, 0x1b}, + {0x520b, 0x07}, + {0x520c, 0x0f}, + {0x5300, 0x04}, + {0x5301, 0x0c}, + {0x5302, 0x0c}, + {0x5303, 0x0f}, + {0x5304, 0x00}, + {0x5305, 0x70}, + {0x5306, 0x00}, + {0x5307, 0x80}, + {0x5308, 0x00}, + {0x5309, 0xa5}, + {0x530a, 0x00}, + {0x530b, 0xd3}, + {0x530c, 0x00}, + {0x530d, 0xf0}, + {0x530e, 0x01}, + {0x530f, 0x10}, + {0x5310, 0x01}, + {0x5311, 0x20}, + {0x5312, 0x01}, + {0x5313, 0x20}, + {0x5314, 0x01}, + {0x5315, 0x20}, + {0x5316, 0x08}, + {0x5317, 0x08}, + {0x5318, 0x10}, + {0x5319, 0x88}, + {0x531a, 0x88}, + {0x531b, 0xa9}, + {0x531c, 0xaa}, + {0x531d, 0x0a}, + {0x5405, 0x02}, + {0x5406, 0x67}, + {0x5407, 0x01}, + {0x5408, 0x4a}, +}; + +static const struct ov13858_reg mode_2112x1568_regs[] = { + {0x3013, 0x32}, + {0x301b, 0xf0}, + {0x301f, 0xd0}, + {0x3106, 0x15}, + {0x3107, 0x23}, + {0x350a, 0x00}, + {0x350e, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x2b}, + {0x3601, 0x52}, + {0x3602, 0x60}, + {0x3612, 0x05}, + {0x3613, 0xa4}, + {0x3620, 0x80}, + {0x3621, 0x10}, + {0x3622, 0x30}, + {0x3624, 0x1c}, + {0x3640, 0x10}, + {0x3641, 0x70}, + {0x3660, 0x04}, + {0x3661, 0x80}, + {0x3662, 0x10}, + {0x3664, 0x73}, + {0x3665, 0xa7}, + {0x366e, 0xff}, + {0x366f, 0xf4}, + {0x3674, 0x00}, + {0x3679, 0x0c}, + {0x367f, 0x01}, + {0x3680, 0x0c}, + {0x3681, 0x50}, + {0x3682, 0x50}, + {0x3683, 0xa9}, + {0x3684, 0xa9}, + {0x3709, 0x5f}, + {0x3714, 0x28}, + {0x371a, 0x3e}, + {0x3737, 0x08}, + {0x3738, 0xcc}, + {0x3739, 0x20}, + {0x373d, 0x26}, + {0x3764, 0x20}, + {0x3765, 0x20}, + {0x37a1, 0x36}, + {0x37a8, 0x3b}, + {0x37ab, 0x31}, + {0x37c2, 0x14}, + {0x37c3, 0xf1}, + {0x37c5, 0x00}, + {0x37d8, 0x03}, + {0x37d9, 0x0c}, + {0x37da, 0xc2}, + {0x37dc, 0x02}, + {0x37e0, 0x00}, + {0x37e1, 0x0a}, + {0x37e2, 0x14}, + {0x37e3, 0x08}, + {0x37e4, 0x38}, + {0x37e5, 0x03}, + {0x37e6, 0x08}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x10}, + {0x3805, 0x9f}, + {0x3806, 0x0c}, + {0x3807, 0x5f}, + {0x3808, 0x08}, + {0x3809, 0x40}, + {0x380a, 0x06}, + {0x380b, 0x20}, + {0x380c, 0x04}, + {0x380d, 0x62}, + {0x380e, 0x0c}, + {0x380f, 0x8e}, + {0x3811, 0x04}, + {0x3813, 0x05}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x03}, + {0x3817, 0x01}, + {0x3820, 0xab}, + {0x3821, 0x00}, + {0x3822, 0xc2}, + {0x3823, 0x18}, + {0x3826, 0x04}, + {0x3827, 0x90}, + {0x3829, 0x07}, + {0x3832, 0x00}, + {0x3c80, 0x00}, + {0x3c87, 0x01}, + {0x3c8c, 0x19}, + {0x3c8d, 0x1c}, + {0x3c90, 0x00}, + {0x3c91, 0x00}, + {0x3c92, 0x00}, + {0x3c93, 0x00}, + {0x3c94, 0x40}, + {0x3c95, 0x54}, + {0x3c96, 0x34}, + {0x3c97, 0x04}, + {0x3c98, 0x00}, + {0x3d8c, 0x73}, + {0x3d8d, 0xc0}, + {0x3f00, 0x0b}, + {0x3f03, 0x00}, + {0x4001, 0xe0}, + {0x4008, 0x00}, + {0x4009, 0x0d}, + {0x4011, 0xf0}, + {0x4017, 0x08}, + {0x4050, 0x04}, + {0x4051, 0x0b}, + {0x4052, 0x00}, + {0x4053, 0x80}, + {0x4054, 0x00}, + {0x4055, 0x80}, + {0x4056, 0x00}, + {0x4057, 0x80}, + {0x4058, 0x00}, + {0x4059, 0x80}, + {0x405e, 0x20}, + {0x4500, 0x07}, + {0x4503, 0x00}, + {0x450a, 0x04}, + {0x4809, 0x04}, + {0x480c, 0x12}, + {0x481f, 0x30}, + {0x4833, 0x10}, + {0x4837, 0x1c}, + {0x4902, 0x01}, + {0x4d00, 0x03}, + {0x4d01, 0xc9}, + {0x4d02, 0xbc}, + {0x4d03, 0xd7}, + {0x4d04, 0xf0}, + {0x4d05, 0xa2}, + {0x5000, 0xfd}, + {0x5001, 0x01}, + {0x5040, 0x39}, + {0x5041, 0x10}, + {0x5042, 0x10}, + {0x5043, 0x84}, + {0x5044, 0x62}, + {0x5180, 0x00}, + {0x5181, 0x10}, + {0x5182, 0x02}, + {0x5183, 0x0f}, + {0x5200, 0x1b}, + {0x520b, 0x07}, + {0x520c, 0x0f}, + {0x5300, 0x04}, + {0x5301, 0x0c}, + {0x5302, 0x0c}, + {0x5303, 0x0f}, + {0x5304, 0x00}, + {0x5305, 0x70}, + {0x5306, 0x00}, + {0x5307, 0x80}, + {0x5308, 0x00}, + {0x5309, 0xa5}, + {0x530a, 0x00}, + {0x530b, 0xd3}, + {0x530c, 0x00}, + {0x530d, 0xf0}, + {0x530e, 0x01}, + {0x530f, 0x10}, + {0x5310, 0x01}, + {0x5311, 0x20}, + {0x5312, 0x01}, + {0x5313, 0x20}, + {0x5314, 0x01}, + {0x5315, 0x20}, + {0x5316, 0x08}, + {0x5317, 0x08}, + {0x5318, 0x10}, + {0x5319, 0x88}, + {0x531a, 0x88}, + {0x531b, 0xa9}, + {0x531c, 0xaa}, + {0x531d, 0x0a}, + {0x5405, 0x02}, + {0x5406, 0x67}, + {0x5407, 0x01}, + {0x5408, 0x4a}, +}; + +static const struct ov13858_reg mode_2112x1188_regs[] = { + {0x3013, 0x32}, + {0x301b, 0xf0}, + {0x301f, 0xd0}, + {0x3106, 0x15}, + {0x3107, 0x23}, + {0x350a, 0x00}, + {0x350e, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x2b}, + {0x3601, 0x52}, + {0x3602, 0x60}, + {0x3612, 0x05}, + {0x3613, 0xa4}, + {0x3620, 0x80}, + {0x3621, 0x10}, + {0x3622, 0x30}, + {0x3624, 0x1c}, + {0x3640, 0x10}, + {0x3641, 0x70}, + {0x3660, 0x04}, + {0x3661, 0x80}, + {0x3662, 0x10}, + {0x3664, 0x73}, + {0x3665, 0xa7}, + {0x366e, 0xff}, + {0x366f, 0xf4}, + {0x3674, 0x00}, + {0x3679, 0x0c}, + {0x367f, 0x01}, + {0x3680, 0x0c}, + {0x3681, 0x50}, + {0x3682, 0x50}, + {0x3683, 0xa9}, + {0x3684, 0xa9}, + {0x3709, 0x5f}, + {0x3714, 0x28}, + {0x371a, 0x3e}, + {0x3737, 0x08}, + {0x3738, 0xcc}, + {0x3739, 0x20}, + {0x373d, 0x26}, + {0x3764, 0x20}, + {0x3765, 0x20}, + {0x37a1, 0x36}, + {0x37a8, 0x3b}, + {0x37ab, 0x31}, + {0x37c2, 0x14}, + {0x37c3, 0xf1}, + {0x37c5, 0x00}, + {0x37d8, 0x03}, + {0x37d9, 0x0c}, + {0x37da, 0xc2}, + {0x37dc, 0x02}, + {0x37e0, 0x00}, + {0x37e1, 0x0a}, + {0x37e2, 0x14}, + {0x37e3, 0x08}, + {0x37e4, 0x38}, + {0x37e5, 0x03}, + {0x37e6, 0x08}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x01}, + {0x3803, 0x84}, + {0x3804, 0x10}, + {0x3805, 0x9f}, + {0x3806, 0x0a}, + {0x3807, 0xd3}, + {0x3808, 0x08}, + {0x3809, 0x40}, + {0x380a, 0x04}, + {0x380b, 0xa4}, + {0x380c, 0x04}, + {0x380d, 0x62}, + {0x380e, 0x0c}, + {0x380f, 0x8e}, + {0x3811, 0x08}, + {0x3813, 0x03}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x03}, + {0x3817, 0x01}, + {0x3820, 0xab}, + {0x3821, 0x00}, + {0x3822, 0xc2}, + {0x3823, 0x18}, + {0x3826, 0x04}, + {0x3827, 0x90}, + {0x3829, 0x07}, + {0x3832, 0x00}, + {0x3c80, 0x00}, + {0x3c87, 0x01}, + {0x3c8c, 0x19}, + {0x3c8d, 0x1c}, + {0x3c90, 0x00}, + {0x3c91, 0x00}, + {0x3c92, 0x00}, + {0x3c93, 0x00}, + {0x3c94, 0x40}, + {0x3c95, 0x54}, + {0x3c96, 0x34}, + {0x3c97, 0x04}, + {0x3c98, 0x00}, + {0x3d8c, 0x73}, + {0x3d8d, 0xc0}, + {0x3f00, 0x0b}, + {0x3f03, 0x00}, + {0x4001, 0xe0}, + {0x4008, 0x00}, + {0x4009, 0x0d}, + {0x4011, 0xf0}, + {0x4017, 0x08}, + {0x4050, 0x04}, + {0x4051, 0x0b}, + {0x4052, 0x00}, + {0x4053, 0x80}, + {0x4054, 0x00}, + {0x4055, 0x80}, + {0x4056, 0x00}, + {0x4057, 0x80}, + {0x4058, 0x00}, + {0x4059, 0x80}, + {0x405e, 0x20}, + {0x4500, 0x07}, + {0x4503, 0x00}, + {0x450a, 0x04}, + {0x4809, 0x04}, + {0x480c, 0x12}, + {0x481f, 0x30}, + {0x4833, 0x10}, + {0x4837, 0x1c}, + {0x4902, 0x01}, + {0x4d00, 0x03}, + {0x4d01, 0xc9}, + {0x4d02, 0xbc}, + {0x4d03, 0xd7}, + {0x4d04, 0xf0}, + {0x4d05, 0xa2}, + {0x5000, 0xfd}, + {0x5001, 0x01}, + {0x5040, 0x39}, + {0x5041, 0x10}, + {0x5042, 0x10}, + {0x5043, 0x84}, + {0x5044, 0x62}, + {0x5180, 0x00}, + {0x5181, 0x10}, + {0x5182, 0x02}, + {0x5183, 0x0f}, + {0x5200, 0x1b}, + {0x520b, 0x07}, + {0x520c, 0x0f}, + {0x5300, 0x04}, + {0x5301, 0x0c}, + {0x5302, 0x0c}, + {0x5303, 0x0f}, + {0x5304, 0x00}, + {0x5305, 0x70}, + {0x5306, 0x00}, + {0x5307, 0x80}, + {0x5308, 0x00}, + {0x5309, 0xa5}, + {0x530a, 0x00}, + {0x530b, 0xd3}, + {0x530c, 0x00}, + {0x530d, 0xf0}, + {0x530e, 0x01}, + {0x530f, 0x10}, + {0x5310, 0x01}, + {0x5311, 0x20}, + {0x5312, 0x01}, + {0x5313, 0x20}, + {0x5314, 0x01}, + {0x5315, 0x20}, + {0x5316, 0x08}, + {0x5317, 0x08}, + {0x5318, 0x10}, + {0x5319, 0x88}, + {0x531a, 0x88}, + {0x531b, 0xa9}, + {0x531c, 0xaa}, + {0x531d, 0x0a}, + {0x5405, 0x02}, + {0x5406, 0x67}, + {0x5407, 0x01}, + {0x5408, 0x4a}, +}; + +static const struct ov13858_reg mode_1056x784_regs[] = { + {0x3013, 0x32}, + {0x301b, 0xf0}, + {0x301f, 0xd0}, + {0x3106, 0x15}, + {0x3107, 0x23}, + {0x350a, 0x00}, + {0x350e, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x2b}, + {0x3601, 0x52}, + {0x3602, 0x60}, + {0x3612, 0x05}, + {0x3613, 0xa4}, + {0x3620, 0x80}, + {0x3621, 0x10}, + {0x3622, 0x30}, + {0x3624, 0x1c}, + {0x3640, 0x10}, + {0x3641, 0x70}, + {0x3660, 0x04}, + {0x3661, 0x80}, + {0x3662, 0x08}, + {0x3664, 0x73}, + {0x3665, 0xa7}, + {0x366e, 0xff}, + {0x366f, 0xf4}, + {0x3674, 0x00}, + {0x3679, 0x0c}, + {0x367f, 0x01}, + {0x3680, 0x0c}, + {0x3681, 0x50}, + {0x3682, 0x50}, + {0x3683, 0xa9}, + {0x3684, 0xa9}, + {0x3709, 0x5f}, + {0x3714, 0x30}, + {0x371a, 0x3e}, + {0x3737, 0x08}, + {0x3738, 0xcc}, + {0x3739, 0x20}, + {0x373d, 0x26}, + {0x3764, 0x20}, + {0x3765, 0x20}, + {0x37a1, 0x36}, + {0x37a8, 0x3b}, + {0x37ab, 0x31}, + {0x37c2, 0x2c}, + {0x37c3, 0xf1}, + {0x37c5, 0x00}, + {0x37d8, 0x03}, + {0x37d9, 0x06}, + {0x37da, 0xc2}, + {0x37dc, 0x02}, + {0x37e0, 0x00}, + {0x37e1, 0x0a}, + {0x37e2, 0x14}, + {0x37e3, 0x08}, + {0x37e4, 0x36}, + {0x37e5, 0x03}, + {0x37e6, 0x08}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x10}, + {0x3805, 0x9f}, + {0x3806, 0x0c}, + {0x3807, 0x5f}, + {0x3808, 0x04}, + {0x3809, 0x20}, + {0x380a, 0x03}, + {0x380b, 0x10}, + {0x380c, 0x04}, + {0x380d, 0x62}, + {0x380e, 0x0c}, + {0x380f, 0x8e}, + {0x3811, 0x04}, + {0x3813, 0x05}, + {0x3814, 0x07}, + {0x3815, 0x01}, + {0x3816, 0x07}, + {0x3817, 0x01}, + {0x3820, 0xac}, + {0x3821, 0x00}, + {0x3822, 0xc2}, + {0x3823, 0x18}, + {0x3826, 0x04}, + {0x3827, 0x48}, + {0x3829, 0x03}, + {0x3832, 0x00}, + {0x3c80, 0x00}, + {0x3c87, 0x01}, + {0x3c8c, 0x19}, + {0x3c8d, 0x1c}, + {0x3c90, 0x00}, + {0x3c91, 0x00}, + {0x3c92, 0x00}, + {0x3c93, 0x00}, + {0x3c94, 0x40}, + {0x3c95, 0x54}, + {0x3c96, 0x34}, + {0x3c97, 0x04}, + {0x3c98, 0x00}, + {0x3d8c, 0x73}, + {0x3d8d, 0xc0}, + {0x3f00, 0x0b}, + {0x3f03, 0x00}, + {0x4001, 0xe0}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x4011, 0xf0}, + {0x4017, 0x08}, + {0x4050, 0x02}, + {0x4051, 0x05}, + {0x4052, 0x00}, + {0x4053, 0x80}, + {0x4054, 0x00}, + {0x4055, 0x80}, + {0x4056, 0x00}, + {0x4057, 0x80}, + {0x4058, 0x00}, + {0x4059, 0x80}, + {0x405e, 0x20}, + {0x4500, 0x07}, + {0x4503, 0x00}, + {0x450a, 0x04}, + {0x4809, 0x04}, + {0x480c, 0x12}, + {0x481f, 0x30}, + {0x4833, 0x10}, + {0x4837, 0x1e}, + {0x4902, 0x02}, + {0x4d00, 0x03}, + {0x4d01, 0xc9}, + {0x4d02, 0xbc}, + {0x4d03, 0xd7}, + {0x4d04, 0xf0}, + {0x4d05, 0xa2}, + {0x5000, 0xfd}, + {0x5001, 0x01}, + {0x5040, 0x39}, + {0x5041, 0x10}, + {0x5042, 0x10}, + {0x5043, 0x84}, + {0x5044, 0x62}, + {0x5180, 0x00}, + {0x5181, 0x10}, + {0x5182, 0x02}, + {0x5183, 0x0f}, + {0x5200, 0x1b}, + {0x520b, 0x07}, + {0x520c, 0x0f}, + {0x5300, 0x04}, + {0x5301, 0x0c}, + {0x5302, 0x0c}, + {0x5303, 0x0f}, + {0x5304, 0x00}, + {0x5305, 0x70}, + {0x5306, 0x00}, + {0x5307, 0x80}, + {0x5308, 0x00}, + {0x5309, 0xa5}, + {0x530a, 0x00}, + {0x530b, 0xd3}, + {0x530c, 0x00}, + {0x530d, 0xf0}, + {0x530e, 0x01}, + {0x530f, 0x10}, + {0x5310, 0x01}, + {0x5311, 0x20}, + {0x5312, 0x01}, + {0x5313, 0x20}, + {0x5314, 0x01}, + {0x5315, 0x20}, + {0x5316, 0x08}, + {0x5317, 0x08}, + {0x5318, 0x10}, + {0x5319, 0x88}, + {0x531a, 0x88}, + {0x531b, 0xa9}, + {0x531c, 0xaa}, + {0x531d, 0x0a}, + {0x5405, 0x02}, + {0x5406, 0x67}, + {0x5407, 0x01}, + {0x5408, 0x4a}, +}; + +static const char * const ov13858_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* Configurations for supported link frequencies */ +#define OV13858_NUM_OF_LINK_FREQS 2 +#define OV13858_LINK_FREQ_540MHZ 540000000ULL +#define OV13858_LINK_FREQ_270MHZ 270000000ULL +#define OV13858_LINK_FREQ_INDEX_0 0 +#define OV13858_LINK_FREQ_INDEX_1 1 + +/* + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample + * data rate => double data rate; number of lanes => 4; bits per pixel => 10 + */ +static u64 link_freq_to_pixel_rate(u64 f) +{ + f *= 2 * 4; + do_div(f, 10); + + return f; +} + +/* Menu items for LINK_FREQ V4L2 control */ +static const s64 link_freq_menu_items[OV13858_NUM_OF_LINK_FREQS] = { + OV13858_LINK_FREQ_540MHZ, + OV13858_LINK_FREQ_270MHZ +}; + +/* Link frequency configs */ +static const struct ov13858_link_freq_config + link_freq_configs[OV13858_NUM_OF_LINK_FREQS] = { + { + .pixels_per_line = OV13858_PPL_540MHZ, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_1080mbps), + .regs = mipi_data_rate_1080mbps, + } + }, + { + .pixels_per_line = OV13858_PPL_270MHZ, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_540mbps), + .regs = mipi_data_rate_540mbps, + } + } +}; + +/* Mode configs */ +static const struct ov13858_mode supported_modes[] = { + { + .width = 4224, + .height = 3136, + .vts_def = OV13858_VTS_30FPS, + .vts_min = OV13858_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4224x3136_regs), + .regs = mode_4224x3136_regs, + }, + .link_freq_index = OV13858_LINK_FREQ_INDEX_0, + }, + { + .width = 2112, + .height = 1568, + .vts_def = OV13858_VTS_30FPS, + .vts_min = 1608, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2112x1568_regs), + .regs = mode_2112x1568_regs, + }, + .link_freq_index = OV13858_LINK_FREQ_INDEX_1, + }, + { + .width = 2112, + .height = 1188, + .vts_def = OV13858_VTS_30FPS, + .vts_min = 1608, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2112x1188_regs), + .regs = mode_2112x1188_regs, + }, + .link_freq_index = OV13858_LINK_FREQ_INDEX_1, + }, + { + .width = 1056, + .height = 784, + .vts_def = OV13858_VTS_30FPS, + .vts_min = 804, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1056x784_regs), + .regs = mode_1056x784_regs, + }, + .link_freq_index = OV13858_LINK_FREQ_INDEX_1, + } +}; + +struct ov13858 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct ov13858_mode *cur_mode; + + /* Mutex for serialized access */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +#define to_ov13858(_sd) container_of(_sd, struct ov13858, sd) + +/* Read registers up to 4 at a time */ +static int ov13858_read_reg(struct ov13858 *ov13858, u16 reg, u32 len, + u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + struct i2c_msg msgs[2]; + u8 *data_be_p; + int ret; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + + if (len > 4) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +/* Write registers up to 4 at a time */ +static int ov13858_write_reg(struct ov13858 *ov13858, u16 reg, u32 len, + u32 __val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + int buf_i, val_i; + u8 buf[6], *val_p; + __be32 val; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val = cpu_to_be32(__val); + val_p = (u8 *)&val; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int ov13858_write_regs(struct ov13858 *ov13858, + const struct ov13858_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + int ret; + u32 i; + + for (i = 0; i < len; i++) { + ret = ov13858_write_reg(ov13858, regs[i].address, 1, + regs[i].val); + if (ret) { + dev_err_ratelimited( + &client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +static int ov13858_write_reg_list(struct ov13858 *ov13858, + const struct ov13858_reg_list *r_list) +{ + return ov13858_write_regs(ov13858, r_list->regs, r_list->num_of_regs); +} + +/* Open sub-device */ +static int ov13858_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov13858 *ov13858 = to_ov13858(sd); + struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_get_try_format(sd, + fh->pad, + 0); + + mutex_lock(&ov13858->mutex); + + /* Initialize try_fmt */ + try_fmt->width = ov13858->cur_mode->width; + try_fmt->height = ov13858->cur_mode->height; + try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + + /* No crop or compose */ + mutex_unlock(&ov13858->mutex); + + return 0; +} + +static int ov13858_update_digital_gain(struct ov13858 *ov13858, u32 d_gain) +{ + int ret; + + ret = ov13858_write_reg(ov13858, OV13858_REG_B_MWB_GAIN, + OV13858_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = ov13858_write_reg(ov13858, OV13858_REG_G_MWB_GAIN, + OV13858_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = ov13858_write_reg(ov13858, OV13858_REG_R_MWB_GAIN, + OV13858_REG_VALUE_16BIT, d_gain); + + return ret; +} + +static int ov13858_enable_test_pattern(struct ov13858 *ov13858, u32 pattern) +{ + int ret; + u32 val; + + ret = ov13858_read_reg(ov13858, OV13858_REG_TEST_PATTERN, + OV13858_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + if (pattern) { + val &= OV13858_TEST_PATTERN_MASK; + val |= (pattern - 1) | OV13858_TEST_PATTERN_ENABLE; + } else { + val &= ~OV13858_TEST_PATTERN_ENABLE; + } + + return ov13858_write_reg(ov13858, OV13858_REG_TEST_PATTERN, + OV13858_REG_VALUE_08BIT, val); +} + +static int ov13858_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov13858 *ov13858 = container_of(ctrl->handler, + struct ov13858, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + s64 max; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = ov13858->cur_mode->height + ctrl->val - 8; + __v4l2_ctrl_modify_range(ov13858->exposure, + ov13858->exposure->minimum, + max, ov13858->exposure->step, max); + break; + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + ret = 0; + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ov13858_write_reg(ov13858, OV13858_REG_ANALOG_GAIN, + OV13858_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = ov13858_update_digital_gain(ov13858, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = ov13858_write_reg(ov13858, OV13858_REG_EXPOSURE, + OV13858_REG_VALUE_24BIT, + ctrl->val << 4); + break; + case V4L2_CID_VBLANK: + /* Update VTS that meets expected vertical blanking */ + ret = ov13858_write_reg(ov13858, OV13858_REG_VTS, + OV13858_REG_VALUE_16BIT, + ov13858->cur_mode->height + + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov13858_enable_test_pattern(ov13858, ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov13858_ctrl_ops = { + .s_ctrl = ov13858_set_ctrl, +}; + +static int ov13858_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Only one bayer order(GRBG) is supported */ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int ov13858_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void ov13858_update_pad_format(const struct ov13858_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int ov13858_do_get_pad_format(struct ov13858 *ov13858, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + struct v4l2_subdev *sd = &ov13858->sd; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + fmt->format = *framefmt; + } else { + ov13858_update_pad_format(ov13858->cur_mode, fmt); + } + + return 0; +} + +static int ov13858_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov13858 *ov13858 = to_ov13858(sd); + int ret; + + mutex_lock(&ov13858->mutex); + ret = ov13858_do_get_pad_format(ov13858, cfg, fmt); + mutex_unlock(&ov13858->mutex); + + return ret; +} + +static int +ov13858_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov13858 *ov13858 = to_ov13858(sd); + const struct ov13858_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + s32 vblank_def; + s32 vblank_min; + s64 h_blank; + s64 pixel_rate; + s64 link_freq; + + mutex_lock(&ov13858->mutex); + + /* Only one raw bayer(GRBG) order is supported */ + if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10) + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + ov13858_update_pad_format(mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *framefmt = fmt->format; + } else { + ov13858->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ov13858->link_freq, mode->link_freq_index); + link_freq = link_freq_menu_items[mode->link_freq_index]; + pixel_rate = link_freq_to_pixel_rate(link_freq); + __v4l2_ctrl_s_ctrl_int64(ov13858->pixel_rate, pixel_rate); + + /* Update limits and set FPS to default */ + vblank_def = ov13858->cur_mode->vts_def - + ov13858->cur_mode->height; + vblank_min = ov13858->cur_mode->vts_min - + ov13858->cur_mode->height; + __v4l2_ctrl_modify_range( + ov13858->vblank, vblank_min, + OV13858_VTS_MAX - ov13858->cur_mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ov13858->vblank, vblank_def); + h_blank = + link_freq_configs[mode->link_freq_index].pixels_per_line + - ov13858->cur_mode->width; + __v4l2_ctrl_modify_range(ov13858->hblank, h_blank, + h_blank, 1, h_blank); + } + + mutex_unlock(&ov13858->mutex); + + return 0; +} + +static int ov13858_get_skip_frames(struct v4l2_subdev *sd, u32 *frames) +{ + *frames = OV13858_NUM_OF_SKIP_FRAMES; + + return 0; +} + +/* Start streaming */ +static int ov13858_start_streaming(struct ov13858 *ov13858) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + const struct ov13858_reg_list *reg_list; + int ret, link_freq_index; + + /* Get out of from software reset */ + ret = ov13858_write_reg(ov13858, OV13858_REG_SOFTWARE_RST, + OV13858_REG_VALUE_08BIT, OV13858_SOFTWARE_RST); + if (ret) { + dev_err(&client->dev, "%s failed to set powerup registers\n", + __func__); + return ret; + } + + /* Setup PLL */ + link_freq_index = ov13858->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = ov13858_write_reg_list(ov13858, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set plls\n", __func__); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &ov13858->cur_mode->reg_list; + ret = ov13858_write_reg_list(ov13858, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(ov13858->sd.ctrl_handler); + if (ret) + return ret; + + return ov13858_write_reg(ov13858, OV13858_REG_MODE_SELECT, + OV13858_REG_VALUE_08BIT, + OV13858_MODE_STREAMING); +} + +/* Stop streaming */ +static int ov13858_stop_streaming(struct ov13858 *ov13858) +{ + return ov13858_write_reg(ov13858, OV13858_REG_MODE_SELECT, + OV13858_REG_VALUE_08BIT, OV13858_MODE_STANDBY); +} + +static int ov13858_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov13858 *ov13858 = to_ov13858(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&ov13858->mutex); + if (ov13858->streaming == enable) { + mutex_unlock(&ov13858->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = ov13858_start_streaming(ov13858); + if (ret) + goto err_rpm_put; + } else { + ov13858_stop_streaming(ov13858); + pm_runtime_put(&client->dev); + } + + ov13858->streaming = enable; + mutex_unlock(&ov13858->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&ov13858->mutex); + + return ret; +} + +static int __maybe_unused ov13858_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13858 *ov13858 = to_ov13858(sd); + + if (ov13858->streaming) + ov13858_stop_streaming(ov13858); + + return 0; +} + +static int __maybe_unused ov13858_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13858 *ov13858 = to_ov13858(sd); + int ret; + + if (ov13858->streaming) { + ret = ov13858_start_streaming(ov13858); + if (ret) + goto error; + } + + return 0; + +error: + ov13858_stop_streaming(ov13858); + ov13858->streaming = false; + return ret; +} + +/* Verify chip ID */ +static int ov13858_identify_module(struct ov13858 *ov13858) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + int ret; + u32 val; + + ret = ov13858_read_reg(ov13858, OV13858_REG_CHIP_ID, + OV13858_REG_VALUE_24BIT, &val); + if (ret) + return ret; + + if (val != OV13858_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + OV13858_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops ov13858_video_ops = { + .s_stream = ov13858_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ov13858_pad_ops = { + .enum_mbus_code = ov13858_enum_mbus_code, + .get_fmt = ov13858_get_pad_format, + .set_fmt = ov13858_set_pad_format, + .enum_frame_size = ov13858_enum_frame_size, +}; + +static const struct v4l2_subdev_sensor_ops ov13858_sensor_ops = { + .g_skip_frames = ov13858_get_skip_frames, +}; + +static const struct v4l2_subdev_ops ov13858_subdev_ops = { + .video = &ov13858_video_ops, + .pad = &ov13858_pad_ops, + .sensor = &ov13858_sensor_ops, +}; + +static const struct media_entity_operations ov13858_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov13858_internal_ops = { + .open = ov13858_open, +}; + +/* Initialize control handlers */ +static int ov13858_init_controls(struct ov13858 *ov13858) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max; + s64 vblank_def; + s64 vblank_min; + s64 hblank; + s64 pixel_rate_min; + s64 pixel_rate_max; + const struct ov13858_mode *mode; + int ret; + + ctrl_hdlr = &ov13858->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + mutex_init(&ov13858->mutex); + ctrl_hdlr->lock = &ov13858->mutex; + ov13858->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, + &ov13858_ctrl_ops, + V4L2_CID_LINK_FREQ, + OV13858_NUM_OF_LINK_FREQS - 1, + 0, + link_freq_menu_items); + if (ov13858->link_freq) + ov13858->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate_max = link_freq_to_pixel_rate(link_freq_menu_items[0]); + pixel_rate_min = link_freq_to_pixel_rate(link_freq_menu_items[1]); + /* By default, PIXEL_RATE is read only */ + ov13858->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov13858_ctrl_ops, + V4L2_CID_PIXEL_RATE, + pixel_rate_min, pixel_rate_max, + 1, pixel_rate_max); + + mode = ov13858->cur_mode; + vblank_def = mode->vts_def - mode->height; + vblank_min = mode->vts_min - mode->height; + ov13858->vblank = v4l2_ctrl_new_std( + ctrl_hdlr, &ov13858_ctrl_ops, V4L2_CID_VBLANK, + vblank_min, OV13858_VTS_MAX - mode->height, 1, + vblank_def); + + hblank = link_freq_configs[mode->link_freq_index].pixels_per_line - + mode->width; + ov13858->hblank = v4l2_ctrl_new_std( + ctrl_hdlr, &ov13858_ctrl_ops, V4L2_CID_HBLANK, + hblank, hblank, 1, hblank); + if (ov13858->hblank) + ov13858->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + exposure_max = mode->vts_def - 8; + ov13858->exposure = v4l2_ctrl_new_std( + ctrl_hdlr, &ov13858_ctrl_ops, + V4L2_CID_EXPOSURE, OV13858_EXPOSURE_MIN, + exposure_max, OV13858_EXPOSURE_STEP, + OV13858_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov13858_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV13858_ANA_GAIN_MIN, OV13858_ANA_GAIN_MAX, + OV13858_ANA_GAIN_STEP, OV13858_ANA_GAIN_DEFAULT); + + /* Digital gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &ov13858_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV13858_DGTL_GAIN_MIN, OV13858_DGTL_GAIN_MAX, + OV13858_DGTL_GAIN_STEP, OV13858_DGTL_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov13858_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov13858_test_pattern_menu) - 1, + 0, 0, ov13858_test_pattern_menu); + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov13858_ctrl_ops, + &props); + if (ret) + goto error; + + ov13858->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&ov13858->mutex); + + return ret; +} + +static void ov13858_free_controls(struct ov13858 *ov13858) +{ + v4l2_ctrl_handler_free(ov13858->sd.ctrl_handler); + mutex_destroy(&ov13858->mutex); +} + +static int ov13858_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ov13858 *ov13858; + int ret; + u32 val = 0; + + device_property_read_u32(&client->dev, "clock-frequency", &val); + if (val != 19200000) + return -EINVAL; + + ov13858 = devm_kzalloc(&client->dev, sizeof(*ov13858), GFP_KERNEL); + if (!ov13858) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&ov13858->sd, client, &ov13858_subdev_ops); + + /* Check module identity */ + ret = ov13858_identify_module(ov13858); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d\n", ret); + return ret; + } + + /* Set default mode to max resolution */ + ov13858->cur_mode = &supported_modes[0]; + + ret = ov13858_init_controls(ov13858); + if (ret) + return ret; + + /* Initialize subdev */ + ov13858->sd.internal_ops = &ov13858_internal_ops; + ov13858->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov13858->sd.entity.ops = &ov13858_subdev_entity_ops; + ov13858->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + ov13858->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov13858->sd.entity, 1, &ov13858->pad); + if (ret) { + dev_err(&client->dev, "%s failed:%d\n", __func__, ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor_common(&ov13858->sd); + if (ret < 0) + goto error_media_entity; + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&ov13858->sd.entity); + +error_handler_free: + ov13858_free_controls(ov13858); + dev_err(&client->dev, "%s failed:%d\n", __func__, ret); + + return ret; +} + +static int ov13858_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13858 *ov13858 = to_ov13858(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + ov13858_free_controls(ov13858); + + pm_runtime_disable(&client->dev); + + return 0; +} + +static const struct i2c_device_id ov13858_id_table[] = { + {"ov13858", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov13858_id_table); + +static const struct dev_pm_ops ov13858_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov13858_suspend, ov13858_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ov13858_acpi_ids[] = { + {"OVTID858"}, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(acpi, ov13858_acpi_ids); +#endif + +static struct i2c_driver ov13858_i2c_driver = { + .driver = { + .name = "ov13858", + .pm = &ov13858_pm_ops, + .acpi_match_table = ACPI_PTR(ov13858_acpi_ids), + }, + .probe = ov13858_probe, + .remove = ov13858_remove, + .id_table = ov13858_id_table, +}; + +module_i2c_driver(ov13858_i2c_driver); + +MODULE_AUTHOR("Kan, Chris <chris.kan@intel.com>"); +MODULE_AUTHOR("Rapolu, Chiranjeevi <chiranjeevi.rapolu@intel.com>"); +MODULE_AUTHOR("Yang, Hyungwoo <hyungwoo.yang@intel.com>"); +MODULE_DESCRIPTION("Omnivision ov13858 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov2640.c b/drivers/media/i2c/ov2640.c new file mode 100644 index 000000000..4a4bd5b66 --- /dev/null +++ b/drivers/media/i2c/ov2640.c @@ -0,0 +1,1313 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ov2640 Camera Driver + * + * Copyright (C) 2010 Alberto Panizzo <maramaopercheseimorto@gmail.com> + * + * Based on ov772x, ov9640 drivers and previous non merged implementations. + * + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2006, OmniVision + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/of_gpio.h> +#include <linux/v4l2-mediabus.h> +#include <linux/videodev2.h> + +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-image-sizes.h> + +#define VAL_SET(x, mask, rshift, lshift) \ + ((((x) >> rshift) & mask) << lshift) +/* + * DSP registers + * register offset for BANK_SEL == BANK_SEL_DSP + */ +#define R_BYPASS 0x05 /* Bypass DSP */ +#define R_BYPASS_DSP_BYPAS 0x01 /* Bypass DSP, sensor out directly */ +#define R_BYPASS_USE_DSP 0x00 /* Use the internal DSP */ +#define QS 0x44 /* Quantization Scale Factor */ +#define CTRLI 0x50 +#define CTRLI_LP_DP 0x80 +#define CTRLI_ROUND 0x40 +#define CTRLI_V_DIV_SET(x) VAL_SET(x, 0x3, 0, 3) +#define CTRLI_H_DIV_SET(x) VAL_SET(x, 0x3, 0, 0) +#define HSIZE 0x51 /* H_SIZE[7:0] (real/4) */ +#define HSIZE_SET(x) VAL_SET(x, 0xFF, 2, 0) +#define VSIZE 0x52 /* V_SIZE[7:0] (real/4) */ +#define VSIZE_SET(x) VAL_SET(x, 0xFF, 2, 0) +#define XOFFL 0x53 /* OFFSET_X[7:0] */ +#define XOFFL_SET(x) VAL_SET(x, 0xFF, 0, 0) +#define YOFFL 0x54 /* OFFSET_Y[7:0] */ +#define YOFFL_SET(x) VAL_SET(x, 0xFF, 0, 0) +#define VHYX 0x55 /* Offset and size completion */ +#define VHYX_VSIZE_SET(x) VAL_SET(x, 0x1, (8+2), 7) +#define VHYX_HSIZE_SET(x) VAL_SET(x, 0x1, (8+2), 3) +#define VHYX_YOFF_SET(x) VAL_SET(x, 0x3, 8, 4) +#define VHYX_XOFF_SET(x) VAL_SET(x, 0x3, 8, 0) +#define DPRP 0x56 +#define TEST 0x57 /* Horizontal size completion */ +#define TEST_HSIZE_SET(x) VAL_SET(x, 0x1, (9+2), 7) +#define ZMOW 0x5A /* Zoom: Out Width OUTW[7:0] (real/4) */ +#define ZMOW_OUTW_SET(x) VAL_SET(x, 0xFF, 2, 0) +#define ZMOH 0x5B /* Zoom: Out Height OUTH[7:0] (real/4) */ +#define ZMOH_OUTH_SET(x) VAL_SET(x, 0xFF, 2, 0) +#define ZMHH 0x5C /* Zoom: Speed and H&W completion */ +#define ZMHH_ZSPEED_SET(x) VAL_SET(x, 0x0F, 0, 4) +#define ZMHH_OUTH_SET(x) VAL_SET(x, 0x1, (8+2), 2) +#define ZMHH_OUTW_SET(x) VAL_SET(x, 0x3, (8+2), 0) +#define BPADDR 0x7C /* SDE Indirect Register Access: Address */ +#define BPDATA 0x7D /* SDE Indirect Register Access: Data */ +#define CTRL2 0x86 /* DSP Module enable 2 */ +#define CTRL2_DCW_EN 0x20 +#define CTRL2_SDE_EN 0x10 +#define CTRL2_UV_ADJ_EN 0x08 +#define CTRL2_UV_AVG_EN 0x04 +#define CTRL2_CMX_EN 0x01 +#define CTRL3 0x87 /* DSP Module enable 3 */ +#define CTRL3_BPC_EN 0x80 +#define CTRL3_WPC_EN 0x40 +#define SIZEL 0x8C /* Image Size Completion */ +#define SIZEL_HSIZE8_11_SET(x) VAL_SET(x, 0x1, 11, 6) +#define SIZEL_HSIZE8_SET(x) VAL_SET(x, 0x7, 0, 3) +#define SIZEL_VSIZE8_SET(x) VAL_SET(x, 0x7, 0, 0) +#define HSIZE8 0xC0 /* Image Horizontal Size HSIZE[10:3] */ +#define HSIZE8_SET(x) VAL_SET(x, 0xFF, 3, 0) +#define VSIZE8 0xC1 /* Image Vertical Size VSIZE[10:3] */ +#define VSIZE8_SET(x) VAL_SET(x, 0xFF, 3, 0) +#define CTRL0 0xC2 /* DSP Module enable 0 */ +#define CTRL0_AEC_EN 0x80 +#define CTRL0_AEC_SEL 0x40 +#define CTRL0_STAT_SEL 0x20 +#define CTRL0_VFIRST 0x10 +#define CTRL0_YUV422 0x08 +#define CTRL0_YUV_EN 0x04 +#define CTRL0_RGB_EN 0x02 +#define CTRL0_RAW_EN 0x01 +#define CTRL1 0xC3 /* DSP Module enable 1 */ +#define CTRL1_CIP 0x80 +#define CTRL1_DMY 0x40 +#define CTRL1_RAW_GMA 0x20 +#define CTRL1_DG 0x10 +#define CTRL1_AWB 0x08 +#define CTRL1_AWB_GAIN 0x04 +#define CTRL1_LENC 0x02 +#define CTRL1_PRE 0x01 +/* REG 0xC7 (unknown name): affects Auto White Balance (AWB) + * AWB_OFF 0x40 + * AWB_SIMPLE 0x10 + * AWB_ON 0x00 (Advanced AWB ?) */ +#define R_DVP_SP 0xD3 /* DVP output speed control */ +#define R_DVP_SP_AUTO_MODE 0x80 +#define R_DVP_SP_DVP_MASK 0x3F /* DVP PCLK = sysclk (48)/[6:0] (YUV0); + * = sysclk (48)/(2*[6:0]) (RAW);*/ +#define IMAGE_MODE 0xDA /* Image Output Format Select */ +#define IMAGE_MODE_Y8_DVP_EN 0x40 +#define IMAGE_MODE_JPEG_EN 0x10 +#define IMAGE_MODE_YUV422 0x00 +#define IMAGE_MODE_RAW10 0x04 /* (DVP) */ +#define IMAGE_MODE_RGB565 0x08 +#define IMAGE_MODE_HREF_VSYNC 0x02 /* HREF timing select in DVP JPEG output + * mode (0 for HREF is same as sensor) */ +#define IMAGE_MODE_LBYTE_FIRST 0x01 /* Byte swap enable for DVP + * 1: Low byte first UYVY (C2[4] =0) + * VYUY (C2[4] =1) + * 0: High byte first YUYV (C2[4]=0) + * YVYU (C2[4] = 1) */ +#define RESET 0xE0 /* Reset */ +#define RESET_MICROC 0x40 +#define RESET_SCCB 0x20 +#define RESET_JPEG 0x10 +#define RESET_DVP 0x04 +#define RESET_IPU 0x02 +#define RESET_CIF 0x01 +#define REGED 0xED /* Register ED */ +#define REGED_CLK_OUT_DIS 0x10 +#define MS_SP 0xF0 /* SCCB Master Speed */ +#define SS_ID 0xF7 /* SCCB Slave ID */ +#define SS_CTRL 0xF8 /* SCCB Slave Control */ +#define SS_CTRL_ADD_AUTO_INC 0x20 +#define SS_CTRL_EN 0x08 +#define SS_CTRL_DELAY_CLK 0x04 +#define SS_CTRL_ACC_EN 0x02 +#define SS_CTRL_SEN_PASS_THR 0x01 +#define MC_BIST 0xF9 /* Microcontroller misc register */ +#define MC_BIST_RESET 0x80 /* Microcontroller Reset */ +#define MC_BIST_BOOT_ROM_SEL 0x40 +#define MC_BIST_12KB_SEL 0x20 +#define MC_BIST_12KB_MASK 0x30 +#define MC_BIST_512KB_SEL 0x08 +#define MC_BIST_512KB_MASK 0x0C +#define MC_BIST_BUSY_BIT_R 0x02 +#define MC_BIST_MC_RES_ONE_SH_W 0x02 +#define MC_BIST_LAUNCH 0x01 +#define BANK_SEL 0xFF /* Register Bank Select */ +#define BANK_SEL_DSP 0x00 +#define BANK_SEL_SENS 0x01 + +/* + * Sensor registers + * register offset for BANK_SEL == BANK_SEL_SENS + */ +#define GAIN 0x00 /* AGC - Gain control gain setting */ +#define COM1 0x03 /* Common control 1 */ +#define COM1_1_DUMMY_FR 0x40 +#define COM1_3_DUMMY_FR 0x80 +#define COM1_7_DUMMY_FR 0xC0 +#define COM1_VWIN_LSB_UXGA 0x0F +#define COM1_VWIN_LSB_SVGA 0x0A +#define COM1_VWIN_LSB_CIF 0x06 +#define REG04 0x04 /* Register 04 */ +#define REG04_DEF 0x20 /* Always set */ +#define REG04_HFLIP_IMG 0x80 /* Horizontal mirror image ON/OFF */ +#define REG04_VFLIP_IMG 0x40 /* Vertical flip image ON/OFF */ +#define REG04_VREF_EN 0x10 +#define REG04_HREF_EN 0x08 +#define REG04_AEC_SET(x) VAL_SET(x, 0x3, 0, 0) +#define REG08 0x08 /* Frame Exposure One-pin Control Pre-charge Row Num */ +#define COM2 0x09 /* Common control 2 */ +#define COM2_SOFT_SLEEP_MODE 0x10 /* Soft sleep mode */ + /* Output drive capability */ +#define COM2_OCAP_Nx_SET(N) (((N) - 1) & 0x03) /* N = [1x .. 4x] */ +#define PID 0x0A /* Product ID Number MSB */ +#define VER 0x0B /* Product ID Number LSB */ +#define COM3 0x0C /* Common control 3 */ +#define COM3_BAND_50H 0x04 /* 0 For Banding at 60H */ +#define COM3_BAND_AUTO 0x02 /* Auto Banding */ +#define COM3_SING_FR_SNAPSH 0x01 /* 0 For enable live video output after the + * snapshot sequence*/ +#define AEC 0x10 /* AEC[9:2] Exposure Value */ +#define CLKRC 0x11 /* Internal clock */ +#define CLKRC_EN 0x80 +#define CLKRC_DIV_SET(x) (((x) - 1) & 0x1F) /* CLK = XVCLK/(x) */ +#define COM7 0x12 /* Common control 7 */ +#define COM7_SRST 0x80 /* Initiates system reset. All registers are + * set to factory default values after which + * the chip resumes normal operation */ +#define COM7_RES_UXGA 0x00 /* Resolution selectors for UXGA */ +#define COM7_RES_SVGA 0x40 /* SVGA */ +#define COM7_RES_CIF 0x20 /* CIF */ +#define COM7_ZOOM_EN 0x04 /* Enable Zoom mode */ +#define COM7_COLOR_BAR_TEST 0x02 /* Enable Color Bar Test Pattern */ +#define COM8 0x13 /* Common control 8 */ +#define COM8_DEF 0xC0 +#define COM8_BNDF_EN 0x20 /* Banding filter ON/OFF */ +#define COM8_AGC_EN 0x04 /* AGC Auto/Manual control selection */ +#define COM8_AEC_EN 0x01 /* Auto/Manual Exposure control */ +#define COM9 0x14 /* Common control 9 + * Automatic gain ceiling - maximum AGC value [7:5]*/ +#define COM9_AGC_GAIN_2x 0x00 /* 000 : 2x */ +#define COM9_AGC_GAIN_4x 0x20 /* 001 : 4x */ +#define COM9_AGC_GAIN_8x 0x40 /* 010 : 8x */ +#define COM9_AGC_GAIN_16x 0x60 /* 011 : 16x */ +#define COM9_AGC_GAIN_32x 0x80 /* 100 : 32x */ +#define COM9_AGC_GAIN_64x 0xA0 /* 101 : 64x */ +#define COM9_AGC_GAIN_128x 0xC0 /* 110 : 128x */ +#define COM10 0x15 /* Common control 10 */ +#define COM10_PCLK_HREF 0x20 /* PCLK output qualified by HREF */ +#define COM10_PCLK_RISE 0x10 /* Data is updated at the rising edge of + * PCLK (user can latch data at the next + * falling edge of PCLK). + * 0 otherwise. */ +#define COM10_HREF_INV 0x08 /* Invert HREF polarity: + * HREF negative for valid data*/ +#define COM10_VSINC_INV 0x02 /* Invert VSYNC polarity */ +#define HSTART 0x17 /* Horizontal Window start MSB 8 bit */ +#define HEND 0x18 /* Horizontal Window end MSB 8 bit */ +#define VSTART 0x19 /* Vertical Window start MSB 8 bit */ +#define VEND 0x1A /* Vertical Window end MSB 8 bit */ +#define MIDH 0x1C /* Manufacturer ID byte - high */ +#define MIDL 0x1D /* Manufacturer ID byte - low */ +#define AEW 0x24 /* AGC/AEC - Stable operating region (upper limit) */ +#define AEB 0x25 /* AGC/AEC - Stable operating region (lower limit) */ +#define VV 0x26 /* AGC/AEC Fast mode operating region */ +#define VV_HIGH_TH_SET(x) VAL_SET(x, 0xF, 0, 4) +#define VV_LOW_TH_SET(x) VAL_SET(x, 0xF, 0, 0) +#define REG2A 0x2A /* Dummy pixel insert MSB */ +#define FRARL 0x2B /* Dummy pixel insert LSB */ +#define ADDVFL 0x2D /* LSB of insert dummy lines in Vertical direction */ +#define ADDVFH 0x2E /* MSB of insert dummy lines in Vertical direction */ +#define YAVG 0x2F /* Y/G Channel Average value */ +#define REG32 0x32 /* Common Control 32 */ +#define REG32_PCLK_DIV_2 0x80 /* PCLK freq divided by 2 */ +#define REG32_PCLK_DIV_4 0xC0 /* PCLK freq divided by 4 */ +#define ARCOM2 0x34 /* Zoom: Horizontal start point */ +#define REG45 0x45 /* Register 45 */ +#define FLL 0x46 /* Frame Length Adjustment LSBs */ +#define FLH 0x47 /* Frame Length Adjustment MSBs */ +#define COM19 0x48 /* Zoom: Vertical start point */ +#define ZOOMS 0x49 /* Zoom: Vertical start point */ +#define COM22 0x4B /* Flash light control */ +#define COM25 0x4E /* For Banding operations */ +#define COM25_50HZ_BANDING_AEC_MSBS_MASK 0xC0 /* 50Hz Bd. AEC 2 MSBs */ +#define COM25_60HZ_BANDING_AEC_MSBS_MASK 0x30 /* 60Hz Bd. AEC 2 MSBs */ +#define COM25_50HZ_BANDING_AEC_MSBS_SET(x) VAL_SET(x, 0x3, 8, 6) +#define COM25_60HZ_BANDING_AEC_MSBS_SET(x) VAL_SET(x, 0x3, 8, 4) +#define BD50 0x4F /* 50Hz Banding AEC 8 LSBs */ +#define BD50_50HZ_BANDING_AEC_LSBS_SET(x) VAL_SET(x, 0xFF, 0, 0) +#define BD60 0x50 /* 60Hz Banding AEC 8 LSBs */ +#define BD60_60HZ_BANDING_AEC_LSBS_SET(x) VAL_SET(x, 0xFF, 0, 0) +#define REG5A 0x5A /* 50/60Hz Banding Maximum AEC Step */ +#define BD50_MAX_AEC_STEP_MASK 0xF0 /* 50Hz Banding Max. AEC Step */ +#define BD60_MAX_AEC_STEP_MASK 0x0F /* 60Hz Banding Max. AEC Step */ +#define BD50_MAX_AEC_STEP_SET(x) VAL_SET((x - 1), 0x0F, 0, 4) +#define BD60_MAX_AEC_STEP_SET(x) VAL_SET((x - 1), 0x0F, 0, 0) +#define REG5D 0x5D /* AVGsel[7:0], 16-zone average weight option */ +#define REG5E 0x5E /* AVGsel[15:8], 16-zone average weight option */ +#define REG5F 0x5F /* AVGsel[23:16], 16-zone average weight option */ +#define REG60 0x60 /* AVGsel[31:24], 16-zone average weight option */ +#define HISTO_LOW 0x61 /* Histogram Algorithm Low Level */ +#define HISTO_HIGH 0x62 /* Histogram Algorithm High Level */ + +/* + * ID + */ +#define MANUFACTURER_ID 0x7FA2 +#define PID_OV2640 0x2642 +#define VERSION(pid, ver) ((pid << 8) | (ver & 0xFF)) + +/* + * Struct + */ +struct regval_list { + u8 reg_num; + u8 value; +}; + +struct ov2640_win_size { + char *name; + u32 width; + u32 height; + const struct regval_list *regs; +}; + + +struct ov2640_priv { + struct v4l2_subdev subdev; +#if defined(CONFIG_MEDIA_CONTROLLER) + struct media_pad pad; +#endif + struct v4l2_ctrl_handler hdl; + u32 cfmt_code; + struct clk *clk; + const struct ov2640_win_size *win; + + struct gpio_desc *resetb_gpio; + struct gpio_desc *pwdn_gpio; + + struct mutex lock; /* lock to protect streaming and power_count */ + bool streaming; + int power_count; +}; + +/* + * Registers settings + */ + +#define ENDMARKER { 0xff, 0xff } + +static const struct regval_list ov2640_init_regs[] = { + { BANK_SEL, BANK_SEL_DSP }, + { 0x2c, 0xff }, + { 0x2e, 0xdf }, + { BANK_SEL, BANK_SEL_SENS }, + { 0x3c, 0x32 }, + { CLKRC, CLKRC_DIV_SET(1) }, + { COM2, COM2_OCAP_Nx_SET(3) }, + { REG04, REG04_DEF | REG04_HREF_EN }, + { COM8, COM8_DEF | COM8_BNDF_EN | COM8_AGC_EN | COM8_AEC_EN }, + { COM9, COM9_AGC_GAIN_8x | 0x08}, + { 0x2c, 0x0c }, + { 0x33, 0x78 }, + { 0x3a, 0x33 }, + { 0x3b, 0xfb }, + { 0x3e, 0x00 }, + { 0x43, 0x11 }, + { 0x16, 0x10 }, + { 0x39, 0x02 }, + { 0x35, 0x88 }, + { 0x22, 0x0a }, + { 0x37, 0x40 }, + { 0x23, 0x00 }, + { ARCOM2, 0xa0 }, + { 0x06, 0x02 }, + { 0x06, 0x88 }, + { 0x07, 0xc0 }, + { 0x0d, 0xb7 }, + { 0x0e, 0x01 }, + { 0x4c, 0x00 }, + { 0x4a, 0x81 }, + { 0x21, 0x99 }, + { AEW, 0x40 }, + { AEB, 0x38 }, + { VV, VV_HIGH_TH_SET(0x08) | VV_LOW_TH_SET(0x02) }, + { 0x5c, 0x00 }, + { 0x63, 0x00 }, + { FLL, 0x22 }, + { COM3, 0x38 | COM3_BAND_AUTO }, + { REG5D, 0x55 }, + { REG5E, 0x7d }, + { REG5F, 0x7d }, + { REG60, 0x55 }, + { HISTO_LOW, 0x70 }, + { HISTO_HIGH, 0x80 }, + { 0x7c, 0x05 }, + { 0x20, 0x80 }, + { 0x28, 0x30 }, + { 0x6c, 0x00 }, + { 0x6d, 0x80 }, + { 0x6e, 0x00 }, + { 0x70, 0x02 }, + { 0x71, 0x94 }, + { 0x73, 0xc1 }, + { 0x3d, 0x34 }, + { COM7, COM7_RES_UXGA | COM7_ZOOM_EN }, + { REG5A, BD50_MAX_AEC_STEP_SET(6) + | BD60_MAX_AEC_STEP_SET(8) }, /* 0x57 */ + { COM25, COM25_50HZ_BANDING_AEC_MSBS_SET(0x0bb) + | COM25_60HZ_BANDING_AEC_MSBS_SET(0x09c) }, /* 0x00 */ + { BD50, BD50_50HZ_BANDING_AEC_LSBS_SET(0x0bb) }, /* 0xbb */ + { BD60, BD60_60HZ_BANDING_AEC_LSBS_SET(0x09c) }, /* 0x9c */ + { BANK_SEL, BANK_SEL_DSP }, + { 0xe5, 0x7f }, + { MC_BIST, MC_BIST_RESET | MC_BIST_BOOT_ROM_SEL }, + { 0x41, 0x24 }, + { RESET, RESET_JPEG | RESET_DVP }, + { 0x76, 0xff }, + { 0x33, 0xa0 }, + { 0x42, 0x20 }, + { 0x43, 0x18 }, + { 0x4c, 0x00 }, + { CTRL3, CTRL3_BPC_EN | CTRL3_WPC_EN | 0x10 }, + { 0x88, 0x3f }, + { 0xd7, 0x03 }, + { 0xd9, 0x10 }, + { R_DVP_SP, R_DVP_SP_AUTO_MODE | 0x2 }, + { 0xc8, 0x08 }, + { 0xc9, 0x80 }, + { BPADDR, 0x00 }, + { BPDATA, 0x00 }, + { BPADDR, 0x03 }, + { BPDATA, 0x48 }, + { BPDATA, 0x48 }, + { BPADDR, 0x08 }, + { BPDATA, 0x20 }, + { BPDATA, 0x10 }, + { BPDATA, 0x0e }, + { 0x90, 0x00 }, + { 0x91, 0x0e }, + { 0x91, 0x1a }, + { 0x91, 0x31 }, + { 0x91, 0x5a }, + { 0x91, 0x69 }, + { 0x91, 0x75 }, + { 0x91, 0x7e }, + { 0x91, 0x88 }, + { 0x91, 0x8f }, + { 0x91, 0x96 }, + { 0x91, 0xa3 }, + { 0x91, 0xaf }, + { 0x91, 0xc4 }, + { 0x91, 0xd7 }, + { 0x91, 0xe8 }, + { 0x91, 0x20 }, + { 0x92, 0x00 }, + { 0x93, 0x06 }, + { 0x93, 0xe3 }, + { 0x93, 0x03 }, + { 0x93, 0x03 }, + { 0x93, 0x00 }, + { 0x93, 0x02 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x96, 0x00 }, + { 0x97, 0x08 }, + { 0x97, 0x19 }, + { 0x97, 0x02 }, + { 0x97, 0x0c }, + { 0x97, 0x24 }, + { 0x97, 0x30 }, + { 0x97, 0x28 }, + { 0x97, 0x26 }, + { 0x97, 0x02 }, + { 0x97, 0x98 }, + { 0x97, 0x80 }, + { 0x97, 0x00 }, + { 0x97, 0x00 }, + { 0xa4, 0x00 }, + { 0xa8, 0x00 }, + { 0xc5, 0x11 }, + { 0xc6, 0x51 }, + { 0xbf, 0x80 }, + { 0xc7, 0x10 }, /* simple AWB */ + { 0xb6, 0x66 }, + { 0xb8, 0xA5 }, + { 0xb7, 0x64 }, + { 0xb9, 0x7C }, + { 0xb3, 0xaf }, + { 0xb4, 0x97 }, + { 0xb5, 0xFF }, + { 0xb0, 0xC5 }, + { 0xb1, 0x94 }, + { 0xb2, 0x0f }, + { 0xc4, 0x5c }, + { 0xa6, 0x00 }, + { 0xa7, 0x20 }, + { 0xa7, 0xd8 }, + { 0xa7, 0x1b }, + { 0xa7, 0x31 }, + { 0xa7, 0x00 }, + { 0xa7, 0x18 }, + { 0xa7, 0x20 }, + { 0xa7, 0xd8 }, + { 0xa7, 0x19 }, + { 0xa7, 0x31 }, + { 0xa7, 0x00 }, + { 0xa7, 0x18 }, + { 0xa7, 0x20 }, + { 0xa7, 0xd8 }, + { 0xa7, 0x19 }, + { 0xa7, 0x31 }, + { 0xa7, 0x00 }, + { 0xa7, 0x18 }, + { 0x7f, 0x00 }, + { 0xe5, 0x1f }, + { 0xe1, 0x77 }, + { 0xdd, 0x7f }, + { CTRL0, CTRL0_YUV422 | CTRL0_YUV_EN | CTRL0_RGB_EN }, + ENDMARKER, +}; + +/* + * Register settings for window size + * The preamble, setup the internal DSP to input an UXGA (1600x1200) image. + * Then the different zooming configurations will setup the output image size. + */ +static const struct regval_list ov2640_size_change_preamble_regs[] = { + { BANK_SEL, BANK_SEL_DSP }, + { RESET, RESET_DVP }, + { SIZEL, SIZEL_HSIZE8_11_SET(UXGA_WIDTH) | + SIZEL_HSIZE8_SET(UXGA_WIDTH) | + SIZEL_VSIZE8_SET(UXGA_HEIGHT) }, + { HSIZE8, HSIZE8_SET(UXGA_WIDTH) }, + { VSIZE8, VSIZE8_SET(UXGA_HEIGHT) }, + { CTRL2, CTRL2_DCW_EN | CTRL2_SDE_EN | + CTRL2_UV_AVG_EN | CTRL2_CMX_EN | CTRL2_UV_ADJ_EN }, + { HSIZE, HSIZE_SET(UXGA_WIDTH) }, + { VSIZE, VSIZE_SET(UXGA_HEIGHT) }, + { XOFFL, XOFFL_SET(0) }, + { YOFFL, YOFFL_SET(0) }, + { VHYX, VHYX_HSIZE_SET(UXGA_WIDTH) | VHYX_VSIZE_SET(UXGA_HEIGHT) | + VHYX_XOFF_SET(0) | VHYX_YOFF_SET(0)}, + { TEST, TEST_HSIZE_SET(UXGA_WIDTH) }, + ENDMARKER, +}; + +#define PER_SIZE_REG_SEQ(x, y, v_div, h_div, pclk_div) \ + { CTRLI, CTRLI_LP_DP | CTRLI_V_DIV_SET(v_div) | \ + CTRLI_H_DIV_SET(h_div)}, \ + { ZMOW, ZMOW_OUTW_SET(x) }, \ + { ZMOH, ZMOH_OUTH_SET(y) }, \ + { ZMHH, ZMHH_OUTW_SET(x) | ZMHH_OUTH_SET(y) }, \ + { R_DVP_SP, pclk_div }, \ + { RESET, 0x00} + +static const struct regval_list ov2640_qcif_regs[] = { + PER_SIZE_REG_SEQ(QCIF_WIDTH, QCIF_HEIGHT, 3, 3, 4), + ENDMARKER, +}; + +static const struct regval_list ov2640_qvga_regs[] = { + PER_SIZE_REG_SEQ(QVGA_WIDTH, QVGA_HEIGHT, 2, 2, 4), + ENDMARKER, +}; + +static const struct regval_list ov2640_cif_regs[] = { + PER_SIZE_REG_SEQ(CIF_WIDTH, CIF_HEIGHT, 2, 2, 8), + ENDMARKER, +}; + +static const struct regval_list ov2640_vga_regs[] = { + PER_SIZE_REG_SEQ(VGA_WIDTH, VGA_HEIGHT, 0, 0, 2), + ENDMARKER, +}; + +static const struct regval_list ov2640_svga_regs[] = { + PER_SIZE_REG_SEQ(SVGA_WIDTH, SVGA_HEIGHT, 1, 1, 2), + ENDMARKER, +}; + +static const struct regval_list ov2640_xga_regs[] = { + PER_SIZE_REG_SEQ(XGA_WIDTH, XGA_HEIGHT, 0, 0, 2), + { CTRLI, 0x00}, + ENDMARKER, +}; + +static const struct regval_list ov2640_sxga_regs[] = { + PER_SIZE_REG_SEQ(SXGA_WIDTH, SXGA_HEIGHT, 0, 0, 2), + { CTRLI, 0x00}, + { R_DVP_SP, 2 | R_DVP_SP_AUTO_MODE }, + ENDMARKER, +}; + +static const struct regval_list ov2640_uxga_regs[] = { + PER_SIZE_REG_SEQ(UXGA_WIDTH, UXGA_HEIGHT, 0, 0, 0), + { CTRLI, 0x00}, + { R_DVP_SP, 0 | R_DVP_SP_AUTO_MODE }, + ENDMARKER, +}; + +#define OV2640_SIZE(n, w, h, r) \ + {.name = n, .width = w , .height = h, .regs = r } + +static const struct ov2640_win_size ov2640_supported_win_sizes[] = { + OV2640_SIZE("QCIF", QCIF_WIDTH, QCIF_HEIGHT, ov2640_qcif_regs), + OV2640_SIZE("QVGA", QVGA_WIDTH, QVGA_HEIGHT, ov2640_qvga_regs), + OV2640_SIZE("CIF", CIF_WIDTH, CIF_HEIGHT, ov2640_cif_regs), + OV2640_SIZE("VGA", VGA_WIDTH, VGA_HEIGHT, ov2640_vga_regs), + OV2640_SIZE("SVGA", SVGA_WIDTH, SVGA_HEIGHT, ov2640_svga_regs), + OV2640_SIZE("XGA", XGA_WIDTH, XGA_HEIGHT, ov2640_xga_regs), + OV2640_SIZE("SXGA", SXGA_WIDTH, SXGA_HEIGHT, ov2640_sxga_regs), + OV2640_SIZE("UXGA", UXGA_WIDTH, UXGA_HEIGHT, ov2640_uxga_regs), +}; + +/* + * Register settings for pixel formats + */ +static const struct regval_list ov2640_format_change_preamble_regs[] = { + { BANK_SEL, BANK_SEL_DSP }, + { R_BYPASS, R_BYPASS_USE_DSP }, + ENDMARKER, +}; + +static const struct regval_list ov2640_yuyv_regs[] = { + { IMAGE_MODE, IMAGE_MODE_YUV422 }, + { 0xd7, 0x03 }, + { 0x33, 0xa0 }, + { 0xe5, 0x1f }, + { 0xe1, 0x67 }, + { RESET, 0x00 }, + { R_BYPASS, R_BYPASS_USE_DSP }, + ENDMARKER, +}; + +static const struct regval_list ov2640_uyvy_regs[] = { + { IMAGE_MODE, IMAGE_MODE_LBYTE_FIRST | IMAGE_MODE_YUV422 }, + { 0xd7, 0x01 }, + { 0x33, 0xa0 }, + { 0xe1, 0x67 }, + { RESET, 0x00 }, + { R_BYPASS, R_BYPASS_USE_DSP }, + ENDMARKER, +}; + +static const struct regval_list ov2640_rgb565_be_regs[] = { + { IMAGE_MODE, IMAGE_MODE_RGB565 }, + { 0xd7, 0x03 }, + { RESET, 0x00 }, + { R_BYPASS, R_BYPASS_USE_DSP }, + ENDMARKER, +}; + +static const struct regval_list ov2640_rgb565_le_regs[] = { + { IMAGE_MODE, IMAGE_MODE_LBYTE_FIRST | IMAGE_MODE_RGB565 }, + { 0xd7, 0x03 }, + { RESET, 0x00 }, + { R_BYPASS, R_BYPASS_USE_DSP }, + ENDMARKER, +}; + +static u32 ov2640_codes[] = { + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_VYUY8_2X8, + MEDIA_BUS_FMT_RGB565_2X8_BE, + MEDIA_BUS_FMT_RGB565_2X8_LE, +}; + +/* + * General functions + */ +static struct ov2640_priv *to_ov2640(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ov2640_priv, + subdev); +} + +static int ov2640_write_array(struct i2c_client *client, + const struct regval_list *vals) +{ + int ret; + + while ((vals->reg_num != 0xff) || (vals->value != 0xff)) { + ret = i2c_smbus_write_byte_data(client, + vals->reg_num, vals->value); + dev_vdbg(&client->dev, "array: 0x%02x, 0x%02x", + vals->reg_num, vals->value); + + if (ret < 0) + return ret; + vals++; + } + return 0; +} + +static int ov2640_mask_set(struct i2c_client *client, + u8 reg, u8 mask, u8 set) +{ + s32 val = i2c_smbus_read_byte_data(client, reg); + if (val < 0) + return val; + + val &= ~mask; + val |= set & mask; + + dev_vdbg(&client->dev, "masks: 0x%02x, 0x%02x", reg, val); + + return i2c_smbus_write_byte_data(client, reg, val); +} + +static int ov2640_reset(struct i2c_client *client) +{ + int ret; + static const struct regval_list reset_seq[] = { + {BANK_SEL, BANK_SEL_SENS}, + {COM7, COM7_SRST}, + ENDMARKER, + }; + + ret = ov2640_write_array(client, reset_seq); + if (ret) + goto err; + + msleep(5); +err: + dev_dbg(&client->dev, "%s: (ret %d)", __func__, ret); + return ret; +} + +static const char * const ov2640_test_pattern_menu[] = { + "Disabled", + "Eight Vertical Colour Bars", +}; + +/* + * functions + */ +static int ov2640_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = + &container_of(ctrl->handler, struct ov2640_priv, hdl)->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov2640_priv *priv = to_ov2640(client); + u8 val; + int ret; + + /* v4l2_ctrl_lock() locks our own mutex */ + + /* + * If the device is not powered up by the host driver, do not apply any + * controls to H/W at this time. Instead the controls will be restored + * when the streaming is started. + */ + if (!priv->power_count) + return 0; + + ret = i2c_smbus_write_byte_data(client, BANK_SEL, BANK_SEL_SENS); + if (ret < 0) + return ret; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + val = ctrl->val ? REG04_VFLIP_IMG | REG04_VREF_EN : 0x00; + return ov2640_mask_set(client, REG04, + REG04_VFLIP_IMG | REG04_VREF_EN, val); + /* NOTE: REG04_VREF_EN: 1 line shift / even/odd line swap */ + case V4L2_CID_HFLIP: + val = ctrl->val ? REG04_HFLIP_IMG : 0x00; + return ov2640_mask_set(client, REG04, REG04_HFLIP_IMG, val); + case V4L2_CID_TEST_PATTERN: + val = ctrl->val ? COM7_COLOR_BAR_TEST : 0x00; + return ov2640_mask_set(client, COM7, COM7_COLOR_BAR_TEST, val); + } + + return -EINVAL; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov2640_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + reg->size = 1; + if (reg->reg > 0xff) + return -EINVAL; + + ret = i2c_smbus_read_byte_data(client, reg->reg); + if (ret < 0) + return ret; + + reg->val = ret; + + return 0; +} + +static int ov2640_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0xff || + reg->val > 0xff) + return -EINVAL; + + return i2c_smbus_write_byte_data(client, reg->reg, reg->val); +} +#endif + +static void ov2640_set_power(struct ov2640_priv *priv, int on) +{ +#ifdef CONFIG_GPIOLIB + if (priv->pwdn_gpio) + gpiod_direction_output(priv->pwdn_gpio, !on); + if (on && priv->resetb_gpio) { + /* Active the resetb pin to perform a reset pulse */ + gpiod_direction_output(priv->resetb_gpio, 1); + usleep_range(3000, 5000); + gpiod_set_value(priv->resetb_gpio, 0); + } +#endif +} + +static int ov2640_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov2640_priv *priv = to_ov2640(client); + + mutex_lock(&priv->lock); + + /* + * If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (priv->power_count == !on) + ov2640_set_power(priv, on); + priv->power_count += on ? 1 : -1; + WARN_ON(priv->power_count < 0); + mutex_unlock(&priv->lock); + + return 0; +} + +/* Select the nearest higher resolution for capture */ +static const struct ov2640_win_size *ov2640_select_win(u32 width, u32 height) +{ + int i, default_size = ARRAY_SIZE(ov2640_supported_win_sizes) - 1; + + for (i = 0; i < ARRAY_SIZE(ov2640_supported_win_sizes); i++) { + if (ov2640_supported_win_sizes[i].width >= width && + ov2640_supported_win_sizes[i].height >= height) + return &ov2640_supported_win_sizes[i]; + } + + return &ov2640_supported_win_sizes[default_size]; +} + +static int ov2640_set_params(struct i2c_client *client, + const struct ov2640_win_size *win, u32 code) +{ + const struct regval_list *selected_cfmt_regs; + u8 val; + int ret; + + switch (code) { + case MEDIA_BUS_FMT_RGB565_2X8_BE: + dev_dbg(&client->dev, "%s: Selected cfmt RGB565 BE", __func__); + selected_cfmt_regs = ov2640_rgb565_be_regs; + break; + case MEDIA_BUS_FMT_RGB565_2X8_LE: + dev_dbg(&client->dev, "%s: Selected cfmt RGB565 LE", __func__); + selected_cfmt_regs = ov2640_rgb565_le_regs; + break; + case MEDIA_BUS_FMT_YUYV8_2X8: + dev_dbg(&client->dev, "%s: Selected cfmt YUYV (YUV422)", __func__); + selected_cfmt_regs = ov2640_yuyv_regs; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + dev_dbg(&client->dev, "%s: Selected cfmt UYVY", __func__); + selected_cfmt_regs = ov2640_uyvy_regs; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + dev_dbg(&client->dev, "%s: Selected cfmt YVYU", __func__); + selected_cfmt_regs = ov2640_yuyv_regs; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + dev_dbg(&client->dev, "%s: Selected cfmt VYUY", __func__); + selected_cfmt_regs = ov2640_uyvy_regs; + break; + } + + /* reset hardware */ + ov2640_reset(client); + + /* initialize the sensor with default data */ + dev_dbg(&client->dev, "%s: Init default", __func__); + ret = ov2640_write_array(client, ov2640_init_regs); + if (ret < 0) + goto err; + + /* select preamble */ + dev_dbg(&client->dev, "%s: Set size to %s", __func__, win->name); + ret = ov2640_write_array(client, ov2640_size_change_preamble_regs); + if (ret < 0) + goto err; + + /* set size win */ + ret = ov2640_write_array(client, win->regs); + if (ret < 0) + goto err; + + /* cfmt preamble */ + dev_dbg(&client->dev, "%s: Set cfmt", __func__); + ret = ov2640_write_array(client, ov2640_format_change_preamble_regs); + if (ret < 0) + goto err; + + /* set cfmt */ + ret = ov2640_write_array(client, selected_cfmt_regs); + if (ret < 0) + goto err; + val = (code == MEDIA_BUS_FMT_YVYU8_2X8) + || (code == MEDIA_BUS_FMT_VYUY8_2X8) ? CTRL0_VFIRST : 0x00; + ret = ov2640_mask_set(client, CTRL0, CTRL0_VFIRST, val); + if (ret < 0) + goto err; + + return 0; + +err: + dev_err(&client->dev, "%s: Error %d", __func__, ret); + ov2640_reset(client); + + return ret; +} + +static int ov2640_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov2640_priv *priv = to_ov2640(client); + + if (format->pad) + return -EINVAL; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + format->format = *mf; + return 0; +#else + return -EINVAL; +#endif + } + + mf->width = priv->win->width; + mf->height = priv->win->height; + mf->code = priv->cfmt_code; + mf->colorspace = V4L2_COLORSPACE_SRGB; + mf->field = V4L2_FIELD_NONE; + mf->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + mf->quantization = V4L2_QUANTIZATION_DEFAULT; + mf->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return 0; +} + +static int ov2640_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov2640_priv *priv = to_ov2640(client); + const struct ov2640_win_size *win; + int ret = 0; + + if (format->pad) + return -EINVAL; + + mutex_lock(&priv->lock); + + /* select suitable win */ + win = ov2640_select_win(mf->width, mf->height); + mf->width = win->width; + mf->height = win->height; + + mf->field = V4L2_FIELD_NONE; + mf->colorspace = V4L2_COLORSPACE_SRGB; + mf->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + mf->quantization = V4L2_QUANTIZATION_DEFAULT; + mf->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + switch (mf->code) { + case MEDIA_BUS_FMT_RGB565_2X8_BE: + case MEDIA_BUS_FMT_RGB565_2X8_LE: + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_YVYU8_2X8: + case MEDIA_BUS_FMT_VYUY8_2X8: + break; + default: + mf->code = MEDIA_BUS_FMT_UYVY8_2X8; + break; + } + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + struct ov2640_priv *priv = to_ov2640(client); + + if (priv->streaming) { + ret = -EBUSY; + goto out; + } + /* select win */ + priv->win = win; + /* select format */ + priv->cfmt_code = mf->code; + } else { + cfg->try_fmt = *mf; + } +out: + mutex_unlock(&priv->lock); + + return ret; +} + +static int ov2640_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, cfg, 0); + const struct ov2640_win_size *win = + ov2640_select_win(SVGA_WIDTH, SVGA_HEIGHT); + + try_fmt->width = win->width; + try_fmt->height = win->height; + try_fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + try_fmt->colorspace = V4L2_COLORSPACE_SRGB; + try_fmt->field = V4L2_FIELD_NONE; + try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT; + try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +#endif + return 0; +} + +static int ov2640_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(ov2640_codes)) + return -EINVAL; + + code->code = ov2640_codes[code->index]; + return 0; +} + +static int ov2640_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = UXGA_WIDTH; + sel->r.height = UXGA_HEIGHT; + return 0; + default: + return -EINVAL; + } +} + +static int ov2640_s_stream(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov2640_priv *priv = to_ov2640(client); + int ret = 0; + + mutex_lock(&priv->lock); + if (priv->streaming == !on) { + if (on) { + ret = ov2640_set_params(client, priv->win, + priv->cfmt_code); + if (!ret) + ret = __v4l2_ctrl_handler_setup(&priv->hdl); + } + } + if (!ret) + priv->streaming = on; + mutex_unlock(&priv->lock); + + return ret; +} + +static int ov2640_video_probe(struct i2c_client *client) +{ + struct ov2640_priv *priv = to_ov2640(client); + u8 pid, ver, midh, midl; + const char *devname; + int ret; + + ret = ov2640_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* + * check and show product ID and manufacturer ID + */ + i2c_smbus_write_byte_data(client, BANK_SEL, BANK_SEL_SENS); + pid = i2c_smbus_read_byte_data(client, PID); + ver = i2c_smbus_read_byte_data(client, VER); + midh = i2c_smbus_read_byte_data(client, MIDH); + midl = i2c_smbus_read_byte_data(client, MIDL); + + switch (VERSION(pid, ver)) { + case PID_OV2640: + devname = "ov2640"; + break; + default: + dev_err(&client->dev, + "Product ID error %x:%x\n", pid, ver); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, + "%s Product ID %0x:%0x Manufacturer ID %x:%x\n", + devname, pid, ver, midh, midl); + +done: + ov2640_s_power(&priv->subdev, 0); + return ret; +} + +static const struct v4l2_ctrl_ops ov2640_ctrl_ops = { + .s_ctrl = ov2640_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops ov2640_subdev_core_ops = { + .log_status = v4l2_ctrl_subdev_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov2640_g_register, + .s_register = ov2640_s_register, +#endif + .s_power = ov2640_s_power, +}; + +static const struct v4l2_subdev_pad_ops ov2640_subdev_pad_ops = { + .init_cfg = ov2640_init_cfg, + .enum_mbus_code = ov2640_enum_mbus_code, + .get_selection = ov2640_get_selection, + .get_fmt = ov2640_get_fmt, + .set_fmt = ov2640_set_fmt, +}; + +static const struct v4l2_subdev_video_ops ov2640_subdev_video_ops = { + .s_stream = ov2640_s_stream, +}; + +static const struct v4l2_subdev_ops ov2640_subdev_ops = { + .core = &ov2640_subdev_core_ops, + .pad = &ov2640_subdev_pad_ops, + .video = &ov2640_subdev_video_ops, +}; + +static int ov2640_probe_dt(struct i2c_client *client, + struct ov2640_priv *priv) +{ + int ret; + + /* Request the reset GPIO deasserted */ + priv->resetb_gpio = devm_gpiod_get_optional(&client->dev, "resetb", + GPIOD_OUT_LOW); + + if (!priv->resetb_gpio) + dev_dbg(&client->dev, "resetb gpio is not assigned!\n"); + + ret = PTR_ERR_OR_ZERO(priv->resetb_gpio); + if (ret && ret != -ENOSYS) { + dev_dbg(&client->dev, + "Error %d while getting resetb gpio\n", ret); + return ret; + } + + /* Request the power down GPIO asserted */ + priv->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "pwdn", + GPIOD_OUT_HIGH); + + if (!priv->pwdn_gpio) + dev_dbg(&client->dev, "pwdn gpio is not assigned!\n"); + + ret = PTR_ERR_OR_ZERO(priv->pwdn_gpio); + if (ret && ret != -ENOSYS) { + dev_dbg(&client->dev, + "Error %d while getting pwdn gpio\n", ret); + return ret; + } + + return 0; +} + +/* + * i2c_driver functions + */ +static int ov2640_probe(struct i2c_client *client) +{ + struct ov2640_priv *priv; + struct i2c_adapter *adapter = client->adapter; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&adapter->dev, + "OV2640: I2C-Adapter doesn't support SMBUS\n"); + return -EIO; + } + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (client->dev.of_node) { + priv->clk = devm_clk_get(&client->dev, "xvclk"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + } + + ret = ov2640_probe_dt(client, priv); + if (ret) + goto err_clk; + + priv->win = ov2640_select_win(SVGA_WIDTH, SVGA_HEIGHT); + priv->cfmt_code = MEDIA_BUS_FMT_UYVY8_2X8; + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov2640_subdev_ops); + priv->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + mutex_init(&priv->lock); + v4l2_ctrl_handler_init(&priv->hdl, 3); + priv->hdl.lock = &priv->lock; + v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std_menu_items(&priv->hdl, &ov2640_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov2640_test_pattern_menu) - 1, 0, 0, + ov2640_test_pattern_menu); + priv->subdev.ctrl_handler = &priv->hdl; + if (priv->hdl.error) { + ret = priv->hdl.error; + goto err_hdl; + } +#if defined(CONFIG_MEDIA_CONTROLLER) + priv->pad.flags = MEDIA_PAD_FL_SOURCE; + priv->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&priv->subdev.entity, 1, &priv->pad); + if (ret < 0) + goto err_hdl; +#endif + + ret = ov2640_video_probe(client); + if (ret < 0) + goto err_videoprobe; + + ret = v4l2_async_register_subdev(&priv->subdev); + if (ret < 0) + goto err_videoprobe; + + dev_info(&adapter->dev, "OV2640 Probed\n"); + + return 0; + +err_videoprobe: + media_entity_cleanup(&priv->subdev.entity); +err_hdl: + v4l2_ctrl_handler_free(&priv->hdl); + mutex_destroy(&priv->lock); +err_clk: + clk_disable_unprepare(priv->clk); + return ret; +} + +static int ov2640_remove(struct i2c_client *client) +{ + struct ov2640_priv *priv = to_ov2640(client); + + v4l2_async_unregister_subdev(&priv->subdev); + v4l2_ctrl_handler_free(&priv->hdl); + mutex_destroy(&priv->lock); + media_entity_cleanup(&priv->subdev.entity); + v4l2_device_unregister_subdev(&priv->subdev); + clk_disable_unprepare(priv->clk); + return 0; +} + +static const struct i2c_device_id ov2640_id[] = { + { "ov2640", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov2640_id); + +static const struct of_device_id ov2640_of_match[] = { + {.compatible = "ovti,ov2640", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ov2640_of_match); + +static struct i2c_driver ov2640_i2c_driver = { + .driver = { + .name = "ov2640", + .of_match_table = of_match_ptr(ov2640_of_match), + }, + .probe_new = ov2640_probe, + .remove = ov2640_remove, + .id_table = ov2640_id, +}; + +module_i2c_driver(ov2640_i2c_driver); + +MODULE_DESCRIPTION("Driver for Omni Vision 2640 sensor"); +MODULE_AUTHOR("Alberto Panizzo"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov2659.c b/drivers/media/i2c/ov2659.c new file mode 100644 index 000000000..fb78a1ced --- /dev/null +++ b/drivers/media/i2c/ov2659.c @@ -0,0 +1,1601 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Omnivision OV2659 CMOS Image Sensor driver + * + * Copyright (C) 2015 Texas Instruments, Inc. + * + * Benoit Parrot <bparrot@ti.com> + * Lad, Prabhakar <prabhakar.csengg@gmail.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/pm_runtime.h> + +#include <media/i2c/ov2659.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-subdev.h> + +#define DRIVER_NAME "ov2659" + +/* + * OV2659 register definitions + */ +#define REG_SOFTWARE_STANDBY 0x0100 +#define REG_SOFTWARE_RESET 0x0103 +#define REG_IO_CTRL00 0x3000 +#define REG_IO_CTRL01 0x3001 +#define REG_IO_CTRL02 0x3002 +#define REG_OUTPUT_VALUE00 0x3008 +#define REG_OUTPUT_VALUE01 0x3009 +#define REG_OUTPUT_VALUE02 0x300d +#define REG_OUTPUT_SELECT00 0x300e +#define REG_OUTPUT_SELECT01 0x300f +#define REG_OUTPUT_SELECT02 0x3010 +#define REG_OUTPUT_DRIVE 0x3011 +#define REG_INPUT_READOUT00 0x302d +#define REG_INPUT_READOUT01 0x302e +#define REG_INPUT_READOUT02 0x302f + +#define REG_SC_PLL_CTRL0 0x3003 +#define REG_SC_PLL_CTRL1 0x3004 +#define REG_SC_PLL_CTRL2 0x3005 +#define REG_SC_PLL_CTRL3 0x3006 +#define REG_SC_CHIP_ID_H 0x300a +#define REG_SC_CHIP_ID_L 0x300b +#define REG_SC_PWC 0x3014 +#define REG_SC_CLKRST0 0x301a +#define REG_SC_CLKRST1 0x301b +#define REG_SC_CLKRST2 0x301c +#define REG_SC_CLKRST3 0x301d +#define REG_SC_SUB_ID 0x302a +#define REG_SC_SCCB_ID 0x302b + +#define REG_GROUP_ADDRESS_00 0x3200 +#define REG_GROUP_ADDRESS_01 0x3201 +#define REG_GROUP_ADDRESS_02 0x3202 +#define REG_GROUP_ADDRESS_03 0x3203 +#define REG_GROUP_ACCESS 0x3208 + +#define REG_AWB_R_GAIN_H 0x3400 +#define REG_AWB_R_GAIN_L 0x3401 +#define REG_AWB_G_GAIN_H 0x3402 +#define REG_AWB_G_GAIN_L 0x3403 +#define REG_AWB_B_GAIN_H 0x3404 +#define REG_AWB_B_GAIN_L 0x3405 +#define REG_AWB_MANUAL_CONTROL 0x3406 + +#define REG_TIMING_HS_H 0x3800 +#define REG_TIMING_HS_L 0x3801 +#define REG_TIMING_VS_H 0x3802 +#define REG_TIMING_VS_L 0x3803 +#define REG_TIMING_HW_H 0x3804 +#define REG_TIMING_HW_L 0x3805 +#define REG_TIMING_VH_H 0x3806 +#define REG_TIMING_VH_L 0x3807 +#define REG_TIMING_DVPHO_H 0x3808 +#define REG_TIMING_DVPHO_L 0x3809 +#define REG_TIMING_DVPVO_H 0x380a +#define REG_TIMING_DVPVO_L 0x380b +#define REG_TIMING_HTS_H 0x380c +#define REG_TIMING_HTS_L 0x380d +#define REG_TIMING_VTS_H 0x380e +#define REG_TIMING_VTS_L 0x380f +#define REG_TIMING_HOFFS_H 0x3810 +#define REG_TIMING_HOFFS_L 0x3811 +#define REG_TIMING_VOFFS_H 0x3812 +#define REG_TIMING_VOFFS_L 0x3813 +#define REG_TIMING_XINC 0x3814 +#define REG_TIMING_YINC 0x3815 +#define REG_TIMING_VERT_FORMAT 0x3820 +#define REG_TIMING_HORIZ_FORMAT 0x3821 + +#define REG_FORMAT_CTRL00 0x4300 + +#define REG_VFIFO_READ_START_H 0x4608 +#define REG_VFIFO_READ_START_L 0x4609 + +#define REG_DVP_CTRL02 0x4708 + +#define REG_ISP_CTRL00 0x5000 +#define REG_ISP_CTRL01 0x5001 +#define REG_ISP_CTRL02 0x5002 + +#define REG_LENC_RED_X0_H 0x500c +#define REG_LENC_RED_X0_L 0x500d +#define REG_LENC_RED_Y0_H 0x500e +#define REG_LENC_RED_Y0_L 0x500f +#define REG_LENC_RED_A1 0x5010 +#define REG_LENC_RED_B1 0x5011 +#define REG_LENC_RED_A2_B2 0x5012 +#define REG_LENC_GREEN_X0_H 0x5013 +#define REG_LENC_GREEN_X0_L 0x5014 +#define REG_LENC_GREEN_Y0_H 0x5015 +#define REG_LENC_GREEN_Y0_L 0x5016 +#define REG_LENC_GREEN_A1 0x5017 +#define REG_LENC_GREEN_B1 0x5018 +#define REG_LENC_GREEN_A2_B2 0x5019 +#define REG_LENC_BLUE_X0_H 0x501a +#define REG_LENC_BLUE_X0_L 0x501b +#define REG_LENC_BLUE_Y0_H 0x501c +#define REG_LENC_BLUE_Y0_L 0x501d +#define REG_LENC_BLUE_A1 0x501e +#define REG_LENC_BLUE_B1 0x501f +#define REG_LENC_BLUE_A2_B2 0x5020 + +#define REG_AWB_CTRL00 0x5035 +#define REG_AWB_CTRL01 0x5036 +#define REG_AWB_CTRL02 0x5037 +#define REG_AWB_CTRL03 0x5038 +#define REG_AWB_CTRL04 0x5039 +#define REG_AWB_LOCAL_LIMIT 0x503a +#define REG_AWB_CTRL12 0x5049 +#define REG_AWB_CTRL13 0x504a +#define REG_AWB_CTRL14 0x504b + +#define REG_SHARPENMT_THRESH1 0x5064 +#define REG_SHARPENMT_THRESH2 0x5065 +#define REG_SHARPENMT_OFFSET1 0x5066 +#define REG_SHARPENMT_OFFSET2 0x5067 +#define REG_DENOISE_THRESH1 0x5068 +#define REG_DENOISE_THRESH2 0x5069 +#define REG_DENOISE_OFFSET1 0x506a +#define REG_DENOISE_OFFSET2 0x506b +#define REG_SHARPEN_THRESH1 0x506c +#define REG_SHARPEN_THRESH2 0x506d +#define REG_CIP_CTRL00 0x506e +#define REG_CIP_CTRL01 0x506f + +#define REG_CMX_SIGN 0x5079 +#define REG_CMX_MISC_CTRL 0x507a + +#define REG_PRE_ISP_CTRL00 0x50a0 +#define TEST_PATTERN_ENABLE BIT(7) +#define VERTICAL_COLOR_BAR_MASK 0x53 + +#define REG_NULL 0x0000 /* Array end token */ + +#define OV265X_ID(_msb, _lsb) ((_msb) << 8 | (_lsb)) +#define OV2659_ID 0x2656 + +struct sensor_register { + u16 addr; + u8 value; +}; + +struct ov2659_framesize { + u16 width; + u16 height; + u16 max_exp_lines; + const struct sensor_register *regs; +}; + +struct ov2659_pll_ctrl { + u8 ctrl1; + u8 ctrl2; + u8 ctrl3; +}; + +struct ov2659_pixfmt { + u32 code; + /* Output format Register Value (REG_FORMAT_CTRL00) */ + struct sensor_register *format_ctrl_regs; +}; + +struct pll_ctrl_reg { + unsigned int div; + unsigned char reg; +}; + +struct ov2659 { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_mbus_framefmt format; + unsigned int xvclk_frequency; + const struct ov2659_platform_data *pdata; + struct mutex lock; + struct i2c_client *client; + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *link_frequency; + struct clk *clk; + const struct ov2659_framesize *frame_size; + struct sensor_register *format_ctrl_regs; + struct ov2659_pll_ctrl pll; + int streaming; + /* used to control the sensor PWDN pin */ + struct gpio_desc *pwdn_gpio; + /* used to control the sensor RESETB pin */ + struct gpio_desc *resetb_gpio; +}; + +static const struct sensor_register ov2659_init_regs[] = { + { REG_IO_CTRL00, 0x03 }, + { REG_IO_CTRL01, 0xff }, + { REG_IO_CTRL02, 0xe0 }, + { 0x3633, 0x3d }, + { 0x3620, 0x02 }, + { 0x3631, 0x11 }, + { 0x3612, 0x04 }, + { 0x3630, 0x20 }, + { 0x4702, 0x02 }, + { 0x370c, 0x34 }, + { REG_TIMING_HS_H, 0x00 }, + { REG_TIMING_HS_L, 0x00 }, + { REG_TIMING_VS_H, 0x00 }, + { REG_TIMING_VS_L, 0x00 }, + { REG_TIMING_HW_H, 0x06 }, + { REG_TIMING_HW_L, 0x5f }, + { REG_TIMING_VH_H, 0x04 }, + { REG_TIMING_VH_L, 0xb7 }, + { REG_TIMING_DVPHO_H, 0x03 }, + { REG_TIMING_DVPHO_L, 0x20 }, + { REG_TIMING_DVPVO_H, 0x02 }, + { REG_TIMING_DVPVO_L, 0x58 }, + { REG_TIMING_HTS_H, 0x05 }, + { REG_TIMING_HTS_L, 0x14 }, + { REG_TIMING_VTS_H, 0x02 }, + { REG_TIMING_VTS_L, 0x68 }, + { REG_TIMING_HOFFS_L, 0x08 }, + { REG_TIMING_VOFFS_L, 0x02 }, + { REG_TIMING_XINC, 0x31 }, + { REG_TIMING_YINC, 0x31 }, + { 0x3a02, 0x02 }, + { 0x3a03, 0x68 }, + { 0x3a08, 0x00 }, + { 0x3a09, 0x5c }, + { 0x3a0a, 0x00 }, + { 0x3a0b, 0x4d }, + { 0x3a0d, 0x08 }, + { 0x3a0e, 0x06 }, + { 0x3a14, 0x02 }, + { 0x3a15, 0x28 }, + { REG_DVP_CTRL02, 0x01 }, + { 0x3623, 0x00 }, + { 0x3634, 0x76 }, + { 0x3701, 0x44 }, + { 0x3702, 0x18 }, + { 0x3703, 0x24 }, + { 0x3704, 0x24 }, + { 0x3705, 0x0c }, + { REG_TIMING_VERT_FORMAT, 0x81 }, + { REG_TIMING_HORIZ_FORMAT, 0x01 }, + { 0x370a, 0x52 }, + { REG_VFIFO_READ_START_H, 0x00 }, + { REG_VFIFO_READ_START_L, 0x80 }, + { REG_FORMAT_CTRL00, 0x30 }, + { 0x5086, 0x02 }, + { REG_ISP_CTRL00, 0xfb }, + { REG_ISP_CTRL01, 0x1f }, + { REG_ISP_CTRL02, 0x00 }, + { 0x5025, 0x0e }, + { 0x5026, 0x18 }, + { 0x5027, 0x34 }, + { 0x5028, 0x4c }, + { 0x5029, 0x62 }, + { 0x502a, 0x74 }, + { 0x502b, 0x85 }, + { 0x502c, 0x92 }, + { 0x502d, 0x9e }, + { 0x502e, 0xb2 }, + { 0x502f, 0xc0 }, + { 0x5030, 0xcc }, + { 0x5031, 0xe0 }, + { 0x5032, 0xee }, + { 0x5033, 0xf6 }, + { 0x5034, 0x11 }, + { 0x5070, 0x1c }, + { 0x5071, 0x5b }, + { 0x5072, 0x05 }, + { 0x5073, 0x20 }, + { 0x5074, 0x94 }, + { 0x5075, 0xb4 }, + { 0x5076, 0xb4 }, + { 0x5077, 0xaf }, + { 0x5078, 0x05 }, + { REG_CMX_SIGN, 0x98 }, + { REG_CMX_MISC_CTRL, 0x21 }, + { REG_AWB_CTRL00, 0x6a }, + { REG_AWB_CTRL01, 0x11 }, + { REG_AWB_CTRL02, 0x92 }, + { REG_AWB_CTRL03, 0x21 }, + { REG_AWB_CTRL04, 0xe1 }, + { REG_AWB_LOCAL_LIMIT, 0x01 }, + { 0x503c, 0x05 }, + { 0x503d, 0x08 }, + { 0x503e, 0x08 }, + { 0x503f, 0x64 }, + { 0x5040, 0x58 }, + { 0x5041, 0x2a }, + { 0x5042, 0xc5 }, + { 0x5043, 0x2e }, + { 0x5044, 0x3a }, + { 0x5045, 0x3c }, + { 0x5046, 0x44 }, + { 0x5047, 0xf8 }, + { 0x5048, 0x08 }, + { REG_AWB_CTRL12, 0x70 }, + { REG_AWB_CTRL13, 0xf0 }, + { REG_AWB_CTRL14, 0xf0 }, + { REG_LENC_RED_X0_H, 0x03 }, + { REG_LENC_RED_X0_L, 0x20 }, + { REG_LENC_RED_Y0_H, 0x02 }, + { REG_LENC_RED_Y0_L, 0x5c }, + { REG_LENC_RED_A1, 0x48 }, + { REG_LENC_RED_B1, 0x00 }, + { REG_LENC_RED_A2_B2, 0x66 }, + { REG_LENC_GREEN_X0_H, 0x03 }, + { REG_LENC_GREEN_X0_L, 0x30 }, + { REG_LENC_GREEN_Y0_H, 0x02 }, + { REG_LENC_GREEN_Y0_L, 0x7c }, + { REG_LENC_GREEN_A1, 0x40 }, + { REG_LENC_GREEN_B1, 0x00 }, + { REG_LENC_GREEN_A2_B2, 0x66 }, + { REG_LENC_BLUE_X0_H, 0x03 }, + { REG_LENC_BLUE_X0_L, 0x10 }, + { REG_LENC_BLUE_Y0_H, 0x02 }, + { REG_LENC_BLUE_Y0_L, 0x7c }, + { REG_LENC_BLUE_A1, 0x3a }, + { REG_LENC_BLUE_B1, 0x00 }, + { REG_LENC_BLUE_A2_B2, 0x66 }, + { REG_CIP_CTRL00, 0x44 }, + { REG_SHARPENMT_THRESH1, 0x08 }, + { REG_SHARPENMT_THRESH2, 0x10 }, + { REG_SHARPENMT_OFFSET1, 0x12 }, + { REG_SHARPENMT_OFFSET2, 0x02 }, + { REG_SHARPEN_THRESH1, 0x08 }, + { REG_SHARPEN_THRESH2, 0x10 }, + { REG_CIP_CTRL01, 0xa6 }, + { REG_DENOISE_THRESH1, 0x08 }, + { REG_DENOISE_THRESH2, 0x10 }, + { REG_DENOISE_OFFSET1, 0x04 }, + { REG_DENOISE_OFFSET2, 0x12 }, + { 0x507e, 0x40 }, + { 0x507f, 0x20 }, + { 0x507b, 0x02 }, + { REG_CMX_MISC_CTRL, 0x01 }, + { 0x5084, 0x0c }, + { 0x5085, 0x3e }, + { 0x5005, 0x80 }, + { 0x3a0f, 0x30 }, + { 0x3a10, 0x28 }, + { 0x3a1b, 0x32 }, + { 0x3a1e, 0x26 }, + { 0x3a11, 0x60 }, + { 0x3a1f, 0x14 }, + { 0x5060, 0x69 }, + { 0x5061, 0x7d }, + { 0x5062, 0x7d }, + { 0x5063, 0x69 }, + { REG_NULL, 0x00 }, +}; + +/* 1280X720 720p */ +static struct sensor_register ov2659_720p[] = { + { REG_TIMING_HS_H, 0x00 }, + { REG_TIMING_HS_L, 0xa0 }, + { REG_TIMING_VS_H, 0x00 }, + { REG_TIMING_VS_L, 0xf0 }, + { REG_TIMING_HW_H, 0x05 }, + { REG_TIMING_HW_L, 0xbf }, + { REG_TIMING_VH_H, 0x03 }, + { REG_TIMING_VH_L, 0xcb }, + { REG_TIMING_DVPHO_H, 0x05 }, + { REG_TIMING_DVPHO_L, 0x00 }, + { REG_TIMING_DVPVO_H, 0x02 }, + { REG_TIMING_DVPVO_L, 0xd0 }, + { REG_TIMING_HTS_H, 0x06 }, + { REG_TIMING_HTS_L, 0x4c }, + { REG_TIMING_VTS_H, 0x02 }, + { REG_TIMING_VTS_L, 0xe8 }, + { REG_TIMING_HOFFS_L, 0x10 }, + { REG_TIMING_VOFFS_L, 0x06 }, + { REG_TIMING_XINC, 0x11 }, + { REG_TIMING_YINC, 0x11 }, + { REG_TIMING_VERT_FORMAT, 0x80 }, + { REG_TIMING_HORIZ_FORMAT, 0x00 }, + { 0x370a, 0x12 }, + { 0x3a03, 0xe8 }, + { 0x3a09, 0x6f }, + { 0x3a0b, 0x5d }, + { 0x3a15, 0x9a }, + { REG_VFIFO_READ_START_H, 0x00 }, + { REG_VFIFO_READ_START_L, 0x80 }, + { REG_ISP_CTRL02, 0x00 }, + { REG_NULL, 0x00 }, +}; + +/* 1600X1200 UXGA */ +static struct sensor_register ov2659_uxga[] = { + { REG_TIMING_HS_H, 0x00 }, + { REG_TIMING_HS_L, 0x00 }, + { REG_TIMING_VS_H, 0x00 }, + { REG_TIMING_VS_L, 0x00 }, + { REG_TIMING_HW_H, 0x06 }, + { REG_TIMING_HW_L, 0x5f }, + { REG_TIMING_VH_H, 0x04 }, + { REG_TIMING_VH_L, 0xbb }, + { REG_TIMING_DVPHO_H, 0x06 }, + { REG_TIMING_DVPHO_L, 0x40 }, + { REG_TIMING_DVPVO_H, 0x04 }, + { REG_TIMING_DVPVO_L, 0xb0 }, + { REG_TIMING_HTS_H, 0x07 }, + { REG_TIMING_HTS_L, 0x9f }, + { REG_TIMING_VTS_H, 0x04 }, + { REG_TIMING_VTS_L, 0xd0 }, + { REG_TIMING_HOFFS_L, 0x10 }, + { REG_TIMING_VOFFS_L, 0x06 }, + { REG_TIMING_XINC, 0x11 }, + { REG_TIMING_YINC, 0x11 }, + { 0x3a02, 0x04 }, + { 0x3a03, 0xd0 }, + { 0x3a08, 0x00 }, + { 0x3a09, 0xb8 }, + { 0x3a0a, 0x00 }, + { 0x3a0b, 0x9a }, + { 0x3a0d, 0x08 }, + { 0x3a0e, 0x06 }, + { 0x3a14, 0x04 }, + { 0x3a15, 0x50 }, + { 0x3623, 0x00 }, + { 0x3634, 0x44 }, + { 0x3701, 0x44 }, + { 0x3702, 0x30 }, + { 0x3703, 0x48 }, + { 0x3704, 0x48 }, + { 0x3705, 0x18 }, + { REG_TIMING_VERT_FORMAT, 0x80 }, + { REG_TIMING_HORIZ_FORMAT, 0x00 }, + { 0x370a, 0x12 }, + { REG_VFIFO_READ_START_H, 0x00 }, + { REG_VFIFO_READ_START_L, 0x80 }, + { REG_ISP_CTRL02, 0x00 }, + { REG_NULL, 0x00 }, +}; + +/* 1280X1024 SXGA */ +static struct sensor_register ov2659_sxga[] = { + { REG_TIMING_HS_H, 0x00 }, + { REG_TIMING_HS_L, 0x00 }, + { REG_TIMING_VS_H, 0x00 }, + { REG_TIMING_VS_L, 0x00 }, + { REG_TIMING_HW_H, 0x06 }, + { REG_TIMING_HW_L, 0x5f }, + { REG_TIMING_VH_H, 0x04 }, + { REG_TIMING_VH_L, 0xb7 }, + { REG_TIMING_DVPHO_H, 0x05 }, + { REG_TIMING_DVPHO_L, 0x00 }, + { REG_TIMING_DVPVO_H, 0x04 }, + { REG_TIMING_DVPVO_L, 0x00 }, + { REG_TIMING_HTS_H, 0x07 }, + { REG_TIMING_HTS_L, 0x9c }, + { REG_TIMING_VTS_H, 0x04 }, + { REG_TIMING_VTS_L, 0xd0 }, + { REG_TIMING_HOFFS_L, 0x10 }, + { REG_TIMING_VOFFS_L, 0x06 }, + { REG_TIMING_XINC, 0x11 }, + { REG_TIMING_YINC, 0x11 }, + { 0x3a02, 0x02 }, + { 0x3a03, 0x68 }, + { 0x3a08, 0x00 }, + { 0x3a09, 0x5c }, + { 0x3a0a, 0x00 }, + { 0x3a0b, 0x4d }, + { 0x3a0d, 0x08 }, + { 0x3a0e, 0x06 }, + { 0x3a14, 0x02 }, + { 0x3a15, 0x28 }, + { 0x3623, 0x00 }, + { 0x3634, 0x76 }, + { 0x3701, 0x44 }, + { 0x3702, 0x18 }, + { 0x3703, 0x24 }, + { 0x3704, 0x24 }, + { 0x3705, 0x0c }, + { REG_TIMING_VERT_FORMAT, 0x80 }, + { REG_TIMING_HORIZ_FORMAT, 0x00 }, + { 0x370a, 0x52 }, + { REG_VFIFO_READ_START_H, 0x00 }, + { REG_VFIFO_READ_START_L, 0x80 }, + { REG_ISP_CTRL02, 0x00 }, + { REG_NULL, 0x00 }, +}; + +/* 1024X768 SXGA */ +static struct sensor_register ov2659_xga[] = { + { REG_TIMING_HS_H, 0x00 }, + { REG_TIMING_HS_L, 0x00 }, + { REG_TIMING_VS_H, 0x00 }, + { REG_TIMING_VS_L, 0x00 }, + { REG_TIMING_HW_H, 0x06 }, + { REG_TIMING_HW_L, 0x5f }, + { REG_TIMING_VH_H, 0x04 }, + { REG_TIMING_VH_L, 0xb7 }, + { REG_TIMING_DVPHO_H, 0x04 }, + { REG_TIMING_DVPHO_L, 0x00 }, + { REG_TIMING_DVPVO_H, 0x03 }, + { REG_TIMING_DVPVO_L, 0x00 }, + { REG_TIMING_HTS_H, 0x07 }, + { REG_TIMING_HTS_L, 0x9c }, + { REG_TIMING_VTS_H, 0x04 }, + { REG_TIMING_VTS_L, 0xd0 }, + { REG_TIMING_HOFFS_L, 0x10 }, + { REG_TIMING_VOFFS_L, 0x06 }, + { REG_TIMING_XINC, 0x11 }, + { REG_TIMING_YINC, 0x11 }, + { 0x3a02, 0x02 }, + { 0x3a03, 0x68 }, + { 0x3a08, 0x00 }, + { 0x3a09, 0x5c }, + { 0x3a0a, 0x00 }, + { 0x3a0b, 0x4d }, + { 0x3a0d, 0x08 }, + { 0x3a0e, 0x06 }, + { 0x3a14, 0x02 }, + { 0x3a15, 0x28 }, + { 0x3623, 0x00 }, + { 0x3634, 0x76 }, + { 0x3701, 0x44 }, + { 0x3702, 0x18 }, + { 0x3703, 0x24 }, + { 0x3704, 0x24 }, + { 0x3705, 0x0c }, + { REG_TIMING_VERT_FORMAT, 0x80 }, + { REG_TIMING_HORIZ_FORMAT, 0x00 }, + { 0x370a, 0x52 }, + { REG_VFIFO_READ_START_H, 0x00 }, + { REG_VFIFO_READ_START_L, 0x80 }, + { REG_ISP_CTRL02, 0x00 }, + { REG_NULL, 0x00 }, +}; + +/* 800X600 SVGA */ +static struct sensor_register ov2659_svga[] = { + { REG_TIMING_HS_H, 0x00 }, + { REG_TIMING_HS_L, 0x00 }, + { REG_TIMING_VS_H, 0x00 }, + { REG_TIMING_VS_L, 0x00 }, + { REG_TIMING_HW_H, 0x06 }, + { REG_TIMING_HW_L, 0x5f }, + { REG_TIMING_VH_H, 0x04 }, + { REG_TIMING_VH_L, 0xb7 }, + { REG_TIMING_DVPHO_H, 0x03 }, + { REG_TIMING_DVPHO_L, 0x20 }, + { REG_TIMING_DVPVO_H, 0x02 }, + { REG_TIMING_DVPVO_L, 0x58 }, + { REG_TIMING_HTS_H, 0x05 }, + { REG_TIMING_HTS_L, 0x14 }, + { REG_TIMING_VTS_H, 0x02 }, + { REG_TIMING_VTS_L, 0x68 }, + { REG_TIMING_HOFFS_L, 0x08 }, + { REG_TIMING_VOFFS_L, 0x02 }, + { REG_TIMING_XINC, 0x31 }, + { REG_TIMING_YINC, 0x31 }, + { 0x3a02, 0x02 }, + { 0x3a03, 0x68 }, + { 0x3a08, 0x00 }, + { 0x3a09, 0x5c }, + { 0x3a0a, 0x00 }, + { 0x3a0b, 0x4d }, + { 0x3a0d, 0x08 }, + { 0x3a0e, 0x06 }, + { 0x3a14, 0x02 }, + { 0x3a15, 0x28 }, + { 0x3623, 0x00 }, + { 0x3634, 0x76 }, + { 0x3701, 0x44 }, + { 0x3702, 0x18 }, + { 0x3703, 0x24 }, + { 0x3704, 0x24 }, + { 0x3705, 0x0c }, + { REG_TIMING_VERT_FORMAT, 0x81 }, + { REG_TIMING_HORIZ_FORMAT, 0x01 }, + { 0x370a, 0x52 }, + { REG_VFIFO_READ_START_H, 0x00 }, + { REG_VFIFO_READ_START_L, 0x80 }, + { REG_ISP_CTRL02, 0x00 }, + { REG_NULL, 0x00 }, +}; + +/* 640X480 VGA */ +static struct sensor_register ov2659_vga[] = { + { REG_TIMING_HS_H, 0x00 }, + { REG_TIMING_HS_L, 0x00 }, + { REG_TIMING_VS_H, 0x00 }, + { REG_TIMING_VS_L, 0x00 }, + { REG_TIMING_HW_H, 0x06 }, + { REG_TIMING_HW_L, 0x5f }, + { REG_TIMING_VH_H, 0x04 }, + { REG_TIMING_VH_L, 0xb7 }, + { REG_TIMING_DVPHO_H, 0x02 }, + { REG_TIMING_DVPHO_L, 0x80 }, + { REG_TIMING_DVPVO_H, 0x01 }, + { REG_TIMING_DVPVO_L, 0xe0 }, + { REG_TIMING_HTS_H, 0x05 }, + { REG_TIMING_HTS_L, 0x14 }, + { REG_TIMING_VTS_H, 0x02 }, + { REG_TIMING_VTS_L, 0x68 }, + { REG_TIMING_HOFFS_L, 0x08 }, + { REG_TIMING_VOFFS_L, 0x02 }, + { REG_TIMING_XINC, 0x31 }, + { REG_TIMING_YINC, 0x31 }, + { 0x3a02, 0x02 }, + { 0x3a03, 0x68 }, + { 0x3a08, 0x00 }, + { 0x3a09, 0x5c }, + { 0x3a0a, 0x00 }, + { 0x3a0b, 0x4d }, + { 0x3a0d, 0x08 }, + { 0x3a0e, 0x06 }, + { 0x3a14, 0x02 }, + { 0x3a15, 0x28 }, + { 0x3623, 0x00 }, + { 0x3634, 0x76 }, + { 0x3701, 0x44 }, + { 0x3702, 0x18 }, + { 0x3703, 0x24 }, + { 0x3704, 0x24 }, + { 0x3705, 0x0c }, + { REG_TIMING_VERT_FORMAT, 0x81 }, + { REG_TIMING_HORIZ_FORMAT, 0x01 }, + { 0x370a, 0x52 }, + { REG_VFIFO_READ_START_H, 0x00 }, + { REG_VFIFO_READ_START_L, 0xa0 }, + { REG_ISP_CTRL02, 0x10 }, + { REG_NULL, 0x00 }, +}; + +/* 320X240 QVGA */ +static struct sensor_register ov2659_qvga[] = { + { REG_TIMING_HS_H, 0x00 }, + { REG_TIMING_HS_L, 0x00 }, + { REG_TIMING_VS_H, 0x00 }, + { REG_TIMING_VS_L, 0x00 }, + { REG_TIMING_HW_H, 0x06 }, + { REG_TIMING_HW_L, 0x5f }, + { REG_TIMING_VH_H, 0x04 }, + { REG_TIMING_VH_L, 0xb7 }, + { REG_TIMING_DVPHO_H, 0x01 }, + { REG_TIMING_DVPHO_L, 0x40 }, + { REG_TIMING_DVPVO_H, 0x00 }, + { REG_TIMING_DVPVO_L, 0xf0 }, + { REG_TIMING_HTS_H, 0x05 }, + { REG_TIMING_HTS_L, 0x14 }, + { REG_TIMING_VTS_H, 0x02 }, + { REG_TIMING_VTS_L, 0x68 }, + { REG_TIMING_HOFFS_L, 0x08 }, + { REG_TIMING_VOFFS_L, 0x02 }, + { REG_TIMING_XINC, 0x31 }, + { REG_TIMING_YINC, 0x31 }, + { 0x3a02, 0x02 }, + { 0x3a03, 0x68 }, + { 0x3a08, 0x00 }, + { 0x3a09, 0x5c }, + { 0x3a0a, 0x00 }, + { 0x3a0b, 0x4d }, + { 0x3a0d, 0x08 }, + { 0x3a0e, 0x06 }, + { 0x3a14, 0x02 }, + { 0x3a15, 0x28 }, + { 0x3623, 0x00 }, + { 0x3634, 0x76 }, + { 0x3701, 0x44 }, + { 0x3702, 0x18 }, + { 0x3703, 0x24 }, + { 0x3704, 0x24 }, + { 0x3705, 0x0c }, + { REG_TIMING_VERT_FORMAT, 0x81 }, + { REG_TIMING_HORIZ_FORMAT, 0x01 }, + { 0x370a, 0x52 }, + { REG_VFIFO_READ_START_H, 0x00 }, + { REG_VFIFO_READ_START_L, 0xa0 }, + { REG_ISP_CTRL02, 0x10 }, + { REG_NULL, 0x00 }, +}; + +static const struct pll_ctrl_reg ctrl3[] = { + { 1, 0x00 }, + { 2, 0x02 }, + { 3, 0x03 }, + { 4, 0x06 }, + { 6, 0x0d }, + { 8, 0x0e }, + { 12, 0x0f }, + { 16, 0x12 }, + { 24, 0x13 }, + { 32, 0x16 }, + { 48, 0x1b }, + { 64, 0x1e }, + { 96, 0x1f }, + { 0, 0x00 }, +}; + +static const struct pll_ctrl_reg ctrl1[] = { + { 2, 0x10 }, + { 4, 0x20 }, + { 6, 0x30 }, + { 8, 0x40 }, + { 10, 0x50 }, + { 12, 0x60 }, + { 14, 0x70 }, + { 16, 0x80 }, + { 18, 0x90 }, + { 20, 0xa0 }, + { 22, 0xb0 }, + { 24, 0xc0 }, + { 26, 0xd0 }, + { 28, 0xe0 }, + { 30, 0xf0 }, + { 0, 0x00 }, +}; + +static const struct ov2659_framesize ov2659_framesizes[] = { + { /* QVGA */ + .width = 320, + .height = 240, + .regs = ov2659_qvga, + .max_exp_lines = 248, + }, { /* VGA */ + .width = 640, + .height = 480, + .regs = ov2659_vga, + .max_exp_lines = 498, + }, { /* SVGA */ + .width = 800, + .height = 600, + .regs = ov2659_svga, + .max_exp_lines = 498, + }, { /* XGA */ + .width = 1024, + .height = 768, + .regs = ov2659_xga, + .max_exp_lines = 498, + }, { /* 720P */ + .width = 1280, + .height = 720, + .regs = ov2659_720p, + .max_exp_lines = 498, + }, { /* SXGA */ + .width = 1280, + .height = 1024, + .regs = ov2659_sxga, + .max_exp_lines = 1048, + }, { /* UXGA */ + .width = 1600, + .height = 1200, + .regs = ov2659_uxga, + .max_exp_lines = 498, + }, +}; + +/* YUV422 YUYV*/ +static struct sensor_register ov2659_format_yuyv[] = { + { REG_FORMAT_CTRL00, 0x30 }, + { REG_NULL, 0x0 }, +}; + +/* YUV422 UYVY */ +static struct sensor_register ov2659_format_uyvy[] = { + { REG_FORMAT_CTRL00, 0x32 }, + { REG_NULL, 0x0 }, +}; + +/* Raw Bayer BGGR */ +static struct sensor_register ov2659_format_bggr[] = { + { REG_FORMAT_CTRL00, 0x00 }, + { REG_NULL, 0x0 }, +}; + +/* RGB565 */ +static struct sensor_register ov2659_format_rgb565[] = { + { REG_FORMAT_CTRL00, 0x60 }, + { REG_NULL, 0x0 }, +}; + +static const struct ov2659_pixfmt ov2659_formats[] = { + { + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .format_ctrl_regs = ov2659_format_yuyv, + }, { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .format_ctrl_regs = ov2659_format_uyvy, + }, { + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .format_ctrl_regs = ov2659_format_rgb565, + }, { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .format_ctrl_regs = ov2659_format_bggr, + }, +}; + +static inline struct ov2659 *to_ov2659(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov2659, sd); +} + +/* sensor register write */ +static int ov2659_write(struct i2c_client *client, u16 reg, u8 val) +{ + struct i2c_msg msg; + u8 buf[3]; + int ret; + + buf[0] = reg >> 8; + buf[1] = reg & 0xFF; + buf[2] = val; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.buf = buf; + msg.len = sizeof(buf); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret >= 0) + return 0; + + dev_dbg(&client->dev, + "ov2659 write reg(0x%x val:0x%x) failed !\n", reg, val); + + return ret; +} + +/* sensor register read */ +static int ov2659_read(struct i2c_client *client, u16 reg, u8 *val) +{ + struct i2c_msg msg[2]; + u8 buf[2]; + int ret; + + buf[0] = reg >> 8; + buf[1] = reg & 0xFF; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].buf = buf; + msg[0].len = sizeof(buf); + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].buf = buf; + msg[1].len = 1; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret >= 0) { + *val = buf[0]; + return 0; + } + + dev_dbg(&client->dev, + "ov2659 read reg(0x%x val:0x%x) failed !\n", reg, *val); + + return ret; +} + +static int ov2659_write_array(struct i2c_client *client, + const struct sensor_register *regs) +{ + int i, ret = 0; + + for (i = 0; ret == 0 && regs[i].addr; i++) + ret = ov2659_write(client, regs[i].addr, regs[i].value); + + return ret; +} + +static void ov2659_pll_calc_params(struct ov2659 *ov2659) +{ + const struct ov2659_platform_data *pdata = ov2659->pdata; + u8 ctrl1_reg = 0, ctrl2_reg = 0, ctrl3_reg = 0; + struct i2c_client *client = ov2659->client; + unsigned int desired = pdata->link_frequency; + u32 prediv, postdiv, mult; + u32 bestdelta = -1; + u32 delta, actual; + int i, j; + + for (i = 0; ctrl1[i].div != 0; i++) { + postdiv = ctrl1[i].div; + for (j = 0; ctrl3[j].div != 0; j++) { + prediv = ctrl3[j].div; + for (mult = 1; mult <= 63; mult++) { + actual = ov2659->xvclk_frequency; + actual *= mult; + actual /= prediv; + actual /= postdiv; + delta = actual - desired; + delta = abs(delta); + + if ((delta < bestdelta) || (bestdelta == -1)) { + bestdelta = delta; + ctrl1_reg = ctrl1[i].reg; + ctrl2_reg = mult; + ctrl3_reg = ctrl3[j].reg; + } + } + } + } + + ov2659->pll.ctrl1 = ctrl1_reg; + ov2659->pll.ctrl2 = ctrl2_reg; + ov2659->pll.ctrl3 = ctrl3_reg; + + dev_dbg(&client->dev, + "Actual reg config: ctrl1_reg: %02x ctrl2_reg: %02x ctrl3_reg: %02x\n", + ctrl1_reg, ctrl2_reg, ctrl3_reg); +} + +static int ov2659_set_pixel_clock(struct ov2659 *ov2659) +{ + struct i2c_client *client = ov2659->client; + struct sensor_register pll_regs[] = { + {REG_SC_PLL_CTRL1, ov2659->pll.ctrl1}, + {REG_SC_PLL_CTRL2, ov2659->pll.ctrl2}, + {REG_SC_PLL_CTRL3, ov2659->pll.ctrl3}, + {REG_NULL, 0x00}, + }; + + dev_dbg(&client->dev, "%s\n", __func__); + + return ov2659_write_array(client, pll_regs); +}; + +static void ov2659_get_default_format(struct v4l2_mbus_framefmt *format) +{ + format->width = ov2659_framesizes[2].width; + format->height = ov2659_framesizes[2].height; + format->colorspace = V4L2_COLORSPACE_SRGB; + format->code = ov2659_formats[0].code; + format->field = V4L2_FIELD_NONE; +} + +static void ov2659_set_streaming(struct ov2659 *ov2659, int on) +{ + struct i2c_client *client = ov2659->client; + int ret; + + on = !!on; + + dev_dbg(&client->dev, "%s: on: %d\n", __func__, on); + + ret = ov2659_write(client, REG_SOFTWARE_STANDBY, on); + if (ret) + dev_err(&client->dev, "ov2659 soft standby failed\n"); +} + +static int ov2659_init(struct v4l2_subdev *sd, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return ov2659_write_array(client, ov2659_init_regs); +} + +/* + * V4L2 subdev video and pad level operations + */ + +static int ov2659_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + dev_dbg(&client->dev, "%s:\n", __func__); + + if (code->index >= ARRAY_SIZE(ov2659_formats)) + return -EINVAL; + + code->code = ov2659_formats[code->index].code; + + return 0; +} + +static int ov2659_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int i = ARRAY_SIZE(ov2659_formats); + + dev_dbg(&client->dev, "%s:\n", __func__); + + if (fse->index >= ARRAY_SIZE(ov2659_framesizes)) + return -EINVAL; + + while (--i) + if (fse->code == ov2659_formats[i].code) + break; + + fse->code = ov2659_formats[i].code; + + fse->min_width = ov2659_framesizes[fse->index].width; + fse->max_width = fse->min_width; + fse->max_height = ov2659_framesizes[fse->index].height; + fse->min_height = fse->max_height; + + return 0; +} + +static int ov2659_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov2659 *ov2659 = to_ov2659(sd); + + dev_dbg(&client->dev, "ov2659_get_fmt\n"); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + mutex_lock(&ov2659->lock); + fmt->format = *mf; + mutex_unlock(&ov2659->lock); + return 0; +#else + return -EINVAL; +#endif + } + + mutex_lock(&ov2659->lock); + fmt->format = ov2659->format; + mutex_unlock(&ov2659->lock); + + dev_dbg(&client->dev, "ov2659_get_fmt: %x %dx%d\n", + ov2659->format.code, ov2659->format.width, + ov2659->format.height); + + return 0; +} + +static void __ov2659_try_frame_size(struct v4l2_mbus_framefmt *mf, + const struct ov2659_framesize **size) +{ + const struct ov2659_framesize *fsize = &ov2659_framesizes[0]; + const struct ov2659_framesize *match = NULL; + int i = ARRAY_SIZE(ov2659_framesizes); + unsigned int min_err = UINT_MAX; + + while (i--) { + int err = abs(fsize->width - mf->width) + + abs(fsize->height - mf->height); + if ((err < min_err) && (fsize->regs[0].addr)) { + min_err = err; + match = fsize; + } + fsize++; + } + + if (!match) + match = &ov2659_framesizes[2]; + + mf->width = match->width; + mf->height = match->height; + + if (size) + *size = match; +} + +static int ov2659_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int index = ARRAY_SIZE(ov2659_formats); + struct v4l2_mbus_framefmt *mf = &fmt->format; + const struct ov2659_framesize *size = NULL; + struct ov2659 *ov2659 = to_ov2659(sd); + int ret = 0; + + dev_dbg(&client->dev, "ov2659_set_fmt\n"); + + __ov2659_try_frame_size(mf, &size); + + while (--index >= 0) + if (ov2659_formats[index].code == mf->code) + break; + + if (index < 0) { + index = 0; + mf->code = ov2659_formats[index].code; + } + + mf->colorspace = V4L2_COLORSPACE_SRGB; + mf->field = V4L2_FIELD_NONE; + + mutex_lock(&ov2659->lock); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + mf = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *mf = fmt->format; +#endif + } else { + s64 val; + + if (ov2659->streaming) { + mutex_unlock(&ov2659->lock); + return -EBUSY; + } + + ov2659->frame_size = size; + ov2659->format = fmt->format; + ov2659->format_ctrl_regs = + ov2659_formats[index].format_ctrl_regs; + + if (ov2659->format.code != MEDIA_BUS_FMT_SBGGR8_1X8) + val = ov2659->pdata->link_frequency / 2; + else + val = ov2659->pdata->link_frequency; + + ret = v4l2_ctrl_s_ctrl_int64(ov2659->link_frequency, val); + if (ret < 0) + dev_warn(&client->dev, + "failed to set link_frequency rate (%d)\n", + ret); + } + + mutex_unlock(&ov2659->lock); + return ret; +} + +static int ov2659_set_frame_size(struct ov2659 *ov2659) +{ + struct i2c_client *client = ov2659->client; + + dev_dbg(&client->dev, "%s\n", __func__); + + return ov2659_write_array(ov2659->client, ov2659->frame_size->regs); +} + +static int ov2659_set_format(struct ov2659 *ov2659) +{ + struct i2c_client *client = ov2659->client; + + dev_dbg(&client->dev, "%s\n", __func__); + + return ov2659_write_array(ov2659->client, ov2659->format_ctrl_regs); +} + +static int ov2659_s_stream(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov2659 *ov2659 = to_ov2659(sd); + int ret = 0; + + dev_dbg(&client->dev, "%s: on: %d\n", __func__, on); + + mutex_lock(&ov2659->lock); + + on = !!on; + + if (ov2659->streaming == on) + goto unlock; + + if (!on) { + /* Stop Streaming Sequence */ + ov2659_set_streaming(ov2659, 0); + ov2659->streaming = on; + pm_runtime_put(&client->dev); + goto unlock; + } + + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock; + } + + ret = ov2659_init(sd, 0); + if (!ret) + ret = ov2659_set_pixel_clock(ov2659); + if (!ret) + ret = ov2659_set_frame_size(ov2659); + if (!ret) + ret = ov2659_set_format(ov2659); + if (!ret) { + ov2659_set_streaming(ov2659, 1); + ov2659->streaming = on; + } + +unlock: + mutex_unlock(&ov2659->lock); + return ret; +} + +static int ov2659_set_test_pattern(struct ov2659 *ov2659, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov2659->sd); + int ret; + u8 val; + + ret = ov2659_read(client, REG_PRE_ISP_CTRL00, &val); + if (ret < 0) + return ret; + + switch (value) { + case 0: + val &= ~TEST_PATTERN_ENABLE; + break; + case 1: + val &= VERTICAL_COLOR_BAR_MASK; + val |= TEST_PATTERN_ENABLE; + break; + } + + return ov2659_write(client, REG_PRE_ISP_CTRL00, val); +} + +static int ov2659_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov2659 *ov2659 = + container_of(ctrl->handler, struct ov2659, ctrls); + struct i2c_client *client = ov2659->client; + + /* V4L2 controls values will be applied only when power is already up */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + return ov2659_set_test_pattern(ov2659, ctrl->val); + } + + pm_runtime_put(&client->dev); + return 0; +} + +static const struct v4l2_ctrl_ops ov2659_ctrl_ops = { + .s_ctrl = ov2659_s_ctrl, +}; + +static const char * const ov2659_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bars", +}; + +static int ov2659_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2659 *ov2659 = to_ov2659(sd); + + dev_dbg(&client->dev, "%s:\n", __func__); + + gpiod_set_value(ov2659->pwdn_gpio, 1); + + clk_disable_unprepare(ov2659->clk); + + return 0; +} + +static int ov2659_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2659 *ov2659 = to_ov2659(sd); + int ret; + + dev_dbg(&client->dev, "%s:\n", __func__); + + ret = clk_prepare_enable(ov2659->clk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + return ret; + } + + gpiod_set_value(ov2659->pwdn_gpio, 0); + + if (ov2659->resetb_gpio) { + gpiod_set_value(ov2659->resetb_gpio, 1); + usleep_range(500, 1000); + gpiod_set_value(ov2659->resetb_gpio, 0); + usleep_range(3000, 5000); + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev internal operations + */ + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static int ov2659_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + + dev_dbg(&client->dev, "%s:\n", __func__); + + ov2659_get_default_format(format); + + return 0; +} +#endif + +static const struct v4l2_subdev_core_ops ov2659_subdev_core_ops = { + .log_status = v4l2_ctrl_subdev_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops ov2659_subdev_video_ops = { + .s_stream = ov2659_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov2659_subdev_pad_ops = { + .enum_mbus_code = ov2659_enum_mbus_code, + .enum_frame_size = ov2659_enum_frame_sizes, + .get_fmt = ov2659_get_fmt, + .set_fmt = ov2659_set_fmt, +}; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static const struct v4l2_subdev_ops ov2659_subdev_ops = { + .core = &ov2659_subdev_core_ops, + .video = &ov2659_subdev_video_ops, + .pad = &ov2659_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops ov2659_subdev_internal_ops = { + .open = ov2659_open, +}; +#endif + +static int ov2659_detect(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 pid = 0; + u8 ver = 0; + int ret; + + dev_dbg(&client->dev, "%s:\n", __func__); + + ret = ov2659_write(client, REG_SOFTWARE_RESET, 0x01); + if (ret != 0) { + dev_err(&client->dev, "Sensor soft reset failed\n"); + return -ENODEV; + } + usleep_range(1000, 2000); + + /* Check sensor revision */ + ret = ov2659_read(client, REG_SC_CHIP_ID_H, &pid); + if (!ret) + ret = ov2659_read(client, REG_SC_CHIP_ID_L, &ver); + + if (!ret) { + unsigned short id; + + id = OV265X_ID(pid, ver); + if (id != OV2659_ID) { + dev_err(&client->dev, + "Sensor detection failed (%04X, %d)\n", + id, ret); + ret = -ENODEV; + } else { + dev_info(&client->dev, "Found OV%04X sensor\n", id); + } + } + + return ret; +} + +static struct ov2659_platform_data * +ov2659_get_pdata(struct i2c_client *client) +{ + struct ov2659_platform_data *pdata; + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + struct device_node *endpoint; + int ret; + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return client->dev.platform_data; + + endpoint = of_graph_get_next_endpoint(client->dev.of_node, NULL); + if (!endpoint) + return NULL; + + ret = v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(endpoint), + &bus_cfg); + if (ret) { + pdata = NULL; + goto done; + } + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto done; + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(&client->dev, + "link-frequencies property not found or too many\n"); + pdata = NULL; + goto done; + } + + pdata->link_frequency = bus_cfg.link_frequencies[0]; + +done: + v4l2_fwnode_endpoint_free(&bus_cfg); + of_node_put(endpoint); + return pdata; +} + +static int ov2659_probe(struct i2c_client *client) +{ + const struct ov2659_platform_data *pdata = ov2659_get_pdata(client); + struct v4l2_subdev *sd; + struct ov2659 *ov2659; + int ret; + + if (!pdata) { + dev_err(&client->dev, "platform data not specified\n"); + return -EINVAL; + } + + ov2659 = devm_kzalloc(&client->dev, sizeof(*ov2659), GFP_KERNEL); + if (!ov2659) + return -ENOMEM; + + ov2659->pdata = pdata; + ov2659->client = client; + + ov2659->clk = devm_clk_get(&client->dev, "xvclk"); + if (IS_ERR(ov2659->clk)) + return PTR_ERR(ov2659->clk); + + ov2659->xvclk_frequency = clk_get_rate(ov2659->clk); + if (ov2659->xvclk_frequency < 6000000 || + ov2659->xvclk_frequency > 27000000) + return -EINVAL; + + /* Optional gpio don't fail if not present */ + ov2659->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(ov2659->pwdn_gpio)) + return PTR_ERR(ov2659->pwdn_gpio); + + /* Optional gpio don't fail if not present */ + ov2659->resetb_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ov2659->resetb_gpio)) + return PTR_ERR(ov2659->resetb_gpio); + + v4l2_ctrl_handler_init(&ov2659->ctrls, 2); + ov2659->link_frequency = + v4l2_ctrl_new_std(&ov2659->ctrls, &ov2659_ctrl_ops, + V4L2_CID_PIXEL_RATE, + pdata->link_frequency / 2, + pdata->link_frequency, 1, + pdata->link_frequency); + v4l2_ctrl_new_std_menu_items(&ov2659->ctrls, &ov2659_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov2659_test_pattern_menu) - 1, + 0, 0, ov2659_test_pattern_menu); + ov2659->sd.ctrl_handler = &ov2659->ctrls; + + if (ov2659->ctrls.error) { + dev_err(&client->dev, "%s: control initialization error %d\n", + __func__, ov2659->ctrls.error); + return ov2659->ctrls.error; + } + + sd = &ov2659->sd; + client->flags |= I2C_CLIENT_SCCB; +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + v4l2_i2c_subdev_init(sd, client, &ov2659_subdev_ops); + + sd->internal_ops = &ov2659_subdev_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; +#endif + +#if defined(CONFIG_MEDIA_CONTROLLER) + ov2659->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &ov2659->pad); + if (ret < 0) { + v4l2_ctrl_handler_free(&ov2659->ctrls); + return ret; + } +#endif + + mutex_init(&ov2659->lock); + + ov2659_get_default_format(&ov2659->format); + ov2659->frame_size = &ov2659_framesizes[2]; + ov2659->format_ctrl_regs = ov2659_formats[0].format_ctrl_regs; + + ret = ov2659_power_on(&client->dev); + if (ret < 0) + goto error; + + ret = ov2659_detect(sd); + if (ret < 0) + goto error; + + /* Calculate the PLL register value needed */ + ov2659_pll_calc_params(ov2659); + + ret = v4l2_async_register_subdev(&ov2659->sd); + if (ret) + goto error; + + dev_info(&client->dev, "%s sensor driver registered !!\n", sd->name); + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error: + v4l2_ctrl_handler_free(&ov2659->ctrls); + ov2659_power_off(&client->dev); + media_entity_cleanup(&sd->entity); + mutex_destroy(&ov2659->lock); + return ret; +} + +static int ov2659_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2659 *ov2659 = to_ov2659(sd); + + v4l2_ctrl_handler_free(&ov2659->ctrls); + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + mutex_destroy(&ov2659->lock); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + ov2659_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +static const struct dev_pm_ops ov2659_pm_ops = { + SET_RUNTIME_PM_OPS(ov2659_power_off, ov2659_power_on, NULL) +}; + +static const struct i2c_device_id ov2659_id[] = { + { "ov2659", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i2c, ov2659_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id ov2659_of_match[] = { + { .compatible = "ovti,ov2659", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ov2659_of_match); +#endif + +static struct i2c_driver ov2659_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + .pm = &ov2659_pm_ops, + .of_match_table = of_match_ptr(ov2659_of_match), + }, + .probe_new = ov2659_probe, + .remove = ov2659_remove, + .id_table = ov2659_id, +}; + +module_i2c_driver(ov2659_i2c_driver); + +MODULE_AUTHOR("Benoit Parrot <bparrot@ti.com>"); +MODULE_DESCRIPTION("OV2659 CMOS Image Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov2680.c b/drivers/media/i2c/ov2680.c new file mode 100644 index 000000000..731a60f6a --- /dev/null +++ b/drivers/media/i2c/ov2680.c @@ -0,0 +1,1017 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Omnivision OV2680 CMOS Image Sensor driver + * + * Copyright (C) 2018 Linaro Ltd + * + * Based on OV5640 Sensor Driver + * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2014-2017 Mentor Graphics Inc. + * + */ + +#include <asm/unaligned.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#define OV2680_XVCLK_VALUE 24000000 + +#define OV2680_CHIP_ID 0x2680 + +#define OV2680_REG_STREAM_CTRL 0x0100 +#define OV2680_REG_SOFT_RESET 0x0103 + +#define OV2680_REG_CHIP_ID_HIGH 0x300a +#define OV2680_REG_CHIP_ID_LOW 0x300b + +#define OV2680_REG_R_MANUAL 0x3503 +#define OV2680_REG_GAIN_PK 0x350a +#define OV2680_REG_EXPOSURE_PK_HIGH 0x3500 +#define OV2680_REG_TIMING_HTS 0x380c +#define OV2680_REG_TIMING_VTS 0x380e +#define OV2680_REG_FORMAT1 0x3820 +#define OV2680_REG_FORMAT2 0x3821 + +#define OV2680_REG_ISP_CTRL00 0x5080 + +#define OV2680_FRAME_RATE 30 + +#define OV2680_REG_VALUE_8BIT 1 +#define OV2680_REG_VALUE_16BIT 2 +#define OV2680_REG_VALUE_24BIT 3 + +#define OV2680_WIDTH_MAX 1600 +#define OV2680_HEIGHT_MAX 1200 + +enum ov2680_mode_id { + OV2680_MODE_QUXGA_800_600, + OV2680_MODE_720P_1280_720, + OV2680_MODE_UXGA_1600_1200, + OV2680_MODE_MAX, +}; + +struct reg_value { + u16 reg_addr; + u8 val; +}; + +static const char * const ov2680_supply_name[] = { + "DOVDD", + "DVDD", + "AVDD", +}; + +#define OV2680_NUM_SUPPLIES ARRAY_SIZE(ov2680_supply_name) + +struct ov2680_mode_info { + const char *name; + enum ov2680_mode_id id; + u32 width; + u32 height; + const struct reg_value *reg_data; + u32 reg_data_size; +}; + +struct ov2680_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *test_pattern; +}; + +struct ov2680_dev { + struct i2c_client *i2c_client; + struct v4l2_subdev sd; + + struct media_pad pad; + struct clk *xvclk; + u32 xvclk_freq; + struct regulator_bulk_data supplies[OV2680_NUM_SUPPLIES]; + + struct gpio_desc *reset_gpio; + struct mutex lock; /* protect members */ + + bool mode_pending_changes; + bool is_enabled; + bool is_streaming; + + struct ov2680_ctrls ctrls; + struct v4l2_mbus_framefmt fmt; + struct v4l2_fract frame_interval; + + const struct ov2680_mode_info *current_mode; +}; + +static const char * const test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Random Data", + "Square", + "Black Image", +}; + +static const int ov2680_hv_flip_bayer_order[] = { + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, +}; + +static const struct reg_value ov2680_setting_30fps_QUXGA_800_600[] = { + {0x3086, 0x01}, {0x370a, 0x23}, {0x3808, 0x03}, {0x3809, 0x20}, + {0x380a, 0x02}, {0x380b, 0x58}, {0x380c, 0x06}, {0x380d, 0xac}, + {0x380e, 0x02}, {0x380f, 0x84}, {0x3811, 0x04}, {0x3813, 0x04}, + {0x3814, 0x31}, {0x3815, 0x31}, {0x3820, 0xc0}, {0x4008, 0x00}, + {0x4009, 0x03}, {0x4837, 0x1e}, {0x3501, 0x4e}, {0x3502, 0xe0}, + {0x3503, 0x03}, +}; + +static const struct reg_value ov2680_setting_30fps_720P_1280_720[] = { + {0x3086, 0x00}, {0x3808, 0x05}, {0x3809, 0x00}, {0x380a, 0x02}, + {0x380b, 0xd0}, {0x380c, 0x06}, {0x380d, 0xa8}, {0x380e, 0x05}, + {0x380f, 0x0e}, {0x3811, 0x08}, {0x3813, 0x06}, {0x3814, 0x11}, + {0x3815, 0x11}, {0x3820, 0xc0}, {0x4008, 0x00}, +}; + +static const struct reg_value ov2680_setting_30fps_UXGA_1600_1200[] = { + {0x3086, 0x00}, {0x3501, 0x4e}, {0x3502, 0xe0}, {0x3808, 0x06}, + {0x3809, 0x40}, {0x380a, 0x04}, {0x380b, 0xb0}, {0x380c, 0x06}, + {0x380d, 0xa8}, {0x380e, 0x05}, {0x380f, 0x0e}, {0x3811, 0x00}, + {0x3813, 0x00}, {0x3814, 0x11}, {0x3815, 0x11}, {0x3820, 0xc0}, + {0x4008, 0x00}, {0x4837, 0x18} +}; + +static const struct ov2680_mode_info ov2680_mode_init_data = { + "mode_quxga_800_600", OV2680_MODE_QUXGA_800_600, 800, 600, + ov2680_setting_30fps_QUXGA_800_600, + ARRAY_SIZE(ov2680_setting_30fps_QUXGA_800_600), +}; + +static const struct ov2680_mode_info ov2680_mode_data[OV2680_MODE_MAX] = { + {"mode_quxga_800_600", OV2680_MODE_QUXGA_800_600, + 800, 600, ov2680_setting_30fps_QUXGA_800_600, + ARRAY_SIZE(ov2680_setting_30fps_QUXGA_800_600)}, + {"mode_720p_1280_720", OV2680_MODE_720P_1280_720, + 1280, 720, ov2680_setting_30fps_720P_1280_720, + ARRAY_SIZE(ov2680_setting_30fps_720P_1280_720)}, + {"mode_uxga_1600_1200", OV2680_MODE_UXGA_1600_1200, + 1600, 1200, ov2680_setting_30fps_UXGA_1600_1200, + ARRAY_SIZE(ov2680_setting_30fps_UXGA_1600_1200)}, +}; + +static struct ov2680_dev *to_ov2680_dev(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov2680_dev, sd); +} + +static struct device *ov2680_to_dev(struct ov2680_dev *sensor) +{ + return &sensor->i2c_client->dev; +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ov2680_dev, + ctrls.handler)->sd; +} + +static int __ov2680_write_reg(struct ov2680_dev *sensor, u16 reg, + unsigned int len, u32 val) +{ + struct i2c_client *client = sensor->i2c_client; + u8 buf[6]; + int ret; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + ret = i2c_master_send(client, buf, len + 2); + if (ret != len + 2) { + dev_err(&client->dev, "write error: reg=0x%4x: %d\n", reg, ret); + return -EIO; + } + + return 0; +} + +#define ov2680_write_reg(s, r, v) \ + __ov2680_write_reg(s, r, OV2680_REG_VALUE_8BIT, v) + +#define ov2680_write_reg16(s, r, v) \ + __ov2680_write_reg(s, r, OV2680_REG_VALUE_16BIT, v) + +#define ov2680_write_reg24(s, r, v) \ + __ov2680_write_reg(s, r, OV2680_REG_VALUE_24BIT, v) + +static int __ov2680_read_reg(struct ov2680_dev *sensor, u16 reg, + unsigned int len, u32 *val) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) { + dev_err(&client->dev, "read error: reg=0x%4x: %d\n", reg, ret); + return -EIO; + } + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +#define ov2680_read_reg(s, r, v) \ + __ov2680_read_reg(s, r, OV2680_REG_VALUE_8BIT, v) + +#define ov2680_read_reg16(s, r, v) \ + __ov2680_read_reg(s, r, OV2680_REG_VALUE_16BIT, v) + +#define ov2680_read_reg24(s, r, v) \ + __ov2680_read_reg(s, r, OV2680_REG_VALUE_24BIT, v) + +static int ov2680_mod_reg(struct ov2680_dev *sensor, u16 reg, u8 mask, u8 val) +{ + u32 readval; + int ret; + + ret = ov2680_read_reg(sensor, reg, &readval); + if (ret < 0) + return ret; + + readval &= ~mask; + val &= mask; + val |= readval; + + return ov2680_write_reg(sensor, reg, val); +} + +static int ov2680_load_regs(struct ov2680_dev *sensor, + const struct ov2680_mode_info *mode) +{ + const struct reg_value *regs = mode->reg_data; + unsigned int i; + int ret = 0; + u16 reg_addr; + u8 val; + + for (i = 0; i < mode->reg_data_size; ++i, ++regs) { + reg_addr = regs->reg_addr; + val = regs->val; + + ret = ov2680_write_reg(sensor, reg_addr, val); + if (ret) + break; + } + + return ret; +} + +static void ov2680_power_up(struct ov2680_dev *sensor) +{ + if (!sensor->reset_gpio) + return; + + gpiod_set_value(sensor->reset_gpio, 0); + usleep_range(5000, 10000); +} + +static void ov2680_power_down(struct ov2680_dev *sensor) +{ + if (!sensor->reset_gpio) + return; + + gpiod_set_value(sensor->reset_gpio, 1); + usleep_range(5000, 10000); +} + +static void ov2680_set_bayer_order(struct ov2680_dev *sensor) +{ + int hv_flip = 0; + + if (sensor->ctrls.vflip && sensor->ctrls.vflip->val) + hv_flip += 1; + + if (sensor->ctrls.hflip && sensor->ctrls.hflip->val) + hv_flip += 2; + + sensor->fmt.code = ov2680_hv_flip_bayer_order[hv_flip]; +} + +static int ov2680_set_vflip(struct ov2680_dev *sensor, s32 val) +{ + int ret; + + if (sensor->is_streaming) + return -EBUSY; + + ret = ov2680_mod_reg(sensor, OV2680_REG_FORMAT1, + BIT(2), val ? BIT(2) : 0); + if (ret < 0) + return ret; + + ov2680_set_bayer_order(sensor); + return 0; +} + +static int ov2680_set_hflip(struct ov2680_dev *sensor, s32 val) +{ + int ret; + + if (sensor->is_streaming) + return -EBUSY; + + ret = ov2680_mod_reg(sensor, OV2680_REG_FORMAT2, + BIT(2), val ? BIT(2) : 0); + if (ret < 0) + return ret; + + ov2680_set_bayer_order(sensor); + return 0; +} + +static int ov2680_test_pattern_set(struct ov2680_dev *sensor, int value) +{ + int ret; + + if (!value) + return ov2680_mod_reg(sensor, OV2680_REG_ISP_CTRL00, BIT(7), 0); + + ret = ov2680_mod_reg(sensor, OV2680_REG_ISP_CTRL00, 0x03, value - 1); + if (ret < 0) + return ret; + + ret = ov2680_mod_reg(sensor, OV2680_REG_ISP_CTRL00, BIT(7), BIT(7)); + if (ret < 0) + return ret; + + return 0; +} + +static int ov2680_gain_set(struct ov2680_dev *sensor, u32 gain) +{ + return ov2680_write_reg16(sensor, OV2680_REG_GAIN_PK, gain); +} + +static int ov2680_exposure_set(struct ov2680_dev *sensor, u32 exp) +{ + return ov2680_write_reg24(sensor, OV2680_REG_EXPOSURE_PK_HIGH, + exp << 4); +} + +static int ov2680_stream_enable(struct ov2680_dev *sensor) +{ + return ov2680_write_reg(sensor, OV2680_REG_STREAM_CTRL, 1); +} + +static int ov2680_stream_disable(struct ov2680_dev *sensor) +{ + return ov2680_write_reg(sensor, OV2680_REG_STREAM_CTRL, 0); +} + +static int ov2680_mode_set(struct ov2680_dev *sensor) +{ + int ret; + + ret = ov2680_load_regs(sensor, sensor->current_mode); + if (ret < 0) + return ret; + + /* Restore value of all ctrls */ + ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler); + if (ret < 0) + return ret; + + sensor->mode_pending_changes = false; + + return 0; +} + +static int ov2680_mode_restore(struct ov2680_dev *sensor) +{ + int ret; + + ret = ov2680_load_regs(sensor, &ov2680_mode_init_data); + if (ret < 0) + return ret; + + return ov2680_mode_set(sensor); +} + +static int ov2680_power_off(struct ov2680_dev *sensor) +{ + if (!sensor->is_enabled) + return 0; + + clk_disable_unprepare(sensor->xvclk); + ov2680_power_down(sensor); + regulator_bulk_disable(OV2680_NUM_SUPPLIES, sensor->supplies); + sensor->is_enabled = false; + + return 0; +} + +static int ov2680_power_on(struct ov2680_dev *sensor) +{ + struct device *dev = ov2680_to_dev(sensor); + int ret; + + if (sensor->is_enabled) + return 0; + + ret = regulator_bulk_enable(OV2680_NUM_SUPPLIES, sensor->supplies); + if (ret < 0) { + dev_err(dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + if (!sensor->reset_gpio) { + ret = ov2680_write_reg(sensor, OV2680_REG_SOFT_RESET, 0x01); + if (ret != 0) { + dev_err(dev, "sensor soft reset failed\n"); + goto err_disable_regulators; + } + usleep_range(1000, 2000); + } else { + ov2680_power_down(sensor); + ov2680_power_up(sensor); + } + + ret = clk_prepare_enable(sensor->xvclk); + if (ret < 0) + goto err_disable_regulators; + + sensor->is_enabled = true; + + /* Set clock lane into LP-11 state */ + ov2680_stream_enable(sensor); + usleep_range(1000, 2000); + ov2680_stream_disable(sensor); + + return 0; + +err_disable_regulators: + regulator_bulk_disable(OV2680_NUM_SUPPLIES, sensor->supplies); + return ret; +} + +static int ov2680_s_power(struct v4l2_subdev *sd, int on) +{ + struct ov2680_dev *sensor = to_ov2680_dev(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + if (on) + ret = ov2680_power_on(sensor); + else + ret = ov2680_power_off(sensor); + + if (on && ret == 0) + ret = ov2680_mode_restore(sensor); + + mutex_unlock(&sensor->lock); + + return ret; +} + +static int ov2680_s_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ov2680_dev *sensor = to_ov2680_dev(sd); + + mutex_lock(&sensor->lock); + fi->interval = sensor->frame_interval; + mutex_unlock(&sensor->lock); + + return 0; +} + +static int ov2680_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov2680_dev *sensor = to_ov2680_dev(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + if (sensor->is_streaming == !!enable) + goto unlock; + + if (enable && sensor->mode_pending_changes) { + ret = ov2680_mode_set(sensor); + if (ret < 0) + goto unlock; + } + + if (enable) + ret = ov2680_stream_enable(sensor); + else + ret = ov2680_stream_disable(sensor); + + sensor->is_streaming = !!enable; + +unlock: + mutex_unlock(&sensor->lock); + + return ret; +} + +static int ov2680_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct ov2680_dev *sensor = to_ov2680_dev(sd); + + if (code->pad != 0 || code->index != 0) + return -EINVAL; + + code->code = sensor->fmt.code; + + return 0; +} + +static int ov2680_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov2680_dev *sensor = to_ov2680_dev(sd); + struct v4l2_mbus_framefmt *fmt = NULL; + int ret = 0; + + if (format->pad != 0) + return -EINVAL; + + mutex_lock(&sensor->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + fmt = v4l2_subdev_get_try_format(&sensor->sd, cfg, format->pad); +#else + ret = -EINVAL; +#endif + } else { + fmt = &sensor->fmt; + } + + if (fmt) + format->format = *fmt; + + mutex_unlock(&sensor->lock); + + return ret; +} + +static int ov2680_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov2680_dev *sensor = to_ov2680_dev(sd); + struct v4l2_mbus_framefmt *fmt = &format->format; +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + struct v4l2_mbus_framefmt *try_fmt; +#endif + const struct ov2680_mode_info *mode; + int ret = 0; + + if (format->pad != 0) + return -EINVAL; + + mutex_lock(&sensor->lock); + + if (sensor->is_streaming) { + ret = -EBUSY; + goto unlock; + } + + mode = v4l2_find_nearest_size(ov2680_mode_data, + ARRAY_SIZE(ov2680_mode_data), width, + height, fmt->width, fmt->height); + if (!mode) { + ret = -EINVAL; + goto unlock; + } + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + try_fmt = v4l2_subdev_get_try_format(sd, cfg, 0); + format->format = *try_fmt; +#endif + goto unlock; + } + + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = sensor->fmt.code; + fmt->colorspace = sensor->fmt.colorspace; + + sensor->current_mode = mode; + sensor->fmt = format->format; + sensor->mode_pending_changes = true; + +unlock: + mutex_unlock(&sensor->lock); + + return ret; +} + +static int ov2680_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct v4l2_subdev_format fmt = { + .which = cfg ? V4L2_SUBDEV_FORMAT_TRY + : V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .width = 800, + .height = 600, + } + }; + + return ov2680_set_fmt(sd, cfg, &fmt); +} + +static int ov2680_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + int index = fse->index; + + if (index >= OV2680_MODE_MAX || index < 0) + return -EINVAL; + + fse->min_width = ov2680_mode_data[index].width; + fse->min_height = ov2680_mode_data[index].height; + fse->max_width = ov2680_mode_data[index].width; + fse->max_height = ov2680_mode_data[index].height; + + return 0; +} + +static int ov2680_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct v4l2_fract tpf; + + if (fie->index >= OV2680_MODE_MAX || fie->width > OV2680_WIDTH_MAX || + fie->height > OV2680_HEIGHT_MAX || + fie->which > V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + tpf.denominator = OV2680_FRAME_RATE; + tpf.numerator = 1; + + fie->interval = tpf; + + return 0; +} + +static int ov2680_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct ov2680_dev *sensor = to_ov2680_dev(sd); + + if (!sensor->is_enabled) + return 0; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + return ov2680_gain_set(sensor, ctrl->val); + case V4L2_CID_EXPOSURE: + return ov2680_exposure_set(sensor, ctrl->val); + case V4L2_CID_VFLIP: + return ov2680_set_vflip(sensor, ctrl->val); + case V4L2_CID_HFLIP: + return ov2680_set_hflip(sensor, ctrl->val); + case V4L2_CID_TEST_PATTERN: + return ov2680_test_pattern_set(sensor, ctrl->val); + default: + break; + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops ov2680_ctrl_ops = { + .s_ctrl = ov2680_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops ov2680_core_ops = { + .s_power = ov2680_s_power, +}; + +static const struct v4l2_subdev_video_ops ov2680_video_ops = { + .g_frame_interval = ov2680_s_g_frame_interval, + .s_frame_interval = ov2680_s_g_frame_interval, + .s_stream = ov2680_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov2680_pad_ops = { + .init_cfg = ov2680_init_cfg, + .enum_mbus_code = ov2680_enum_mbus_code, + .get_fmt = ov2680_get_fmt, + .set_fmt = ov2680_set_fmt, + .enum_frame_size = ov2680_enum_frame_size, + .enum_frame_interval = ov2680_enum_frame_interval, +}; + +static const struct v4l2_subdev_ops ov2680_subdev_ops = { + .core = &ov2680_core_ops, + .video = &ov2680_video_ops, + .pad = &ov2680_pad_ops, +}; + +static int ov2680_mode_init(struct ov2680_dev *sensor) +{ + const struct ov2680_mode_info *init_mode; + + /* set initial mode */ + sensor->fmt.code = MEDIA_BUS_FMT_SBGGR10_1X10; + sensor->fmt.width = 800; + sensor->fmt.height = 600; + sensor->fmt.field = V4L2_FIELD_NONE; + sensor->fmt.colorspace = V4L2_COLORSPACE_SRGB; + + sensor->frame_interval.denominator = OV2680_FRAME_RATE; + sensor->frame_interval.numerator = 1; + + init_mode = &ov2680_mode_init_data; + + sensor->current_mode = init_mode; + + sensor->mode_pending_changes = true; + + return 0; +} + +static int ov2680_v4l2_register(struct ov2680_dev *sensor) +{ + const struct v4l2_ctrl_ops *ops = &ov2680_ctrl_ops; + struct ov2680_ctrls *ctrls = &sensor->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int ret = 0; + + v4l2_i2c_subdev_init(&sensor->sd, sensor->i2c_client, + &ov2680_subdev_ops); + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + sensor->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; +#endif + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret < 0) + return ret; + + v4l2_ctrl_handler_init(hdl, 5); + + hdl->lock = &sensor->lock; + + ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + + ctrls->test_pattern = v4l2_ctrl_new_std_menu_items(hdl, + &ov2680_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(test_pattern_menu) - 1, + 0, 0, test_pattern_menu); + + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + 0, 32767, 1, 0); + + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, 0, 2047, 1, 0); + + if (hdl->error) { + ret = hdl->error; + goto cleanup_entity; + } + + ctrls->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + ctrls->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + sensor->sd.ctrl_handler = hdl; + + ret = v4l2_async_register_subdev(&sensor->sd); + if (ret < 0) + goto cleanup_entity; + + return 0; + +cleanup_entity: + media_entity_cleanup(&sensor->sd.entity); + v4l2_ctrl_handler_free(hdl); + + return ret; +} + +static int ov2680_get_regulators(struct ov2680_dev *sensor) +{ + int i; + + for (i = 0; i < OV2680_NUM_SUPPLIES; i++) + sensor->supplies[i].supply = ov2680_supply_name[i]; + + return devm_regulator_bulk_get(&sensor->i2c_client->dev, + OV2680_NUM_SUPPLIES, + sensor->supplies); +} + +static int ov2680_check_id(struct ov2680_dev *sensor) +{ + struct device *dev = ov2680_to_dev(sensor); + u32 chip_id; + int ret; + + ov2680_power_on(sensor); + + ret = ov2680_read_reg16(sensor, OV2680_REG_CHIP_ID_HIGH, &chip_id); + if (ret < 0) { + dev_err(dev, "failed to read chip id high\n"); + return -ENODEV; + } + + if (chip_id != OV2680_CHIP_ID) { + dev_err(dev, "chip id: 0x%04x does not match expected 0x%04x\n", + chip_id, OV2680_CHIP_ID); + return -ENODEV; + } + + return 0; +} + +static int ov2680_parse_dt(struct ov2680_dev *sensor) +{ + struct device *dev = ov2680_to_dev(sensor); + int ret; + + sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + ret = PTR_ERR_OR_ZERO(sensor->reset_gpio); + if (ret < 0) { + dev_dbg(dev, "error while getting reset gpio: %d\n", ret); + return ret; + } + + sensor->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(sensor->xvclk)) { + dev_err(dev, "xvclk clock missing or invalid\n"); + return PTR_ERR(sensor->xvclk); + } + + sensor->xvclk_freq = clk_get_rate(sensor->xvclk); + if (sensor->xvclk_freq != OV2680_XVCLK_VALUE) { + dev_err(dev, "wrong xvclk frequency %d HZ, expected: %d Hz\n", + sensor->xvclk_freq, OV2680_XVCLK_VALUE); + return -EINVAL; + } + + return 0; +} + +static int ov2680_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ov2680_dev *sensor; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->i2c_client = client; + + ret = ov2680_parse_dt(sensor); + if (ret < 0) + return -EINVAL; + + ret = ov2680_mode_init(sensor); + if (ret < 0) + return ret; + + ret = ov2680_get_regulators(sensor); + if (ret < 0) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + mutex_init(&sensor->lock); + + ret = ov2680_check_id(sensor); + if (ret < 0) + goto lock_destroy; + + ret = ov2680_v4l2_register(sensor); + if (ret < 0) + goto lock_destroy; + + dev_info(dev, "ov2680 init correctly\n"); + + return 0; + +lock_destroy: + dev_err(dev, "ov2680 init fail: %d\n", ret); + mutex_destroy(&sensor->lock); + + return ret; +} + +static int ov2680_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2680_dev *sensor = to_ov2680_dev(sd); + + v4l2_async_unregister_subdev(&sensor->sd); + mutex_destroy(&sensor->lock); + media_entity_cleanup(&sensor->sd.entity); + v4l2_ctrl_handler_free(&sensor->ctrls.handler); + + return 0; +} + +static int __maybe_unused ov2680_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2680_dev *sensor = to_ov2680_dev(sd); + + if (sensor->is_streaming) + ov2680_stream_disable(sensor); + + return 0; +} + +static int __maybe_unused ov2680_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2680_dev *sensor = to_ov2680_dev(sd); + int ret; + + if (sensor->is_streaming) { + ret = ov2680_stream_enable(sensor); + if (ret < 0) + goto stream_disable; + } + + return 0; + +stream_disable: + ov2680_stream_disable(sensor); + sensor->is_streaming = false; + + return ret; +} + +static const struct dev_pm_ops ov2680_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov2680_suspend, ov2680_resume) +}; + +static const struct of_device_id ov2680_dt_ids[] = { + { .compatible = "ovti,ov2680" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ov2680_dt_ids); + +static struct i2c_driver ov2680_i2c_driver = { + .driver = { + .name = "ov2680", + .pm = &ov2680_pm_ops, + .of_match_table = of_match_ptr(ov2680_dt_ids), + }, + .probe_new = ov2680_probe, + .remove = ov2680_remove, +}; +module_i2c_driver(ov2680_i2c_driver); + +MODULE_AUTHOR("Rui Miguel Silva <rui.silva@linaro.org>"); +MODULE_DESCRIPTION("OV2680 CMOS Image Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov2685.c b/drivers/media/i2c/ov2685.c new file mode 100644 index 000000000..6814583d9 --- /dev/null +++ b/drivers/media/i2c/ov2685.c @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ov2685 driver + * + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/sysfs.h> +#include <media/media-entity.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#define CHIP_ID 0x2685 +#define OV2685_REG_CHIP_ID 0x300a + +#define OV2685_XVCLK_FREQ 24000000 + +#define REG_SC_CTRL_MODE 0x0100 +#define SC_CTRL_MODE_STANDBY 0x0 +#define SC_CTRL_MODE_STREAMING BIT(0) + +#define OV2685_REG_EXPOSURE 0x3500 +#define OV2685_EXPOSURE_MIN 4 +#define OV2685_EXPOSURE_STEP 1 + +#define OV2685_REG_VTS 0x380e +#define OV2685_VTS_MAX 0x7fff + +#define OV2685_REG_GAIN 0x350a +#define OV2685_GAIN_MIN 0 +#define OV2685_GAIN_MAX 0x07ff +#define OV2685_GAIN_STEP 0x1 +#define OV2685_GAIN_DEFAULT 0x0036 + +#define OV2685_REG_TEST_PATTERN 0x5080 +#define OV2685_TEST_PATTERN_DISABLED 0x00 +#define OV2685_TEST_PATTERN_COLOR_BAR 0x80 +#define OV2685_TEST_PATTERN_RANDOM 0x81 +#define OV2685_TEST_PATTERN_COLOR_BAR_FADE 0x88 +#define OV2685_TEST_PATTERN_BW_SQUARE 0x92 +#define OV2685_TEST_PATTERN_COLOR_SQUARE 0x82 + +#define REG_NULL 0xFFFF + +#define OV2685_REG_VALUE_08BIT 1 +#define OV2685_REG_VALUE_16BIT 2 +#define OV2685_REG_VALUE_24BIT 3 + +#define OV2685_LANES 1 +#define OV2685_BITS_PER_SAMPLE 10 + +static const char * const ov2685_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +#define OV2685_NUM_SUPPLIES ARRAY_SIZE(ov2685_supply_names) + +struct regval { + u16 addr; + u8 val; +}; + +struct ov2685_mode { + u32 width; + u32 height; + u32 exp_def; + u32 hts_def; + u32 vts_def; + const struct regval *reg_list; +}; + +struct ov2685 { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[OV2685_NUM_SUPPLIES]; + + bool streaming; + struct mutex mutex; + struct v4l2_subdev subdev; + struct media_pad pad; + struct v4l2_ctrl *anal_gain; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *test_pattern; + struct v4l2_ctrl_handler ctrl_handler; + + const struct ov2685_mode *cur_mode; +}; + +#define to_ov2685(sd) container_of(sd, struct ov2685, subdev) + +/* PLL settings bases on 24M xvclk */ +static struct regval ov2685_1600x1200_regs[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x3002, 0x00}, + {0x3016, 0x1c}, + {0x3018, 0x44}, + {0x301d, 0xf0}, + {0x3020, 0x00}, + {0x3082, 0x37}, + {0x3083, 0x03}, + {0x3084, 0x09}, + {0x3085, 0x04}, + {0x3086, 0x00}, + {0x3087, 0x00}, + {0x3501, 0x4e}, + {0x3502, 0xe0}, + {0x3503, 0x27}, + {0x350b, 0x36}, + {0x3600, 0xb4}, + {0x3603, 0x35}, + {0x3604, 0x24}, + {0x3605, 0x00}, + {0x3620, 0x24}, + {0x3621, 0x34}, + {0x3622, 0x03}, + {0x3628, 0x10}, + {0x3705, 0x3c}, + {0x370a, 0x21}, + {0x370c, 0x50}, + {0x370d, 0xc0}, + {0x3717, 0x58}, + {0x3718, 0x80}, + {0x3720, 0x00}, + {0x3721, 0x09}, + {0x3722, 0x06}, + {0x3723, 0x59}, + {0x3738, 0x99}, + {0x3781, 0x80}, + {0x3784, 0x0c}, + {0x3789, 0x60}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x06}, + {0x3805, 0x4f}, + {0x3806, 0x04}, + {0x3807, 0xbf}, + {0x3808, 0x06}, + {0x3809, 0x40}, + {0x380a, 0x04}, + {0x380b, 0xb0}, + {0x380c, 0x06}, + {0x380d, 0xa4}, + {0x380e, 0x05}, + {0x380f, 0x0e}, + {0x3810, 0x00}, + {0x3811, 0x08}, + {0x3812, 0x00}, + {0x3813, 0x08}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3819, 0x04}, + {0x3820, 0xc0}, + {0x3821, 0x00}, + {0x3a06, 0x01}, + {0x3a07, 0x84}, + {0x3a08, 0x01}, + {0x3a09, 0x43}, + {0x3a0a, 0x24}, + {0x3a0b, 0x60}, + {0x3a0c, 0x28}, + {0x3a0d, 0x60}, + {0x3a0e, 0x04}, + {0x3a0f, 0x8c}, + {0x3a10, 0x05}, + {0x3a11, 0x0c}, + {0x4000, 0x81}, + {0x4001, 0x40}, + {0x4008, 0x02}, + {0x4009, 0x09}, + {0x4300, 0x00}, + {0x430e, 0x00}, + {0x4602, 0x02}, + {0x481b, 0x40}, + {0x481f, 0x40}, + {0x4837, 0x18}, + {0x5000, 0x1f}, + {0x5001, 0x05}, + {0x5002, 0x30}, + {0x5003, 0x04}, + {0x5004, 0x00}, + {0x5005, 0x0c}, + {0x5280, 0x15}, + {0x5281, 0x06}, + {0x5282, 0x06}, + {0x5283, 0x08}, + {0x5284, 0x1c}, + {0x5285, 0x1c}, + {0x5286, 0x20}, + {0x5287, 0x10}, + {REG_NULL, 0x00} +}; + +#define OV2685_LINK_FREQ_330MHZ 330000000 +static const s64 link_freq_menu_items[] = { + OV2685_LINK_FREQ_330MHZ +}; + +static const char * const ov2685_test_pattern_menu[] = { + "Disabled", + "Color Bar", + "Color Bar FADE", + "Random Data", + "Black White Square", + "Color Square" +}; + +static const int ov2685_test_pattern_val[] = { + OV2685_TEST_PATTERN_DISABLED, + OV2685_TEST_PATTERN_COLOR_BAR, + OV2685_TEST_PATTERN_COLOR_BAR_FADE, + OV2685_TEST_PATTERN_RANDOM, + OV2685_TEST_PATTERN_BW_SQUARE, + OV2685_TEST_PATTERN_COLOR_SQUARE, +}; + +static const struct ov2685_mode supported_modes[] = { + { + .width = 1600, + .height = 1200, + .exp_def = 0x04ee, + .hts_def = 0x06a4, + .vts_def = 0x050e, + .reg_list = ov2685_1600x1200_regs, + }, +}; + +/* Write registers up to 4 at a time */ +static int ov2685_write_reg(struct i2c_client *client, u16 reg, + u32 len, u32 val) +{ + u32 val_i, buf_i; + u8 buf[6]; + u8 *val_p; + __be32 val_be; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val_be = cpu_to_be32(val); + val_p = (u8 *)&val_be; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int ov2685_write_array(struct i2c_client *client, + const struct regval *regs) +{ + int ret = 0; + u32 i; + + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) + ret = ov2685_write_reg(client, regs[i].addr, + OV2685_REG_VALUE_08BIT, regs[i].val); + + return ret; +} + +/* Read registers up to 4 at a time */ +static int ov2685_read_reg(struct i2c_client *client, u16 reg, + u32 len, u32 *val) +{ + struct i2c_msg msgs[2]; + u8 *data_be_p; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + int ret; + + if (len > 4) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +static void ov2685_fill_fmt(const struct ov2685_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; +} + +static int ov2685_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov2685 *ov2685 = to_ov2685(sd); + struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format; + + /* only one mode supported for now */ + ov2685_fill_fmt(ov2685->cur_mode, mbus_fmt); + + return 0; +} + +static int ov2685_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov2685 *ov2685 = to_ov2685(sd); + struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format; + + ov2685_fill_fmt(ov2685->cur_mode, mbus_fmt); + + return 0; +} + +static int ov2685_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SBGGR10_1X10; + + return 0; +} + +static int ov2685_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + int index = fse->index; + + if (index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + fse->code = MEDIA_BUS_FMT_SBGGR10_1X10; + + fse->min_width = supported_modes[index].width; + fse->max_width = supported_modes[index].width; + fse->max_height = supported_modes[index].height; + fse->min_height = supported_modes[index].height; + + return 0; +} + +/* Calculate the delay in us by clock rate and clock cycles */ +static inline u32 ov2685_cal_delay(u32 cycles) +{ + return DIV_ROUND_UP(cycles, OV2685_XVCLK_FREQ / 1000 / 1000); +} + +static int __ov2685_power_on(struct ov2685 *ov2685) +{ + int ret; + u32 delay_us; + struct device *dev = &ov2685->client->dev; + + ret = clk_prepare_enable(ov2685->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + + gpiod_set_value_cansleep(ov2685->reset_gpio, 1); + + ret = regulator_bulk_enable(OV2685_NUM_SUPPLIES, ov2685->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + goto disable_clk; + } + + /* The minimum delay between power supplies and reset rising can be 0 */ + gpiod_set_value_cansleep(ov2685->reset_gpio, 0); + /* 8192 xvclk cycles prior to the first SCCB transaction */ + delay_us = ov2685_cal_delay(8192); + usleep_range(delay_us, delay_us * 2); + + /* HACK: ov2685 would output messy data after reset(R0103), + * writing register before .s_stream() as a workaround + */ + ret = ov2685_write_array(ov2685->client, ov2685->cur_mode->reg_list); + if (ret) + goto disable_supplies; + + return 0; + +disable_supplies: + regulator_bulk_disable(OV2685_NUM_SUPPLIES, ov2685->supplies); +disable_clk: + clk_disable_unprepare(ov2685->xvclk); + + return ret; +} + +static void __ov2685_power_off(struct ov2685 *ov2685) +{ + /* 512 xvclk cycles after the last SCCB transaction or MIPI frame end */ + u32 delay_us = ov2685_cal_delay(512); + + usleep_range(delay_us, delay_us * 2); + clk_disable_unprepare(ov2685->xvclk); + gpiod_set_value_cansleep(ov2685->reset_gpio, 1); + regulator_bulk_disable(OV2685_NUM_SUPPLIES, ov2685->supplies); +} + +static int ov2685_s_stream(struct v4l2_subdev *sd, int on) +{ + struct ov2685 *ov2685 = to_ov2685(sd); + struct i2c_client *client = ov2685->client; + int ret = 0; + + mutex_lock(&ov2685->mutex); + + on = !!on; + if (on == ov2685->streaming) + goto unlock_and_return; + + if (on) { + ret = pm_runtime_get_sync(&ov2685->client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + ret = __v4l2_ctrl_handler_setup(&ov2685->ctrl_handler); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + ret = ov2685_write_reg(client, REG_SC_CTRL_MODE, + OV2685_REG_VALUE_08BIT, SC_CTRL_MODE_STREAMING); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + } else { + ov2685_write_reg(client, REG_SC_CTRL_MODE, + OV2685_REG_VALUE_08BIT, SC_CTRL_MODE_STANDBY); + pm_runtime_put(&ov2685->client->dev); + } + + ov2685->streaming = on; + +unlock_and_return: + mutex_unlock(&ov2685->mutex); + + return ret; +} + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static int ov2685_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov2685 *ov2685 = to_ov2685(sd); + struct v4l2_mbus_framefmt *try_fmt; + + mutex_lock(&ov2685->mutex); + + try_fmt = v4l2_subdev_get_try_format(sd, fh->pad, 0); + /* Initialize try_fmt */ + ov2685_fill_fmt(&supported_modes[0], try_fmt); + + mutex_unlock(&ov2685->mutex); + + return 0; +} +#endif + +static int __maybe_unused ov2685_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2685 *ov2685 = to_ov2685(sd); + + return __ov2685_power_on(ov2685); +} + +static int __maybe_unused ov2685_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2685 *ov2685 = to_ov2685(sd); + + __ov2685_power_off(ov2685); + + return 0; +} + +static const struct dev_pm_ops ov2685_pm_ops = { + SET_RUNTIME_PM_OPS(ov2685_runtime_suspend, + ov2685_runtime_resume, NULL) +}; + +static int ov2685_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov2685 *ov2685 = container_of(ctrl->handler, + struct ov2685, ctrl_handler); + struct i2c_client *client = ov2685->client; + s64 max_expo; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max_expo = ov2685->cur_mode->height + ctrl->val - 4; + __v4l2_ctrl_modify_range(ov2685->exposure, + ov2685->exposure->minimum, max_expo, + ov2685->exposure->step, + ov2685->exposure->default_value); + break; + } + + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = ov2685_write_reg(ov2685->client, OV2685_REG_EXPOSURE, + OV2685_REG_VALUE_24BIT, ctrl->val << 4); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = ov2685_write_reg(ov2685->client, OV2685_REG_GAIN, + OV2685_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_VBLANK: + ret = ov2685_write_reg(ov2685->client, OV2685_REG_VTS, + OV2685_REG_VALUE_16BIT, + ctrl->val + ov2685->cur_mode->height); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov2685_write_reg(ov2685->client, OV2685_REG_TEST_PATTERN, + OV2685_REG_VALUE_08BIT, + ov2685_test_pattern_val[ctrl->val]); + break; + default: + dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", + __func__, ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_subdev_video_ops ov2685_video_ops = { + .s_stream = ov2685_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov2685_pad_ops = { + .enum_mbus_code = ov2685_enum_mbus_code, + .enum_frame_size = ov2685_enum_frame_sizes, + .get_fmt = ov2685_get_fmt, + .set_fmt = ov2685_set_fmt, +}; + +static const struct v4l2_subdev_ops ov2685_subdev_ops = { + .video = &ov2685_video_ops, + .pad = &ov2685_pad_ops, +}; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static const struct v4l2_subdev_internal_ops ov2685_internal_ops = { + .open = ov2685_open, +}; +#endif + +static const struct v4l2_ctrl_ops ov2685_ctrl_ops = { + .s_ctrl = ov2685_set_ctrl, +}; + +static int ov2685_initialize_controls(struct ov2685 *ov2685) +{ + const struct ov2685_mode *mode; + struct v4l2_ctrl_handler *handler; + struct v4l2_ctrl *ctrl; + u64 exposure_max; + u32 pixel_rate, h_blank; + int ret; + + handler = &ov2685->ctrl_handler; + mode = ov2685->cur_mode; + ret = v4l2_ctrl_handler_init(handler, 8); + if (ret) + return ret; + handler->lock = &ov2685->mutex; + + ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, + 0, 0, link_freq_menu_items); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate = (link_freq_menu_items[0] * 2 * OV2685_LANES) / + OV2685_BITS_PER_SAMPLE; + v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, + 0, pixel_rate, 1, pixel_rate); + + h_blank = mode->hts_def - mode->width; + ov2685->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, + h_blank, h_blank, 1, h_blank); + if (ov2685->hblank) + ov2685->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ov2685->vblank = v4l2_ctrl_new_std(handler, &ov2685_ctrl_ops, + V4L2_CID_VBLANK, mode->vts_def - mode->height, + OV2685_VTS_MAX - mode->height, 1, + mode->vts_def - mode->height); + + exposure_max = mode->vts_def - 4; + ov2685->exposure = v4l2_ctrl_new_std(handler, &ov2685_ctrl_ops, + V4L2_CID_EXPOSURE, OV2685_EXPOSURE_MIN, + exposure_max, OV2685_EXPOSURE_STEP, + mode->exp_def); + + ov2685->anal_gain = v4l2_ctrl_new_std(handler, &ov2685_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, OV2685_GAIN_MIN, + OV2685_GAIN_MAX, OV2685_GAIN_STEP, + OV2685_GAIN_DEFAULT); + + ov2685->test_pattern = v4l2_ctrl_new_std_menu_items(handler, + &ov2685_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov2685_test_pattern_menu) - 1, + 0, 0, ov2685_test_pattern_menu); + + if (handler->error) { + ret = handler->error; + dev_err(&ov2685->client->dev, + "Failed to init controls(%d)\n", ret); + goto err_free_handler; + } + + ov2685->subdev.ctrl_handler = handler; + + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(handler); + + return ret; +} + +static int ov2685_check_sensor_id(struct ov2685 *ov2685, + struct i2c_client *client) +{ + struct device *dev = &ov2685->client->dev; + int ret; + u32 id = 0; + + ret = ov2685_read_reg(client, OV2685_REG_CHIP_ID, + OV2685_REG_VALUE_16BIT, &id); + if (id != CHIP_ID) { + dev_err(dev, "Unexpected sensor id(%04x), ret(%d)\n", id, ret); + return ret; + } + + dev_info(dev, "Detected OV%04x sensor\n", CHIP_ID); + + return 0; +} + +static int ov2685_configure_regulators(struct ov2685 *ov2685) +{ + int i; + + for (i = 0; i < OV2685_NUM_SUPPLIES; i++) + ov2685->supplies[i].supply = ov2685_supply_names[i]; + + return devm_regulator_bulk_get(&ov2685->client->dev, + OV2685_NUM_SUPPLIES, + ov2685->supplies); +} + +static int ov2685_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ov2685 *ov2685; + int ret; + + ov2685 = devm_kzalloc(dev, sizeof(*ov2685), GFP_KERNEL); + if (!ov2685) + return -ENOMEM; + + ov2685->client = client; + ov2685->cur_mode = &supported_modes[0]; + + ov2685->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(ov2685->xvclk)) { + dev_err(dev, "Failed to get xvclk\n"); + return -EINVAL; + } + ret = clk_set_rate(ov2685->xvclk, OV2685_XVCLK_FREQ); + if (ret < 0) { + dev_err(dev, "Failed to set xvclk rate (24MHz)\n"); + return ret; + } + if (clk_get_rate(ov2685->xvclk) != OV2685_XVCLK_FREQ) + dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n"); + + ov2685->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ov2685->reset_gpio)) { + dev_err(dev, "Failed to get reset-gpios\n"); + return -EINVAL; + } + + ret = ov2685_configure_regulators(ov2685); + if (ret) { + dev_err(dev, "Failed to get power regulators\n"); + return ret; + } + + mutex_init(&ov2685->mutex); + v4l2_i2c_subdev_init(&ov2685->subdev, client, &ov2685_subdev_ops); + ret = ov2685_initialize_controls(ov2685); + if (ret) + goto err_destroy_mutex; + + ret = __ov2685_power_on(ov2685); + if (ret) + goto err_free_handler; + + ret = ov2685_check_sensor_id(ov2685, client); + if (ret) + goto err_power_off; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + ov2685->subdev.internal_ops = &ov2685_internal_ops; + ov2685->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; +#endif +#if defined(CONFIG_MEDIA_CONTROLLER) + ov2685->pad.flags = MEDIA_PAD_FL_SOURCE; + ov2685->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&ov2685->subdev.entity, 1, &ov2685->pad); + if (ret < 0) + goto err_power_off; +#endif + + ret = v4l2_async_register_subdev(&ov2685->subdev); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_clean_entity; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +err_clean_entity: +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&ov2685->subdev.entity); +#endif +err_power_off: + __ov2685_power_off(ov2685); +err_free_handler: + v4l2_ctrl_handler_free(&ov2685->ctrl_handler); +err_destroy_mutex: + mutex_destroy(&ov2685->mutex); + + return ret; +} + +static int ov2685_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2685 *ov2685 = to_ov2685(sd); + + v4l2_async_unregister_subdev(sd); +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif + v4l2_ctrl_handler_free(&ov2685->ctrl_handler); + mutex_destroy(&ov2685->mutex); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + __ov2685_power_off(ov2685); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id ov2685_of_match[] = { + { .compatible = "ovti,ov2685" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ov2685_of_match); +#endif + +static struct i2c_driver ov2685_i2c_driver = { + .driver = { + .name = "ov2685", + .pm = &ov2685_pm_ops, + .of_match_table = of_match_ptr(ov2685_of_match), + }, + .probe = &ov2685_probe, + .remove = &ov2685_remove, +}; + +module_i2c_driver(ov2685_i2c_driver); + +MODULE_DESCRIPTION("OmniVision ov2685 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c new file mode 100644 index 000000000..34d74e575 --- /dev/null +++ b/drivers/media/i2c/ov2740.c @@ -0,0 +1,1161 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020 Intel Corporation. + +#include <asm/unaligned.h> +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/nvmem-provider.h> +#include <linux/regmap.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define OV2740_LINK_FREQ_360MHZ 360000000ULL +#define OV2740_SCLK 72000000LL +#define OV2740_MCLK 19200000 +#define OV2740_DATA_LANES 2 +#define OV2740_RGB_DEPTH 10 + +#define OV2740_REG_CHIP_ID 0x300a +#define OV2740_CHIP_ID 0x2740 + +#define OV2740_REG_MODE_SELECT 0x0100 +#define OV2740_MODE_STANDBY 0x00 +#define OV2740_MODE_STREAMING 0x01 + +/* vertical-timings from sensor */ +#define OV2740_REG_VTS 0x380e +#define OV2740_VTS_DEF 0x088a +#define OV2740_VTS_MIN 0x0460 +#define OV2740_VTS_MAX 0x7fff + +/* horizontal-timings from sensor */ +#define OV2740_REG_HTS 0x380c + +/* Exposure controls from sensor */ +#define OV2740_REG_EXPOSURE 0x3500 +#define OV2740_EXPOSURE_MIN 8 +#define OV2740_EXPOSURE_MAX_MARGIN 8 +#define OV2740_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define OV2740_REG_ANALOG_GAIN 0x3508 +#define OV2740_ANAL_GAIN_MIN 128 +#define OV2740_ANAL_GAIN_MAX 1983 +#define OV2740_ANAL_GAIN_STEP 1 + +/* Digital gain controls from sensor */ +#define OV2740_REG_MWB_R_GAIN 0x500a +#define OV2740_REG_MWB_G_GAIN 0x500c +#define OV2740_REG_MWB_B_GAIN 0x500e +#define OV2740_DGTL_GAIN_MIN 0 +#define OV2740_DGTL_GAIN_MAX 4095 +#define OV2740_DGTL_GAIN_STEP 1 +#define OV2740_DGTL_GAIN_DEFAULT 1024 + +/* Test Pattern Control */ +#define OV2740_REG_TEST_PATTERN 0x5040 +#define OV2740_TEST_PATTERN_ENABLE BIT(7) +#define OV2740_TEST_PATTERN_BAR_SHIFT 2 + +/* ISP CTRL00 */ +#define OV2740_REG_ISP_CTRL00 0x5000 +/* ISP CTRL01 */ +#define OV2740_REG_ISP_CTRL01 0x5001 +/* Customer Addresses: 0x7010 - 0x710F */ +#define CUSTOMER_USE_OTP_SIZE 0x100 +/* OTP registers from sensor */ +#define OV2740_REG_OTP_CUSTOMER 0x7010 + +struct nvm_data { + char *nvm_buffer; + struct nvmem_device *nvmem; + struct regmap *regmap; +}; + +enum { + OV2740_LINK_FREQ_360MHZ_INDEX, +}; + +struct ov2740_reg { + u16 address; + u8 val; +}; + +struct ov2740_reg_list { + u32 num_of_regs; + const struct ov2740_reg *regs; +}; + +struct ov2740_link_freq_config { + const struct ov2740_reg_list reg_list; +}; + +struct ov2740_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timining size */ + u32 hts; + + /* Default vertical timining size */ + u32 vts_def; + + /* Min vertical timining size */ + u32 vts_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* Sensor register settings for this resolution */ + const struct ov2740_reg_list reg_list; +}; + +static const struct ov2740_reg mipi_data_rate_720mbps[] = { + {0x0103, 0x01}, + {0x0302, 0x4b}, + {0x030d, 0x4b}, + {0x030e, 0x02}, + {0x030a, 0x01}, + {0x0312, 0x11}, +}; + +static const struct ov2740_reg mode_1932x1092_regs[] = { + {0x3000, 0x00}, + {0x3018, 0x32}, + {0x3031, 0x0a}, + {0x3080, 0x08}, + {0x3083, 0xB4}, + {0x3103, 0x00}, + {0x3104, 0x01}, + {0x3106, 0x01}, + {0x3500, 0x00}, + {0x3501, 0x44}, + {0x3502, 0x40}, + {0x3503, 0x88}, + {0x3507, 0x00}, + {0x3508, 0x00}, + {0x3509, 0x80}, + {0x350c, 0x00}, + {0x350d, 0x80}, + {0x3510, 0x00}, + {0x3511, 0x00}, + {0x3512, 0x20}, + {0x3632, 0x00}, + {0x3633, 0x10}, + {0x3634, 0x10}, + {0x3635, 0x10}, + {0x3645, 0x13}, + {0x3646, 0x81}, + {0x3636, 0x10}, + {0x3651, 0x0a}, + {0x3656, 0x02}, + {0x3659, 0x04}, + {0x365a, 0xda}, + {0x365b, 0xa2}, + {0x365c, 0x04}, + {0x365d, 0x1d}, + {0x365e, 0x1a}, + {0x3662, 0xd7}, + {0x3667, 0x78}, + {0x3669, 0x0a}, + {0x366a, 0x92}, + {0x3700, 0x54}, + {0x3702, 0x10}, + {0x3706, 0x42}, + {0x3709, 0x30}, + {0x370b, 0xc2}, + {0x3714, 0x63}, + {0x3715, 0x01}, + {0x3716, 0x00}, + {0x371a, 0x3e}, + {0x3732, 0x0e}, + {0x3733, 0x10}, + {0x375f, 0x0e}, + {0x3768, 0x30}, + {0x3769, 0x44}, + {0x376a, 0x22}, + {0x377b, 0x20}, + {0x377c, 0x00}, + {0x377d, 0x0c}, + {0x3798, 0x00}, + {0x37a1, 0x55}, + {0x37a8, 0x6d}, + {0x37c2, 0x04}, + {0x37c5, 0x00}, + {0x37c8, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x07}, + {0x3805, 0x8f}, + {0x3806, 0x04}, + {0x3807, 0x47}, + {0x3808, 0x07}, + {0x3809, 0x88}, + {0x380a, 0x04}, + {0x380b, 0x40}, + {0x380c, 0x04}, + {0x380d, 0x38}, + {0x380e, 0x04}, + {0x380f, 0x60}, + {0x3810, 0x00}, + {0x3811, 0x04}, + {0x3812, 0x00}, + {0x3813, 0x04}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3820, 0x80}, + {0x3821, 0x46}, + {0x3822, 0x84}, + {0x3829, 0x00}, + {0x382a, 0x01}, + {0x382b, 0x01}, + {0x3830, 0x04}, + {0x3836, 0x01}, + {0x3837, 0x08}, + {0x3839, 0x01}, + {0x383a, 0x00}, + {0x383b, 0x08}, + {0x383c, 0x00}, + {0x3f0b, 0x00}, + {0x4001, 0x20}, + {0x4009, 0x07}, + {0x4003, 0x10}, + {0x4010, 0xe0}, + {0x4016, 0x00}, + {0x4017, 0x10}, + {0x4044, 0x02}, + {0x4304, 0x08}, + {0x4307, 0x30}, + {0x4320, 0x80}, + {0x4322, 0x00}, + {0x4323, 0x00}, + {0x4324, 0x00}, + {0x4325, 0x00}, + {0x4326, 0x00}, + {0x4327, 0x00}, + {0x4328, 0x00}, + {0x4329, 0x00}, + {0x432c, 0x03}, + {0x432d, 0x81}, + {0x4501, 0x84}, + {0x4502, 0x40}, + {0x4503, 0x18}, + {0x4504, 0x04}, + {0x4508, 0x02}, + {0x4601, 0x10}, + {0x4800, 0x00}, + {0x4816, 0x52}, + {0x4837, 0x16}, + {0x5000, 0x7f}, + {0x5001, 0x00}, + {0x5005, 0x38}, + {0x501e, 0x0d}, + {0x5040, 0x00}, + {0x5901, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x07}, + {0x3805, 0x8f}, + {0x3806, 0x04}, + {0x3807, 0x47}, + {0x3808, 0x07}, + {0x3809, 0x8c}, + {0x380a, 0x04}, + {0x380b, 0x44}, + {0x3810, 0x00}, + {0x3811, 0x00}, + {0x3812, 0x00}, + {0x3813, 0x01}, +}; + +static const char * const ov2740_test_pattern_menu[] = { + "Disabled", + "Color Bar", + "Top-Bottom Darker Color Bar", + "Right-Left Darker Color Bar", + "Bottom-Top Darker Color Bar", +}; + +static const s64 link_freq_menu_items[] = { + OV2740_LINK_FREQ_360MHZ, +}; + +static const struct ov2740_link_freq_config link_freq_configs[] = { + [OV2740_LINK_FREQ_360MHZ_INDEX] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_720mbps), + .regs = mipi_data_rate_720mbps, + } + }, +}; + +static const struct ov2740_mode supported_modes[] = { + { + .width = 1932, + .height = 1092, + .hts = 1080, + .vts_def = OV2740_VTS_DEF, + .vts_min = OV2740_VTS_MIN, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1932x1092_regs), + .regs = mode_1932x1092_regs, + }, + .link_freq_index = OV2740_LINK_FREQ_360MHZ_INDEX, + }, +}; + +struct ov2740 { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct ov2740_mode *cur_mode; + + /* To serialize asynchronus callbacks */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static inline struct ov2740 *to_ov2740(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct ov2740, sd); +} + +static u64 to_pixel_rate(u32 f_index) +{ + u64 pixel_rate = link_freq_menu_items[f_index] * 2 * OV2740_DATA_LANES; + + do_div(pixel_rate, OV2740_RGB_DEPTH); + + return pixel_rate; +} + +static u64 to_pixels_per_line(u32 hts, u32 f_index) +{ + u64 ppl = hts * to_pixel_rate(f_index); + + do_div(ppl, OV2740_SCLK); + + return ppl; +} + +static int ov2740_read_reg(struct ov2740 *ov2740, u16 reg, u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = {0}; + int ret = 0; + + if (len > sizeof(data_buf)) + return -EINVAL; + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[sizeof(data_buf) - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return ret < 0 ? ret : -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +static int ov2740_write_reg(struct ov2740 *ov2740, u16 reg, u16 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); + u8 buf[6]; + int ret = 0; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << 8 * (4 - len), buf + 2); + + ret = i2c_master_send(client, buf, len + 2); + if (ret != len + 2) + return ret < 0 ? ret : -EIO; + + return 0; +} + +static int ov2740_write_reg_list(struct ov2740 *ov2740, + const struct ov2740_reg_list *r_list) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); + unsigned int i; + int ret = 0; + + for (i = 0; i < r_list->num_of_regs; i++) { + ret = ov2740_write_reg(ov2740, r_list->regs[i].address, 1, + r_list->regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "write reg 0x%4.4x return err = %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int ov2740_update_digital_gain(struct ov2740 *ov2740, u32 d_gain) +{ + int ret = 0; + + ret = ov2740_write_reg(ov2740, OV2740_REG_MWB_R_GAIN, 2, d_gain); + if (ret) + return ret; + + ret = ov2740_write_reg(ov2740, OV2740_REG_MWB_G_GAIN, 2, d_gain); + if (ret) + return ret; + + return ov2740_write_reg(ov2740, OV2740_REG_MWB_B_GAIN, 2, d_gain); +} + +static int ov2740_test_pattern(struct ov2740 *ov2740, u32 pattern) +{ + if (pattern) + pattern = (pattern - 1) << OV2740_TEST_PATTERN_BAR_SHIFT | + OV2740_TEST_PATTERN_ENABLE; + + return ov2740_write_reg(ov2740, OV2740_REG_TEST_PATTERN, 1, pattern); +} + +static int ov2740_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov2740 *ov2740 = container_of(ctrl->handler, + struct ov2740, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); + s64 exposure_max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = ov2740->cur_mode->height + ctrl->val - + OV2740_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(ov2740->exposure, + ov2740->exposure->minimum, + exposure_max, ov2740->exposure->step, + exposure_max); + } + + /* V4L2 controls values will be applied only when power is already up */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ov2740_write_reg(ov2740, OV2740_REG_ANALOG_GAIN, 2, + ctrl->val); + break; + + case V4L2_CID_DIGITAL_GAIN: + ret = ov2740_update_digital_gain(ov2740, ctrl->val); + break; + + case V4L2_CID_EXPOSURE: + /* 4 least significant bits of expsoure are fractional part */ + ret = ov2740_write_reg(ov2740, OV2740_REG_EXPOSURE, 3, + ctrl->val << 4); + break; + + case V4L2_CID_VBLANK: + ret = ov2740_write_reg(ov2740, OV2740_REG_VTS, 2, + ov2740->cur_mode->height + ctrl->val); + break; + + case V4L2_CID_TEST_PATTERN: + ret = ov2740_test_pattern(ov2740, ctrl->val); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov2740_ctrl_ops = { + .s_ctrl = ov2740_set_ctrl, +}; + +static int ov2740_init_controls(struct ov2740 *ov2740) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + const struct ov2740_mode *cur_mode; + s64 exposure_max, h_blank, pixel_rate; + u32 vblank_min, vblank_max, vblank_default; + int size; + int ret = 0; + + ctrl_hdlr = &ov2740->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + ctrl_hdlr->lock = &ov2740->mutex; + cur_mode = ov2740->cur_mode; + size = ARRAY_SIZE(link_freq_menu_items); + + ov2740->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &ov2740_ctrl_ops, + V4L2_CID_LINK_FREQ, + size - 1, 0, + link_freq_menu_items); + if (ov2740->link_freq) + ov2740->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate = to_pixel_rate(OV2740_LINK_FREQ_360MHZ_INDEX); + ov2740->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + pixel_rate, 1, pixel_rate); + + vblank_min = cur_mode->vts_min - cur_mode->height; + vblank_max = OV2740_VTS_MAX - cur_mode->height; + vblank_default = cur_mode->vts_def - cur_mode->height; + ov2740->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, + V4L2_CID_VBLANK, vblank_min, + vblank_max, 1, vblank_default); + + h_blank = to_pixels_per_line(cur_mode->hts, cur_mode->link_freq_index); + h_blank -= cur_mode->width; + ov2740->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (ov2740->hblank) + ov2740->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV2740_ANAL_GAIN_MIN, OV2740_ANAL_GAIN_MAX, + OV2740_ANAL_GAIN_STEP, OV2740_ANAL_GAIN_MIN); + v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV2740_DGTL_GAIN_MIN, OV2740_DGTL_GAIN_MAX, + OV2740_DGTL_GAIN_STEP, OV2740_DGTL_GAIN_DEFAULT); + exposure_max = cur_mode->vts_def - OV2740_EXPOSURE_MAX_MARGIN; + ov2740->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, + V4L2_CID_EXPOSURE, + OV2740_EXPOSURE_MIN, exposure_max, + OV2740_EXPOSURE_STEP, + exposure_max); + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov2740_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov2740_test_pattern_menu) - 1, + 0, 0, ov2740_test_pattern_menu); + if (ctrl_hdlr->error) { + v4l2_ctrl_handler_free(ctrl_hdlr); + return ctrl_hdlr->error; + } + + ov2740->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static void ov2740_update_pad_format(const struct ov2740_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->field = V4L2_FIELD_NONE; +} + +static int ov2740_start_streaming(struct ov2740 *ov2740) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); + const struct ov2740_reg_list *reg_list; + int link_freq_index; + int ret = 0; + + link_freq_index = ov2740->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = ov2740_write_reg_list(ov2740, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set plls"); + return ret; + } + + reg_list = &ov2740->cur_mode->reg_list; + ret = ov2740_write_reg_list(ov2740, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set mode"); + return ret; + } + + ret = __v4l2_ctrl_handler_setup(ov2740->sd.ctrl_handler); + if (ret) + return ret; + + ret = ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, + OV2740_MODE_STREAMING); + if (ret) + dev_err(&client->dev, "failed to start streaming"); + + return ret; +} + +static void ov2740_stop_streaming(struct ov2740 *ov2740) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); + + if (ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, + OV2740_MODE_STANDBY)) + dev_err(&client->dev, "failed to stop streaming"); +} + +static int ov2740_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov2740 *ov2740 = to_ov2740(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + if (ov2740->streaming == enable) + return 0; + + mutex_lock(&ov2740->mutex); + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + mutex_unlock(&ov2740->mutex); + return ret; + } + + ret = ov2740_start_streaming(ov2740); + if (ret) { + enable = 0; + ov2740_stop_streaming(ov2740); + pm_runtime_put(&client->dev); + } + } else { + ov2740_stop_streaming(ov2740); + pm_runtime_put(&client->dev); + } + + ov2740->streaming = enable; + mutex_unlock(&ov2740->mutex); + + return ret; +} + +static int __maybe_unused ov2740_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2740 *ov2740 = to_ov2740(sd); + + mutex_lock(&ov2740->mutex); + if (ov2740->streaming) + ov2740_stop_streaming(ov2740); + + mutex_unlock(&ov2740->mutex); + + return 0; +} + +static int __maybe_unused ov2740_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2740 *ov2740 = to_ov2740(sd); + int ret = 0; + + mutex_lock(&ov2740->mutex); + if (!ov2740->streaming) + goto exit; + + ret = ov2740_start_streaming(ov2740); + if (ret) { + ov2740->streaming = false; + ov2740_stop_streaming(ov2740); + } + +exit: + mutex_unlock(&ov2740->mutex); + return ret; +} + +static int ov2740_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov2740 *ov2740 = to_ov2740(sd); + const struct ov2740_mode *mode; + s32 vblank_def, h_blank; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, + height, fmt->format.width, + fmt->format.height); + + mutex_lock(&ov2740->mutex); + ov2740_update_pad_format(mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; + } else { + ov2740->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ov2740->link_freq, mode->link_freq_index); + __v4l2_ctrl_s_ctrl_int64(ov2740->pixel_rate, + to_pixel_rate(mode->link_freq_index)); + + /* Update limits and set FPS to default */ + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(ov2740->vblank, + mode->vts_min - mode->height, + OV2740_VTS_MAX - mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ov2740->vblank, vblank_def); + h_blank = to_pixels_per_line(mode->hts, mode->link_freq_index) - + mode->width; + __v4l2_ctrl_modify_range(ov2740->hblank, h_blank, h_blank, 1, + h_blank); + } + mutex_unlock(&ov2740->mutex); + + return 0; +} + +static int ov2740_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov2740 *ov2740 = to_ov2740(sd); + + mutex_lock(&ov2740->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(&ov2740->sd, cfg, + fmt->pad); + else + ov2740_update_pad_format(ov2740->cur_mode, &fmt->format); + + mutex_unlock(&ov2740->mutex); + + return 0; +} + +static int ov2740_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int ov2740_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int ov2740_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov2740 *ov2740 = to_ov2740(sd); + + mutex_lock(&ov2740->mutex); + ov2740_update_pad_format(&supported_modes[0], + v4l2_subdev_get_try_format(sd, fh->pad, 0)); + mutex_unlock(&ov2740->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops ov2740_video_ops = { + .s_stream = ov2740_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ov2740_pad_ops = { + .set_fmt = ov2740_set_format, + .get_fmt = ov2740_get_format, + .enum_mbus_code = ov2740_enum_mbus_code, + .enum_frame_size = ov2740_enum_frame_size, +}; + +static const struct v4l2_subdev_ops ov2740_subdev_ops = { + .video = &ov2740_video_ops, + .pad = &ov2740_pad_ops, +}; + +static const struct media_entity_operations ov2740_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov2740_internal_ops = { + .open = ov2740_open, +}; + +static int ov2740_identify_module(struct ov2740 *ov2740) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); + int ret; + u32 val; + + ret = ov2740_read_reg(ov2740, OV2740_REG_CHIP_ID, 3, &val); + if (ret) + return ret; + + if (val != OV2740_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x", + OV2740_CHIP_ID, val); + return -ENXIO; + } + + return 0; +} + +static int ov2740_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + u32 mclk; + int ret; + unsigned int i, j; + + if (!fwnode) + return -ENXIO; + + ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk); + if (ret) + return ret; + + if (mclk != OV2740_MCLK) { + dev_err(dev, "external clock %d is not supported", mclk); + return -EINVAL; + } + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -ENXIO; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV2740_DATA_LANES) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto check_hwcfg_error; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + ret = -EINVAL; + goto check_hwcfg_error; + } + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) { + if (link_freq_menu_items[i] == + bus_cfg.link_frequencies[j]) + break; + } + + if (j == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequency %lld supported", + link_freq_menu_items[i]); + ret = -EINVAL; + goto check_hwcfg_error; + } + } + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int ov2740_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2740 *ov2740 = to_ov2740(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(&client->dev); + mutex_destroy(&ov2740->mutex); + + return 0; +} + +static int ov2740_load_otp_data(struct i2c_client *client, struct nvm_data *nvm) +{ + struct ov2740 *ov2740 = to_ov2740(i2c_get_clientdata(client)); + u32 isp_ctrl00 = 0; + u32 isp_ctrl01 = 0; + int ret; + + ret = ov2740_read_reg(ov2740, OV2740_REG_ISP_CTRL00, 1, &isp_ctrl00); + if (ret) { + dev_err(&client->dev, "failed to read ISP CTRL00\n"); + goto exit; + } + ret = ov2740_read_reg(ov2740, OV2740_REG_ISP_CTRL01, 1, &isp_ctrl01); + if (ret) { + dev_err(&client->dev, "failed to read ISP CTRL01\n"); + goto exit; + } + + /* Clear bit 5 of ISP CTRL00 */ + ret = ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL00, 1, + isp_ctrl00 & ~BIT(5)); + if (ret) { + dev_err(&client->dev, "failed to write ISP CTRL00\n"); + goto exit; + } + + /* Clear bit 7 of ISP CTRL01 */ + ret = ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL01, 1, + isp_ctrl01 & ~BIT(7)); + if (ret) { + dev_err(&client->dev, "failed to write ISP CTRL01\n"); + goto exit; + } + + ret = ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, + OV2740_MODE_STREAMING); + if (ret) { + dev_err(&client->dev, "failed to start streaming\n"); + goto exit; + } + + /* + * Users are not allowed to access OTP-related registers and memory + * during the 20 ms period after streaming starts (0x100 = 0x01). + */ + msleep(20); + + ret = regmap_bulk_read(nvm->regmap, OV2740_REG_OTP_CUSTOMER, + nvm->nvm_buffer, CUSTOMER_USE_OTP_SIZE); + if (ret) { + dev_err(&client->dev, "failed to read OTP data, ret %d\n", ret); + goto exit; + } + + ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, + OV2740_MODE_STANDBY); + ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL01, 1, isp_ctrl01); + ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL00, 1, isp_ctrl00); + +exit: + return ret; +} + +static int ov2740_nvmem_read(void *priv, unsigned int off, void *val, + size_t count) +{ + struct nvm_data *nvm = priv; + + memcpy(val, nvm->nvm_buffer + off, count); + + return 0; +} + +static int ov2740_register_nvmem(struct i2c_client *client) +{ + struct nvm_data *nvm; + struct regmap_config regmap_config = { }; + struct nvmem_config nvmem_config = { }; + struct regmap *regmap; + struct device *dev = &client->dev; + int ret = 0; + + nvm = devm_kzalloc(dev, sizeof(*nvm), GFP_KERNEL); + if (!nvm) + return -ENOMEM; + + nvm->nvm_buffer = devm_kzalloc(dev, CUSTOMER_USE_OTP_SIZE, GFP_KERNEL); + if (!nvm->nvm_buffer) + return -ENOMEM; + + regmap_config.val_bits = 8; + regmap_config.reg_bits = 16; + regmap_config.disable_locking = true; + regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + nvm->regmap = regmap; + + ret = ov2740_load_otp_data(client, nvm); + if (ret) { + dev_err(dev, "failed to load OTP data, ret %d\n", ret); + return ret; + } + + nvmem_config.name = dev_name(dev); + nvmem_config.dev = dev; + nvmem_config.read_only = true; + nvmem_config.root_only = true; + nvmem_config.owner = THIS_MODULE; + nvmem_config.compat = true; + nvmem_config.base_dev = dev; + nvmem_config.reg_read = ov2740_nvmem_read; + nvmem_config.reg_write = NULL; + nvmem_config.priv = nvm; + nvmem_config.stride = 1; + nvmem_config.word_size = 1; + nvmem_config.size = CUSTOMER_USE_OTP_SIZE; + + nvm->nvmem = devm_nvmem_register(dev, &nvmem_config); + + return PTR_ERR_OR_ZERO(nvm->nvmem); +} + +static int ov2740_probe(struct i2c_client *client) +{ + struct ov2740 *ov2740; + int ret = 0; + + ret = ov2740_check_hwcfg(&client->dev); + if (ret) { + dev_err(&client->dev, "failed to check HW configuration: %d", + ret); + return ret; + } + + ov2740 = devm_kzalloc(&client->dev, sizeof(*ov2740), GFP_KERNEL); + if (!ov2740) + return -ENOMEM; + + v4l2_i2c_subdev_init(&ov2740->sd, client, &ov2740_subdev_ops); + ret = ov2740_identify_module(ov2740); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d", ret); + return ret; + } + + mutex_init(&ov2740->mutex); + ov2740->cur_mode = &supported_modes[0]; + ret = ov2740_init_controls(ov2740); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ov2740->sd.internal_ops = &ov2740_internal_ops; + ov2740->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov2740->sd.entity.ops = &ov2740_subdev_entity_ops; + ov2740->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ov2740->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov2740->sd.entity, 1, &ov2740->pad); + if (ret) { + dev_err(&client->dev, "failed to init entity pads: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = v4l2_async_register_subdev_sensor_common(&ov2740->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %d", + ret); + goto probe_error_media_entity_cleanup; + } + + ret = ov2740_register_nvmem(client); + if (ret) + dev_warn(&client->dev, "register nvmem failed, ret %d\n", ret); + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +probe_error_media_entity_cleanup: + media_entity_cleanup(&ov2740->sd.entity); + +probe_error_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(ov2740->sd.ctrl_handler); + mutex_destroy(&ov2740->mutex); + + return ret; +} + +static const struct dev_pm_ops ov2740_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov2740_suspend, ov2740_resume) +}; + +static const struct acpi_device_id ov2740_acpi_ids[] = { + {"INT3474"}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, ov2740_acpi_ids); + +static struct i2c_driver ov2740_i2c_driver = { + .driver = { + .name = "ov2740", + .pm = &ov2740_pm_ops, + .acpi_match_table = ov2740_acpi_ids, + }, + .probe_new = ov2740_probe, + .remove = ov2740_remove, +}; + +module_i2c_driver(ov2740_i2c_driver); + +MODULE_AUTHOR("Qiu, Tianshu <tian.shu.qiu@intel.com>"); +MODULE_AUTHOR("Shawn Tu <shawnx.tu@intel.com>"); +MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>"); +MODULE_DESCRIPTION("OmniVision OV2740 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c new file mode 100644 index 000000000..db4b6095f --- /dev/null +++ b/drivers/media/i2c/ov5640.c @@ -0,0 +1,3193 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2014-2017 Mentor Graphics Inc. + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +/* min/typical/max system clock (xclk) frequencies */ +#define OV5640_XCLK_MIN 6000000 +#define OV5640_XCLK_MAX 54000000 + +#define OV5640_DEFAULT_SLAVE_ID 0x3c + +#define OV5640_REG_SYS_RESET02 0x3002 +#define OV5640_REG_SYS_CLOCK_ENABLE02 0x3006 +#define OV5640_REG_SYS_CTRL0 0x3008 +#define OV5640_REG_SYS_CTRL0_SW_PWDN 0x42 +#define OV5640_REG_SYS_CTRL0_SW_PWUP 0x02 +#define OV5640_REG_CHIP_ID 0x300a +#define OV5640_REG_IO_MIPI_CTRL00 0x300e +#define OV5640_REG_PAD_OUTPUT_ENABLE01 0x3017 +#define OV5640_REG_PAD_OUTPUT_ENABLE02 0x3018 +#define OV5640_REG_PAD_OUTPUT00 0x3019 +#define OV5640_REG_SYSTEM_CONTROL1 0x302e +#define OV5640_REG_SC_PLL_CTRL0 0x3034 +#define OV5640_REG_SC_PLL_CTRL1 0x3035 +#define OV5640_REG_SC_PLL_CTRL2 0x3036 +#define OV5640_REG_SC_PLL_CTRL3 0x3037 +#define OV5640_REG_SLAVE_ID 0x3100 +#define OV5640_REG_SCCB_SYS_CTRL1 0x3103 +#define OV5640_REG_SYS_ROOT_DIVIDER 0x3108 +#define OV5640_REG_AWB_R_GAIN 0x3400 +#define OV5640_REG_AWB_G_GAIN 0x3402 +#define OV5640_REG_AWB_B_GAIN 0x3404 +#define OV5640_REG_AWB_MANUAL_CTRL 0x3406 +#define OV5640_REG_AEC_PK_EXPOSURE_HI 0x3500 +#define OV5640_REG_AEC_PK_EXPOSURE_MED 0x3501 +#define OV5640_REG_AEC_PK_EXPOSURE_LO 0x3502 +#define OV5640_REG_AEC_PK_MANUAL 0x3503 +#define OV5640_REG_AEC_PK_REAL_GAIN 0x350a +#define OV5640_REG_AEC_PK_VTS 0x350c +#define OV5640_REG_TIMING_DVPHO 0x3808 +#define OV5640_REG_TIMING_DVPVO 0x380a +#define OV5640_REG_TIMING_HTS 0x380c +#define OV5640_REG_TIMING_VTS 0x380e +#define OV5640_REG_TIMING_TC_REG20 0x3820 +#define OV5640_REG_TIMING_TC_REG21 0x3821 +#define OV5640_REG_AEC_CTRL00 0x3a00 +#define OV5640_REG_AEC_B50_STEP 0x3a08 +#define OV5640_REG_AEC_B60_STEP 0x3a0a +#define OV5640_REG_AEC_CTRL0D 0x3a0d +#define OV5640_REG_AEC_CTRL0E 0x3a0e +#define OV5640_REG_AEC_CTRL0F 0x3a0f +#define OV5640_REG_AEC_CTRL10 0x3a10 +#define OV5640_REG_AEC_CTRL11 0x3a11 +#define OV5640_REG_AEC_CTRL1B 0x3a1b +#define OV5640_REG_AEC_CTRL1E 0x3a1e +#define OV5640_REG_AEC_CTRL1F 0x3a1f +#define OV5640_REG_HZ5060_CTRL00 0x3c00 +#define OV5640_REG_HZ5060_CTRL01 0x3c01 +#define OV5640_REG_SIGMADELTA_CTRL0C 0x3c0c +#define OV5640_REG_FRAME_CTRL01 0x4202 +#define OV5640_REG_FORMAT_CONTROL00 0x4300 +#define OV5640_REG_VFIFO_HSIZE 0x4602 +#define OV5640_REG_VFIFO_VSIZE 0x4604 +#define OV5640_REG_JPG_MODE_SELECT 0x4713 +#define OV5640_REG_CCIR656_CTRL00 0x4730 +#define OV5640_REG_POLARITY_CTRL00 0x4740 +#define OV5640_REG_MIPI_CTRL00 0x4800 +#define OV5640_REG_DEBUG_MODE 0x4814 +#define OV5640_REG_ISP_FORMAT_MUX_CTRL 0x501f +#define OV5640_REG_PRE_ISP_TEST_SET1 0x503d +#define OV5640_REG_SDE_CTRL0 0x5580 +#define OV5640_REG_SDE_CTRL1 0x5581 +#define OV5640_REG_SDE_CTRL3 0x5583 +#define OV5640_REG_SDE_CTRL4 0x5584 +#define OV5640_REG_SDE_CTRL5 0x5585 +#define OV5640_REG_AVG_READOUT 0x56a1 + +enum ov5640_mode_id { + OV5640_MODE_QCIF_176_144 = 0, + OV5640_MODE_QVGA_320_240, + OV5640_MODE_VGA_640_480, + OV5640_MODE_NTSC_720_480, + OV5640_MODE_PAL_720_576, + OV5640_MODE_XGA_1024_768, + OV5640_MODE_720P_1280_720, + OV5640_MODE_1080P_1920_1080, + OV5640_MODE_QSXGA_2592_1944, + OV5640_NUM_MODES, +}; + +enum ov5640_frame_rate { + OV5640_15_FPS = 0, + OV5640_30_FPS, + OV5640_60_FPS, + OV5640_NUM_FRAMERATES, +}; + +enum ov5640_format_mux { + OV5640_FMT_MUX_YUV422 = 0, + OV5640_FMT_MUX_RGB, + OV5640_FMT_MUX_DITHER, + OV5640_FMT_MUX_RAW_DPC, + OV5640_FMT_MUX_SNR_RAW, + OV5640_FMT_MUX_RAW_CIP, +}; + +struct ov5640_pixfmt { + u32 code; + u32 colorspace; +}; + +static const struct ov5640_pixfmt ov5640_formats[] = { + { MEDIA_BUS_FMT_JPEG_1X8, V4L2_COLORSPACE_JPEG, }, + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_COLORSPACE_SRGB, }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_SRGB, }, + { MEDIA_BUS_FMT_RGB565_2X8_LE, V4L2_COLORSPACE_SRGB, }, + { MEDIA_BUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_SRGB, }, + { MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB, }, + { MEDIA_BUS_FMT_SGBRG8_1X8, V4L2_COLORSPACE_SRGB, }, + { MEDIA_BUS_FMT_SGRBG8_1X8, V4L2_COLORSPACE_SRGB, }, + { MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_COLORSPACE_SRGB, }, +}; + +/* + * FIXME: remove this when a subdev API becomes available + * to set the MIPI CSI-2 virtual channel. + */ +static unsigned int virtual_channel; +module_param(virtual_channel, uint, 0444); +MODULE_PARM_DESC(virtual_channel, + "MIPI CSI-2 virtual channel (0..3), default 0"); + +static const int ov5640_framerates[] = { + [OV5640_15_FPS] = 15, + [OV5640_30_FPS] = 30, + [OV5640_60_FPS] = 60, +}; + +/* regulator supplies */ +static const char * const ov5640_supply_name[] = { + "DOVDD", /* Digital I/O (1.8V) supply */ + "AVDD", /* Analog (2.8V) supply */ + "DVDD", /* Digital Core (1.5V) supply */ +}; + +#define OV5640_NUM_SUPPLIES ARRAY_SIZE(ov5640_supply_name) + +/* + * Image size under 1280 * 960 are SUBSAMPLING + * Image size upper 1280 * 960 are SCALING + */ +enum ov5640_downsize_mode { + SUBSAMPLING, + SCALING, +}; + +struct reg_value { + u16 reg_addr; + u8 val; + u8 mask; + u32 delay_ms; +}; + +struct ov5640_mode_info { + enum ov5640_mode_id id; + enum ov5640_downsize_mode dn_mode; + u32 hact; + u32 htot; + u32 vact; + u32 vtot; + const struct reg_value *reg_data; + u32 reg_data_size; + u32 max_fps; +}; + +struct ov5640_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *pixel_rate; + struct { + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *exposure; + }; + struct { + struct v4l2_ctrl *auto_wb; + struct v4l2_ctrl *blue_balance; + struct v4l2_ctrl *red_balance; + }; + struct { + struct v4l2_ctrl *auto_gain; + struct v4l2_ctrl *gain; + }; + struct v4l2_ctrl *brightness; + struct v4l2_ctrl *light_freq; + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *contrast; + struct v4l2_ctrl *hue; + struct v4l2_ctrl *test_pattern; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; +}; + +struct ov5640_dev { + struct i2c_client *i2c_client; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */ + struct clk *xclk; /* system clock to OV5640 */ + u32 xclk_freq; + + struct regulator_bulk_data supplies[OV5640_NUM_SUPPLIES]; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + bool upside_down; + + /* lock to protect all members below */ + struct mutex lock; + + int power_count; + + struct v4l2_mbus_framefmt fmt; + bool pending_fmt_change; + + const struct ov5640_mode_info *current_mode; + const struct ov5640_mode_info *last_mode; + enum ov5640_frame_rate current_fr; + struct v4l2_fract frame_interval; + + struct ov5640_ctrls ctrls; + + u32 prev_sysclk, prev_hts; + u32 ae_low, ae_high, ae_target; + + bool pending_mode_change; + bool streaming; +}; + +static inline struct ov5640_dev *to_ov5640_dev(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov5640_dev, sd); +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ov5640_dev, + ctrls.handler)->sd; +} + +/* + * FIXME: all of these register tables are likely filled with + * entries that set the register to their power-on default values, + * and which are otherwise not touched by this driver. Those entries + * should be identified and removed to speed register load time + * over i2c. + */ +/* YUV422 UYVY VGA@30fps */ +static const struct reg_value ov5640_init_setting_30fps_VGA[] = { + {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 5}, {0x3008, 0x42, 0, 0}, + {0x3103, 0x03, 0, 0}, {0x3630, 0x36, 0, 0}, + {0x3631, 0x0e, 0, 0}, {0x3632, 0xe2, 0, 0}, {0x3633, 0x12, 0, 0}, + {0x3621, 0xe0, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, + {0x3715, 0x78, 0, 0}, {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0}, + {0x3705, 0x1a, 0, 0}, {0x3905, 0x02, 0, 0}, {0x3906, 0x10, 0, 0}, + {0x3901, 0x0a, 0, 0}, {0x3731, 0x12, 0, 0}, {0x3600, 0x08, 0, 0}, + {0x3601, 0x33, 0, 0}, {0x302d, 0x60, 0, 0}, {0x3620, 0x52, 0, 0}, + {0x371b, 0x20, 0, 0}, {0x471c, 0x50, 0, 0}, {0x3a13, 0x43, 0, 0}, + {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, {0x3635, 0x13, 0, 0}, + {0x3636, 0x03, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3622, 0x01, 0, 0}, + {0x3c01, 0xa4, 0, 0}, {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, + {0x3c06, 0x00, 0, 0}, {0x3c07, 0x08, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3002, 0x1c, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xc3, 0, 0}, + {0x302e, 0x08, 0, 0}, {0x4300, 0x3f, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x440e, 0x00, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x4837, 0x0a, 0, 0}, {0x3824, 0x02, 0, 0}, + {0x5000, 0xa7, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x5180, 0xff, 0, 0}, + {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0}, {0x5183, 0x14, 0, 0}, + {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0}, {0x5186, 0x09, 0, 0}, + {0x5187, 0x09, 0, 0}, {0x5188, 0x09, 0, 0}, {0x5189, 0x88, 0, 0}, + {0x518a, 0x54, 0, 0}, {0x518b, 0xee, 0, 0}, {0x518c, 0xb2, 0, 0}, + {0x518d, 0x50, 0, 0}, {0x518e, 0x34, 0, 0}, {0x518f, 0x6b, 0, 0}, + {0x5190, 0x46, 0, 0}, {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, + {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, + {0x5199, 0x6c, 0, 0}, {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, + {0x519c, 0x09, 0, 0}, {0x519d, 0x2b, 0, 0}, {0x519e, 0x38, 0, 0}, + {0x5381, 0x1e, 0, 0}, {0x5382, 0x5b, 0, 0}, {0x5383, 0x08, 0, 0}, + {0x5384, 0x0a, 0, 0}, {0x5385, 0x7e, 0, 0}, {0x5386, 0x88, 0, 0}, + {0x5387, 0x7c, 0, 0}, {0x5388, 0x6c, 0, 0}, {0x5389, 0x10, 0, 0}, + {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0}, {0x5300, 0x08, 0, 0}, + {0x5301, 0x30, 0, 0}, {0x5302, 0x10, 0, 0}, {0x5303, 0x00, 0, 0}, + {0x5304, 0x08, 0, 0}, {0x5305, 0x30, 0, 0}, {0x5306, 0x08, 0, 0}, + {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0}, {0x530a, 0x30, 0, 0}, + {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0}, {0x5480, 0x01, 0, 0}, + {0x5481, 0x08, 0, 0}, {0x5482, 0x14, 0, 0}, {0x5483, 0x28, 0, 0}, + {0x5484, 0x51, 0, 0}, {0x5485, 0x65, 0, 0}, {0x5486, 0x71, 0, 0}, + {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0}, {0x5489, 0x91, 0, 0}, + {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0}, {0x548c, 0xb8, 0, 0}, + {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0}, {0x548f, 0xea, 0, 0}, + {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x10, 0, 0}, {0x5589, 0x10, 0, 0}, {0x558a, 0x00, 0, 0}, + {0x558b, 0xf8, 0, 0}, {0x5800, 0x23, 0, 0}, {0x5801, 0x14, 0, 0}, + {0x5802, 0x0f, 0, 0}, {0x5803, 0x0f, 0, 0}, {0x5804, 0x12, 0, 0}, + {0x5805, 0x26, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0}, + {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x08, 0, 0}, + {0x580b, 0x0d, 0, 0}, {0x580c, 0x08, 0, 0}, {0x580d, 0x03, 0, 0}, + {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x03, 0, 0}, + {0x5811, 0x09, 0, 0}, {0x5812, 0x07, 0, 0}, {0x5813, 0x03, 0, 0}, + {0x5814, 0x00, 0, 0}, {0x5815, 0x01, 0, 0}, {0x5816, 0x03, 0, 0}, + {0x5817, 0x08, 0, 0}, {0x5818, 0x0d, 0, 0}, {0x5819, 0x08, 0, 0}, + {0x581a, 0x05, 0, 0}, {0x581b, 0x06, 0, 0}, {0x581c, 0x08, 0, 0}, + {0x581d, 0x0e, 0, 0}, {0x581e, 0x29, 0, 0}, {0x581f, 0x17, 0, 0}, + {0x5820, 0x11, 0, 0}, {0x5821, 0x11, 0, 0}, {0x5822, 0x15, 0, 0}, + {0x5823, 0x28, 0, 0}, {0x5824, 0x46, 0, 0}, {0x5825, 0x26, 0, 0}, + {0x5826, 0x08, 0, 0}, {0x5827, 0x26, 0, 0}, {0x5828, 0x64, 0, 0}, + {0x5829, 0x26, 0, 0}, {0x582a, 0x24, 0, 0}, {0x582b, 0x22, 0, 0}, + {0x582c, 0x24, 0, 0}, {0x582d, 0x24, 0, 0}, {0x582e, 0x06, 0, 0}, + {0x582f, 0x22, 0, 0}, {0x5830, 0x40, 0, 0}, {0x5831, 0x42, 0, 0}, + {0x5832, 0x24, 0, 0}, {0x5833, 0x26, 0, 0}, {0x5834, 0x24, 0, 0}, + {0x5835, 0x22, 0, 0}, {0x5836, 0x22, 0, 0}, {0x5837, 0x26, 0, 0}, + {0x5838, 0x44, 0, 0}, {0x5839, 0x24, 0, 0}, {0x583a, 0x26, 0, 0}, + {0x583b, 0x28, 0, 0}, {0x583c, 0x42, 0, 0}, {0x583d, 0xce, 0, 0}, + {0x5025, 0x00, 0, 0}, {0x3a0f, 0x30, 0, 0}, {0x3a10, 0x28, 0, 0}, + {0x3a1b, 0x30, 0, 0}, {0x3a1e, 0x26, 0, 0}, {0x3a11, 0x60, 0, 0}, + {0x3a1f, 0x14, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3c00, 0x04, 0, 300}, +}; + +static const struct reg_value ov5640_setting_VGA_640_480[] = { + {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_XGA_1024_768[] = { + {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_QVGA_320_240[] = { + {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_QCIF_176_144[] = { + {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_NTSC_720_480[] = { + {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x3c, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_PAL_720_576[] = { + {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3810, 0x00, 0, 0}, + {0x3811, 0x38, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_720P_1280_720[] = { + {0x3c07, 0x07, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0xfa, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x06, 0, 0}, {0x3807, 0xa9, 0, 0}, + {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x02, 0, 0}, + {0x3a03, 0xe4, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0xbc, 0, 0}, + {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x72, 0, 0}, {0x3a0e, 0x01, 0, 0}, + {0x3a0d, 0x02, 0, 0}, {0x3a14, 0x02, 0, 0}, {0x3a15, 0xe4, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, + {0x3824, 0x04, 0, 0}, {0x5001, 0x83, 0, 0}, +}; + +static const struct reg_value ov5640_setting_1080P_1920_1080[] = { + {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3814, 0x11, 0, 0}, + {0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x00, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9f, 0, 0}, + {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x04, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x21, 0, 0}, + {0x3709, 0x12, 0, 0}, {0x370c, 0x00, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 0}, + {0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3800, 0x01, 0, 0}, {0x3801, 0x50, 0, 0}, {0x3802, 0x01, 0, 0}, + {0x3803, 0xb2, 0, 0}, {0x3804, 0x08, 0, 0}, {0x3805, 0xef, 0, 0}, + {0x3806, 0x05, 0, 0}, {0x3807, 0xf1, 0, 0}, + {0x3612, 0x2b, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3a02, 0x04, 0, 0}, {0x3a03, 0x60, 0, 0}, {0x3a08, 0x01, 0, 0}, + {0x3a09, 0x50, 0, 0}, {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x18, 0, 0}, + {0x3a0e, 0x03, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x04, 0, 0}, + {0x3a15, 0x60, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3824, 0x04, 0, 0}, + {0x4005, 0x1a, 0, 0}, +}; + +static const struct reg_value ov5640_setting_QSXGA_2592_1944[] = { + {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3814, 0x11, 0, 0}, + {0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x00, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9f, 0, 0}, + {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x04, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x21, 0, 0}, + {0x3709, 0x12, 0, 0}, {0x370c, 0x00, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 70}, +}; + +/* power-on sensor init reg table */ +static const struct ov5640_mode_info ov5640_mode_init_data = { + 0, SUBSAMPLING, 640, 1896, 480, 984, + ov5640_init_setting_30fps_VGA, + ARRAY_SIZE(ov5640_init_setting_30fps_VGA), + OV5640_30_FPS, +}; + +static const struct ov5640_mode_info +ov5640_mode_data[OV5640_NUM_MODES] = { + {OV5640_MODE_QCIF_176_144, SUBSAMPLING, + 176, 1896, 144, 984, + ov5640_setting_QCIF_176_144, + ARRAY_SIZE(ov5640_setting_QCIF_176_144), + OV5640_30_FPS}, + {OV5640_MODE_QVGA_320_240, SUBSAMPLING, + 320, 1896, 240, 984, + ov5640_setting_QVGA_320_240, + ARRAY_SIZE(ov5640_setting_QVGA_320_240), + OV5640_30_FPS}, + {OV5640_MODE_VGA_640_480, SUBSAMPLING, + 640, 1896, 480, 1080, + ov5640_setting_VGA_640_480, + ARRAY_SIZE(ov5640_setting_VGA_640_480), + OV5640_60_FPS}, + {OV5640_MODE_NTSC_720_480, SUBSAMPLING, + 720, 1896, 480, 984, + ov5640_setting_NTSC_720_480, + ARRAY_SIZE(ov5640_setting_NTSC_720_480), + OV5640_30_FPS}, + {OV5640_MODE_PAL_720_576, SUBSAMPLING, + 720, 1896, 576, 984, + ov5640_setting_PAL_720_576, + ARRAY_SIZE(ov5640_setting_PAL_720_576), + OV5640_30_FPS}, + {OV5640_MODE_XGA_1024_768, SUBSAMPLING, + 1024, 1896, 768, 1080, + ov5640_setting_XGA_1024_768, + ARRAY_SIZE(ov5640_setting_XGA_1024_768), + OV5640_30_FPS}, + {OV5640_MODE_720P_1280_720, SUBSAMPLING, + 1280, 1892, 720, 740, + ov5640_setting_720P_1280_720, + ARRAY_SIZE(ov5640_setting_720P_1280_720), + OV5640_30_FPS}, + {OV5640_MODE_1080P_1920_1080, SCALING, + 1920, 2500, 1080, 1120, + ov5640_setting_1080P_1920_1080, + ARRAY_SIZE(ov5640_setting_1080P_1920_1080), + OV5640_30_FPS}, + {OV5640_MODE_QSXGA_2592_1944, SCALING, + 2592, 2844, 1944, 1968, + ov5640_setting_QSXGA_2592_1944, + ARRAY_SIZE(ov5640_setting_QSXGA_2592_1944), + OV5640_15_FPS}, +}; + +static int ov5640_init_slave_id(struct ov5640_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg; + u8 buf[3]; + int ret; + + if (client->addr == OV5640_DEFAULT_SLAVE_ID) + return 0; + + buf[0] = OV5640_REG_SLAVE_ID >> 8; + buf[1] = OV5640_REG_SLAVE_ID & 0xff; + buf[2] = client->addr << 1; + + msg.addr = OV5640_DEFAULT_SLAVE_ID; + msg.flags = 0; + msg.buf = buf; + msg.len = sizeof(buf); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_err(&client->dev, "%s: failed with %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int ov5640_write_reg(struct ov5640_dev *sensor, u16 reg, u8 val) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg; + u8 buf[3]; + int ret; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + buf[2] = val; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.buf = buf; + msg.len = sizeof(buf); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_err(&client->dev, "%s: error: reg=%x, val=%x\n", + __func__, reg, val); + return ret; + } + + return 0; +} + +static int ov5640_read_reg(struct ov5640_dev *sensor, u16 reg, u8 *val) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg[2]; + u8 buf[2]; + int ret; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].buf = buf; + msg[0].len = sizeof(buf); + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].buf = buf; + msg[1].len = 1; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_err(&client->dev, "%s: error: reg=%x\n", + __func__, reg); + return ret; + } + + *val = buf[0]; + return 0; +} + +static int ov5640_read_reg16(struct ov5640_dev *sensor, u16 reg, u16 *val) +{ + u8 hi, lo; + int ret; + + ret = ov5640_read_reg(sensor, reg, &hi); + if (ret) + return ret; + ret = ov5640_read_reg(sensor, reg + 1, &lo); + if (ret) + return ret; + + *val = ((u16)hi << 8) | (u16)lo; + return 0; +} + +static int ov5640_write_reg16(struct ov5640_dev *sensor, u16 reg, u16 val) +{ + int ret; + + ret = ov5640_write_reg(sensor, reg, val >> 8); + if (ret) + return ret; + + return ov5640_write_reg(sensor, reg + 1, val & 0xff); +} + +static int ov5640_mod_reg(struct ov5640_dev *sensor, u16 reg, + u8 mask, u8 val) +{ + u8 readval; + int ret; + + ret = ov5640_read_reg(sensor, reg, &readval); + if (ret) + return ret; + + readval &= ~mask; + val &= mask; + val |= readval; + + return ov5640_write_reg(sensor, reg, val); +} + +/* + * After trying the various combinations, reading various + * documentations spread around the net, and from the various + * feedback, the clock tree is probably as follows: + * + * +--------------+ + * | Ext. Clock | + * +-+------------+ + * | +----------+ + * +->| PLL1 | - reg 0x3036, for the multiplier + * +-+--------+ - reg 0x3037, bits 0-3 for the pre-divider + * | +--------------+ + * +->| System Clock | - reg 0x3035, bits 4-7 + * +-+------------+ + * | +--------------+ + * +->| MIPI Divider | - reg 0x3035, bits 0-3 + * | +-+------------+ + * | +----------------> MIPI SCLK + * | + +-----+ + * | +->| / 2 |-------> MIPI BIT CLK + * | +-----+ + * | +--------------+ + * +->| PLL Root Div | - reg 0x3037, bit 4 + * +-+------------+ + * | +---------+ + * +->| Bit Div | - reg 0x3034, bits 0-3 + * +-+-------+ + * | +-------------+ + * +->| SCLK Div | - reg 0x3108, bits 0-1 + * | +-+-----------+ + * | +---------------> SCLK + * | +-------------+ + * +->| SCLK 2X Div | - reg 0x3108, bits 2-3 + * | +-+-----------+ + * | +---------------> SCLK 2X + * | +-------------+ + * +->| PCLK Div | - reg 0x3108, bits 4-5 + * ++------------+ + * + +-----------+ + * +->| P_DIV | - reg 0x3035, bits 0-3 + * +-----+-----+ + * +------------> PCLK + * + * This is deviating from the datasheet at least for the register + * 0x3108, since it's said here that the PCLK would be clocked from + * the PLL. + * + * There seems to be also (unverified) constraints: + * - the PLL pre-divider output rate should be in the 4-27MHz range + * - the PLL multiplier output rate should be in the 500-1000MHz range + * - PCLK >= SCLK * 2 in YUV, >= SCLK in Raw or JPEG + * + * In the two latter cases, these constraints are met since our + * factors are hardcoded. If we were to change that, we would need to + * take this into account. The only varying parts are the PLL + * multiplier and the system clock divider, which are shared between + * all these clocks so won't cause any issue. + */ + +/* + * This is supposed to be ranging from 1 to 8, but the value is always + * set to 3 in the vendor kernels. + */ +#define OV5640_PLL_PREDIV 3 + +#define OV5640_PLL_MULT_MIN 4 +#define OV5640_PLL_MULT_MAX 252 + +/* + * This is supposed to be ranging from 1 to 16, but the value is + * always set to either 1 or 2 in the vendor kernels. + */ +#define OV5640_SYSDIV_MIN 1 +#define OV5640_SYSDIV_MAX 16 + +/* + * Hardcode these values for scaler and non-scaler modes. + * FIXME: to be re-calcualted for 1 data lanes setups + */ +#define OV5640_MIPI_DIV_PCLK 2 +#define OV5640_MIPI_DIV_SCLK 1 + +/* + * This is supposed to be ranging from 1 to 2, but the value is always + * set to 2 in the vendor kernels. + */ +#define OV5640_PLL_ROOT_DIV 2 +#define OV5640_PLL_CTRL3_PLL_ROOT_DIV_2 BIT(4) + +/* + * We only supports 8-bit formats at the moment + */ +#define OV5640_BIT_DIV 2 +#define OV5640_PLL_CTRL0_MIPI_MODE_8BIT 0x08 + +/* + * This is supposed to be ranging from 1 to 8, but the value is always + * set to 2 in the vendor kernels. + */ +#define OV5640_SCLK_ROOT_DIV 2 + +/* + * This is hardcoded so that the consistency is maintained between SCLK and + * SCLK 2x. + */ +#define OV5640_SCLK2X_ROOT_DIV (OV5640_SCLK_ROOT_DIV / 2) + +/* + * This is supposed to be ranging from 1 to 8, but the value is always + * set to 1 in the vendor kernels. + */ +#define OV5640_PCLK_ROOT_DIV 1 +#define OV5640_PLL_SYS_ROOT_DIVIDER_BYPASS 0x00 + +static unsigned long ov5640_compute_sys_clk(struct ov5640_dev *sensor, + u8 pll_prediv, u8 pll_mult, + u8 sysdiv) +{ + unsigned long sysclk = sensor->xclk_freq / pll_prediv * pll_mult; + + /* PLL1 output cannot exceed 1GHz. */ + if (sysclk / 1000000 > 1000) + return 0; + + return sysclk / sysdiv; +} + +static unsigned long ov5640_calc_sys_clk(struct ov5640_dev *sensor, + unsigned long rate, + u8 *pll_prediv, u8 *pll_mult, + u8 *sysdiv) +{ + unsigned long best = ~0; + u8 best_sysdiv = 1, best_mult = 1; + u8 _sysdiv, _pll_mult; + + for (_sysdiv = OV5640_SYSDIV_MIN; + _sysdiv <= OV5640_SYSDIV_MAX; + _sysdiv++) { + for (_pll_mult = OV5640_PLL_MULT_MIN; + _pll_mult <= OV5640_PLL_MULT_MAX; + _pll_mult++) { + unsigned long _rate; + + /* + * The PLL multiplier cannot be odd if above + * 127. + */ + if (_pll_mult > 127 && (_pll_mult % 2)) + continue; + + _rate = ov5640_compute_sys_clk(sensor, + OV5640_PLL_PREDIV, + _pll_mult, _sysdiv); + + /* + * We have reached the maximum allowed PLL1 output, + * increase sysdiv. + */ + if (!_rate) + break; + + /* + * Prefer rates above the expected clock rate than + * below, even if that means being less precise. + */ + if (_rate < rate) + continue; + + if (abs(rate - _rate) < abs(rate - best)) { + best = _rate; + best_sysdiv = _sysdiv; + best_mult = _pll_mult; + } + + if (_rate == rate) + goto out; + } + } + +out: + *sysdiv = best_sysdiv; + *pll_prediv = OV5640_PLL_PREDIV; + *pll_mult = best_mult; + + return best; +} + +/* + * ov5640_set_mipi_pclk() - Calculate the clock tree configuration values + * for the MIPI CSI-2 output. + * + * @rate: The requested bandwidth per lane in bytes per second. + * 'Bandwidth Per Lane' is calculated as: + * bpl = HTOT * VTOT * FPS * bpp / num_lanes; + * + * This function use the requested bandwidth to calculate: + * - sample_rate = bpl / (bpp / num_lanes); + * = bpl / (PLL_RDIV * BIT_DIV * PCLK_DIV * MIPI_DIV / num_lanes); + * + * - mipi_sclk = bpl / MIPI_DIV / 2; ( / 2 is for CSI-2 DDR) + * + * with these fixed parameters: + * PLL_RDIV = 2; + * BIT_DIVIDER = 2; (MIPI_BIT_MODE == 8 ? 2 : 2,5); + * PCLK_DIV = 1; + * + * The MIPI clock generation differs for modes that use the scaler and modes + * that do not. In case the scaler is in use, the MIPI_SCLK generates the MIPI + * BIT CLk, and thus: + * + * - mipi_sclk = bpl / MIPI_DIV / 2; + * MIPI_DIV = 1; + * + * For modes that do not go through the scaler, the MIPI BIT CLOCK is generated + * from the pixel clock, and thus: + * + * - sample_rate = bpl / (bpp / num_lanes); + * = bpl / (2 * 2 * 1 * MIPI_DIV / num_lanes); + * = bpl / (4 * MIPI_DIV / num_lanes); + * - MIPI_DIV = bpp / (4 * num_lanes); + * + * FIXME: this have been tested with 16bpp and 2 lanes setup only. + * MIPI_DIV is fixed to value 2, but it -might- be changed according to the + * above formula for setups with 1 lane or image formats with different bpp. + * + * FIXME: this deviates from the sensor manual documentation which is quite + * thin on the MIPI clock tree generation part. + */ +static int ov5640_set_mipi_pclk(struct ov5640_dev *sensor, + unsigned long rate) +{ + const struct ov5640_mode_info *mode = sensor->current_mode; + u8 prediv, mult, sysdiv; + u8 mipi_div; + int ret; + + /* + * 1280x720 is reported to use 'SUBSAMPLING' only, + * but according to the sensor manual it goes through the + * scaler before subsampling. + */ + if (mode->dn_mode == SCALING || + (mode->id == OV5640_MODE_720P_1280_720)) + mipi_div = OV5640_MIPI_DIV_SCLK; + else + mipi_div = OV5640_MIPI_DIV_PCLK; + + ov5640_calc_sys_clk(sensor, rate, &prediv, &mult, &sysdiv); + + ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL0, + 0x0f, OV5640_PLL_CTRL0_MIPI_MODE_8BIT); + + ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL1, + 0xff, sysdiv << 4 | mipi_div); + if (ret) + return ret; + + ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL2, 0xff, mult); + if (ret) + return ret; + + ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL3, + 0x1f, OV5640_PLL_CTRL3_PLL_ROOT_DIV_2 | prediv); + if (ret) + return ret; + + return ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, + 0x30, OV5640_PLL_SYS_ROOT_DIVIDER_BYPASS); +} + +static unsigned long ov5640_calc_pclk(struct ov5640_dev *sensor, + unsigned long rate, + u8 *pll_prediv, u8 *pll_mult, u8 *sysdiv, + u8 *pll_rdiv, u8 *bit_div, u8 *pclk_div) +{ + unsigned long _rate = rate * OV5640_PLL_ROOT_DIV * OV5640_BIT_DIV * + OV5640_PCLK_ROOT_DIV; + + _rate = ov5640_calc_sys_clk(sensor, _rate, pll_prediv, pll_mult, + sysdiv); + *pll_rdiv = OV5640_PLL_ROOT_DIV; + *bit_div = OV5640_BIT_DIV; + *pclk_div = OV5640_PCLK_ROOT_DIV; + + return _rate / *pll_rdiv / *bit_div / *pclk_div; +} + +static int ov5640_set_dvp_pclk(struct ov5640_dev *sensor, unsigned long rate) +{ + u8 prediv, mult, sysdiv, pll_rdiv, bit_div, pclk_div; + int ret; + + ov5640_calc_pclk(sensor, rate, &prediv, &mult, &sysdiv, &pll_rdiv, + &bit_div, &pclk_div); + + if (bit_div == 2) + bit_div = 8; + + ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL0, + 0x0f, bit_div); + if (ret) + return ret; + + /* + * We need to set sysdiv according to the clock, and to clear + * the MIPI divider. + */ + ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL1, + 0xff, sysdiv << 4); + if (ret) + return ret; + + ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL2, + 0xff, mult); + if (ret) + return ret; + + ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL3, + 0x1f, prediv | ((pll_rdiv - 1) << 4)); + if (ret) + return ret; + + return ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, 0x30, + (ilog2(pclk_div) << 4)); +} + +/* set JPEG framing sizes */ +static int ov5640_set_jpeg_timings(struct ov5640_dev *sensor, + const struct ov5640_mode_info *mode) +{ + int ret; + + /* + * compression mode 3 timing + * + * Data is transmitted with programmable width (VFIFO_HSIZE). + * No padding done. Last line may have less data. Varying + * number of lines per frame, depending on amount of data. + */ + ret = ov5640_mod_reg(sensor, OV5640_REG_JPG_MODE_SELECT, 0x7, 0x3); + if (ret < 0) + return ret; + + ret = ov5640_write_reg16(sensor, OV5640_REG_VFIFO_HSIZE, mode->hact); + if (ret < 0) + return ret; + + return ov5640_write_reg16(sensor, OV5640_REG_VFIFO_VSIZE, mode->vact); +} + +/* download ov5640 settings to sensor through i2c */ +static int ov5640_set_timings(struct ov5640_dev *sensor, + const struct ov5640_mode_info *mode) +{ + int ret; + + if (sensor->fmt.code == MEDIA_BUS_FMT_JPEG_1X8) { + ret = ov5640_set_jpeg_timings(sensor, mode); + if (ret < 0) + return ret; + } + + ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPHO, mode->hact); + if (ret < 0) + return ret; + + ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPVO, mode->vact); + if (ret < 0) + return ret; + + ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_HTS, mode->htot); + if (ret < 0) + return ret; + + return ov5640_write_reg16(sensor, OV5640_REG_TIMING_VTS, mode->vtot); +} + +static int ov5640_load_regs(struct ov5640_dev *sensor, + const struct ov5640_mode_info *mode) +{ + const struct reg_value *regs = mode->reg_data; + unsigned int i; + u32 delay_ms; + u16 reg_addr; + u8 mask, val; + int ret = 0; + + for (i = 0; i < mode->reg_data_size; ++i, ++regs) { + delay_ms = regs->delay_ms; + reg_addr = regs->reg_addr; + val = regs->val; + mask = regs->mask; + + /* remain in power down mode for DVP */ + if (regs->reg_addr == OV5640_REG_SYS_CTRL0 && + val == OV5640_REG_SYS_CTRL0_SW_PWUP && + sensor->ep.bus_type != V4L2_MBUS_CSI2_DPHY) + continue; + + if (mask) + ret = ov5640_mod_reg(sensor, reg_addr, mask, val); + else + ret = ov5640_write_reg(sensor, reg_addr, val); + if (ret) + break; + + if (delay_ms) + usleep_range(1000 * delay_ms, 1000 * delay_ms + 100); + } + + return ov5640_set_timings(sensor, mode); +} + +static int ov5640_set_autoexposure(struct ov5640_dev *sensor, bool on) +{ + return ov5640_mod_reg(sensor, OV5640_REG_AEC_PK_MANUAL, + BIT(0), on ? 0 : BIT(0)); +} + +/* read exposure, in number of line periods */ +static int ov5640_get_exposure(struct ov5640_dev *sensor) +{ + int exp, ret; + u8 temp; + + ret = ov5640_read_reg(sensor, OV5640_REG_AEC_PK_EXPOSURE_HI, &temp); + if (ret) + return ret; + exp = ((int)temp & 0x0f) << 16; + ret = ov5640_read_reg(sensor, OV5640_REG_AEC_PK_EXPOSURE_MED, &temp); + if (ret) + return ret; + exp |= ((int)temp << 8); + ret = ov5640_read_reg(sensor, OV5640_REG_AEC_PK_EXPOSURE_LO, &temp); + if (ret) + return ret; + exp |= (int)temp; + + return exp >> 4; +} + +/* write exposure, given number of line periods */ +static int ov5640_set_exposure(struct ov5640_dev *sensor, u32 exposure) +{ + int ret; + + exposure <<= 4; + + ret = ov5640_write_reg(sensor, + OV5640_REG_AEC_PK_EXPOSURE_LO, + exposure & 0xff); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, + OV5640_REG_AEC_PK_EXPOSURE_MED, + (exposure >> 8) & 0xff); + if (ret) + return ret; + return ov5640_write_reg(sensor, + OV5640_REG_AEC_PK_EXPOSURE_HI, + (exposure >> 16) & 0x0f); +} + +static int ov5640_get_gain(struct ov5640_dev *sensor) +{ + u16 gain; + int ret; + + ret = ov5640_read_reg16(sensor, OV5640_REG_AEC_PK_REAL_GAIN, &gain); + if (ret) + return ret; + + return gain & 0x3ff; +} + +static int ov5640_set_gain(struct ov5640_dev *sensor, int gain) +{ + return ov5640_write_reg16(sensor, OV5640_REG_AEC_PK_REAL_GAIN, + (u16)gain & 0x3ff); +} + +static int ov5640_set_autogain(struct ov5640_dev *sensor, bool on) +{ + return ov5640_mod_reg(sensor, OV5640_REG_AEC_PK_MANUAL, + BIT(1), on ? 0 : BIT(1)); +} + +static int ov5640_set_stream_dvp(struct ov5640_dev *sensor, bool on) +{ + return ov5640_write_reg(sensor, OV5640_REG_SYS_CTRL0, on ? + OV5640_REG_SYS_CTRL0_SW_PWUP : + OV5640_REG_SYS_CTRL0_SW_PWDN); +} + +static int ov5640_set_stream_mipi(struct ov5640_dev *sensor, bool on) +{ + int ret; + + /* + * Enable/disable the MIPI interface + * + * 0x300e = on ? 0x45 : 0x40 + * + * FIXME: the sensor manual (version 2.03) reports + * [7:5] = 000 : 1 data lane mode + * [7:5] = 001 : 2 data lanes mode + * But this settings do not work, while the following ones + * have been validated for 2 data lanes mode. + * + * [7:5] = 010 : 2 data lanes mode + * [4] = 0 : Power up MIPI HS Tx + * [3] = 0 : Power up MIPI LS Rx + * [2] = 1/0 : MIPI interface enable/disable + * [1:0] = 01/00: FIXME: 'debug' + */ + ret = ov5640_write_reg(sensor, OV5640_REG_IO_MIPI_CTRL00, + on ? 0x45 : 0x40); + if (ret) + return ret; + + return ov5640_write_reg(sensor, OV5640_REG_FRAME_CTRL01, + on ? 0x00 : 0x0f); +} + +static int ov5640_get_sysclk(struct ov5640_dev *sensor) +{ + /* calculate sysclk */ + u32 xvclk = sensor->xclk_freq / 10000; + u32 multiplier, prediv, VCO, sysdiv, pll_rdiv; + u32 sclk_rdiv_map[] = {1, 2, 4, 8}; + u32 bit_div2x = 1, sclk_rdiv, sysclk; + u8 temp1, temp2; + int ret; + + ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL0, &temp1); + if (ret) + return ret; + temp2 = temp1 & 0x0f; + if (temp2 == 8 || temp2 == 10) + bit_div2x = temp2 / 2; + + ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL1, &temp1); + if (ret) + return ret; + sysdiv = temp1 >> 4; + if (sysdiv == 0) + sysdiv = 16; + + ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL2, &temp1); + if (ret) + return ret; + multiplier = temp1; + + ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL3, &temp1); + if (ret) + return ret; + prediv = temp1 & 0x0f; + pll_rdiv = ((temp1 >> 4) & 0x01) + 1; + + ret = ov5640_read_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, &temp1); + if (ret) + return ret; + temp2 = temp1 & 0x03; + sclk_rdiv = sclk_rdiv_map[temp2]; + + if (!prediv || !sysdiv || !pll_rdiv || !bit_div2x) + return -EINVAL; + + VCO = xvclk * multiplier / prediv; + + sysclk = VCO / sysdiv / pll_rdiv * 2 / bit_div2x / sclk_rdiv; + + return sysclk; +} + +static int ov5640_set_night_mode(struct ov5640_dev *sensor) +{ + /* read HTS from register settings */ + u8 mode; + int ret; + + ret = ov5640_read_reg(sensor, OV5640_REG_AEC_CTRL00, &mode); + if (ret) + return ret; + mode &= 0xfb; + return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL00, mode); +} + +static int ov5640_get_hts(struct ov5640_dev *sensor) +{ + /* read HTS from register settings */ + u16 hts; + int ret; + + ret = ov5640_read_reg16(sensor, OV5640_REG_TIMING_HTS, &hts); + if (ret) + return ret; + return hts; +} + +static int ov5640_get_vts(struct ov5640_dev *sensor) +{ + u16 vts; + int ret; + + ret = ov5640_read_reg16(sensor, OV5640_REG_TIMING_VTS, &vts); + if (ret) + return ret; + return vts; +} + +static int ov5640_set_vts(struct ov5640_dev *sensor, int vts) +{ + return ov5640_write_reg16(sensor, OV5640_REG_TIMING_VTS, vts); +} + +static int ov5640_get_light_freq(struct ov5640_dev *sensor) +{ + /* get banding filter value */ + int ret, light_freq = 0; + u8 temp, temp1; + + ret = ov5640_read_reg(sensor, OV5640_REG_HZ5060_CTRL01, &temp); + if (ret) + return ret; + + if (temp & 0x80) { + /* manual */ + ret = ov5640_read_reg(sensor, OV5640_REG_HZ5060_CTRL00, + &temp1); + if (ret) + return ret; + if (temp1 & 0x04) { + /* 50Hz */ + light_freq = 50; + } else { + /* 60Hz */ + light_freq = 60; + } + } else { + /* auto */ + ret = ov5640_read_reg(sensor, OV5640_REG_SIGMADELTA_CTRL0C, + &temp1); + if (ret) + return ret; + + if (temp1 & 0x01) { + /* 50Hz */ + light_freq = 50; + } else { + /* 60Hz */ + } + } + + return light_freq; +} + +static int ov5640_set_bandingfilter(struct ov5640_dev *sensor) +{ + u32 band_step60, max_band60, band_step50, max_band50, prev_vts; + int ret; + + /* read preview PCLK */ + ret = ov5640_get_sysclk(sensor); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + sensor->prev_sysclk = ret; + /* read preview HTS */ + ret = ov5640_get_hts(sensor); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + sensor->prev_hts = ret; + + /* read preview VTS */ + ret = ov5640_get_vts(sensor); + if (ret < 0) + return ret; + prev_vts = ret; + + /* calculate banding filter */ + /* 60Hz */ + band_step60 = sensor->prev_sysclk * 100 / sensor->prev_hts * 100 / 120; + ret = ov5640_write_reg16(sensor, OV5640_REG_AEC_B60_STEP, band_step60); + if (ret) + return ret; + if (!band_step60) + return -EINVAL; + max_band60 = (int)((prev_vts - 4) / band_step60); + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0D, max_band60); + if (ret) + return ret; + + /* 50Hz */ + band_step50 = sensor->prev_sysclk * 100 / sensor->prev_hts; + ret = ov5640_write_reg16(sensor, OV5640_REG_AEC_B50_STEP, band_step50); + if (ret) + return ret; + if (!band_step50) + return -EINVAL; + max_band50 = (int)((prev_vts - 4) / band_step50); + return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0E, max_band50); +} + +static int ov5640_set_ae_target(struct ov5640_dev *sensor, int target) +{ + /* stable in high */ + u32 fast_high, fast_low; + int ret; + + sensor->ae_low = target * 23 / 25; /* 0.92 */ + sensor->ae_high = target * 27 / 25; /* 1.08 */ + + fast_high = sensor->ae_high << 1; + if (fast_high > 255) + fast_high = 255; + + fast_low = sensor->ae_low >> 1; + + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0F, sensor->ae_high); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL10, sensor->ae_low); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1B, sensor->ae_high); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1E, sensor->ae_low); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL11, fast_high); + if (ret) + return ret; + return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1F, fast_low); +} + +static int ov5640_get_binning(struct ov5640_dev *sensor) +{ + u8 temp; + int ret; + + ret = ov5640_read_reg(sensor, OV5640_REG_TIMING_TC_REG21, &temp); + if (ret) + return ret; + + return temp & BIT(0); +} + +static int ov5640_set_binning(struct ov5640_dev *sensor, bool enable) +{ + int ret; + + /* + * TIMING TC REG21: + * - [0]: Horizontal binning enable + */ + ret = ov5640_mod_reg(sensor, OV5640_REG_TIMING_TC_REG21, + BIT(0), enable ? BIT(0) : 0); + if (ret) + return ret; + /* + * TIMING TC REG20: + * - [0]: Undocumented, but hardcoded init sequences + * are always setting REG21/REG20 bit 0 to same value... + */ + return ov5640_mod_reg(sensor, OV5640_REG_TIMING_TC_REG20, + BIT(0), enable ? BIT(0) : 0); +} + +static int ov5640_set_virtual_channel(struct ov5640_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + u8 temp, channel = virtual_channel; + int ret; + + if (channel > 3) { + dev_err(&client->dev, + "%s: wrong virtual_channel parameter, expected (0..3), got %d\n", + __func__, channel); + return -EINVAL; + } + + ret = ov5640_read_reg(sensor, OV5640_REG_DEBUG_MODE, &temp); + if (ret) + return ret; + temp &= ~(3 << 6); + temp |= (channel << 6); + return ov5640_write_reg(sensor, OV5640_REG_DEBUG_MODE, temp); +} + +static const struct ov5640_mode_info * +ov5640_find_mode(struct ov5640_dev *sensor, enum ov5640_frame_rate fr, + int width, int height, bool nearest) +{ + const struct ov5640_mode_info *mode; + + mode = v4l2_find_nearest_size(ov5640_mode_data, + ARRAY_SIZE(ov5640_mode_data), + hact, vact, + width, height); + + if (!mode || + (!nearest && (mode->hact != width || mode->vact != height))) + return NULL; + + /* Check to see if the current mode exceeds the max frame rate */ + if (ov5640_framerates[fr] > ov5640_framerates[mode->max_fps]) + return NULL; + + return mode; +} + +static u64 ov5640_calc_pixel_rate(struct ov5640_dev *sensor) +{ + u64 rate; + + rate = sensor->current_mode->vtot * sensor->current_mode->htot; + rate *= ov5640_framerates[sensor->current_fr]; + + return rate; +} + +/* + * sensor changes between scaling and subsampling, go through + * exposure calculation + */ +static int ov5640_set_mode_exposure_calc(struct ov5640_dev *sensor, + const struct ov5640_mode_info *mode) +{ + u32 prev_shutter, prev_gain16; + u32 cap_shutter, cap_gain16; + u32 cap_sysclk, cap_hts, cap_vts; + u32 light_freq, cap_bandfilt, cap_maxband; + u32 cap_gain16_shutter; + u8 average; + int ret; + + if (!mode->reg_data) + return -EINVAL; + + /* read preview shutter */ + ret = ov5640_get_exposure(sensor); + if (ret < 0) + return ret; + prev_shutter = ret; + ret = ov5640_get_binning(sensor); + if (ret < 0) + return ret; + if (ret && mode->id != OV5640_MODE_720P_1280_720 && + mode->id != OV5640_MODE_1080P_1920_1080) + prev_shutter *= 2; + + /* read preview gain */ + ret = ov5640_get_gain(sensor); + if (ret < 0) + return ret; + prev_gain16 = ret; + + /* get average */ + ret = ov5640_read_reg(sensor, OV5640_REG_AVG_READOUT, &average); + if (ret) + return ret; + + /* turn off night mode for capture */ + ret = ov5640_set_night_mode(sensor); + if (ret < 0) + return ret; + + /* Write capture setting */ + ret = ov5640_load_regs(sensor, mode); + if (ret < 0) + return ret; + + /* read capture VTS */ + ret = ov5640_get_vts(sensor); + if (ret < 0) + return ret; + cap_vts = ret; + ret = ov5640_get_hts(sensor); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + cap_hts = ret; + + ret = ov5640_get_sysclk(sensor); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + cap_sysclk = ret; + + /* calculate capture banding filter */ + ret = ov5640_get_light_freq(sensor); + if (ret < 0) + return ret; + light_freq = ret; + + if (light_freq == 60) { + /* 60Hz */ + cap_bandfilt = cap_sysclk * 100 / cap_hts * 100 / 120; + } else { + /* 50Hz */ + cap_bandfilt = cap_sysclk * 100 / cap_hts; + } + + if (!sensor->prev_sysclk) { + ret = ov5640_get_sysclk(sensor); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + sensor->prev_sysclk = ret; + } + + if (!cap_bandfilt) + return -EINVAL; + + cap_maxband = (int)((cap_vts - 4) / cap_bandfilt); + + /* calculate capture shutter/gain16 */ + if (average > sensor->ae_low && average < sensor->ae_high) { + /* in stable range */ + cap_gain16_shutter = + prev_gain16 * prev_shutter * + cap_sysclk / sensor->prev_sysclk * + sensor->prev_hts / cap_hts * + sensor->ae_target / average; + } else { + cap_gain16_shutter = + prev_gain16 * prev_shutter * + cap_sysclk / sensor->prev_sysclk * + sensor->prev_hts / cap_hts; + } + + /* gain to shutter */ + if (cap_gain16_shutter < (cap_bandfilt * 16)) { + /* shutter < 1/100 */ + cap_shutter = cap_gain16_shutter / 16; + if (cap_shutter < 1) + cap_shutter = 1; + + cap_gain16 = cap_gain16_shutter / cap_shutter; + if (cap_gain16 < 16) + cap_gain16 = 16; + } else { + if (cap_gain16_shutter > (cap_bandfilt * cap_maxband * 16)) { + /* exposure reach max */ + cap_shutter = cap_bandfilt * cap_maxband; + if (!cap_shutter) + return -EINVAL; + + cap_gain16 = cap_gain16_shutter / cap_shutter; + } else { + /* 1/100 < (cap_shutter = n/100) =< max */ + cap_shutter = + ((int)(cap_gain16_shutter / 16 / cap_bandfilt)) + * cap_bandfilt; + if (!cap_shutter) + return -EINVAL; + + cap_gain16 = cap_gain16_shutter / cap_shutter; + } + } + + /* set capture gain */ + ret = ov5640_set_gain(sensor, cap_gain16); + if (ret) + return ret; + + /* write capture shutter */ + if (cap_shutter > (cap_vts - 4)) { + cap_vts = cap_shutter + 4; + ret = ov5640_set_vts(sensor, cap_vts); + if (ret < 0) + return ret; + } + + /* set exposure */ + return ov5640_set_exposure(sensor, cap_shutter); +} + +/* + * if sensor changes inside scaling or subsampling + * change mode directly + */ +static int ov5640_set_mode_direct(struct ov5640_dev *sensor, + const struct ov5640_mode_info *mode) +{ + if (!mode->reg_data) + return -EINVAL; + + /* Write capture setting */ + return ov5640_load_regs(sensor, mode); +} + +static int ov5640_set_mode(struct ov5640_dev *sensor) +{ + const struct ov5640_mode_info *mode = sensor->current_mode; + const struct ov5640_mode_info *orig_mode = sensor->last_mode; + enum ov5640_downsize_mode dn_mode, orig_dn_mode; + bool auto_gain = sensor->ctrls.auto_gain->val == 1; + bool auto_exp = sensor->ctrls.auto_exp->val == V4L2_EXPOSURE_AUTO; + unsigned long rate; + int ret; + + dn_mode = mode->dn_mode; + orig_dn_mode = orig_mode->dn_mode; + + /* auto gain and exposure must be turned off when changing modes */ + if (auto_gain) { + ret = ov5640_set_autogain(sensor, false); + if (ret) + return ret; + } + + if (auto_exp) { + ret = ov5640_set_autoexposure(sensor, false); + if (ret) + goto restore_auto_gain; + } + + /* + * All the formats we support have 16 bits per pixel, seems to require + * the same rate than YUV, so we can just use 16 bpp all the time. + */ + rate = ov5640_calc_pixel_rate(sensor) * 16; + if (sensor->ep.bus_type == V4L2_MBUS_CSI2_DPHY) { + rate = rate / sensor->ep.bus.mipi_csi2.num_data_lanes; + ret = ov5640_set_mipi_pclk(sensor, rate); + } else { + rate = rate / sensor->ep.bus.parallel.bus_width; + ret = ov5640_set_dvp_pclk(sensor, rate); + } + + if (ret < 0) + return 0; + + if ((dn_mode == SUBSAMPLING && orig_dn_mode == SCALING) || + (dn_mode == SCALING && orig_dn_mode == SUBSAMPLING)) { + /* + * change between subsampling and scaling + * go through exposure calculation + */ + ret = ov5640_set_mode_exposure_calc(sensor, mode); + } else { + /* + * change inside subsampling or scaling + * download firmware directly + */ + ret = ov5640_set_mode_direct(sensor, mode); + } + if (ret < 0) + goto restore_auto_exp_gain; + + /* restore auto gain and exposure */ + if (auto_gain) + ov5640_set_autogain(sensor, true); + if (auto_exp) + ov5640_set_autoexposure(sensor, true); + + ret = ov5640_set_binning(sensor, dn_mode != SCALING); + if (ret < 0) + return ret; + ret = ov5640_set_ae_target(sensor, sensor->ae_target); + if (ret < 0) + return ret; + ret = ov5640_get_light_freq(sensor); + if (ret < 0) + return ret; + ret = ov5640_set_bandingfilter(sensor); + if (ret < 0) + return ret; + ret = ov5640_set_virtual_channel(sensor); + if (ret < 0) + return ret; + + sensor->pending_mode_change = false; + sensor->last_mode = mode; + + return 0; + +restore_auto_exp_gain: + if (auto_exp) + ov5640_set_autoexposure(sensor, true); +restore_auto_gain: + if (auto_gain) + ov5640_set_autogain(sensor, true); + + return ret; +} + +static int ov5640_set_framefmt(struct ov5640_dev *sensor, + struct v4l2_mbus_framefmt *format); + +/* restore the last set video mode after chip power-on */ +static int ov5640_restore_mode(struct ov5640_dev *sensor) +{ + int ret; + + /* first load the initial register values */ + ret = ov5640_load_regs(sensor, &ov5640_mode_init_data); + if (ret < 0) + return ret; + sensor->last_mode = &ov5640_mode_init_data; + + ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, 0x3f, + (ilog2(OV5640_SCLK2X_ROOT_DIV) << 2) | + ilog2(OV5640_SCLK_ROOT_DIV)); + if (ret) + return ret; + + /* now restore the last capture mode */ + ret = ov5640_set_mode(sensor); + if (ret < 0) + return ret; + + return ov5640_set_framefmt(sensor, &sensor->fmt); +} + +static void ov5640_power(struct ov5640_dev *sensor, bool enable) +{ + gpiod_set_value_cansleep(sensor->pwdn_gpio, enable ? 0 : 1); +} + +static void ov5640_reset(struct ov5640_dev *sensor) +{ + if (!sensor->reset_gpio) + return; + + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + + /* camera power cycle */ + ov5640_power(sensor, false); + usleep_range(5000, 10000); + ov5640_power(sensor, true); + usleep_range(5000, 10000); + + gpiod_set_value_cansleep(sensor->reset_gpio, 1); + usleep_range(1000, 2000); + + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(20000, 25000); +} + +static int ov5640_set_power_on(struct ov5640_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + int ret; + + ret = clk_prepare_enable(sensor->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + return ret; + } + + ret = regulator_bulk_enable(OV5640_NUM_SUPPLIES, + sensor->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + goto xclk_off; + } + + ov5640_reset(sensor); + ov5640_power(sensor, true); + + ret = ov5640_init_slave_id(sensor); + if (ret) + goto power_off; + + return 0; + +power_off: + ov5640_power(sensor, false); + regulator_bulk_disable(OV5640_NUM_SUPPLIES, sensor->supplies); +xclk_off: + clk_disable_unprepare(sensor->xclk); + return ret; +} + +static void ov5640_set_power_off(struct ov5640_dev *sensor) +{ + ov5640_power(sensor, false); + regulator_bulk_disable(OV5640_NUM_SUPPLIES, sensor->supplies); + clk_disable_unprepare(sensor->xclk); +} + +static int ov5640_set_power_mipi(struct ov5640_dev *sensor, bool on) +{ + int ret; + + if (!on) { + /* Reset MIPI bus settings to their default values. */ + ov5640_write_reg(sensor, OV5640_REG_IO_MIPI_CTRL00, 0x58); + ov5640_write_reg(sensor, OV5640_REG_MIPI_CTRL00, 0x04); + ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT00, 0x00); + return 0; + } + + /* + * Power up MIPI HS Tx and LS Rx; 2 data lanes mode + * + * 0x300e = 0x40 + * [7:5] = 010 : 2 data lanes mode (see FIXME note in + * "ov5640_set_stream_mipi()") + * [4] = 0 : Power up MIPI HS Tx + * [3] = 0 : Power up MIPI LS Rx + * [2] = 1 : MIPI interface enabled + */ + ret = ov5640_write_reg(sensor, OV5640_REG_IO_MIPI_CTRL00, 0x44); + if (ret) + return ret; + + /* + * Gate clock and set LP11 in 'no packets mode' (idle) + * + * 0x4800 = 0x24 + * [5] = 1 : Gate clock when 'no packets' + * [2] = 1 : MIPI bus in LP11 when 'no packets' + */ + ret = ov5640_write_reg(sensor, OV5640_REG_MIPI_CTRL00, 0x24); + if (ret) + return ret; + + /* + * Set data lanes and clock in LP11 when 'sleeping' + * + * 0x3019 = 0x70 + * [6] = 1 : MIPI data lane 2 in LP11 when 'sleeping' + * [5] = 1 : MIPI data lane 1 in LP11 when 'sleeping' + * [4] = 1 : MIPI clock lane in LP11 when 'sleeping' + */ + ret = ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT00, 0x70); + if (ret) + return ret; + + /* Give lanes some time to coax into LP11 state. */ + usleep_range(500, 1000); + + return 0; +} + +static int ov5640_set_power_dvp(struct ov5640_dev *sensor, bool on) +{ + unsigned int flags = sensor->ep.bus.parallel.flags; + bool bt656 = sensor->ep.bus_type == V4L2_MBUS_BT656; + u8 polarities = 0; + int ret; + + if (!on) { + /* Reset settings to their default values. */ + ov5640_write_reg(sensor, OV5640_REG_CCIR656_CTRL00, 0x00); + ov5640_write_reg(sensor, OV5640_REG_IO_MIPI_CTRL00, 0x58); + ov5640_write_reg(sensor, OV5640_REG_POLARITY_CTRL00, 0x20); + ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT_ENABLE01, 0x00); + ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT_ENABLE02, 0x00); + return 0; + } + + /* + * Note about parallel port configuration. + * + * When configured in parallel mode, the OV5640 will + * output 10 bits data on DVP data lines [9:0]. + * If only 8 bits data are wanted, the 8 bits data lines + * of the camera interface must be physically connected + * on the DVP data lines [9:2]. + * + * Control lines polarity can be configured through + * devicetree endpoint control lines properties. + * If no endpoint control lines properties are set, + * polarity will be as below: + * - VSYNC: active high + * - HREF: active low + * - PCLK: active low + * + * VSYNC & HREF are not configured if BT656 bus mode is selected + */ + + /* + * BT656 embedded synchronization configuration + * + * CCIR656 CTRL00 + * - [7]: SYNC code selection (0: auto generate sync code, + * 1: sync code from regs 0x4732-0x4735) + * - [6]: f value in CCIR656 SYNC code when fixed f value + * - [5]: Fixed f value + * - [4:3]: Blank toggle data options (00: data=1'h040/1'h200, + * 01: data from regs 0x4736-0x4738, 10: always keep 0) + * - [1]: Clip data disable + * - [0]: CCIR656 mode enable + * + * Default CCIR656 SAV/EAV mode with default codes + * SAV=0xff000080 & EAV=0xff00009d is enabled here with settings: + * - CCIR656 mode enable + * - auto generation of sync codes + * - blank toggle data 1'h040/1'h200 + * - clip reserved data (0x00 & 0xff changed to 0x01 & 0xfe) + */ + ret = ov5640_write_reg(sensor, OV5640_REG_CCIR656_CTRL00, + bt656 ? 0x01 : 0x00); + if (ret) + return ret; + + /* + * configure parallel port control lines polarity + * + * POLARITY CTRL0 + * - [5]: PCLK polarity (0: active low, 1: active high) + * - [1]: HREF polarity (0: active low, 1: active high) + * - [0]: VSYNC polarity (mismatch here between + * datasheet and hardware, 0 is active high + * and 1 is active low...) + */ + if (!bt656) { + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + polarities |= BIT(1); + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + polarities |= BIT(0); + } + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + polarities |= BIT(5); + + ret = ov5640_write_reg(sensor, OV5640_REG_POLARITY_CTRL00, polarities); + if (ret) + return ret; + + /* + * powerdown MIPI TX/RX PHY & enable DVP + * + * MIPI CONTROL 00 + * [4] = 1 : Power down MIPI HS Tx + * [3] = 1 : Power down MIPI LS Rx + * [2] = 0 : DVP enable (MIPI disable) + */ + ret = ov5640_write_reg(sensor, OV5640_REG_IO_MIPI_CTRL00, 0x18); + if (ret) + return ret; + + /* + * enable VSYNC/HREF/PCLK DVP control lines + * & D[9:6] DVP data lines + * + * PAD OUTPUT ENABLE 01 + * - 6: VSYNC output enable + * - 5: HREF output enable + * - 4: PCLK output enable + * - [3:0]: D[9:6] output enable + */ + ret = ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT_ENABLE01, + bt656 ? 0x1f : 0x7f); + if (ret) + return ret; + + /* + * enable D[5:0] DVP data lines + * + * PAD OUTPUT ENABLE 02 + * - [7:2]: D[5:0] output enable + */ + return ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT_ENABLE02, 0xfc); +} + +static int ov5640_set_power(struct ov5640_dev *sensor, bool on) +{ + int ret = 0; + + if (on) { + ret = ov5640_set_power_on(sensor); + if (ret) + return ret; + + ret = ov5640_restore_mode(sensor); + if (ret) + goto power_off; + } + + if (sensor->ep.bus_type == V4L2_MBUS_CSI2_DPHY) + ret = ov5640_set_power_mipi(sensor, on); + else + ret = ov5640_set_power_dvp(sensor, on); + if (ret) + goto power_off; + + if (!on) + ov5640_set_power_off(sensor); + + return 0; + +power_off: + ov5640_set_power_off(sensor); + return ret; +} + +/* --------------- Subdev Operations --------------- */ + +static int ov5640_s_power(struct v4l2_subdev *sd, int on) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + /* + * If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (sensor->power_count == !on) { + ret = ov5640_set_power(sensor, !!on); + if (ret) + goto out; + } + + /* Update the power count. */ + sensor->power_count += on ? 1 : -1; + WARN_ON(sensor->power_count < 0); +out: + mutex_unlock(&sensor->lock); + + if (on && !ret && sensor->power_count == 1) { + /* restore controls */ + ret = v4l2_ctrl_handler_setup(&sensor->ctrls.handler); + } + + return ret; +} + +static int ov5640_try_frame_interval(struct ov5640_dev *sensor, + struct v4l2_fract *fi, + u32 width, u32 height) +{ + const struct ov5640_mode_info *mode; + enum ov5640_frame_rate rate = OV5640_15_FPS; + int minfps, maxfps, best_fps, fps; + int i; + + minfps = ov5640_framerates[OV5640_15_FPS]; + maxfps = ov5640_framerates[OV5640_60_FPS]; + + if (fi->numerator == 0) { + fi->denominator = maxfps; + fi->numerator = 1; + rate = OV5640_60_FPS; + goto find_mode; + } + + fps = clamp_val(DIV_ROUND_CLOSEST(fi->denominator, fi->numerator), + minfps, maxfps); + + best_fps = minfps; + for (i = 0; i < ARRAY_SIZE(ov5640_framerates); i++) { + int curr_fps = ov5640_framerates[i]; + + if (abs(curr_fps - fps) < abs(best_fps - fps)) { + best_fps = curr_fps; + rate = i; + } + } + + fi->numerator = 1; + fi->denominator = best_fps; + +find_mode: + mode = ov5640_find_mode(sensor, rate, width, height, false); + return mode ? rate : -EINVAL; +} + +static int ov5640_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->pad != 0) + return -EINVAL; + + mutex_lock(&sensor->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + fmt = v4l2_subdev_get_try_format(&sensor->sd, cfg, + format->pad); + else + fmt = &sensor->fmt; + + format->format = *fmt; + + mutex_unlock(&sensor->lock); + + return 0; +} + +static int ov5640_try_fmt_internal(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt, + enum ov5640_frame_rate fr, + const struct ov5640_mode_info **new_mode) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + const struct ov5640_mode_info *mode; + int i; + + mode = ov5640_find_mode(sensor, fr, fmt->width, fmt->height, true); + if (!mode) + return -EINVAL; + fmt->width = mode->hact; + fmt->height = mode->vact; + + if (new_mode) + *new_mode = mode; + + for (i = 0; i < ARRAY_SIZE(ov5640_formats); i++) + if (ov5640_formats[i].code == fmt->code) + break; + if (i >= ARRAY_SIZE(ov5640_formats)) + i = 0; + + fmt->code = ov5640_formats[i].code; + fmt->colorspace = ov5640_formats[i].colorspace; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + + return 0; +} + +static int ov5640_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + const struct ov5640_mode_info *new_mode; + struct v4l2_mbus_framefmt *mbus_fmt = &format->format; + struct v4l2_mbus_framefmt *fmt; + int ret; + + if (format->pad != 0) + return -EINVAL; + + mutex_lock(&sensor->lock); + + if (sensor->streaming) { + ret = -EBUSY; + goto out; + } + + ret = ov5640_try_fmt_internal(sd, mbus_fmt, + sensor->current_fr, &new_mode); + if (ret) + goto out; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + fmt = v4l2_subdev_get_try_format(sd, cfg, 0); + else + fmt = &sensor->fmt; + + *fmt = *mbus_fmt; + + if (new_mode != sensor->current_mode) { + sensor->current_mode = new_mode; + sensor->pending_mode_change = true; + } + if (mbus_fmt->code != sensor->fmt.code) + sensor->pending_fmt_change = true; + + __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, + ov5640_calc_pixel_rate(sensor)); +out: + mutex_unlock(&sensor->lock); + return ret; +} + +static int ov5640_set_framefmt(struct ov5640_dev *sensor, + struct v4l2_mbus_framefmt *format) +{ + int ret = 0; + bool is_jpeg = false; + u8 fmt, mux; + + switch (format->code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + /* YUV422, UYVY */ + fmt = 0x3f; + mux = OV5640_FMT_MUX_YUV422; + break; + case MEDIA_BUS_FMT_YUYV8_2X8: + /* YUV422, YUYV */ + fmt = 0x30; + mux = OV5640_FMT_MUX_YUV422; + break; + case MEDIA_BUS_FMT_RGB565_2X8_LE: + /* RGB565 {g[2:0],b[4:0]},{r[4:0],g[5:3]} */ + fmt = 0x6F; + mux = OV5640_FMT_MUX_RGB; + break; + case MEDIA_BUS_FMT_RGB565_2X8_BE: + /* RGB565 {r[4:0],g[5:3]},{g[2:0],b[4:0]} */ + fmt = 0x61; + mux = OV5640_FMT_MUX_RGB; + break; + case MEDIA_BUS_FMT_JPEG_1X8: + /* YUV422, YUYV */ + fmt = 0x30; + mux = OV5640_FMT_MUX_YUV422; + is_jpeg = true; + break; + case MEDIA_BUS_FMT_SBGGR8_1X8: + /* Raw, BGBG... / GRGR... */ + fmt = 0x00; + mux = OV5640_FMT_MUX_RAW_DPC; + break; + case MEDIA_BUS_FMT_SGBRG8_1X8: + /* Raw bayer, GBGB... / RGRG... */ + fmt = 0x01; + mux = OV5640_FMT_MUX_RAW_DPC; + break; + case MEDIA_BUS_FMT_SGRBG8_1X8: + /* Raw bayer, GRGR... / BGBG... */ + fmt = 0x02; + mux = OV5640_FMT_MUX_RAW_DPC; + break; + case MEDIA_BUS_FMT_SRGGB8_1X8: + /* Raw bayer, RGRG... / GBGB... */ + fmt = 0x03; + mux = OV5640_FMT_MUX_RAW_DPC; + break; + default: + return -EINVAL; + } + + /* FORMAT CONTROL00: YUV and RGB formatting */ + ret = ov5640_write_reg(sensor, OV5640_REG_FORMAT_CONTROL00, fmt); + if (ret) + return ret; + + /* FORMAT MUX CONTROL: ISP YUV or RGB */ + ret = ov5640_write_reg(sensor, OV5640_REG_ISP_FORMAT_MUX_CTRL, mux); + if (ret) + return ret; + + /* + * TIMING TC REG21: + * - [5]: JPEG enable + */ + ret = ov5640_mod_reg(sensor, OV5640_REG_TIMING_TC_REG21, + BIT(5), is_jpeg ? BIT(5) : 0); + if (ret) + return ret; + + /* + * SYSTEM RESET02: + * - [4]: Reset JFIFO + * - [3]: Reset SFIFO + * - [2]: Reset JPEG + */ + ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_RESET02, + BIT(4) | BIT(3) | BIT(2), + is_jpeg ? 0 : (BIT(4) | BIT(3) | BIT(2))); + if (ret) + return ret; + + /* + * CLOCK ENABLE02: + * - [5]: Enable JPEG 2x clock + * - [3]: Enable JPEG clock + */ + return ov5640_mod_reg(sensor, OV5640_REG_SYS_CLOCK_ENABLE02, + BIT(5) | BIT(3), + is_jpeg ? (BIT(5) | BIT(3)) : 0); +} + +/* + * Sensor Controls. + */ + +static int ov5640_set_ctrl_hue(struct ov5640_dev *sensor, int value) +{ + int ret; + + if (value) { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, + BIT(0), BIT(0)); + if (ret) + return ret; + ret = ov5640_write_reg16(sensor, OV5640_REG_SDE_CTRL1, value); + } else { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, BIT(0), 0); + } + + return ret; +} + +static int ov5640_set_ctrl_contrast(struct ov5640_dev *sensor, int value) +{ + int ret; + + if (value) { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, + BIT(2), BIT(2)); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_SDE_CTRL5, + value & 0xff); + } else { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, BIT(2), 0); + } + + return ret; +} + +static int ov5640_set_ctrl_saturation(struct ov5640_dev *sensor, int value) +{ + int ret; + + if (value) { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, + BIT(1), BIT(1)); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_SDE_CTRL3, + value & 0xff); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_SDE_CTRL4, + value & 0xff); + } else { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, BIT(1), 0); + } + + return ret; +} + +static int ov5640_set_ctrl_white_balance(struct ov5640_dev *sensor, int awb) +{ + int ret; + + ret = ov5640_mod_reg(sensor, OV5640_REG_AWB_MANUAL_CTRL, + BIT(0), awb ? 0 : 1); + if (ret) + return ret; + + if (!awb) { + u16 red = (u16)sensor->ctrls.red_balance->val; + u16 blue = (u16)sensor->ctrls.blue_balance->val; + + ret = ov5640_write_reg16(sensor, OV5640_REG_AWB_R_GAIN, red); + if (ret) + return ret; + ret = ov5640_write_reg16(sensor, OV5640_REG_AWB_B_GAIN, blue); + } + + return ret; +} + +static int ov5640_set_ctrl_exposure(struct ov5640_dev *sensor, + enum v4l2_exposure_auto_type auto_exposure) +{ + struct ov5640_ctrls *ctrls = &sensor->ctrls; + bool auto_exp = (auto_exposure == V4L2_EXPOSURE_AUTO); + int ret = 0; + + if (ctrls->auto_exp->is_new) { + ret = ov5640_set_autoexposure(sensor, auto_exp); + if (ret) + return ret; + } + + if (!auto_exp && ctrls->exposure->is_new) { + u16 max_exp; + + ret = ov5640_read_reg16(sensor, OV5640_REG_AEC_PK_VTS, + &max_exp); + if (ret) + return ret; + ret = ov5640_get_vts(sensor); + if (ret < 0) + return ret; + max_exp += ret; + ret = 0; + + if (ctrls->exposure->val < max_exp) + ret = ov5640_set_exposure(sensor, ctrls->exposure->val); + } + + return ret; +} + +static int ov5640_set_ctrl_gain(struct ov5640_dev *sensor, bool auto_gain) +{ + struct ov5640_ctrls *ctrls = &sensor->ctrls; + int ret = 0; + + if (ctrls->auto_gain->is_new) { + ret = ov5640_set_autogain(sensor, auto_gain); + if (ret) + return ret; + } + + if (!auto_gain && ctrls->gain->is_new) + ret = ov5640_set_gain(sensor, ctrls->gain->val); + + return ret; +} + +static const char * const test_pattern_menu[] = { + "Disabled", + "Color bars", + "Color bars w/ rolling bar", + "Color squares", + "Color squares w/ rolling bar", +}; + +#define OV5640_TEST_ENABLE BIT(7) +#define OV5640_TEST_ROLLING BIT(6) /* rolling horizontal bar */ +#define OV5640_TEST_TRANSPARENT BIT(5) +#define OV5640_TEST_SQUARE_BW BIT(4) /* black & white squares */ +#define OV5640_TEST_BAR_STANDARD (0 << 2) +#define OV5640_TEST_BAR_VERT_CHANGE_1 (1 << 2) +#define OV5640_TEST_BAR_HOR_CHANGE (2 << 2) +#define OV5640_TEST_BAR_VERT_CHANGE_2 (3 << 2) +#define OV5640_TEST_BAR (0 << 0) +#define OV5640_TEST_RANDOM (1 << 0) +#define OV5640_TEST_SQUARE (2 << 0) +#define OV5640_TEST_BLACK (3 << 0) + +static const u8 test_pattern_val[] = { + 0, + OV5640_TEST_ENABLE | OV5640_TEST_BAR_VERT_CHANGE_1 | + OV5640_TEST_BAR, + OV5640_TEST_ENABLE | OV5640_TEST_ROLLING | + OV5640_TEST_BAR_VERT_CHANGE_1 | OV5640_TEST_BAR, + OV5640_TEST_ENABLE | OV5640_TEST_SQUARE, + OV5640_TEST_ENABLE | OV5640_TEST_ROLLING | OV5640_TEST_SQUARE, +}; + +static int ov5640_set_ctrl_test_pattern(struct ov5640_dev *sensor, int value) +{ + return ov5640_write_reg(sensor, OV5640_REG_PRE_ISP_TEST_SET1, + test_pattern_val[value]); +} + +static int ov5640_set_ctrl_light_freq(struct ov5640_dev *sensor, int value) +{ + int ret; + + ret = ov5640_mod_reg(sensor, OV5640_REG_HZ5060_CTRL01, BIT(7), + (value == V4L2_CID_POWER_LINE_FREQUENCY_AUTO) ? + 0 : BIT(7)); + if (ret) + return ret; + + return ov5640_mod_reg(sensor, OV5640_REG_HZ5060_CTRL00, BIT(2), + (value == V4L2_CID_POWER_LINE_FREQUENCY_50HZ) ? + BIT(2) : 0); +} + +static int ov5640_set_ctrl_hflip(struct ov5640_dev *sensor, int value) +{ + /* + * If sensor is mounted upside down, mirror logic is inversed. + * + * Sensor is a BSI (Back Side Illuminated) one, + * so image captured is physically mirrored. + * This is why mirror logic is inversed in + * order to cancel this mirror effect. + */ + + /* + * TIMING TC REG21: + * - [2]: ISP mirror + * - [1]: Sensor mirror + */ + return ov5640_mod_reg(sensor, OV5640_REG_TIMING_TC_REG21, + BIT(2) | BIT(1), + (!(value ^ sensor->upside_down)) ? + (BIT(2) | BIT(1)) : 0); +} + +static int ov5640_set_ctrl_vflip(struct ov5640_dev *sensor, int value) +{ + /* If sensor is mounted upside down, flip logic is inversed */ + + /* + * TIMING TC REG20: + * - [2]: ISP vflip + * - [1]: Sensor vflip + */ + return ov5640_mod_reg(sensor, OV5640_REG_TIMING_TC_REG20, + BIT(2) | BIT(1), + (value ^ sensor->upside_down) ? + (BIT(2) | BIT(1)) : 0); +} + +static int ov5640_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct ov5640_dev *sensor = to_ov5640_dev(sd); + int val; + + /* v4l2_ctrl_lock() locks our own mutex */ + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + val = ov5640_get_gain(sensor); + if (val < 0) + return val; + sensor->ctrls.gain->val = val; + break; + case V4L2_CID_EXPOSURE_AUTO: + val = ov5640_get_exposure(sensor); + if (val < 0) + return val; + sensor->ctrls.exposure->val = val; + break; + } + + return 0; +} + +static int ov5640_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct ov5640_dev *sensor = to_ov5640_dev(sd); + int ret; + + /* v4l2_ctrl_lock() locks our own mutex */ + + /* + * If the device is not powered up by the host driver do + * not apply any controls to H/W at this time. Instead + * the controls will be restored right after power-up. + */ + if (sensor->power_count == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + ret = ov5640_set_ctrl_gain(sensor, ctrl->val); + break; + case V4L2_CID_EXPOSURE_AUTO: + ret = ov5640_set_ctrl_exposure(sensor, ctrl->val); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov5640_set_ctrl_white_balance(sensor, ctrl->val); + break; + case V4L2_CID_HUE: + ret = ov5640_set_ctrl_hue(sensor, ctrl->val); + break; + case V4L2_CID_CONTRAST: + ret = ov5640_set_ctrl_contrast(sensor, ctrl->val); + break; + case V4L2_CID_SATURATION: + ret = ov5640_set_ctrl_saturation(sensor, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov5640_set_ctrl_test_pattern(sensor, ctrl->val); + break; + case V4L2_CID_POWER_LINE_FREQUENCY: + ret = ov5640_set_ctrl_light_freq(sensor, ctrl->val); + break; + case V4L2_CID_HFLIP: + ret = ov5640_set_ctrl_hflip(sensor, ctrl->val); + break; + case V4L2_CID_VFLIP: + ret = ov5640_set_ctrl_vflip(sensor, ctrl->val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops ov5640_ctrl_ops = { + .g_volatile_ctrl = ov5640_g_volatile_ctrl, + .s_ctrl = ov5640_s_ctrl, +}; + +static int ov5640_init_controls(struct ov5640_dev *sensor) +{ + const struct v4l2_ctrl_ops *ops = &ov5640_ctrl_ops; + struct ov5640_ctrls *ctrls = &sensor->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int ret; + + v4l2_ctrl_handler_init(hdl, 32); + + /* we can use our own mutex for the ctrl lock */ + hdl->lock = &sensor->lock; + + /* Clock related controls */ + ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, + 0, INT_MAX, 1, + ov5640_calc_pixel_rate(sensor)); + + /* Auto/manual white balance */ + ctrls->auto_wb = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, + 0, 4095, 1, 0); + ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, + 0, 4095, 1, 0); + /* Auto/manual exposure */ + ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, + V4L2_EXPOSURE_AUTO); + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + 0, 65535, 1, 0); + /* Auto/manual gain */ + ctrls->auto_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTOGAIN, + 0, 1, 1, 1); + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, + 0, 1023, 1, 0); + + ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, + 0, 255, 1, 64); + ctrls->hue = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HUE, + 0, 359, 1, 0); + ctrls->contrast = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, + 0, 255, 1, 0); + ctrls->test_pattern = + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(test_pattern_menu) - 1, + 0, 0, test_pattern_menu); + ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, + 0, 1, 1, 0); + ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, + 0, 1, 1, 0); + + ctrls->light_freq = + v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, + V4L2_CID_POWER_LINE_FREQUENCY_50HZ); + + if (hdl->error) { + ret = hdl->error; + goto free_ctrls; + } + + ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; + ctrls->gain->flags |= V4L2_CTRL_FLAG_VOLATILE; + ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; + + v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false); + v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true); + v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, 1, true); + + sensor->sd.ctrl_handler = hdl; + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(hdl); + return ret; +} + +static int ov5640_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->pad != 0) + return -EINVAL; + if (fse->index >= OV5640_NUM_MODES) + return -EINVAL; + + fse->min_width = + ov5640_mode_data[fse->index].hact; + fse->max_width = fse->min_width; + fse->min_height = + ov5640_mode_data[fse->index].vact; + fse->max_height = fse->min_height; + + return 0; +} + +static int ov5640_enum_frame_interval( + struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + struct v4l2_fract tpf; + int ret; + + if (fie->pad != 0) + return -EINVAL; + if (fie->index >= OV5640_NUM_FRAMERATES) + return -EINVAL; + + tpf.numerator = 1; + tpf.denominator = ov5640_framerates[fie->index]; + + ret = ov5640_try_frame_interval(sensor, &tpf, + fie->width, fie->height); + if (ret < 0) + return -EINVAL; + + fie->interval = tpf; + return 0; +} + +static int ov5640_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + + mutex_lock(&sensor->lock); + fi->interval = sensor->frame_interval; + mutex_unlock(&sensor->lock); + + return 0; +} + +static int ov5640_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + const struct ov5640_mode_info *mode; + int frame_rate, ret = 0; + + if (fi->pad != 0) + return -EINVAL; + + mutex_lock(&sensor->lock); + + if (sensor->streaming) { + ret = -EBUSY; + goto out; + } + + mode = sensor->current_mode; + + frame_rate = ov5640_try_frame_interval(sensor, &fi->interval, + mode->hact, mode->vact); + if (frame_rate < 0) { + /* Always return a valid frame interval value */ + fi->interval = sensor->frame_interval; + goto out; + } + + mode = ov5640_find_mode(sensor, frame_rate, mode->hact, + mode->vact, true); + if (!mode) { + ret = -EINVAL; + goto out; + } + + if (mode != sensor->current_mode || + frame_rate != sensor->current_fr) { + sensor->current_fr = frame_rate; + sensor->frame_interval = fi->interval; + sensor->current_mode = mode; + sensor->pending_mode_change = true; + + __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, + ov5640_calc_pixel_rate(sensor)); + } +out: + mutex_unlock(&sensor->lock); + return ret; +} + +static int ov5640_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad != 0) + return -EINVAL; + if (code->index >= ARRAY_SIZE(ov5640_formats)) + return -EINVAL; + + code->code = ov5640_formats[code->index].code; + return 0; +} + +static int ov5640_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + if (sensor->streaming == !enable) { + if (enable && sensor->pending_mode_change) { + ret = ov5640_set_mode(sensor); + if (ret) + goto out; + } + + if (enable && sensor->pending_fmt_change) { + ret = ov5640_set_framefmt(sensor, &sensor->fmt); + if (ret) + goto out; + sensor->pending_fmt_change = false; + } + + if (sensor->ep.bus_type == V4L2_MBUS_CSI2_DPHY) + ret = ov5640_set_stream_mipi(sensor, enable); + else + ret = ov5640_set_stream_dvp(sensor, enable); + + if (!ret) + sensor->streaming = enable; + } +out: + mutex_unlock(&sensor->lock); + return ret; +} + +static const struct v4l2_subdev_core_ops ov5640_core_ops = { + .s_power = ov5640_s_power, + .log_status = v4l2_ctrl_subdev_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops ov5640_video_ops = { + .g_frame_interval = ov5640_g_frame_interval, + .s_frame_interval = ov5640_s_frame_interval, + .s_stream = ov5640_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov5640_pad_ops = { + .enum_mbus_code = ov5640_enum_mbus_code, + .get_fmt = ov5640_get_fmt, + .set_fmt = ov5640_set_fmt, + .enum_frame_size = ov5640_enum_frame_size, + .enum_frame_interval = ov5640_enum_frame_interval, +}; + +static const struct v4l2_subdev_ops ov5640_subdev_ops = { + .core = &ov5640_core_ops, + .video = &ov5640_video_ops, + .pad = &ov5640_pad_ops, +}; + +static int ov5640_get_regulators(struct ov5640_dev *sensor) +{ + int i; + + for (i = 0; i < OV5640_NUM_SUPPLIES; i++) + sensor->supplies[i].supply = ov5640_supply_name[i]; + + return devm_regulator_bulk_get(&sensor->i2c_client->dev, + OV5640_NUM_SUPPLIES, + sensor->supplies); +} + +static int ov5640_check_chip_id(struct ov5640_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + int ret = 0; + u16 chip_id; + + ret = ov5640_set_power_on(sensor); + if (ret) + return ret; + + ret = ov5640_read_reg16(sensor, OV5640_REG_CHIP_ID, &chip_id); + if (ret) { + dev_err(&client->dev, "%s: failed to read chip identifier\n", + __func__); + goto power_off; + } + + if (chip_id != 0x5640) { + dev_err(&client->dev, "%s: wrong chip identifier, expected 0x5640, got 0x%x\n", + __func__, chip_id); + ret = -ENXIO; + } + +power_off: + ov5640_set_power_off(sensor); + return ret; +} + +static int ov5640_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct fwnode_handle *endpoint; + struct ov5640_dev *sensor; + struct v4l2_mbus_framefmt *fmt; + u32 rotation; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->i2c_client = client; + + /* + * default init sequence initialize sensor to + * YUV422 UYVY VGA@30fps + */ + fmt = &sensor->fmt; + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + fmt->width = 640; + fmt->height = 480; + fmt->field = V4L2_FIELD_NONE; + sensor->frame_interval.numerator = 1; + sensor->frame_interval.denominator = ov5640_framerates[OV5640_30_FPS]; + sensor->current_fr = OV5640_30_FPS; + sensor->current_mode = + &ov5640_mode_data[OV5640_MODE_VGA_640_480]; + sensor->last_mode = sensor->current_mode; + + sensor->ae_target = 52; + + /* optional indication of physical rotation of sensor */ + ret = fwnode_property_read_u32(dev_fwnode(&client->dev), "rotation", + &rotation); + if (!ret) { + switch (rotation) { + case 180: + sensor->upside_down = true; + fallthrough; + case 0: + break; + default: + dev_warn(dev, "%u degrees rotation is not supported, ignoring...\n", + rotation); + } + } + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), + NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep); + fwnode_handle_put(endpoint); + if (ret) { + dev_err(dev, "Could not parse endpoint\n"); + return ret; + } + + if (sensor->ep.bus_type != V4L2_MBUS_PARALLEL && + sensor->ep.bus_type != V4L2_MBUS_CSI2_DPHY && + sensor->ep.bus_type != V4L2_MBUS_BT656) { + dev_err(dev, "Unsupported bus type %d\n", sensor->ep.bus_type); + return -EINVAL; + } + + /* get system clock (xclk) */ + sensor->xclk = devm_clk_get(dev, "xclk"); + if (IS_ERR(sensor->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(sensor->xclk); + } + + sensor->xclk_freq = clk_get_rate(sensor->xclk); + if (sensor->xclk_freq < OV5640_XCLK_MIN || + sensor->xclk_freq > OV5640_XCLK_MAX) { + dev_err(dev, "xclk frequency out of range: %d Hz\n", + sensor->xclk_freq); + return -EINVAL; + } + + /* request optional power down pin */ + sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(sensor->pwdn_gpio)) + return PTR_ERR(sensor->pwdn_gpio); + + /* request optional reset pin */ + sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(sensor->reset_gpio)) + return PTR_ERR(sensor->reset_gpio); + + v4l2_i2c_subdev_init(&sensor->sd, client, &ov5640_subdev_ops); + + sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret) + return ret; + + ret = ov5640_get_regulators(sensor); + if (ret) + return ret; + + mutex_init(&sensor->lock); + + ret = ov5640_check_chip_id(sensor); + if (ret) + goto entity_cleanup; + + ret = ov5640_init_controls(sensor); + if (ret) + goto entity_cleanup; + + ret = v4l2_async_register_subdev_sensor_common(&sensor->sd); + if (ret) + goto free_ctrls; + + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(&sensor->ctrls.handler); +entity_cleanup: + media_entity_cleanup(&sensor->sd.entity); + mutex_destroy(&sensor->lock); + return ret; +} + +static int ov5640_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5640_dev *sensor = to_ov5640_dev(sd); + + v4l2_async_unregister_subdev(&sensor->sd); + media_entity_cleanup(&sensor->sd.entity); + v4l2_ctrl_handler_free(&sensor->ctrls.handler); + mutex_destroy(&sensor->lock); + + return 0; +} + +static const struct i2c_device_id ov5640_id[] = { + {"ov5640", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ov5640_id); + +static const struct of_device_id ov5640_dt_ids[] = { + { .compatible = "ovti,ov5640" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ov5640_dt_ids); + +static struct i2c_driver ov5640_i2c_driver = { + .driver = { + .name = "ov5640", + .of_match_table = ov5640_dt_ids, + }, + .id_table = ov5640_id, + .probe_new = ov5640_probe, + .remove = ov5640_remove, +}; + +module_i2c_driver(ov5640_i2c_driver); + +MODULE_DESCRIPTION("OV5640 MIPI Camera Subdev Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ov5645.c b/drivers/media/i2c/ov5645.c new file mode 100644 index 000000000..a6c17d15d --- /dev/null +++ b/drivers/media/i2c/ov5645.c @@ -0,0 +1,1296 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the OV5645 camera sensor. + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015 By Tech Design S.L. All Rights Reserved. + * Copyright (C) 2012-2013 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Based on: + * - the OV5645 driver from QC msm-3.10 kernel on codeaurora.org: + * https://us.codeaurora.org/cgit/quic/la/kernel/msm-3.10/tree/drivers/ + * media/platform/msm/camera_v2/sensor/ov5645.c?h=LA.BR.1.2.4_rb1.41 + * - the OV5640 driver posted on linux-media: + * https://www.mail-archive.com/linux-media%40vger.kernel.org/msg92671.html + */ + +/* + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define OV5645_SYSTEM_CTRL0 0x3008 +#define OV5645_SYSTEM_CTRL0_START 0x02 +#define OV5645_SYSTEM_CTRL0_STOP 0x42 +#define OV5645_CHIP_ID_HIGH 0x300a +#define OV5645_CHIP_ID_HIGH_BYTE 0x56 +#define OV5645_CHIP_ID_LOW 0x300b +#define OV5645_CHIP_ID_LOW_BYTE 0x45 +#define OV5645_IO_MIPI_CTRL00 0x300e +#define OV5645_PAD_OUTPUT00 0x3019 +#define OV5645_AWB_MANUAL_CONTROL 0x3406 +#define OV5645_AWB_MANUAL_ENABLE BIT(0) +#define OV5645_AEC_PK_MANUAL 0x3503 +#define OV5645_AEC_MANUAL_ENABLE BIT(0) +#define OV5645_AGC_MANUAL_ENABLE BIT(1) +#define OV5645_TIMING_TC_REG20 0x3820 +#define OV5645_SENSOR_VFLIP BIT(1) +#define OV5645_ISP_VFLIP BIT(2) +#define OV5645_TIMING_TC_REG21 0x3821 +#define OV5645_SENSOR_MIRROR BIT(1) +#define OV5645_MIPI_CTRL00 0x4800 +#define OV5645_PRE_ISP_TEST_SETTING_1 0x503d +#define OV5645_TEST_PATTERN_MASK 0x3 +#define OV5645_SET_TEST_PATTERN(x) ((x) & OV5645_TEST_PATTERN_MASK) +#define OV5645_TEST_PATTERN_ENABLE BIT(7) +#define OV5645_SDE_SAT_U 0x5583 +#define OV5645_SDE_SAT_V 0x5584 + +/* regulator supplies */ +static const char * const ov5645_supply_name[] = { + "vdddo", /* Digital I/O (1.8V) supply */ + "vdda", /* Analog (2.8V) supply */ + "vddd", /* Digital Core (1.5V) supply */ +}; + +#define OV5645_NUM_SUPPLIES ARRAY_SIZE(ov5645_supply_name) + +struct reg_value { + u16 reg; + u8 val; +}; + +struct ov5645_mode_info { + u32 width; + u32 height; + const struct reg_value *data; + u32 data_size; + u32 pixel_clock; + u32 link_freq; +}; + +struct ov5645 { + struct i2c_client *i2c_client; + struct device *dev; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_fwnode_endpoint ep; + struct v4l2_mbus_framefmt fmt; + struct v4l2_rect crop; + struct clk *xclk; + + struct regulator_bulk_data supplies[OV5645_NUM_SUPPLIES]; + + const struct ov5645_mode_info *current_mode; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *pixel_clock; + struct v4l2_ctrl *link_freq; + + /* Cached register values */ + u8 aec_pk_manual; + u8 timing_tc_reg20; + u8 timing_tc_reg21; + + struct mutex power_lock; /* lock to protect power state */ + int power_count; + + struct gpio_desc *enable_gpio; + struct gpio_desc *rst_gpio; +}; + +static inline struct ov5645 *to_ov5645(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov5645, sd); +} + +static const struct reg_value ov5645_global_init_setting[] = { + { 0x3103, 0x11 }, + { 0x3008, 0x82 }, + { 0x3008, 0x42 }, + { 0x3103, 0x03 }, + { 0x3503, 0x07 }, + { 0x3002, 0x1c }, + { 0x3006, 0xc3 }, + { 0x3017, 0x00 }, + { 0x3018, 0x00 }, + { 0x302e, 0x0b }, + { 0x3037, 0x13 }, + { 0x3108, 0x01 }, + { 0x3611, 0x06 }, + { 0x3500, 0x00 }, + { 0x3501, 0x01 }, + { 0x3502, 0x00 }, + { 0x350a, 0x00 }, + { 0x350b, 0x3f }, + { 0x3620, 0x33 }, + { 0x3621, 0xe0 }, + { 0x3622, 0x01 }, + { 0x3630, 0x2e }, + { 0x3631, 0x00 }, + { 0x3632, 0x32 }, + { 0x3633, 0x52 }, + { 0x3634, 0x70 }, + { 0x3635, 0x13 }, + { 0x3636, 0x03 }, + { 0x3703, 0x5a }, + { 0x3704, 0xa0 }, + { 0x3705, 0x1a }, + { 0x3709, 0x12 }, + { 0x370b, 0x61 }, + { 0x370f, 0x10 }, + { 0x3715, 0x78 }, + { 0x3717, 0x01 }, + { 0x371b, 0x20 }, + { 0x3731, 0x12 }, + { 0x3901, 0x0a }, + { 0x3905, 0x02 }, + { 0x3906, 0x10 }, + { 0x3719, 0x86 }, + { 0x3810, 0x00 }, + { 0x3811, 0x10 }, + { 0x3812, 0x00 }, + { 0x3821, 0x01 }, + { 0x3824, 0x01 }, + { 0x3826, 0x03 }, + { 0x3828, 0x08 }, + { 0x3a19, 0xf8 }, + { 0x3c01, 0x34 }, + { 0x3c04, 0x28 }, + { 0x3c05, 0x98 }, + { 0x3c07, 0x07 }, + { 0x3c09, 0xc2 }, + { 0x3c0a, 0x9c }, + { 0x3c0b, 0x40 }, + { 0x3c01, 0x34 }, + { 0x4001, 0x02 }, + { 0x4514, 0x00 }, + { 0x4520, 0xb0 }, + { 0x460b, 0x37 }, + { 0x460c, 0x20 }, + { 0x4818, 0x01 }, + { 0x481d, 0xf0 }, + { 0x481f, 0x50 }, + { 0x4823, 0x70 }, + { 0x4831, 0x14 }, + { 0x5000, 0xa7 }, + { 0x5001, 0x83 }, + { 0x501d, 0x00 }, + { 0x501f, 0x00 }, + { 0x503d, 0x00 }, + { 0x505c, 0x30 }, + { 0x5181, 0x59 }, + { 0x5183, 0x00 }, + { 0x5191, 0xf0 }, + { 0x5192, 0x03 }, + { 0x5684, 0x10 }, + { 0x5685, 0xa0 }, + { 0x5686, 0x0c }, + { 0x5687, 0x78 }, + { 0x5a00, 0x08 }, + { 0x5a21, 0x00 }, + { 0x5a24, 0x00 }, + { 0x3008, 0x02 }, + { 0x3503, 0x00 }, + { 0x5180, 0xff }, + { 0x5181, 0xf2 }, + { 0x5182, 0x00 }, + { 0x5183, 0x14 }, + { 0x5184, 0x25 }, + { 0x5185, 0x24 }, + { 0x5186, 0x09 }, + { 0x5187, 0x09 }, + { 0x5188, 0x0a }, + { 0x5189, 0x75 }, + { 0x518a, 0x52 }, + { 0x518b, 0xea }, + { 0x518c, 0xa8 }, + { 0x518d, 0x42 }, + { 0x518e, 0x38 }, + { 0x518f, 0x56 }, + { 0x5190, 0x42 }, + { 0x5191, 0xf8 }, + { 0x5192, 0x04 }, + { 0x5193, 0x70 }, + { 0x5194, 0xf0 }, + { 0x5195, 0xf0 }, + { 0x5196, 0x03 }, + { 0x5197, 0x01 }, + { 0x5198, 0x04 }, + { 0x5199, 0x12 }, + { 0x519a, 0x04 }, + { 0x519b, 0x00 }, + { 0x519c, 0x06 }, + { 0x519d, 0x82 }, + { 0x519e, 0x38 }, + { 0x5381, 0x1e }, + { 0x5382, 0x5b }, + { 0x5383, 0x08 }, + { 0x5384, 0x0a }, + { 0x5385, 0x7e }, + { 0x5386, 0x88 }, + { 0x5387, 0x7c }, + { 0x5388, 0x6c }, + { 0x5389, 0x10 }, + { 0x538a, 0x01 }, + { 0x538b, 0x98 }, + { 0x5300, 0x08 }, + { 0x5301, 0x30 }, + { 0x5302, 0x10 }, + { 0x5303, 0x00 }, + { 0x5304, 0x08 }, + { 0x5305, 0x30 }, + { 0x5306, 0x08 }, + { 0x5307, 0x16 }, + { 0x5309, 0x08 }, + { 0x530a, 0x30 }, + { 0x530b, 0x04 }, + { 0x530c, 0x06 }, + { 0x5480, 0x01 }, + { 0x5481, 0x08 }, + { 0x5482, 0x14 }, + { 0x5483, 0x28 }, + { 0x5484, 0x51 }, + { 0x5485, 0x65 }, + { 0x5486, 0x71 }, + { 0x5487, 0x7d }, + { 0x5488, 0x87 }, + { 0x5489, 0x91 }, + { 0x548a, 0x9a }, + { 0x548b, 0xaa }, + { 0x548c, 0xb8 }, + { 0x548d, 0xcd }, + { 0x548e, 0xdd }, + { 0x548f, 0xea }, + { 0x5490, 0x1d }, + { 0x5580, 0x02 }, + { 0x5583, 0x40 }, + { 0x5584, 0x10 }, + { 0x5589, 0x10 }, + { 0x558a, 0x00 }, + { 0x558b, 0xf8 }, + { 0x5800, 0x3f }, + { 0x5801, 0x16 }, + { 0x5802, 0x0e }, + { 0x5803, 0x0d }, + { 0x5804, 0x17 }, + { 0x5805, 0x3f }, + { 0x5806, 0x0b }, + { 0x5807, 0x06 }, + { 0x5808, 0x04 }, + { 0x5809, 0x04 }, + { 0x580a, 0x06 }, + { 0x580b, 0x0b }, + { 0x580c, 0x09 }, + { 0x580d, 0x03 }, + { 0x580e, 0x00 }, + { 0x580f, 0x00 }, + { 0x5810, 0x03 }, + { 0x5811, 0x08 }, + { 0x5812, 0x0a }, + { 0x5813, 0x03 }, + { 0x5814, 0x00 }, + { 0x5815, 0x00 }, + { 0x5816, 0x04 }, + { 0x5817, 0x09 }, + { 0x5818, 0x0f }, + { 0x5819, 0x08 }, + { 0x581a, 0x06 }, + { 0x581b, 0x06 }, + { 0x581c, 0x08 }, + { 0x581d, 0x0c }, + { 0x581e, 0x3f }, + { 0x581f, 0x1e }, + { 0x5820, 0x12 }, + { 0x5821, 0x13 }, + { 0x5822, 0x21 }, + { 0x5823, 0x3f }, + { 0x5824, 0x68 }, + { 0x5825, 0x28 }, + { 0x5826, 0x2c }, + { 0x5827, 0x28 }, + { 0x5828, 0x08 }, + { 0x5829, 0x48 }, + { 0x582a, 0x64 }, + { 0x582b, 0x62 }, + { 0x582c, 0x64 }, + { 0x582d, 0x28 }, + { 0x582e, 0x46 }, + { 0x582f, 0x62 }, + { 0x5830, 0x60 }, + { 0x5831, 0x62 }, + { 0x5832, 0x26 }, + { 0x5833, 0x48 }, + { 0x5834, 0x66 }, + { 0x5835, 0x44 }, + { 0x5836, 0x64 }, + { 0x5837, 0x28 }, + { 0x5838, 0x66 }, + { 0x5839, 0x48 }, + { 0x583a, 0x2c }, + { 0x583b, 0x28 }, + { 0x583c, 0x26 }, + { 0x583d, 0xae }, + { 0x5025, 0x00 }, + { 0x3a0f, 0x30 }, + { 0x3a10, 0x28 }, + { 0x3a1b, 0x30 }, + { 0x3a1e, 0x26 }, + { 0x3a11, 0x60 }, + { 0x3a1f, 0x14 }, + { 0x0601, 0x02 }, + { 0x3008, 0x42 }, + { 0x3008, 0x02 }, + { OV5645_IO_MIPI_CTRL00, 0x40 }, + { OV5645_MIPI_CTRL00, 0x24 }, + { OV5645_PAD_OUTPUT00, 0x70 } +}; + +static const struct reg_value ov5645_setting_sxga[] = { + { 0x3612, 0xa9 }, + { 0x3614, 0x50 }, + { 0x3618, 0x00 }, + { 0x3034, 0x18 }, + { 0x3035, 0x21 }, + { 0x3036, 0x70 }, + { 0x3600, 0x09 }, + { 0x3601, 0x43 }, + { 0x3708, 0x66 }, + { 0x370c, 0xc3 }, + { 0x3800, 0x00 }, + { 0x3801, 0x00 }, + { 0x3802, 0x00 }, + { 0x3803, 0x06 }, + { 0x3804, 0x0a }, + { 0x3805, 0x3f }, + { 0x3806, 0x07 }, + { 0x3807, 0x9d }, + { 0x3808, 0x05 }, + { 0x3809, 0x00 }, + { 0x380a, 0x03 }, + { 0x380b, 0xc0 }, + { 0x380c, 0x07 }, + { 0x380d, 0x68 }, + { 0x380e, 0x03 }, + { 0x380f, 0xd8 }, + { 0x3813, 0x06 }, + { 0x3814, 0x31 }, + { 0x3815, 0x31 }, + { 0x3820, 0x47 }, + { 0x3a02, 0x03 }, + { 0x3a03, 0xd8 }, + { 0x3a08, 0x01 }, + { 0x3a09, 0xf8 }, + { 0x3a0a, 0x01 }, + { 0x3a0b, 0xa4 }, + { 0x3a0e, 0x02 }, + { 0x3a0d, 0x02 }, + { 0x3a14, 0x03 }, + { 0x3a15, 0xd8 }, + { 0x3a18, 0x00 }, + { 0x4004, 0x02 }, + { 0x4005, 0x18 }, + { 0x4300, 0x32 }, + { 0x4202, 0x00 } +}; + +static const struct reg_value ov5645_setting_1080p[] = { + { 0x3612, 0xab }, + { 0x3614, 0x50 }, + { 0x3618, 0x04 }, + { 0x3034, 0x18 }, + { 0x3035, 0x11 }, + { 0x3036, 0x54 }, + { 0x3600, 0x08 }, + { 0x3601, 0x33 }, + { 0x3708, 0x63 }, + { 0x370c, 0xc0 }, + { 0x3800, 0x01 }, + { 0x3801, 0x50 }, + { 0x3802, 0x01 }, + { 0x3803, 0xb2 }, + { 0x3804, 0x08 }, + { 0x3805, 0xef }, + { 0x3806, 0x05 }, + { 0x3807, 0xf1 }, + { 0x3808, 0x07 }, + { 0x3809, 0x80 }, + { 0x380a, 0x04 }, + { 0x380b, 0x38 }, + { 0x380c, 0x09 }, + { 0x380d, 0xc4 }, + { 0x380e, 0x04 }, + { 0x380f, 0x60 }, + { 0x3813, 0x04 }, + { 0x3814, 0x11 }, + { 0x3815, 0x11 }, + { 0x3820, 0x47 }, + { 0x4514, 0x88 }, + { 0x3a02, 0x04 }, + { 0x3a03, 0x60 }, + { 0x3a08, 0x01 }, + { 0x3a09, 0x50 }, + { 0x3a0a, 0x01 }, + { 0x3a0b, 0x18 }, + { 0x3a0e, 0x03 }, + { 0x3a0d, 0x04 }, + { 0x3a14, 0x04 }, + { 0x3a15, 0x60 }, + { 0x3a18, 0x00 }, + { 0x4004, 0x06 }, + { 0x4005, 0x18 }, + { 0x4300, 0x32 }, + { 0x4202, 0x00 }, + { 0x4837, 0x0b } +}; + +static const struct reg_value ov5645_setting_full[] = { + { 0x3612, 0xab }, + { 0x3614, 0x50 }, + { 0x3618, 0x04 }, + { 0x3034, 0x18 }, + { 0x3035, 0x11 }, + { 0x3036, 0x54 }, + { 0x3600, 0x08 }, + { 0x3601, 0x33 }, + { 0x3708, 0x63 }, + { 0x370c, 0xc0 }, + { 0x3800, 0x00 }, + { 0x3801, 0x00 }, + { 0x3802, 0x00 }, + { 0x3803, 0x00 }, + { 0x3804, 0x0a }, + { 0x3805, 0x3f }, + { 0x3806, 0x07 }, + { 0x3807, 0x9f }, + { 0x3808, 0x0a }, + { 0x3809, 0x20 }, + { 0x380a, 0x07 }, + { 0x380b, 0x98 }, + { 0x380c, 0x0b }, + { 0x380d, 0x1c }, + { 0x380e, 0x07 }, + { 0x380f, 0xb0 }, + { 0x3813, 0x06 }, + { 0x3814, 0x11 }, + { 0x3815, 0x11 }, + { 0x3820, 0x47 }, + { 0x4514, 0x88 }, + { 0x3a02, 0x07 }, + { 0x3a03, 0xb0 }, + { 0x3a08, 0x01 }, + { 0x3a09, 0x27 }, + { 0x3a0a, 0x00 }, + { 0x3a0b, 0xf6 }, + { 0x3a0e, 0x06 }, + { 0x3a0d, 0x08 }, + { 0x3a14, 0x07 }, + { 0x3a15, 0xb0 }, + { 0x3a18, 0x01 }, + { 0x4004, 0x06 }, + { 0x4005, 0x18 }, + { 0x4300, 0x32 }, + { 0x4837, 0x0b }, + { 0x4202, 0x00 } +}; + +static const s64 link_freq[] = { + 224000000, + 336000000 +}; + +static const struct ov5645_mode_info ov5645_mode_info_data[] = { + { + .width = 1280, + .height = 960, + .data = ov5645_setting_sxga, + .data_size = ARRAY_SIZE(ov5645_setting_sxga), + .pixel_clock = 112000000, + .link_freq = 0 /* an index in link_freq[] */ + }, + { + .width = 1920, + .height = 1080, + .data = ov5645_setting_1080p, + .data_size = ARRAY_SIZE(ov5645_setting_1080p), + .pixel_clock = 168000000, + .link_freq = 1 /* an index in link_freq[] */ + }, + { + .width = 2592, + .height = 1944, + .data = ov5645_setting_full, + .data_size = ARRAY_SIZE(ov5645_setting_full), + .pixel_clock = 168000000, + .link_freq = 1 /* an index in link_freq[] */ + }, +}; + +static int ov5645_write_reg(struct ov5645 *ov5645, u16 reg, u8 val) +{ + u8 regbuf[3]; + int ret; + + regbuf[0] = reg >> 8; + regbuf[1] = reg & 0xff; + regbuf[2] = val; + + ret = i2c_master_send(ov5645->i2c_client, regbuf, 3); + if (ret < 0) { + dev_err(ov5645->dev, "%s: write reg error %d: reg=%x, val=%x\n", + __func__, ret, reg, val); + return ret; + } + + return 0; +} + +static int ov5645_read_reg(struct ov5645 *ov5645, u16 reg, u8 *val) +{ + u8 regbuf[2]; + int ret; + + regbuf[0] = reg >> 8; + regbuf[1] = reg & 0xff; + + ret = i2c_master_send(ov5645->i2c_client, regbuf, 2); + if (ret < 0) { + dev_err(ov5645->dev, "%s: write reg error %d: reg=%x\n", + __func__, ret, reg); + return ret; + } + + ret = i2c_master_recv(ov5645->i2c_client, val, 1); + if (ret < 0) { + dev_err(ov5645->dev, "%s: read reg error %d: reg=%x\n", + __func__, ret, reg); + return ret; + } + + return 0; +} + +static int ov5645_set_aec_mode(struct ov5645 *ov5645, u32 mode) +{ + u8 val = ov5645->aec_pk_manual; + int ret; + + if (mode == V4L2_EXPOSURE_AUTO) + val &= ~OV5645_AEC_MANUAL_ENABLE; + else /* V4L2_EXPOSURE_MANUAL */ + val |= OV5645_AEC_MANUAL_ENABLE; + + ret = ov5645_write_reg(ov5645, OV5645_AEC_PK_MANUAL, val); + if (!ret) + ov5645->aec_pk_manual = val; + + return ret; +} + +static int ov5645_set_agc_mode(struct ov5645 *ov5645, u32 enable) +{ + u8 val = ov5645->aec_pk_manual; + int ret; + + if (enable) + val &= ~OV5645_AGC_MANUAL_ENABLE; + else + val |= OV5645_AGC_MANUAL_ENABLE; + + ret = ov5645_write_reg(ov5645, OV5645_AEC_PK_MANUAL, val); + if (!ret) + ov5645->aec_pk_manual = val; + + return ret; +} + +static int ov5645_set_register_array(struct ov5645 *ov5645, + const struct reg_value *settings, + unsigned int num_settings) +{ + unsigned int i; + int ret; + + for (i = 0; i < num_settings; ++i, ++settings) { + ret = ov5645_write_reg(ov5645, settings->reg, settings->val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int ov5645_set_power_on(struct ov5645 *ov5645) +{ + int ret; + + ret = regulator_bulk_enable(OV5645_NUM_SUPPLIES, ov5645->supplies); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(ov5645->xclk); + if (ret < 0) { + dev_err(ov5645->dev, "clk prepare enable failed\n"); + regulator_bulk_disable(OV5645_NUM_SUPPLIES, ov5645->supplies); + return ret; + } + + usleep_range(5000, 15000); + gpiod_set_value_cansleep(ov5645->enable_gpio, 1); + + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ov5645->rst_gpio, 0); + + msleep(20); + + return 0; +} + +static void ov5645_set_power_off(struct ov5645 *ov5645) +{ + gpiod_set_value_cansleep(ov5645->rst_gpio, 1); + gpiod_set_value_cansleep(ov5645->enable_gpio, 0); + clk_disable_unprepare(ov5645->xclk); + regulator_bulk_disable(OV5645_NUM_SUPPLIES, ov5645->supplies); +} + +static int ov5645_s_power(struct v4l2_subdev *sd, int on) +{ + struct ov5645 *ov5645 = to_ov5645(sd); + int ret = 0; + + mutex_lock(&ov5645->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (ov5645->power_count == !on) { + if (on) { + ret = ov5645_set_power_on(ov5645); + if (ret < 0) + goto exit; + + ret = ov5645_set_register_array(ov5645, + ov5645_global_init_setting, + ARRAY_SIZE(ov5645_global_init_setting)); + if (ret < 0) { + dev_err(ov5645->dev, + "could not set init registers\n"); + ov5645_set_power_off(ov5645); + goto exit; + } + + usleep_range(500, 1000); + } else { + ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x58); + ov5645_set_power_off(ov5645); + } + } + + /* Update the power count. */ + ov5645->power_count += on ? 1 : -1; + WARN_ON(ov5645->power_count < 0); + +exit: + mutex_unlock(&ov5645->power_lock); + + return ret; +} + +static int ov5645_set_saturation(struct ov5645 *ov5645, s32 value) +{ + u32 reg_value = (value * 0x10) + 0x40; + int ret; + + ret = ov5645_write_reg(ov5645, OV5645_SDE_SAT_U, reg_value); + if (ret < 0) + return ret; + + return ov5645_write_reg(ov5645, OV5645_SDE_SAT_V, reg_value); +} + +static int ov5645_set_hflip(struct ov5645 *ov5645, s32 value) +{ + u8 val = ov5645->timing_tc_reg21; + int ret; + + if (value == 0) + val &= ~(OV5645_SENSOR_MIRROR); + else + val |= (OV5645_SENSOR_MIRROR); + + ret = ov5645_write_reg(ov5645, OV5645_TIMING_TC_REG21, val); + if (!ret) + ov5645->timing_tc_reg21 = val; + + return ret; +} + +static int ov5645_set_vflip(struct ov5645 *ov5645, s32 value) +{ + u8 val = ov5645->timing_tc_reg20; + int ret; + + if (value == 0) + val |= (OV5645_SENSOR_VFLIP | OV5645_ISP_VFLIP); + else + val &= ~(OV5645_SENSOR_VFLIP | OV5645_ISP_VFLIP); + + ret = ov5645_write_reg(ov5645, OV5645_TIMING_TC_REG20, val); + if (!ret) + ov5645->timing_tc_reg20 = val; + + return ret; +} + +static int ov5645_set_test_pattern(struct ov5645 *ov5645, s32 value) +{ + u8 val = 0; + + if (value) { + val = OV5645_SET_TEST_PATTERN(value - 1); + val |= OV5645_TEST_PATTERN_ENABLE; + } + + return ov5645_write_reg(ov5645, OV5645_PRE_ISP_TEST_SETTING_1, val); +} + +static const char * const ov5645_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bars", + "Pseudo-Random Data", + "Color Square", + "Black Image", +}; + +static int ov5645_set_awb(struct ov5645 *ov5645, s32 enable_auto) +{ + u8 val = 0; + + if (!enable_auto) + val = OV5645_AWB_MANUAL_ENABLE; + + return ov5645_write_reg(ov5645, OV5645_AWB_MANUAL_CONTROL, val); +} + +static int ov5645_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov5645 *ov5645 = container_of(ctrl->handler, + struct ov5645, ctrls); + int ret; + + mutex_lock(&ov5645->power_lock); + if (!ov5645->power_count) { + mutex_unlock(&ov5645->power_lock); + return 0; + } + + switch (ctrl->id) { + case V4L2_CID_SATURATION: + ret = ov5645_set_saturation(ov5645, ctrl->val); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov5645_set_awb(ov5645, ctrl->val); + break; + case V4L2_CID_AUTOGAIN: + ret = ov5645_set_agc_mode(ov5645, ctrl->val); + break; + case V4L2_CID_EXPOSURE_AUTO: + ret = ov5645_set_aec_mode(ov5645, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov5645_set_test_pattern(ov5645, ctrl->val); + break; + case V4L2_CID_HFLIP: + ret = ov5645_set_hflip(ov5645, ctrl->val); + break; + case V4L2_CID_VFLIP: + ret = ov5645_set_vflip(ov5645, ctrl->val); + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&ov5645->power_lock); + + return ret; +} + +static const struct v4l2_ctrl_ops ov5645_ctrl_ops = { + .s_ctrl = ov5645_s_ctrl, +}; + +static int ov5645_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_2X8; + + return 0; +} + +static int ov5645_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->code != MEDIA_BUS_FMT_UYVY8_2X8) + return -EINVAL; + + if (fse->index >= ARRAY_SIZE(ov5645_mode_info_data)) + return -EINVAL; + + fse->min_width = ov5645_mode_info_data[fse->index].width; + fse->max_width = ov5645_mode_info_data[fse->index].width; + fse->min_height = ov5645_mode_info_data[fse->index].height; + fse->max_height = ov5645_mode_info_data[fse->index].height; + + return 0; +} + +static struct v4l2_mbus_framefmt * +__ov5645_get_pad_format(struct ov5645 *ov5645, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&ov5645->sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &ov5645->fmt; + default: + return NULL; + } +} + +static int ov5645_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov5645 *ov5645 = to_ov5645(sd); + + format->format = *__ov5645_get_pad_format(ov5645, cfg, format->pad, + format->which); + return 0; +} + +static struct v4l2_rect * +__ov5645_get_pad_crop(struct ov5645 *ov5645, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&ov5645->sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &ov5645->crop; + default: + return NULL; + } +} + +static int ov5645_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov5645 *ov5645 = to_ov5645(sd); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + const struct ov5645_mode_info *new_mode; + int ret; + + __crop = __ov5645_get_pad_crop(ov5645, cfg, format->pad, + format->which); + + new_mode = v4l2_find_nearest_size(ov5645_mode_info_data, + ARRAY_SIZE(ov5645_mode_info_data), + width, height, + format->format.width, format->format.height); + + __crop->width = new_mode->width; + __crop->height = new_mode->height; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ret = v4l2_ctrl_s_ctrl_int64(ov5645->pixel_clock, + new_mode->pixel_clock); + if (ret < 0) + return ret; + + ret = v4l2_ctrl_s_ctrl(ov5645->link_freq, + new_mode->link_freq); + if (ret < 0) + return ret; + + ov5645->current_mode = new_mode; + } + + __format = __ov5645_get_pad_format(ov5645, cfg, format->pad, + format->which); + __format->width = __crop->width; + __format->height = __crop->height; + __format->code = MEDIA_BUS_FMT_UYVY8_2X8; + __format->field = V4L2_FIELD_NONE; + __format->colorspace = V4L2_COLORSPACE_SRGB; + + format->format = *__format; + + return 0; +} + +static int ov5645_entity_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg) +{ + struct v4l2_subdev_format fmt = { 0 }; + + fmt.which = cfg ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.format.width = 1920; + fmt.format.height = 1080; + + ov5645_set_format(subdev, cfg, &fmt); + + return 0; +} + +static int ov5645_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct ov5645 *ov5645 = to_ov5645(sd); + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + sel->r = *__ov5645_get_pad_crop(ov5645, cfg, sel->pad, + sel->which); + return 0; +} + +static int ov5645_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct ov5645 *ov5645 = to_ov5645(subdev); + int ret; + + if (enable) { + ret = ov5645_set_register_array(ov5645, + ov5645->current_mode->data, + ov5645->current_mode->data_size); + if (ret < 0) { + dev_err(ov5645->dev, "could not set mode %dx%d\n", + ov5645->current_mode->width, + ov5645->current_mode->height); + return ret; + } + ret = v4l2_ctrl_handler_setup(&ov5645->ctrls); + if (ret < 0) { + dev_err(ov5645->dev, "could not sync v4l2 controls\n"); + return ret; + } + + ret = ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x45); + if (ret < 0) + return ret; + + ret = ov5645_write_reg(ov5645, OV5645_SYSTEM_CTRL0, + OV5645_SYSTEM_CTRL0_START); + if (ret < 0) + return ret; + } else { + ret = ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x40); + if (ret < 0) + return ret; + + ret = ov5645_write_reg(ov5645, OV5645_SYSTEM_CTRL0, + OV5645_SYSTEM_CTRL0_STOP); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct v4l2_subdev_core_ops ov5645_core_ops = { + .s_power = ov5645_s_power, +}; + +static const struct v4l2_subdev_video_ops ov5645_video_ops = { + .s_stream = ov5645_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov5645_subdev_pad_ops = { + .init_cfg = ov5645_entity_init_cfg, + .enum_mbus_code = ov5645_enum_mbus_code, + .enum_frame_size = ov5645_enum_frame_size, + .get_fmt = ov5645_get_format, + .set_fmt = ov5645_set_format, + .get_selection = ov5645_get_selection, +}; + +static const struct v4l2_subdev_ops ov5645_subdev_ops = { + .core = &ov5645_core_ops, + .video = &ov5645_video_ops, + .pad = &ov5645_subdev_pad_ops, +}; + +static int ov5645_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device_node *endpoint; + struct ov5645 *ov5645; + u8 chip_id_high, chip_id_low; + unsigned int i; + u32 xclk_freq; + int ret; + + ov5645 = devm_kzalloc(dev, sizeof(struct ov5645), GFP_KERNEL); + if (!ov5645) + return -ENOMEM; + + ov5645->i2c_client = client; + ov5645->dev = dev; + + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), + &ov5645->ep); + + of_node_put(endpoint); + + if (ret < 0) { + dev_err(dev, "parsing endpoint node failed\n"); + return ret; + } + + if (ov5645->ep.bus_type != V4L2_MBUS_CSI2_DPHY) { + dev_err(dev, "invalid bus type, must be CSI2\n"); + return -EINVAL; + } + + /* get system clock (xclk) */ + ov5645->xclk = devm_clk_get(dev, "xclk"); + if (IS_ERR(ov5645->xclk)) { + dev_err(dev, "could not get xclk"); + return PTR_ERR(ov5645->xclk); + } + + ret = of_property_read_u32(dev->of_node, "clock-frequency", &xclk_freq); + if (ret) { + dev_err(dev, "could not get xclk frequency\n"); + return ret; + } + + /* external clock must be 24MHz, allow 1% tolerance */ + if (xclk_freq < 23760000 || xclk_freq > 24240000) { + dev_err(dev, "external clock frequency %u is not supported\n", + xclk_freq); + return -EINVAL; + } + + ret = clk_set_rate(ov5645->xclk, xclk_freq); + if (ret) { + dev_err(dev, "could not set xclk frequency\n"); + return ret; + } + + for (i = 0; i < OV5645_NUM_SUPPLIES; i++) + ov5645->supplies[i].supply = ov5645_supply_name[i]; + + ret = devm_regulator_bulk_get(dev, OV5645_NUM_SUPPLIES, + ov5645->supplies); + if (ret < 0) + return ret; + + ov5645->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(ov5645->enable_gpio)) { + dev_err(dev, "cannot get enable gpio\n"); + return PTR_ERR(ov5645->enable_gpio); + } + + ov5645->rst_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ov5645->rst_gpio)) { + dev_err(dev, "cannot get reset gpio\n"); + return PTR_ERR(ov5645->rst_gpio); + } + + mutex_init(&ov5645->power_lock); + + v4l2_ctrl_handler_init(&ov5645->ctrls, 9); + v4l2_ctrl_new_std(&ov5645->ctrls, &ov5645_ctrl_ops, + V4L2_CID_SATURATION, -4, 4, 1, 0); + v4l2_ctrl_new_std(&ov5645->ctrls, &ov5645_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&ov5645->ctrls, &ov5645_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&ov5645->ctrls, &ov5645_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + v4l2_ctrl_new_std(&ov5645->ctrls, &ov5645_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + v4l2_ctrl_new_std_menu(&ov5645->ctrls, &ov5645_ctrl_ops, + V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, + 0, V4L2_EXPOSURE_AUTO); + v4l2_ctrl_new_std_menu_items(&ov5645->ctrls, &ov5645_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov5645_test_pattern_menu) - 1, + 0, 0, ov5645_test_pattern_menu); + ov5645->pixel_clock = v4l2_ctrl_new_std(&ov5645->ctrls, + &ov5645_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 1, INT_MAX, 1, 1); + ov5645->link_freq = v4l2_ctrl_new_int_menu(&ov5645->ctrls, + &ov5645_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq) - 1, + 0, link_freq); + if (ov5645->link_freq) + ov5645->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ov5645->sd.ctrl_handler = &ov5645->ctrls; + + if (ov5645->ctrls.error) { + dev_err(dev, "%s: control initialization error %d\n", + __func__, ov5645->ctrls.error); + ret = ov5645->ctrls.error; + goto free_ctrl; + } + + v4l2_i2c_subdev_init(&ov5645->sd, client, &ov5645_subdev_ops); + ov5645->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov5645->pad.flags = MEDIA_PAD_FL_SOURCE; + ov5645->sd.dev = &client->dev; + ov5645->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&ov5645->sd.entity, 1, &ov5645->pad); + if (ret < 0) { + dev_err(dev, "could not register media entity\n"); + goto free_ctrl; + } + + ret = ov5645_s_power(&ov5645->sd, true); + if (ret < 0) { + dev_err(dev, "could not power up OV5645\n"); + goto free_entity; + } + + ret = ov5645_read_reg(ov5645, OV5645_CHIP_ID_HIGH, &chip_id_high); + if (ret < 0 || chip_id_high != OV5645_CHIP_ID_HIGH_BYTE) { + dev_err(dev, "could not read ID high\n"); + ret = -ENODEV; + goto power_down; + } + ret = ov5645_read_reg(ov5645, OV5645_CHIP_ID_LOW, &chip_id_low); + if (ret < 0 || chip_id_low != OV5645_CHIP_ID_LOW_BYTE) { + dev_err(dev, "could not read ID low\n"); + ret = -ENODEV; + goto power_down; + } + + dev_info(dev, "OV5645 detected at address 0x%02x\n", client->addr); + + ret = ov5645_read_reg(ov5645, OV5645_AEC_PK_MANUAL, + &ov5645->aec_pk_manual); + if (ret < 0) { + dev_err(dev, "could not read AEC/AGC mode\n"); + ret = -ENODEV; + goto power_down; + } + + ret = ov5645_read_reg(ov5645, OV5645_TIMING_TC_REG20, + &ov5645->timing_tc_reg20); + if (ret < 0) { + dev_err(dev, "could not read vflip value\n"); + ret = -ENODEV; + goto power_down; + } + + ret = ov5645_read_reg(ov5645, OV5645_TIMING_TC_REG21, + &ov5645->timing_tc_reg21); + if (ret < 0) { + dev_err(dev, "could not read hflip value\n"); + ret = -ENODEV; + goto power_down; + } + + ov5645_s_power(&ov5645->sd, false); + + ret = v4l2_async_register_subdev(&ov5645->sd); + if (ret < 0) { + dev_err(dev, "could not register v4l2 device\n"); + goto free_entity; + } + + ov5645_entity_init_cfg(&ov5645->sd, NULL); + + return 0; + +power_down: + ov5645_s_power(&ov5645->sd, false); +free_entity: + media_entity_cleanup(&ov5645->sd.entity); +free_ctrl: + v4l2_ctrl_handler_free(&ov5645->ctrls); + mutex_destroy(&ov5645->power_lock); + + return ret; +} + +static int ov5645_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5645 *ov5645 = to_ov5645(sd); + + v4l2_async_unregister_subdev(&ov5645->sd); + media_entity_cleanup(&ov5645->sd.entity); + v4l2_ctrl_handler_free(&ov5645->ctrls); + mutex_destroy(&ov5645->power_lock); + + return 0; +} + +static const struct i2c_device_id ov5645_id[] = { + { "ov5645", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ov5645_id); + +static const struct of_device_id ov5645_of_match[] = { + { .compatible = "ovti,ov5645" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ov5645_of_match); + +static struct i2c_driver ov5645_i2c_driver = { + .driver = { + .of_match_table = of_match_ptr(ov5645_of_match), + .name = "ov5645", + }, + .probe_new = ov5645_probe, + .remove = ov5645_remove, + .id_table = ov5645_id, +}; + +module_i2c_driver(ov5645_i2c_driver); + +MODULE_DESCRIPTION("Omnivision OV5645 Camera Driver"); +MODULE_AUTHOR("Todor Tomov <todor.tomov@linaro.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c new file mode 100644 index 000000000..e7d2e5b4a --- /dev/null +++ b/drivers/media/i2c/ov5647.c @@ -0,0 +1,655 @@ +/* + * A V4L2 driver for OmniVision OV5647 cameras. + * + * Based on Samsung S5K6AAFX SXGA 1/6" 1.3M CMOS Image Sensor driver + * Copyright (C) 2011 Sylwester Nawrocki <s.nawrocki@samsung.com> + * + * Based on Omnivision OV7670 Camera Driver + * Copyright (C) 2006-7 Jonathan Corbet <corbet@lwn.net> + * + * Copyright (C) 2016, Synopsys, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-mediabus.h> + +#define SENSOR_NAME "ov5647" + +#define MIPI_CTRL00_CLOCK_LANE_GATE BIT(5) +#define MIPI_CTRL00_BUS_IDLE BIT(2) +#define MIPI_CTRL00_CLOCK_LANE_DISABLE BIT(0) + +#define OV5647_SW_STANDBY 0x0100 +#define OV5647_SW_RESET 0x0103 +#define OV5647_REG_CHIPID_H 0x300A +#define OV5647_REG_CHIPID_L 0x300B +#define OV5640_REG_PAD_OUT 0x300D +#define OV5647_REG_FRAME_OFF_NUMBER 0x4202 +#define OV5647_REG_MIPI_CTRL00 0x4800 +#define OV5647_REG_MIPI_CTRL14 0x4814 + +#define REG_TERM 0xfffe +#define VAL_TERM 0xfe +#define REG_DLY 0xffff + +#define OV5647_ROW_START 0x01 +#define OV5647_ROW_START_MIN 0 +#define OV5647_ROW_START_MAX 2004 +#define OV5647_ROW_START_DEF 54 + +#define OV5647_COLUMN_START 0x02 +#define OV5647_COLUMN_START_MIN 0 +#define OV5647_COLUMN_START_MAX 2750 +#define OV5647_COLUMN_START_DEF 16 + +#define OV5647_WINDOW_HEIGHT 0x03 +#define OV5647_WINDOW_HEIGHT_MIN 2 +#define OV5647_WINDOW_HEIGHT_MAX 2006 +#define OV5647_WINDOW_HEIGHT_DEF 1944 + +#define OV5647_WINDOW_WIDTH 0x04 +#define OV5647_WINDOW_WIDTH_MIN 2 +#define OV5647_WINDOW_WIDTH_MAX 2752 +#define OV5647_WINDOW_WIDTH_DEF 2592 + +struct regval_list { + u16 addr; + u8 data; +}; + +struct ov5647 { + struct v4l2_subdev sd; + struct media_pad pad; + struct mutex lock; + struct v4l2_mbus_framefmt format; + unsigned int width; + unsigned int height; + int power_count; + struct clk *xclk; +}; + +static inline struct ov5647 *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov5647, sd); +} + +static struct regval_list sensor_oe_disable_regs[] = { + {0x3000, 0x00}, + {0x3001, 0x00}, + {0x3002, 0x00}, +}; + +static struct regval_list sensor_oe_enable_regs[] = { + {0x3000, 0x0f}, + {0x3001, 0xff}, + {0x3002, 0xe4}, +}; + +static struct regval_list ov5647_640x480[] = { + {0x0100, 0x00}, + {0x0103, 0x01}, + {0x3034, 0x08}, + {0x3035, 0x21}, + {0x3036, 0x46}, + {0x303c, 0x11}, + {0x3106, 0xf5}, + {0x3821, 0x07}, + {0x3820, 0x41}, + {0x3827, 0xec}, + {0x370c, 0x0f}, + {0x3612, 0x59}, + {0x3618, 0x00}, + {0x5000, 0x06}, + {0x5001, 0x01}, + {0x5002, 0x41}, + {0x5003, 0x08}, + {0x5a00, 0x08}, + {0x3000, 0x00}, + {0x3001, 0x00}, + {0x3002, 0x00}, + {0x3016, 0x08}, + {0x3017, 0xe0}, + {0x3018, 0x44}, + {0x301c, 0xf8}, + {0x301d, 0xf0}, + {0x3a18, 0x00}, + {0x3a19, 0xf8}, + {0x3c01, 0x80}, + {0x3b07, 0x0c}, + {0x380c, 0x07}, + {0x380d, 0x68}, + {0x380e, 0x03}, + {0x380f, 0xd8}, + {0x3814, 0x31}, + {0x3815, 0x31}, + {0x3708, 0x64}, + {0x3709, 0x52}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xE0}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x3f}, + {0x3806, 0x07}, + {0x3807, 0xa1}, + {0x3811, 0x08}, + {0x3813, 0x02}, + {0x3630, 0x2e}, + {0x3632, 0xe2}, + {0x3633, 0x23}, + {0x3634, 0x44}, + {0x3636, 0x06}, + {0x3620, 0x64}, + {0x3621, 0xe0}, + {0x3600, 0x37}, + {0x3704, 0xa0}, + {0x3703, 0x5a}, + {0x3715, 0x78}, + {0x3717, 0x01}, + {0x3731, 0x02}, + {0x370b, 0x60}, + {0x3705, 0x1a}, + {0x3f05, 0x02}, + {0x3f06, 0x10}, + {0x3f01, 0x0a}, + {0x3a08, 0x01}, + {0x3a09, 0x27}, + {0x3a0a, 0x00}, + {0x3a0b, 0xf6}, + {0x3a0d, 0x04}, + {0x3a0e, 0x03}, + {0x3a0f, 0x58}, + {0x3a10, 0x50}, + {0x3a1b, 0x58}, + {0x3a1e, 0x50}, + {0x3a11, 0x60}, + {0x3a1f, 0x28}, + {0x4001, 0x02}, + {0x4004, 0x02}, + {0x4000, 0x09}, + {0x4837, 0x24}, + {0x4050, 0x6e}, + {0x4051, 0x8f}, + {0x0100, 0x01}, +}; + +static int ov5647_write(struct v4l2_subdev *sd, u16 reg, u8 val) +{ + int ret; + unsigned char data[3] = { reg >> 8, reg & 0xff, val}; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + ret = i2c_master_send(client, data, 3); + if (ret < 0) + dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n", + __func__, reg); + + return ret; +} + +static int ov5647_read(struct v4l2_subdev *sd, u16 reg, u8 *val) +{ + int ret; + unsigned char data_w[2] = { reg >> 8, reg & 0xff }; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + ret = i2c_master_send(client, data_w, 2); + if (ret < 0) { + dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n", + __func__, reg); + return ret; + } + + ret = i2c_master_recv(client, val, 1); + if (ret < 0) + dev_dbg(&client->dev, "%s: i2c read error, reg: %x\n", + __func__, reg); + + return ret; +} + +static int ov5647_write_array(struct v4l2_subdev *sd, + struct regval_list *regs, int array_size) +{ + int i, ret; + + for (i = 0; i < array_size; i++) { + ret = ov5647_write(sd, regs[i].addr, regs[i].data); + if (ret < 0) + return ret; + } + + return 0; +} + +static int ov5647_set_virtual_channel(struct v4l2_subdev *sd, int channel) +{ + u8 channel_id; + int ret; + + ret = ov5647_read(sd, OV5647_REG_MIPI_CTRL14, &channel_id); + if (ret < 0) + return ret; + + channel_id &= ~(3 << 6); + return ov5647_write(sd, OV5647_REG_MIPI_CTRL14, channel_id | (channel << 6)); +} + +static int ov5647_stream_on(struct v4l2_subdev *sd) +{ + int ret; + + ret = ov5647_write(sd, OV5647_REG_MIPI_CTRL00, MIPI_CTRL00_BUS_IDLE); + if (ret < 0) + return ret; + + ret = ov5647_write(sd, OV5647_REG_FRAME_OFF_NUMBER, 0x00); + if (ret < 0) + return ret; + + return ov5647_write(sd, OV5640_REG_PAD_OUT, 0x00); +} + +static int ov5647_stream_off(struct v4l2_subdev *sd) +{ + int ret; + + ret = ov5647_write(sd, OV5647_REG_MIPI_CTRL00, MIPI_CTRL00_CLOCK_LANE_GATE + | MIPI_CTRL00_BUS_IDLE | MIPI_CTRL00_CLOCK_LANE_DISABLE); + if (ret < 0) + return ret; + + ret = ov5647_write(sd, OV5647_REG_FRAME_OFF_NUMBER, 0x0f); + if (ret < 0) + return ret; + + return ov5647_write(sd, OV5640_REG_PAD_OUT, 0x01); +} + +static int set_sw_standby(struct v4l2_subdev *sd, bool standby) +{ + int ret; + u8 rdval; + + ret = ov5647_read(sd, OV5647_SW_STANDBY, &rdval); + if (ret < 0) + return ret; + + if (standby) + rdval &= ~0x01; + else + rdval |= 0x01; + + return ov5647_write(sd, OV5647_SW_STANDBY, rdval); +} + +static int __sensor_init(struct v4l2_subdev *sd) +{ + int ret; + u8 resetval, rdval; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + ret = ov5647_read(sd, OV5647_SW_STANDBY, &rdval); + if (ret < 0) + return ret; + + ret = ov5647_write_array(sd, ov5647_640x480, + ARRAY_SIZE(ov5647_640x480)); + if (ret < 0) { + dev_err(&client->dev, "write sensor default regs error\n"); + return ret; + } + + ret = ov5647_set_virtual_channel(sd, 0); + if (ret < 0) + return ret; + + ret = ov5647_read(sd, OV5647_SW_STANDBY, &resetval); + if (ret < 0) + return ret; + + if (!(resetval & 0x01)) { + dev_err(&client->dev, "Device was in SW standby"); + ret = ov5647_write(sd, OV5647_SW_STANDBY, 0x01); + if (ret < 0) + return ret; + } + + /* + * stream off to make the clock lane into LP-11 state. + */ + return ov5647_stream_off(sd); +} + +static int ov5647_sensor_power(struct v4l2_subdev *sd, int on) +{ + int ret = 0; + struct ov5647 *ov5647 = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + mutex_lock(&ov5647->lock); + + if (on && !ov5647->power_count) { + dev_dbg(&client->dev, "OV5647 power on\n"); + + ret = clk_prepare_enable(ov5647->xclk); + if (ret < 0) { + dev_err(&client->dev, "clk prepare enable failed\n"); + goto out; + } + + ret = ov5647_write_array(sd, sensor_oe_enable_regs, + ARRAY_SIZE(sensor_oe_enable_regs)); + if (ret < 0) { + clk_disable_unprepare(ov5647->xclk); + dev_err(&client->dev, + "write sensor_oe_enable_regs error\n"); + goto out; + } + + ret = __sensor_init(sd); + if (ret < 0) { + clk_disable_unprepare(ov5647->xclk); + dev_err(&client->dev, + "Camera not available, check Power\n"); + goto out; + } + } else if (!on && ov5647->power_count == 1) { + dev_dbg(&client->dev, "OV5647 power off\n"); + + ret = ov5647_write_array(sd, sensor_oe_disable_regs, + ARRAY_SIZE(sensor_oe_disable_regs)); + + if (ret < 0) + dev_dbg(&client->dev, "disable oe failed\n"); + + ret = set_sw_standby(sd, true); + + if (ret < 0) + dev_dbg(&client->dev, "soft stby failed\n"); + + clk_disable_unprepare(ov5647->xclk); + } + + /* Update the power count. */ + ov5647->power_count += on ? 1 : -1; + WARN_ON(ov5647->power_count < 0); + +out: + mutex_unlock(&ov5647->lock); + + return ret; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov5647_sensor_get_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + u8 val; + int ret; + + ret = ov5647_read(sd, reg->reg & 0xff, &val); + if (ret < 0) + return ret; + + reg->val = val; + reg->size = 1; + + return 0; +} + +static int ov5647_sensor_set_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + return ov5647_write(sd, reg->reg & 0xff, reg->val & 0xff); +} +#endif + +/* + * Subdev core operations registration + */ +static const struct v4l2_subdev_core_ops ov5647_subdev_core_ops = { + .s_power = ov5647_sensor_power, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov5647_sensor_get_register, + .s_register = ov5647_sensor_set_register, +#endif +}; + +static int ov5647_s_stream(struct v4l2_subdev *sd, int enable) +{ + if (enable) + return ov5647_stream_on(sd); + else + return ov5647_stream_off(sd); +} + +static const struct v4l2_subdev_video_ops ov5647_subdev_video_ops = { + .s_stream = ov5647_s_stream, +}; + +static int ov5647_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SBGGR8_1X8; + + return 0; +} + +static const struct v4l2_subdev_pad_ops ov5647_subdev_pad_ops = { + .enum_mbus_code = ov5647_enum_mbus_code, +}; + +static const struct v4l2_subdev_ops ov5647_subdev_ops = { + .core = &ov5647_subdev_core_ops, + .video = &ov5647_subdev_video_ops, + .pad = &ov5647_subdev_pad_ops, +}; + +static int ov5647_detect(struct v4l2_subdev *sd) +{ + u8 read; + int ret; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + ret = ov5647_write(sd, OV5647_SW_RESET, 0x01); + if (ret < 0) + return ret; + + ret = ov5647_read(sd, OV5647_REG_CHIPID_H, &read); + if (ret < 0) + return ret; + + if (read != 0x56) { + dev_err(&client->dev, "ID High expected 0x56 got %x", read); + return -ENODEV; + } + + ret = ov5647_read(sd, OV5647_REG_CHIPID_L, &read); + if (ret < 0) + return ret; + + if (read != 0x47) { + dev_err(&client->dev, "ID Low expected 0x47 got %x", read); + return -ENODEV; + } + + return ov5647_write(sd, OV5647_SW_RESET, 0x00); +} + +static int ov5647_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + struct v4l2_rect *crop = + v4l2_subdev_get_try_crop(sd, fh->pad, 0); + + crop->left = OV5647_COLUMN_START_DEF; + crop->top = OV5647_ROW_START_DEF; + crop->width = OV5647_WINDOW_WIDTH_DEF; + crop->height = OV5647_WINDOW_HEIGHT_DEF; + + format->code = MEDIA_BUS_FMT_SBGGR8_1X8; + + format->width = OV5647_WINDOW_WIDTH_DEF; + format->height = OV5647_WINDOW_HEIGHT_DEF; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + return 0; +} + +static const struct v4l2_subdev_internal_ops ov5647_subdev_internal_ops = { + .open = ov5647_open, +}; + +static int ov5647_parse_dt(struct device_node *np) +{ + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + struct device_node *ep; + + int ret; + + ep = of_graph_get_next_endpoint(np, NULL); + if (!ep) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &bus_cfg); + + of_node_put(ep); + return ret; +} + +static int ov5647_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ov5647 *sensor; + int ret; + struct v4l2_subdev *sd; + struct device_node *np = client->dev.of_node; + u32 xclk_freq; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + if (IS_ENABLED(CONFIG_OF) && np) { + ret = ov5647_parse_dt(np); + if (ret) { + dev_err(dev, "DT parsing error: %d\n", ret); + return ret; + } + } + + /* get system clock (xclk) */ + sensor->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(sensor->xclk)) { + dev_err(dev, "could not get xclk"); + return PTR_ERR(sensor->xclk); + } + + xclk_freq = clk_get_rate(sensor->xclk); + if (xclk_freq != 25000000) { + dev_err(dev, "Unsupported clock frequency: %u\n", xclk_freq); + return -EINVAL; + } + + mutex_init(&sensor->lock); + + sd = &sensor->sd; + v4l2_i2c_subdev_init(sd, client, &ov5647_subdev_ops); + sensor->sd.internal_ops = &ov5647_subdev_internal_ops; + sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad); + if (ret < 0) + goto mutex_remove; + + ret = ov5647_detect(sd); + if (ret < 0) + goto error; + + ret = v4l2_async_register_subdev(sd); + if (ret < 0) + goto error; + + dev_dbg(dev, "OmniVision OV5647 camera driver probed\n"); + return 0; +error: + media_entity_cleanup(&sd->entity); +mutex_remove: + mutex_destroy(&sensor->lock); + return ret; +} + +static int ov5647_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5647 *ov5647 = to_state(sd); + + v4l2_async_unregister_subdev(&ov5647->sd); + media_entity_cleanup(&ov5647->sd.entity); + v4l2_device_unregister_subdev(sd); + mutex_destroy(&ov5647->lock); + + return 0; +} + +static const struct i2c_device_id ov5647_id[] = { + { "ov5647", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov5647_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id ov5647_of_match[] = { + { .compatible = "ovti,ov5647" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ov5647_of_match); +#endif + +static struct i2c_driver ov5647_driver = { + .driver = { + .of_match_table = of_match_ptr(ov5647_of_match), + .name = SENSOR_NAME, + }, + .probe_new = ov5647_probe, + .remove = ov5647_remove, + .id_table = ov5647_id, +}; + +module_i2c_driver(ov5647_driver); + +MODULE_AUTHOR("Ramiro Oliveira <roliveir@synopsys.com>"); +MODULE_DESCRIPTION("A low-level driver for OmniVision ov5647 sensors"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov5670.c b/drivers/media/i2c/ov5670.c new file mode 100644 index 000000000..04d3f1490 --- /dev/null +++ b/drivers/media/i2c/ov5670.c @@ -0,0 +1,2584 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017 Intel Corporation. + +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define OV5670_REG_CHIP_ID 0x300a +#define OV5670_CHIP_ID 0x005670 + +#define OV5670_REG_MODE_SELECT 0x0100 +#define OV5670_MODE_STANDBY 0x00 +#define OV5670_MODE_STREAMING 0x01 + +#define OV5670_REG_SOFTWARE_RST 0x0103 +#define OV5670_SOFTWARE_RST 0x01 + +/* vertical-timings from sensor */ +#define OV5670_REG_VTS 0x380e +#define OV5670_VTS_30FPS 0x0808 /* default for 30 fps */ +#define OV5670_VTS_MAX 0xffff + +/* horizontal-timings from sensor */ +#define OV5670_REG_HTS 0x380c + +/* + * Pixels-per-line(PPL) = Time-per-line * pixel-rate + * In OV5670, Time-per-line = HTS/SCLK. + * HTS is fixed for all resolutions, not recommended to change. + */ +#define OV5670_FIXED_PPL 2724 /* Pixels per line */ + +/* Exposure controls from sensor */ +#define OV5670_REG_EXPOSURE 0x3500 +#define OV5670_EXPOSURE_MIN 4 +#define OV5670_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define OV5670_REG_ANALOG_GAIN 0x3508 +#define ANALOG_GAIN_MIN 0 +#define ANALOG_GAIN_MAX 8191 +#define ANALOG_GAIN_STEP 1 +#define ANALOG_GAIN_DEFAULT 128 + +/* Digital gain controls from sensor */ +#define OV5670_REG_R_DGTL_GAIN 0x5032 +#define OV5670_REG_G_DGTL_GAIN 0x5034 +#define OV5670_REG_B_DGTL_GAIN 0x5036 +#define OV5670_DGTL_GAIN_MIN 0 +#define OV5670_DGTL_GAIN_MAX 4095 +#define OV5670_DGTL_GAIN_STEP 1 +#define OV5670_DGTL_GAIN_DEFAULT 1024 + +/* Test Pattern Control */ +#define OV5670_REG_TEST_PATTERN 0x4303 +#define OV5670_TEST_PATTERN_ENABLE BIT(3) +#define OV5670_REG_TEST_PATTERN_CTRL 0x4320 + +#define OV5670_REG_VALUE_08BIT 1 +#define OV5670_REG_VALUE_16BIT 2 +#define OV5670_REG_VALUE_24BIT 3 + +/* Initial number of frames to skip to avoid possible garbage */ +#define OV5670_NUM_OF_SKIP_FRAMES 2 + +struct ov5670_reg { + u16 address; + u8 val; +}; + +struct ov5670_reg_list { + u32 num_of_regs; + const struct ov5670_reg *regs; +}; + +struct ov5670_link_freq_config { + u32 pixel_rate; + const struct ov5670_reg_list reg_list; +}; + +struct ov5670_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Default vertical timining size */ + u32 vts_def; + + /* Min vertical timining size */ + u32 vts_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* Sensor register settings for this resolution */ + const struct ov5670_reg_list reg_list; +}; + +static const struct ov5670_reg mipi_data_rate_840mbps[] = { + {0x0300, 0x04}, + {0x0301, 0x00}, + {0x0302, 0x84}, + {0x0303, 0x00}, + {0x0304, 0x03}, + {0x0305, 0x01}, + {0x0306, 0x01}, + {0x030a, 0x00}, + {0x030b, 0x00}, + {0x030c, 0x00}, + {0x030d, 0x26}, + {0x030e, 0x00}, + {0x030f, 0x06}, + {0x0312, 0x01}, + {0x3031, 0x0a}, +}; + +static const struct ov5670_reg mode_2592x1944_regs[] = { + {0x3000, 0x00}, + {0x3002, 0x21}, + {0x3005, 0xf0}, + {0x3007, 0x00}, + {0x3015, 0x0f}, + {0x3018, 0x32}, + {0x301a, 0xf0}, + {0x301b, 0xf0}, + {0x301c, 0xf0}, + {0x301d, 0xf0}, + {0x301e, 0xf0}, + {0x3030, 0x00}, + {0x3031, 0x0a}, + {0x303c, 0xff}, + {0x303e, 0xff}, + {0x3040, 0xf0}, + {0x3041, 0x00}, + {0x3042, 0xf0}, + {0x3106, 0x11}, + {0x3500, 0x00}, + {0x3501, 0x80}, + {0x3502, 0x00}, + {0x3503, 0x04}, + {0x3504, 0x03}, + {0x3505, 0x83}, + {0x3508, 0x04}, + {0x3509, 0x00}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3601, 0xc8}, + {0x3610, 0x88}, + {0x3612, 0x48}, + {0x3614, 0x5b}, + {0x3615, 0x96}, + {0x3621, 0xd0}, + {0x3622, 0x00}, + {0x3623, 0x00}, + {0x3633, 0x13}, + {0x3634, 0x13}, + {0x3635, 0x13}, + {0x3636, 0x13}, + {0x3645, 0x13}, + {0x3646, 0x82}, + {0x3650, 0x00}, + {0x3652, 0xff}, + {0x3655, 0x20}, + {0x3656, 0xff}, + {0x365a, 0xff}, + {0x365e, 0xff}, + {0x3668, 0x00}, + {0x366a, 0x07}, + {0x366e, 0x10}, + {0x366d, 0x00}, + {0x366f, 0x80}, + {0x3700, 0x28}, + {0x3701, 0x10}, + {0x3702, 0x3a}, + {0x3703, 0x19}, + {0x3704, 0x10}, + {0x3705, 0x00}, + {0x3706, 0x66}, + {0x3707, 0x08}, + {0x3708, 0x34}, + {0x3709, 0x40}, + {0x370a, 0x01}, + {0x370b, 0x1b}, + {0x3714, 0x24}, + {0x371a, 0x3e}, + {0x3733, 0x00}, + {0x3734, 0x00}, + {0x373a, 0x05}, + {0x373b, 0x06}, + {0x373c, 0x0a}, + {0x373f, 0xa0}, + {0x3755, 0x00}, + {0x3758, 0x00}, + {0x375b, 0x0e}, + {0x3766, 0x5f}, + {0x3768, 0x00}, + {0x3769, 0x22}, + {0x3773, 0x08}, + {0x3774, 0x1f}, + {0x3776, 0x06}, + {0x37a0, 0x88}, + {0x37a1, 0x5c}, + {0x37a7, 0x88}, + {0x37a8, 0x70}, + {0x37aa, 0x88}, + {0x37ab, 0x48}, + {0x37b3, 0x66}, + {0x37c2, 0x04}, + {0x37c5, 0x00}, + {0x37c8, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x04}, + {0x3804, 0x0a}, + {0x3805, 0x33}, + {0x3806, 0x07}, + {0x3807, 0xa3}, + {0x3808, 0x0a}, + {0x3809, 0x20}, + {0x380a, 0x07}, + {0x380b, 0x98}, + {0x380c, 0x06}, + {0x380d, 0x90}, + {0x380e, 0x08}, + {0x380f, 0x08}, + {0x3811, 0x04}, + {0x3813, 0x02}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x00}, + {0x3817, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x3820, 0x84}, + {0x3821, 0x46}, + {0x3822, 0x48}, + {0x3826, 0x00}, + {0x3827, 0x08}, + {0x382a, 0x01}, + {0x382b, 0x01}, + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3837, 0x00}, + {0x3838, 0x10}, + {0x3841, 0xff}, + {0x3846, 0x48}, + {0x3861, 0x00}, + {0x3862, 0x04}, + {0x3863, 0x06}, + {0x3a11, 0x01}, + {0x3a12, 0x78}, + {0x3b00, 0x00}, + {0x3b02, 0x00}, + {0x3b03, 0x00}, + {0x3b04, 0x00}, + {0x3b05, 0x00}, + {0x3c00, 0x89}, + {0x3c01, 0xab}, + {0x3c02, 0x01}, + {0x3c03, 0x00}, + {0x3c04, 0x00}, + {0x3c05, 0x03}, + {0x3c06, 0x00}, + {0x3c07, 0x05}, + {0x3c0c, 0x00}, + {0x3c0d, 0x00}, + {0x3c0e, 0x00}, + {0x3c0f, 0x00}, + {0x3c40, 0x00}, + {0x3c41, 0xa3}, + {0x3c43, 0x7d}, + {0x3c45, 0xd7}, + {0x3c47, 0xfc}, + {0x3c50, 0x05}, + {0x3c52, 0xaa}, + {0x3c54, 0x71}, + {0x3c56, 0x80}, + {0x3d85, 0x17}, + {0x3f03, 0x00}, + {0x3f0a, 0x00}, + {0x3f0b, 0x00}, + {0x4001, 0x60}, + {0x4009, 0x0d}, + {0x4020, 0x00}, + {0x4021, 0x00}, + {0x4022, 0x00}, + {0x4023, 0x00}, + {0x4024, 0x00}, + {0x4025, 0x00}, + {0x4026, 0x00}, + {0x4027, 0x00}, + {0x4028, 0x00}, + {0x4029, 0x00}, + {0x402a, 0x00}, + {0x402b, 0x00}, + {0x402c, 0x00}, + {0x402d, 0x00}, + {0x402e, 0x00}, + {0x402f, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x03}, + {0x4042, 0x00}, + {0x4043, 0x7A}, + {0x4044, 0x00}, + {0x4045, 0x7A}, + {0x4046, 0x00}, + {0x4047, 0x7A}, + {0x4048, 0x00}, + {0x4049, 0x7A}, + {0x4307, 0x30}, + {0x4500, 0x58}, + {0x4501, 0x04}, + {0x4502, 0x40}, + {0x4503, 0x10}, + {0x4508, 0xaa}, + {0x4509, 0xaa}, + {0x450a, 0x00}, + {0x450b, 0x00}, + {0x4600, 0x01}, + {0x4601, 0x03}, + {0x4700, 0xa4}, + {0x4800, 0x4c}, + {0x4816, 0x53}, + {0x481f, 0x40}, + {0x4837, 0x13}, + {0x5000, 0x56}, + {0x5001, 0x01}, + {0x5002, 0x28}, + {0x5004, 0x0c}, + {0x5006, 0x0c}, + {0x5007, 0xe0}, + {0x5008, 0x01}, + {0x5009, 0xb0}, + {0x5901, 0x00}, + {0x5a01, 0x00}, + {0x5a03, 0x00}, + {0x5a04, 0x0c}, + {0x5a05, 0xe0}, + {0x5a06, 0x09}, + {0x5a07, 0xb0}, + {0x5a08, 0x06}, + {0x5e00, 0x00}, + {0x3734, 0x40}, + {0x5b00, 0x01}, + {0x5b01, 0x10}, + {0x5b02, 0x01}, + {0x5b03, 0xdb}, + {0x3d8c, 0x71}, + {0x3d8d, 0xea}, + {0x4017, 0x08}, + {0x3618, 0x2a}, + {0x5780, 0x3e}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x06}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x3503, 0x00}, + {0x5045, 0x05}, + {0x4003, 0x40}, + {0x5048, 0x40} +}; + +static const struct ov5670_reg mode_1296x972_regs[] = { + {0x3000, 0x00}, + {0x3002, 0x21}, + {0x3005, 0xf0}, + {0x3007, 0x00}, + {0x3015, 0x0f}, + {0x3018, 0x32}, + {0x301a, 0xf0}, + {0x301b, 0xf0}, + {0x301c, 0xf0}, + {0x301d, 0xf0}, + {0x301e, 0xf0}, + {0x3030, 0x00}, + {0x3031, 0x0a}, + {0x303c, 0xff}, + {0x303e, 0xff}, + {0x3040, 0xf0}, + {0x3041, 0x00}, + {0x3042, 0xf0}, + {0x3106, 0x11}, + {0x3500, 0x00}, + {0x3501, 0x80}, + {0x3502, 0x00}, + {0x3503, 0x04}, + {0x3504, 0x03}, + {0x3505, 0x83}, + {0x3508, 0x07}, + {0x3509, 0x80}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3601, 0xc8}, + {0x3610, 0x88}, + {0x3612, 0x48}, + {0x3614, 0x5b}, + {0x3615, 0x96}, + {0x3621, 0xd0}, + {0x3622, 0x00}, + {0x3623, 0x00}, + {0x3633, 0x13}, + {0x3634, 0x13}, + {0x3635, 0x13}, + {0x3636, 0x13}, + {0x3645, 0x13}, + {0x3646, 0x82}, + {0x3650, 0x00}, + {0x3652, 0xff}, + {0x3655, 0x20}, + {0x3656, 0xff}, + {0x365a, 0xff}, + {0x365e, 0xff}, + {0x3668, 0x00}, + {0x366a, 0x07}, + {0x366e, 0x08}, + {0x366d, 0x00}, + {0x366f, 0x80}, + {0x3700, 0x28}, + {0x3701, 0x10}, + {0x3702, 0x3a}, + {0x3703, 0x19}, + {0x3704, 0x10}, + {0x3705, 0x00}, + {0x3706, 0x66}, + {0x3707, 0x08}, + {0x3708, 0x34}, + {0x3709, 0x40}, + {0x370a, 0x01}, + {0x370b, 0x1b}, + {0x3714, 0x24}, + {0x371a, 0x3e}, + {0x3733, 0x00}, + {0x3734, 0x00}, + {0x373a, 0x05}, + {0x373b, 0x06}, + {0x373c, 0x0a}, + {0x373f, 0xa0}, + {0x3755, 0x00}, + {0x3758, 0x00}, + {0x375b, 0x0e}, + {0x3766, 0x5f}, + {0x3768, 0x00}, + {0x3769, 0x22}, + {0x3773, 0x08}, + {0x3774, 0x1f}, + {0x3776, 0x06}, + {0x37a0, 0x88}, + {0x37a1, 0x5c}, + {0x37a7, 0x88}, + {0x37a8, 0x70}, + {0x37aa, 0x88}, + {0x37ab, 0x48}, + {0x37b3, 0x66}, + {0x37c2, 0x04}, + {0x37c5, 0x00}, + {0x37c8, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x04}, + {0x3804, 0x0a}, + {0x3805, 0x33}, + {0x3806, 0x07}, + {0x3807, 0xa3}, + {0x3808, 0x05}, + {0x3809, 0x10}, + {0x380a, 0x03}, + {0x380b, 0xcc}, + {0x380c, 0x06}, + {0x380d, 0x90}, + {0x380e, 0x08}, + {0x380f, 0x08}, + {0x3811, 0x04}, + {0x3813, 0x04}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x00}, + {0x3817, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x3820, 0x94}, + {0x3821, 0x47}, + {0x3822, 0x48}, + {0x3826, 0x00}, + {0x3827, 0x08}, + {0x382a, 0x03}, + {0x382b, 0x01}, + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3837, 0x00}, + {0x3838, 0x10}, + {0x3841, 0xff}, + {0x3846, 0x48}, + {0x3861, 0x00}, + {0x3862, 0x04}, + {0x3863, 0x06}, + {0x3a11, 0x01}, + {0x3a12, 0x78}, + {0x3b00, 0x00}, + {0x3b02, 0x00}, + {0x3b03, 0x00}, + {0x3b04, 0x00}, + {0x3b05, 0x00}, + {0x3c00, 0x89}, + {0x3c01, 0xab}, + {0x3c02, 0x01}, + {0x3c03, 0x00}, + {0x3c04, 0x00}, + {0x3c05, 0x03}, + {0x3c06, 0x00}, + {0x3c07, 0x05}, + {0x3c0c, 0x00}, + {0x3c0d, 0x00}, + {0x3c0e, 0x00}, + {0x3c0f, 0x00}, + {0x3c40, 0x00}, + {0x3c41, 0xa3}, + {0x3c43, 0x7d}, + {0x3c45, 0xd7}, + {0x3c47, 0xfc}, + {0x3c50, 0x05}, + {0x3c52, 0xaa}, + {0x3c54, 0x71}, + {0x3c56, 0x80}, + {0x3d85, 0x17}, + {0x3f03, 0x00}, + {0x3f0a, 0x00}, + {0x3f0b, 0x00}, + {0x4001, 0x60}, + {0x4009, 0x05}, + {0x4020, 0x00}, + {0x4021, 0x00}, + {0x4022, 0x00}, + {0x4023, 0x00}, + {0x4024, 0x00}, + {0x4025, 0x00}, + {0x4026, 0x00}, + {0x4027, 0x00}, + {0x4028, 0x00}, + {0x4029, 0x00}, + {0x402a, 0x00}, + {0x402b, 0x00}, + {0x402c, 0x00}, + {0x402d, 0x00}, + {0x402e, 0x00}, + {0x402f, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x03}, + {0x4042, 0x00}, + {0x4043, 0x7A}, + {0x4044, 0x00}, + {0x4045, 0x7A}, + {0x4046, 0x00}, + {0x4047, 0x7A}, + {0x4048, 0x00}, + {0x4049, 0x7A}, + {0x4307, 0x30}, + {0x4500, 0x58}, + {0x4501, 0x04}, + {0x4502, 0x48}, + {0x4503, 0x10}, + {0x4508, 0x55}, + {0x4509, 0x55}, + {0x450a, 0x00}, + {0x450b, 0x00}, + {0x4600, 0x00}, + {0x4601, 0x81}, + {0x4700, 0xa4}, + {0x4800, 0x4c}, + {0x4816, 0x53}, + {0x481f, 0x40}, + {0x4837, 0x13}, + {0x5000, 0x56}, + {0x5001, 0x01}, + {0x5002, 0x28}, + {0x5004, 0x0c}, + {0x5006, 0x0c}, + {0x5007, 0xe0}, + {0x5008, 0x01}, + {0x5009, 0xb0}, + {0x5901, 0x00}, + {0x5a01, 0x00}, + {0x5a03, 0x00}, + {0x5a04, 0x0c}, + {0x5a05, 0xe0}, + {0x5a06, 0x09}, + {0x5a07, 0xb0}, + {0x5a08, 0x06}, + {0x5e00, 0x00}, + {0x3734, 0x40}, + {0x5b00, 0x01}, + {0x5b01, 0x10}, + {0x5b02, 0x01}, + {0x5b03, 0xdb}, + {0x3d8c, 0x71}, + {0x3d8d, 0xea}, + {0x4017, 0x10}, + {0x3618, 0x2a}, + {0x5780, 0x3e}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x3503, 0x00}, + {0x5045, 0x05}, + {0x4003, 0x40}, + {0x5048, 0x40} +}; + +static const struct ov5670_reg mode_648x486_regs[] = { + {0x3000, 0x00}, + {0x3002, 0x21}, + {0x3005, 0xf0}, + {0x3007, 0x00}, + {0x3015, 0x0f}, + {0x3018, 0x32}, + {0x301a, 0xf0}, + {0x301b, 0xf0}, + {0x301c, 0xf0}, + {0x301d, 0xf0}, + {0x301e, 0xf0}, + {0x3030, 0x00}, + {0x3031, 0x0a}, + {0x303c, 0xff}, + {0x303e, 0xff}, + {0x3040, 0xf0}, + {0x3041, 0x00}, + {0x3042, 0xf0}, + {0x3106, 0x11}, + {0x3500, 0x00}, + {0x3501, 0x80}, + {0x3502, 0x00}, + {0x3503, 0x04}, + {0x3504, 0x03}, + {0x3505, 0x83}, + {0x3508, 0x04}, + {0x3509, 0x00}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3601, 0xc8}, + {0x3610, 0x88}, + {0x3612, 0x48}, + {0x3614, 0x5b}, + {0x3615, 0x96}, + {0x3621, 0xd0}, + {0x3622, 0x00}, + {0x3623, 0x04}, + {0x3633, 0x13}, + {0x3634, 0x13}, + {0x3635, 0x13}, + {0x3636, 0x13}, + {0x3645, 0x13}, + {0x3646, 0x82}, + {0x3650, 0x00}, + {0x3652, 0xff}, + {0x3655, 0x20}, + {0x3656, 0xff}, + {0x365a, 0xff}, + {0x365e, 0xff}, + {0x3668, 0x00}, + {0x366a, 0x07}, + {0x366e, 0x08}, + {0x366d, 0x00}, + {0x366f, 0x80}, + {0x3700, 0x28}, + {0x3701, 0x10}, + {0x3702, 0x3a}, + {0x3703, 0x19}, + {0x3704, 0x10}, + {0x3705, 0x00}, + {0x3706, 0x66}, + {0x3707, 0x08}, + {0x3708, 0x34}, + {0x3709, 0x40}, + {0x370a, 0x01}, + {0x370b, 0x1b}, + {0x3714, 0x24}, + {0x371a, 0x3e}, + {0x3733, 0x00}, + {0x3734, 0x00}, + {0x373a, 0x05}, + {0x373b, 0x06}, + {0x373c, 0x0a}, + {0x373f, 0xa0}, + {0x3755, 0x00}, + {0x3758, 0x00}, + {0x375b, 0x0e}, + {0x3766, 0x5f}, + {0x3768, 0x00}, + {0x3769, 0x22}, + {0x3773, 0x08}, + {0x3774, 0x1f}, + {0x3776, 0x06}, + {0x37a0, 0x88}, + {0x37a1, 0x5c}, + {0x37a7, 0x88}, + {0x37a8, 0x70}, + {0x37aa, 0x88}, + {0x37ab, 0x48}, + {0x37b3, 0x66}, + {0x37c2, 0x04}, + {0x37c5, 0x00}, + {0x37c8, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x04}, + {0x3804, 0x0a}, + {0x3805, 0x33}, + {0x3806, 0x07}, + {0x3807, 0xa3}, + {0x3808, 0x02}, + {0x3809, 0x88}, + {0x380a, 0x01}, + {0x380b, 0xe6}, + {0x380c, 0x06}, + {0x380d, 0x90}, + {0x380e, 0x08}, + {0x380f, 0x08}, + {0x3811, 0x04}, + {0x3813, 0x02}, + {0x3814, 0x07}, + {0x3815, 0x01}, + {0x3816, 0x00}, + {0x3817, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x3820, 0x94}, + {0x3821, 0xc6}, + {0x3822, 0x48}, + {0x3826, 0x00}, + {0x3827, 0x08}, + {0x382a, 0x07}, + {0x382b, 0x01}, + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3837, 0x00}, + {0x3838, 0x10}, + {0x3841, 0xff}, + {0x3846, 0x48}, + {0x3861, 0x00}, + {0x3862, 0x04}, + {0x3863, 0x06}, + {0x3a11, 0x01}, + {0x3a12, 0x78}, + {0x3b00, 0x00}, + {0x3b02, 0x00}, + {0x3b03, 0x00}, + {0x3b04, 0x00}, + {0x3b05, 0x00}, + {0x3c00, 0x89}, + {0x3c01, 0xab}, + {0x3c02, 0x01}, + {0x3c03, 0x00}, + {0x3c04, 0x00}, + {0x3c05, 0x03}, + {0x3c06, 0x00}, + {0x3c07, 0x05}, + {0x3c0c, 0x00}, + {0x3c0d, 0x00}, + {0x3c0e, 0x00}, + {0x3c0f, 0x00}, + {0x3c40, 0x00}, + {0x3c41, 0xa3}, + {0x3c43, 0x7d}, + {0x3c45, 0xd7}, + {0x3c47, 0xfc}, + {0x3c50, 0x05}, + {0x3c52, 0xaa}, + {0x3c54, 0x71}, + {0x3c56, 0x80}, + {0x3d85, 0x17}, + {0x3f03, 0x00}, + {0x3f0a, 0x00}, + {0x3f0b, 0x00}, + {0x4001, 0x60}, + {0x4009, 0x05}, + {0x4020, 0x00}, + {0x4021, 0x00}, + {0x4022, 0x00}, + {0x4023, 0x00}, + {0x4024, 0x00}, + {0x4025, 0x00}, + {0x4026, 0x00}, + {0x4027, 0x00}, + {0x4028, 0x00}, + {0x4029, 0x00}, + {0x402a, 0x00}, + {0x402b, 0x00}, + {0x402c, 0x00}, + {0x402d, 0x00}, + {0x402e, 0x00}, + {0x402f, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x03}, + {0x4042, 0x00}, + {0x4043, 0x7A}, + {0x4044, 0x00}, + {0x4045, 0x7A}, + {0x4046, 0x00}, + {0x4047, 0x7A}, + {0x4048, 0x00}, + {0x4049, 0x7A}, + {0x4307, 0x30}, + {0x4500, 0x58}, + {0x4501, 0x04}, + {0x4502, 0x40}, + {0x4503, 0x10}, + {0x4508, 0x55}, + {0x4509, 0x55}, + {0x450a, 0x02}, + {0x450b, 0x00}, + {0x4600, 0x00}, + {0x4601, 0x40}, + {0x4700, 0xa4}, + {0x4800, 0x4c}, + {0x4816, 0x53}, + {0x481f, 0x40}, + {0x4837, 0x13}, + {0x5000, 0x56}, + {0x5001, 0x01}, + {0x5002, 0x28}, + {0x5004, 0x0c}, + {0x5006, 0x0c}, + {0x5007, 0xe0}, + {0x5008, 0x01}, + {0x5009, 0xb0}, + {0x5901, 0x00}, + {0x5a01, 0x00}, + {0x5a03, 0x00}, + {0x5a04, 0x0c}, + {0x5a05, 0xe0}, + {0x5a06, 0x09}, + {0x5a07, 0xb0}, + {0x5a08, 0x06}, + {0x5e00, 0x00}, + {0x3734, 0x40}, + {0x5b00, 0x01}, + {0x5b01, 0x10}, + {0x5b02, 0x01}, + {0x5b03, 0xdb}, + {0x3d8c, 0x71}, + {0x3d8d, 0xea}, + {0x4017, 0x10}, + {0x3618, 0x2a}, + {0x5780, 0x3e}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x06}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x3503, 0x00}, + {0x5045, 0x05}, + {0x4003, 0x40}, + {0x5048, 0x40} +}; + +static const struct ov5670_reg mode_2560x1440_regs[] = { + {0x3000, 0x00}, + {0x3002, 0x21}, + {0x3005, 0xf0}, + {0x3007, 0x00}, + {0x3015, 0x0f}, + {0x3018, 0x32}, + {0x301a, 0xf0}, + {0x301b, 0xf0}, + {0x301c, 0xf0}, + {0x301d, 0xf0}, + {0x301e, 0xf0}, + {0x3030, 0x00}, + {0x3031, 0x0a}, + {0x303c, 0xff}, + {0x303e, 0xff}, + {0x3040, 0xf0}, + {0x3041, 0x00}, + {0x3042, 0xf0}, + {0x3106, 0x11}, + {0x3500, 0x00}, + {0x3501, 0x80}, + {0x3502, 0x00}, + {0x3503, 0x04}, + {0x3504, 0x03}, + {0x3505, 0x83}, + {0x3508, 0x04}, + {0x3509, 0x00}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3601, 0xc8}, + {0x3610, 0x88}, + {0x3612, 0x48}, + {0x3614, 0x5b}, + {0x3615, 0x96}, + {0x3621, 0xd0}, + {0x3622, 0x00}, + {0x3623, 0x00}, + {0x3633, 0x13}, + {0x3634, 0x13}, + {0x3635, 0x13}, + {0x3636, 0x13}, + {0x3645, 0x13}, + {0x3646, 0x82}, + {0x3650, 0x00}, + {0x3652, 0xff}, + {0x3655, 0x20}, + {0x3656, 0xff}, + {0x365a, 0xff}, + {0x365e, 0xff}, + {0x3668, 0x00}, + {0x366a, 0x07}, + {0x366e, 0x10}, + {0x366d, 0x00}, + {0x366f, 0x80}, + {0x3700, 0x28}, + {0x3701, 0x10}, + {0x3702, 0x3a}, + {0x3703, 0x19}, + {0x3704, 0x10}, + {0x3705, 0x00}, + {0x3706, 0x66}, + {0x3707, 0x08}, + {0x3708, 0x34}, + {0x3709, 0x40}, + {0x370a, 0x01}, + {0x370b, 0x1b}, + {0x3714, 0x24}, + {0x371a, 0x3e}, + {0x3733, 0x00}, + {0x3734, 0x00}, + {0x373a, 0x05}, + {0x373b, 0x06}, + {0x373c, 0x0a}, + {0x373f, 0xa0}, + {0x3755, 0x00}, + {0x3758, 0x00}, + {0x375b, 0x0e}, + {0x3766, 0x5f}, + {0x3768, 0x00}, + {0x3769, 0x22}, + {0x3773, 0x08}, + {0x3774, 0x1f}, + {0x3776, 0x06}, + {0x37a0, 0x88}, + {0x37a1, 0x5c}, + {0x37a7, 0x88}, + {0x37a8, 0x70}, + {0x37aa, 0x88}, + {0x37ab, 0x48}, + {0x37b3, 0x66}, + {0x37c2, 0x04}, + {0x37c5, 0x00}, + {0x37c8, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x04}, + {0x3804, 0x0a}, + {0x3805, 0x33}, + {0x3806, 0x07}, + {0x3807, 0xa3}, + {0x3808, 0x0a}, + {0x3809, 0x00}, + {0x380a, 0x05}, + {0x380b, 0xa0}, + {0x380c, 0x06}, + {0x380d, 0x90}, + {0x380e, 0x08}, + {0x380f, 0x08}, + {0x3811, 0x04}, + {0x3813, 0x02}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x00}, + {0x3817, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x3820, 0x84}, + {0x3821, 0x46}, + {0x3822, 0x48}, + {0x3826, 0x00}, + {0x3827, 0x08}, + {0x382a, 0x01}, + {0x382b, 0x01}, + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3837, 0x00}, + {0x3838, 0x10}, + {0x3841, 0xff}, + {0x3846, 0x48}, + {0x3861, 0x00}, + {0x3862, 0x04}, + {0x3863, 0x06}, + {0x3a11, 0x01}, + {0x3a12, 0x78}, + {0x3b00, 0x00}, + {0x3b02, 0x00}, + {0x3b03, 0x00}, + {0x3b04, 0x00}, + {0x3b05, 0x00}, + {0x3c00, 0x89}, + {0x3c01, 0xab}, + {0x3c02, 0x01}, + {0x3c03, 0x00}, + {0x3c04, 0x00}, + {0x3c05, 0x03}, + {0x3c06, 0x00}, + {0x3c07, 0x05}, + {0x3c0c, 0x00}, + {0x3c0d, 0x00}, + {0x3c0e, 0x00}, + {0x3c0f, 0x00}, + {0x3c40, 0x00}, + {0x3c41, 0xa3}, + {0x3c43, 0x7d}, + {0x3c45, 0xd7}, + {0x3c47, 0xfc}, + {0x3c50, 0x05}, + {0x3c52, 0xaa}, + {0x3c54, 0x71}, + {0x3c56, 0x80}, + {0x3d85, 0x17}, + {0x3f03, 0x00}, + {0x3f0a, 0x00}, + {0x3f0b, 0x00}, + {0x4001, 0x60}, + {0x4009, 0x0d}, + {0x4020, 0x00}, + {0x4021, 0x00}, + {0x4022, 0x00}, + {0x4023, 0x00}, + {0x4024, 0x00}, + {0x4025, 0x00}, + {0x4026, 0x00}, + {0x4027, 0x00}, + {0x4028, 0x00}, + {0x4029, 0x00}, + {0x402a, 0x00}, + {0x402b, 0x00}, + {0x402c, 0x00}, + {0x402d, 0x00}, + {0x402e, 0x00}, + {0x402f, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x03}, + {0x4042, 0x00}, + {0x4043, 0x7A}, + {0x4044, 0x00}, + {0x4045, 0x7A}, + {0x4046, 0x00}, + {0x4047, 0x7A}, + {0x4048, 0x00}, + {0x4049, 0x7A}, + {0x4307, 0x30}, + {0x4500, 0x58}, + {0x4501, 0x04}, + {0x4502, 0x40}, + {0x4503, 0x10}, + {0x4508, 0xaa}, + {0x4509, 0xaa}, + {0x450a, 0x00}, + {0x450b, 0x00}, + {0x4600, 0x01}, + {0x4601, 0x00}, + {0x4700, 0xa4}, + {0x4800, 0x4c}, + {0x4816, 0x53}, + {0x481f, 0x40}, + {0x4837, 0x13}, + {0x5000, 0x56}, + {0x5001, 0x01}, + {0x5002, 0x28}, + {0x5004, 0x0c}, + {0x5006, 0x0c}, + {0x5007, 0xe0}, + {0x5008, 0x01}, + {0x5009, 0xb0}, + {0x5901, 0x00}, + {0x5a01, 0x00}, + {0x5a03, 0x00}, + {0x5a04, 0x0c}, + {0x5a05, 0xe0}, + {0x5a06, 0x09}, + {0x5a07, 0xb0}, + {0x5a08, 0x06}, + {0x5e00, 0x00}, + {0x3734, 0x40}, + {0x5b00, 0x01}, + {0x5b01, 0x10}, + {0x5b02, 0x01}, + {0x5b03, 0xdb}, + {0x3d8c, 0x71}, + {0x3d8d, 0xea}, + {0x4017, 0x08}, + {0x3618, 0x2a}, + {0x5780, 0x3e}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x06}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x5045, 0x05}, + {0x4003, 0x40}, + {0x5048, 0x40} +}; + +static const struct ov5670_reg mode_1280x720_regs[] = { + {0x3000, 0x00}, + {0x3002, 0x21}, + {0x3005, 0xf0}, + {0x3007, 0x00}, + {0x3015, 0x0f}, + {0x3018, 0x32}, + {0x301a, 0xf0}, + {0x301b, 0xf0}, + {0x301c, 0xf0}, + {0x301d, 0xf0}, + {0x301e, 0xf0}, + {0x3030, 0x00}, + {0x3031, 0x0a}, + {0x303c, 0xff}, + {0x303e, 0xff}, + {0x3040, 0xf0}, + {0x3041, 0x00}, + {0x3042, 0xf0}, + {0x3106, 0x11}, + {0x3500, 0x00}, + {0x3501, 0x80}, + {0x3502, 0x00}, + {0x3503, 0x04}, + {0x3504, 0x03}, + {0x3505, 0x83}, + {0x3508, 0x04}, + {0x3509, 0x00}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3601, 0xc8}, + {0x3610, 0x88}, + {0x3612, 0x48}, + {0x3614, 0x5b}, + {0x3615, 0x96}, + {0x3621, 0xd0}, + {0x3622, 0x00}, + {0x3623, 0x00}, + {0x3633, 0x13}, + {0x3634, 0x13}, + {0x3635, 0x13}, + {0x3636, 0x13}, + {0x3645, 0x13}, + {0x3646, 0x82}, + {0x3650, 0x00}, + {0x3652, 0xff}, + {0x3655, 0x20}, + {0x3656, 0xff}, + {0x365a, 0xff}, + {0x365e, 0xff}, + {0x3668, 0x00}, + {0x366a, 0x07}, + {0x366e, 0x08}, + {0x366d, 0x00}, + {0x366f, 0x80}, + {0x3700, 0x28}, + {0x3701, 0x10}, + {0x3702, 0x3a}, + {0x3703, 0x19}, + {0x3704, 0x10}, + {0x3705, 0x00}, + {0x3706, 0x66}, + {0x3707, 0x08}, + {0x3708, 0x34}, + {0x3709, 0x40}, + {0x370a, 0x01}, + {0x370b, 0x1b}, + {0x3714, 0x24}, + {0x371a, 0x3e}, + {0x3733, 0x00}, + {0x3734, 0x00}, + {0x373a, 0x05}, + {0x373b, 0x06}, + {0x373c, 0x0a}, + {0x373f, 0xa0}, + {0x3755, 0x00}, + {0x3758, 0x00}, + {0x375b, 0x0e}, + {0x3766, 0x5f}, + {0x3768, 0x00}, + {0x3769, 0x22}, + {0x3773, 0x08}, + {0x3774, 0x1f}, + {0x3776, 0x06}, + {0x37a0, 0x88}, + {0x37a1, 0x5c}, + {0x37a7, 0x88}, + {0x37a8, 0x70}, + {0x37aa, 0x88}, + {0x37ab, 0x48}, + {0x37b3, 0x66}, + {0x37c2, 0x04}, + {0x37c5, 0x00}, + {0x37c8, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x04}, + {0x3804, 0x0a}, + {0x3805, 0x33}, + {0x3806, 0x07}, + {0x3807, 0xa3}, + {0x3808, 0x05}, + {0x3809, 0x00}, + {0x380a, 0x02}, + {0x380b, 0xd0}, + {0x380c, 0x06}, + {0x380d, 0x90}, + {0x380e, 0x08}, + {0x380f, 0x08}, + {0x3811, 0x04}, + {0x3813, 0x02}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x00}, + {0x3817, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x3820, 0x94}, + {0x3821, 0x47}, + {0x3822, 0x48}, + {0x3826, 0x00}, + {0x3827, 0x08}, + {0x382a, 0x03}, + {0x382b, 0x01}, + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3837, 0x00}, + {0x3838, 0x10}, + {0x3841, 0xff}, + {0x3846, 0x48}, + {0x3861, 0x00}, + {0x3862, 0x04}, + {0x3863, 0x06}, + {0x3a11, 0x01}, + {0x3a12, 0x78}, + {0x3b00, 0x00}, + {0x3b02, 0x00}, + {0x3b03, 0x00}, + {0x3b04, 0x00}, + {0x3b05, 0x00}, + {0x3c00, 0x89}, + {0x3c01, 0xab}, + {0x3c02, 0x01}, + {0x3c03, 0x00}, + {0x3c04, 0x00}, + {0x3c05, 0x03}, + {0x3c06, 0x00}, + {0x3c07, 0x05}, + {0x3c0c, 0x00}, + {0x3c0d, 0x00}, + {0x3c0e, 0x00}, + {0x3c0f, 0x00}, + {0x3c40, 0x00}, + {0x3c41, 0xa3}, + {0x3c43, 0x7d}, + {0x3c45, 0xd7}, + {0x3c47, 0xfc}, + {0x3c50, 0x05}, + {0x3c52, 0xaa}, + {0x3c54, 0x71}, + {0x3c56, 0x80}, + {0x3d85, 0x17}, + {0x3f03, 0x00}, + {0x3f0a, 0x00}, + {0x3f0b, 0x00}, + {0x4001, 0x60}, + {0x4009, 0x05}, + {0x4020, 0x00}, + {0x4021, 0x00}, + {0x4022, 0x00}, + {0x4023, 0x00}, + {0x4024, 0x00}, + {0x4025, 0x00}, + {0x4026, 0x00}, + {0x4027, 0x00}, + {0x4028, 0x00}, + {0x4029, 0x00}, + {0x402a, 0x00}, + {0x402b, 0x00}, + {0x402c, 0x00}, + {0x402d, 0x00}, + {0x402e, 0x00}, + {0x402f, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x03}, + {0x4042, 0x00}, + {0x4043, 0x7A}, + {0x4044, 0x00}, + {0x4045, 0x7A}, + {0x4046, 0x00}, + {0x4047, 0x7A}, + {0x4048, 0x00}, + {0x4049, 0x7A}, + {0x4307, 0x30}, + {0x4500, 0x58}, + {0x4501, 0x04}, + {0x4502, 0x48}, + {0x4503, 0x10}, + {0x4508, 0x55}, + {0x4509, 0x55}, + {0x450a, 0x00}, + {0x450b, 0x00}, + {0x4600, 0x00}, + {0x4601, 0x80}, + {0x4700, 0xa4}, + {0x4800, 0x4c}, + {0x4816, 0x53}, + {0x481f, 0x40}, + {0x4837, 0x13}, + {0x5000, 0x56}, + {0x5001, 0x01}, + {0x5002, 0x28}, + {0x5004, 0x0c}, + {0x5006, 0x0c}, + {0x5007, 0xe0}, + {0x5008, 0x01}, + {0x5009, 0xb0}, + {0x5901, 0x00}, + {0x5a01, 0x00}, + {0x5a03, 0x00}, + {0x5a04, 0x0c}, + {0x5a05, 0xe0}, + {0x5a06, 0x09}, + {0x5a07, 0xb0}, + {0x5a08, 0x06}, + {0x5e00, 0x00}, + {0x3734, 0x40}, + {0x5b00, 0x01}, + {0x5b01, 0x10}, + {0x5b02, 0x01}, + {0x5b03, 0xdb}, + {0x3d8c, 0x71}, + {0x3d8d, 0xea}, + {0x4017, 0x10}, + {0x3618, 0x2a}, + {0x5780, 0x3e}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x06}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x3503, 0x00}, + {0x5045, 0x05}, + {0x4003, 0x40}, + {0x5048, 0x40} +}; + +static const struct ov5670_reg mode_640x360_regs[] = { + {0x3000, 0x00}, + {0x3002, 0x21}, + {0x3005, 0xf0}, + {0x3007, 0x00}, + {0x3015, 0x0f}, + {0x3018, 0x32}, + {0x301a, 0xf0}, + {0x301b, 0xf0}, + {0x301c, 0xf0}, + {0x301d, 0xf0}, + {0x301e, 0xf0}, + {0x3030, 0x00}, + {0x3031, 0x0a}, + {0x303c, 0xff}, + {0x303e, 0xff}, + {0x3040, 0xf0}, + {0x3041, 0x00}, + {0x3042, 0xf0}, + {0x3106, 0x11}, + {0x3500, 0x00}, + {0x3501, 0x80}, + {0x3502, 0x00}, + {0x3503, 0x04}, + {0x3504, 0x03}, + {0x3505, 0x83}, + {0x3508, 0x04}, + {0x3509, 0x00}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3601, 0xc8}, + {0x3610, 0x88}, + {0x3612, 0x48}, + {0x3614, 0x5b}, + {0x3615, 0x96}, + {0x3621, 0xd0}, + {0x3622, 0x00}, + {0x3623, 0x04}, + {0x3633, 0x13}, + {0x3634, 0x13}, + {0x3635, 0x13}, + {0x3636, 0x13}, + {0x3645, 0x13}, + {0x3646, 0x82}, + {0x3650, 0x00}, + {0x3652, 0xff}, + {0x3655, 0x20}, + {0x3656, 0xff}, + {0x365a, 0xff}, + {0x365e, 0xff}, + {0x3668, 0x00}, + {0x366a, 0x07}, + {0x366e, 0x08}, + {0x366d, 0x00}, + {0x366f, 0x80}, + {0x3700, 0x28}, + {0x3701, 0x10}, + {0x3702, 0x3a}, + {0x3703, 0x19}, + {0x3704, 0x10}, + {0x3705, 0x00}, + {0x3706, 0x66}, + {0x3707, 0x08}, + {0x3708, 0x34}, + {0x3709, 0x40}, + {0x370a, 0x01}, + {0x370b, 0x1b}, + {0x3714, 0x24}, + {0x371a, 0x3e}, + {0x3733, 0x00}, + {0x3734, 0x00}, + {0x373a, 0x05}, + {0x373b, 0x06}, + {0x373c, 0x0a}, + {0x373f, 0xa0}, + {0x3755, 0x00}, + {0x3758, 0x00}, + {0x375b, 0x0e}, + {0x3766, 0x5f}, + {0x3768, 0x00}, + {0x3769, 0x22}, + {0x3773, 0x08}, + {0x3774, 0x1f}, + {0x3776, 0x06}, + {0x37a0, 0x88}, + {0x37a1, 0x5c}, + {0x37a7, 0x88}, + {0x37a8, 0x70}, + {0x37aa, 0x88}, + {0x37ab, 0x48}, + {0x37b3, 0x66}, + {0x37c2, 0x04}, + {0x37c5, 0x00}, + {0x37c8, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x04}, + {0x3804, 0x0a}, + {0x3805, 0x33}, + {0x3806, 0x07}, + {0x3807, 0xa3}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0x68}, + {0x380c, 0x06}, + {0x380d, 0x90}, + {0x380e, 0x08}, + {0x380f, 0x08}, + {0x3811, 0x04}, + {0x3813, 0x02}, + {0x3814, 0x07}, + {0x3815, 0x01}, + {0x3816, 0x00}, + {0x3817, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x3820, 0x94}, + {0x3821, 0xc6}, + {0x3822, 0x48}, + {0x3826, 0x00}, + {0x3827, 0x08}, + {0x382a, 0x07}, + {0x382b, 0x01}, + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3837, 0x00}, + {0x3838, 0x10}, + {0x3841, 0xff}, + {0x3846, 0x48}, + {0x3861, 0x00}, + {0x3862, 0x04}, + {0x3863, 0x06}, + {0x3a11, 0x01}, + {0x3a12, 0x78}, + {0x3b00, 0x00}, + {0x3b02, 0x00}, + {0x3b03, 0x00}, + {0x3b04, 0x00}, + {0x3b05, 0x00}, + {0x3c00, 0x89}, + {0x3c01, 0xab}, + {0x3c02, 0x01}, + {0x3c03, 0x00}, + {0x3c04, 0x00}, + {0x3c05, 0x03}, + {0x3c06, 0x00}, + {0x3c07, 0x05}, + {0x3c0c, 0x00}, + {0x3c0d, 0x00}, + {0x3c0e, 0x00}, + {0x3c0f, 0x00}, + {0x3c40, 0x00}, + {0x3c41, 0xa3}, + {0x3c43, 0x7d}, + {0x3c45, 0xd7}, + {0x3c47, 0xfc}, + {0x3c50, 0x05}, + {0x3c52, 0xaa}, + {0x3c54, 0x71}, + {0x3c56, 0x80}, + {0x3d85, 0x17}, + {0x3f03, 0x00}, + {0x3f0a, 0x00}, + {0x3f0b, 0x00}, + {0x4001, 0x60}, + {0x4009, 0x05}, + {0x4020, 0x00}, + {0x4021, 0x00}, + {0x4022, 0x00}, + {0x4023, 0x00}, + {0x4024, 0x00}, + {0x4025, 0x00}, + {0x4026, 0x00}, + {0x4027, 0x00}, + {0x4028, 0x00}, + {0x4029, 0x00}, + {0x402a, 0x00}, + {0x402b, 0x00}, + {0x402c, 0x00}, + {0x402d, 0x00}, + {0x402e, 0x00}, + {0x402f, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x03}, + {0x4042, 0x00}, + {0x4043, 0x7A}, + {0x4044, 0x00}, + {0x4045, 0x7A}, + {0x4046, 0x00}, + {0x4047, 0x7A}, + {0x4048, 0x00}, + {0x4049, 0x7A}, + {0x4307, 0x30}, + {0x4500, 0x58}, + {0x4501, 0x04}, + {0x4502, 0x40}, + {0x4503, 0x10}, + {0x4508, 0x55}, + {0x4509, 0x55}, + {0x450a, 0x02}, + {0x450b, 0x00}, + {0x4600, 0x00}, + {0x4601, 0x40}, + {0x4700, 0xa4}, + {0x4800, 0x4c}, + {0x4816, 0x53}, + {0x481f, 0x40}, + {0x4837, 0x13}, + {0x5000, 0x56}, + {0x5001, 0x01}, + {0x5002, 0x28}, + {0x5004, 0x0c}, + {0x5006, 0x0c}, + {0x5007, 0xe0}, + {0x5008, 0x01}, + {0x5009, 0xb0}, + {0x5901, 0x00}, + {0x5a01, 0x00}, + {0x5a03, 0x00}, + {0x5a04, 0x0c}, + {0x5a05, 0xe0}, + {0x5a06, 0x09}, + {0x5a07, 0xb0}, + {0x5a08, 0x06}, + {0x5e00, 0x00}, + {0x3734, 0x40}, + {0x5b00, 0x01}, + {0x5b01, 0x10}, + {0x5b02, 0x01}, + {0x5b03, 0xdb}, + {0x3d8c, 0x71}, + {0x3d8d, 0xea}, + {0x4017, 0x10}, + {0x3618, 0x2a}, + {0x5780, 0x3e}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x06}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x3503, 0x00}, + {0x5045, 0x05}, + {0x4003, 0x40}, + {0x5048, 0x40} +}; + +static const char * const ov5670_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", +}; + +/* Supported link frequencies */ +#define OV5670_LINK_FREQ_422MHZ 422400000 +#define OV5670_LINK_FREQ_422MHZ_INDEX 0 +static const struct ov5670_link_freq_config link_freq_configs[] = { + { + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ + .pixel_rate = (OV5670_LINK_FREQ_422MHZ * 2 * 2) / 10, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_840mbps), + .regs = mipi_data_rate_840mbps, + } + } +}; + +static const s64 link_freq_menu_items[] = { + OV5670_LINK_FREQ_422MHZ +}; + +/* + * OV5670 sensor supports following resolutions with full FOV: + * 4:3 ==> {2592x1944, 1296x972, 648x486} + * 16:9 ==> {2560x1440, 1280x720, 640x360} + */ +static const struct ov5670_mode supported_modes[] = { + { + .width = 2592, + .height = 1944, + .vts_def = OV5670_VTS_30FPS, + .vts_min = OV5670_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2592x1944_regs), + .regs = mode_2592x1944_regs, + }, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + }, + { + .width = 1296, + .height = 972, + .vts_def = OV5670_VTS_30FPS, + .vts_min = 996, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x972_regs), + .regs = mode_1296x972_regs, + }, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + }, + { + .width = 648, + .height = 486, + .vts_def = OV5670_VTS_30FPS, + .vts_min = 516, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_648x486_regs), + .regs = mode_648x486_regs, + }, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + }, + { + .width = 2560, + .height = 1440, + .vts_def = OV5670_VTS_30FPS, + .vts_min = OV5670_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2560x1440_regs), + .regs = mode_2560x1440_regs, + }, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + }, + { + .width = 1280, + .height = 720, + .vts_def = OV5670_VTS_30FPS, + .vts_min = 1020, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, + }, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + }, + { + .width = 640, + .height = 360, + .vts_def = OV5670_VTS_30FPS, + .vts_min = 510, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_640x360_regs), + .regs = mode_640x360_regs, + }, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + } +}; + +struct ov5670 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct ov5670_mode *cur_mode; + + /* To serialize asynchronus callbacks */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +#define to_ov5670(_sd) container_of(_sd, struct ov5670, sd) + +/* Read registers up to 4 at a time */ +static int ov5670_read_reg(struct ov5670 *ov5670, u16 reg, unsigned int len, + u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); + struct i2c_msg msgs[2]; + u8 *data_be_p; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + int ret; + + if (len > 4) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +/* Write registers up to 4 at a time */ +static int ov5670_write_reg(struct ov5670 *ov5670, u16 reg, unsigned int len, + u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); + int buf_i; + int val_i; + u8 buf[6]; + u8 *val_p; + __be32 tmp; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + tmp = cpu_to_be32(val); + val_p = (u8 *)&tmp; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int ov5670_write_regs(struct ov5670 *ov5670, + const struct ov5670_reg *regs, unsigned int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = ov5670_write_reg(ov5670, regs[i].address, 1, regs[i].val); + if (ret) { + dev_err_ratelimited( + &client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +static int ov5670_write_reg_list(struct ov5670 *ov5670, + const struct ov5670_reg_list *r_list) +{ + return ov5670_write_regs(ov5670, r_list->regs, r_list->num_of_regs); +} + +/* Open sub-device */ +static int ov5670_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov5670 *ov5670 = to_ov5670(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + + mutex_lock(&ov5670->mutex); + + /* Initialize try_fmt */ + try_fmt->width = ov5670->cur_mode->width; + try_fmt->height = ov5670->cur_mode->height; + try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + + /* No crop or compose */ + mutex_unlock(&ov5670->mutex); + + return 0; +} + +static int ov5670_update_digital_gain(struct ov5670 *ov5670, u32 d_gain) +{ + int ret; + + ret = ov5670_write_reg(ov5670, OV5670_REG_R_DGTL_GAIN, + OV5670_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = ov5670_write_reg(ov5670, OV5670_REG_G_DGTL_GAIN, + OV5670_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + return ov5670_write_reg(ov5670, OV5670_REG_B_DGTL_GAIN, + OV5670_REG_VALUE_16BIT, d_gain); +} + +static int ov5670_enable_test_pattern(struct ov5670 *ov5670, u32 pattern) +{ + u32 val; + int ret; + + /* Set the bayer order that we support */ + ret = ov5670_write_reg(ov5670, OV5670_REG_TEST_PATTERN_CTRL, + OV5670_REG_VALUE_08BIT, 0); + if (ret) + return ret; + + ret = ov5670_read_reg(ov5670, OV5670_REG_TEST_PATTERN, + OV5670_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + if (pattern) + val |= OV5670_TEST_PATTERN_ENABLE; + else + val &= ~OV5670_TEST_PATTERN_ENABLE; + + return ov5670_write_reg(ov5670, OV5670_REG_TEST_PATTERN, + OV5670_REG_VALUE_08BIT, val); +} + +/* Initialize control handlers */ +static int ov5670_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov5670 *ov5670 = container_of(ctrl->handler, + struct ov5670, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); + s64 max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = ov5670->cur_mode->height + ctrl->val - 8; + __v4l2_ctrl_modify_range(ov5670->exposure, + ov5670->exposure->minimum, max, + ov5670->exposure->step, max); + break; + } + + /* V4L2 controls values will be applied only when power is already up */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ov5670_write_reg(ov5670, OV5670_REG_ANALOG_GAIN, + OV5670_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = ov5670_update_digital_gain(ov5670, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + /* 4 least significant bits of expsoure are fractional part */ + ret = ov5670_write_reg(ov5670, OV5670_REG_EXPOSURE, + OV5670_REG_VALUE_24BIT, ctrl->val << 4); + break; + case V4L2_CID_VBLANK: + /* Update VTS that meets expected vertical blanking */ + ret = ov5670_write_reg(ov5670, OV5670_REG_VTS, + OV5670_REG_VALUE_16BIT, + ov5670->cur_mode->height + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov5670_enable_test_pattern(ov5670, ctrl->val); + break; + default: + dev_info(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", + __func__, ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov5670_ctrl_ops = { + .s_ctrl = ov5670_set_ctrl, +}; + +/* Initialize control handlers */ +static int ov5670_init_controls(struct ov5670 *ov5670) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 vblank_max; + s64 vblank_def; + s64 vblank_min; + s64 exposure_max; + int ret; + + ctrl_hdlr = &ov5670->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + ctrl_hdlr->lock = &ov5670->mutex; + ov5670->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, + &ov5670_ctrl_ops, + V4L2_CID_LINK_FREQ, + 0, 0, link_freq_menu_items); + if (ov5670->link_freq) + ov5670->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* By default, V4L2_CID_PIXEL_RATE is read only */ + ov5670->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov5670_ctrl_ops, + V4L2_CID_PIXEL_RATE, + link_freq_configs[0].pixel_rate, + link_freq_configs[0].pixel_rate, + 1, + link_freq_configs[0].pixel_rate); + + vblank_max = OV5670_VTS_MAX - ov5670->cur_mode->height; + vblank_def = ov5670->cur_mode->vts_def - ov5670->cur_mode->height; + vblank_min = ov5670->cur_mode->vts_min - ov5670->cur_mode->height; + ov5670->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov5670_ctrl_ops, + V4L2_CID_VBLANK, vblank_min, + vblank_max, 1, vblank_def); + + ov5670->hblank = v4l2_ctrl_new_std( + ctrl_hdlr, &ov5670_ctrl_ops, V4L2_CID_HBLANK, + OV5670_FIXED_PPL - ov5670->cur_mode->width, + OV5670_FIXED_PPL - ov5670->cur_mode->width, 1, + OV5670_FIXED_PPL - ov5670->cur_mode->width); + if (ov5670->hblank) + ov5670->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* Get min, max, step, default from sensor */ + v4l2_ctrl_new_std(ctrl_hdlr, &ov5670_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + ANALOG_GAIN_MIN, ANALOG_GAIN_MAX, ANALOG_GAIN_STEP, + ANALOG_GAIN_DEFAULT); + + /* Digital gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &ov5670_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV5670_DGTL_GAIN_MIN, OV5670_DGTL_GAIN_MAX, + OV5670_DGTL_GAIN_STEP, OV5670_DGTL_GAIN_DEFAULT); + + /* Get min, max, step, default from sensor */ + exposure_max = ov5670->cur_mode->vts_def - 8; + ov5670->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov5670_ctrl_ops, + V4L2_CID_EXPOSURE, + OV5670_EXPOSURE_MIN, + exposure_max, OV5670_EXPOSURE_STEP, + exposure_max); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov5670_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov5670_test_pattern_menu) - 1, + 0, 0, ov5670_test_pattern_menu); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov5670_ctrl_ops, + &props); + if (ret) + goto error; + + ov5670->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int ov5670_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Only one bayer order GRBG is supported */ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int ov5670_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void ov5670_update_pad_format(const struct ov5670_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int ov5670_do_get_pad_format(struct ov5670 *ov5670, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(&ov5670->sd, cfg, + fmt->pad); + else + ov5670_update_pad_format(ov5670->cur_mode, fmt); + + return 0; +} + +static int ov5670_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov5670 *ov5670 = to_ov5670(sd); + int ret; + + mutex_lock(&ov5670->mutex); + ret = ov5670_do_get_pad_format(ov5670, cfg, fmt); + mutex_unlock(&ov5670->mutex); + + return ret; +} + +static int ov5670_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov5670 *ov5670 = to_ov5670(sd); + const struct ov5670_mode *mode; + s32 vblank_def; + s32 h_blank; + + mutex_lock(&ov5670->mutex); + + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + ov5670_update_pad_format(mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; + } else { + ov5670->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ov5670->link_freq, mode->link_freq_index); + __v4l2_ctrl_s_ctrl_int64( + ov5670->pixel_rate, + link_freq_configs[mode->link_freq_index].pixel_rate); + /* Update limits and set FPS to default */ + vblank_def = ov5670->cur_mode->vts_def - + ov5670->cur_mode->height; + __v4l2_ctrl_modify_range( + ov5670->vblank, + ov5670->cur_mode->vts_min - ov5670->cur_mode->height, + OV5670_VTS_MAX - ov5670->cur_mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ov5670->vblank, vblank_def); + h_blank = OV5670_FIXED_PPL - ov5670->cur_mode->width; + __v4l2_ctrl_modify_range(ov5670->hblank, h_blank, h_blank, 1, + h_blank); + } + + mutex_unlock(&ov5670->mutex); + + return 0; +} + +static int ov5670_get_skip_frames(struct v4l2_subdev *sd, u32 *frames) +{ + *frames = OV5670_NUM_OF_SKIP_FRAMES; + + return 0; +} + +/* Prepare streaming by writing default values and customized values */ +static int ov5670_start_streaming(struct ov5670 *ov5670) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); + const struct ov5670_reg_list *reg_list; + int link_freq_index; + int ret; + + /* Get out of from software reset */ + ret = ov5670_write_reg(ov5670, OV5670_REG_SOFTWARE_RST, + OV5670_REG_VALUE_08BIT, OV5670_SOFTWARE_RST); + if (ret) { + dev_err(&client->dev, "%s failed to set powerup registers\n", + __func__); + return ret; + } + + /* Setup PLL */ + link_freq_index = ov5670->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = ov5670_write_reg_list(ov5670, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set plls\n", __func__); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &ov5670->cur_mode->reg_list; + ret = ov5670_write_reg_list(ov5670, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + ret = __v4l2_ctrl_handler_setup(ov5670->sd.ctrl_handler); + if (ret) + return ret; + + /* Write stream on list */ + ret = ov5670_write_reg(ov5670, OV5670_REG_MODE_SELECT, + OV5670_REG_VALUE_08BIT, OV5670_MODE_STREAMING); + if (ret) { + dev_err(&client->dev, "%s failed to set stream\n", __func__); + return ret; + } + + return 0; +} + +static int ov5670_stop_streaming(struct ov5670 *ov5670) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); + int ret; + + ret = ov5670_write_reg(ov5670, OV5670_REG_MODE_SELECT, + OV5670_REG_VALUE_08BIT, OV5670_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); + + /* Return success even if it was an error, as there is nothing the + * caller can do about it. + */ + return 0; +} + +static int ov5670_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov5670 *ov5670 = to_ov5670(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&ov5670->mutex); + if (ov5670->streaming == enable) + goto unlock_and_return; + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + + ret = ov5670_start_streaming(ov5670); + if (ret) + goto error; + } else { + ret = ov5670_stop_streaming(ov5670); + pm_runtime_put(&client->dev); + } + ov5670->streaming = enable; + goto unlock_and_return; + +error: + pm_runtime_put(&client->dev); + +unlock_and_return: + mutex_unlock(&ov5670->mutex); + + return ret; +} + +static int __maybe_unused ov5670_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5670 *ov5670 = to_ov5670(sd); + + if (ov5670->streaming) + ov5670_stop_streaming(ov5670); + + return 0; +} + +static int __maybe_unused ov5670_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5670 *ov5670 = to_ov5670(sd); + int ret; + + if (ov5670->streaming) { + ret = ov5670_start_streaming(ov5670); + if (ret) { + ov5670_stop_streaming(ov5670); + return ret; + } + } + + return 0; +} + +/* Verify chip ID */ +static int ov5670_identify_module(struct ov5670 *ov5670) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); + int ret; + u32 val; + + ret = ov5670_read_reg(ov5670, OV5670_REG_CHIP_ID, + OV5670_REG_VALUE_24BIT, &val); + if (ret) + return ret; + + if (val != OV5670_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + OV5670_CHIP_ID, val); + return -ENXIO; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops ov5670_video_ops = { + .s_stream = ov5670_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ov5670_pad_ops = { + .enum_mbus_code = ov5670_enum_mbus_code, + .get_fmt = ov5670_get_pad_format, + .set_fmt = ov5670_set_pad_format, + .enum_frame_size = ov5670_enum_frame_size, +}; + +static const struct v4l2_subdev_sensor_ops ov5670_sensor_ops = { + .g_skip_frames = ov5670_get_skip_frames, +}; + +static const struct v4l2_subdev_ops ov5670_subdev_ops = { + .video = &ov5670_video_ops, + .pad = &ov5670_pad_ops, + .sensor = &ov5670_sensor_ops, +}; + +static const struct media_entity_operations ov5670_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov5670_internal_ops = { + .open = ov5670_open, +}; + +static int ov5670_probe(struct i2c_client *client) +{ + struct ov5670 *ov5670; + const char *err_msg; + u32 input_clk = 0; + int ret; + + device_property_read_u32(&client->dev, "clock-frequency", &input_clk); + if (input_clk != 19200000) + return -EINVAL; + + ov5670 = devm_kzalloc(&client->dev, sizeof(*ov5670), GFP_KERNEL); + if (!ov5670) { + ret = -ENOMEM; + err_msg = "devm_kzalloc() error"; + goto error_print; + } + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&ov5670->sd, client, &ov5670_subdev_ops); + + /* Check module identity */ + ret = ov5670_identify_module(ov5670); + if (ret) { + err_msg = "ov5670_identify_module() error"; + goto error_print; + } + + mutex_init(&ov5670->mutex); + + /* Set default mode to max resolution */ + ov5670->cur_mode = &supported_modes[0]; + + ret = ov5670_init_controls(ov5670); + if (ret) { + err_msg = "ov5670_init_controls() error"; + goto error_mutex_destroy; + } + + ov5670->sd.internal_ops = &ov5670_internal_ops; + ov5670->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov5670->sd.entity.ops = &ov5670_subdev_entity_ops; + ov5670->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Source pad initialization */ + ov5670->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov5670->sd.entity, 1, &ov5670->pad); + if (ret) { + err_msg = "media_entity_pads_init() error"; + goto error_handler_free; + } + + /* Async register for subdev */ + ret = v4l2_async_register_subdev_sensor_common(&ov5670->sd); + if (ret < 0) { + err_msg = "v4l2_async_register_subdev() error"; + goto error_entity_cleanup; + } + + ov5670->streaming = false; + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error_entity_cleanup: + media_entity_cleanup(&ov5670->sd.entity); + +error_handler_free: + v4l2_ctrl_handler_free(ov5670->sd.ctrl_handler); + +error_mutex_destroy: + mutex_destroy(&ov5670->mutex); + +error_print: + dev_err(&client->dev, "%s: %s %d\n", __func__, err_msg, ret); + + return ret; +} + +static int ov5670_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5670 *ov5670 = to_ov5670(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + mutex_destroy(&ov5670->mutex); + + pm_runtime_disable(&client->dev); + + return 0; +} + +static const struct dev_pm_ops ov5670_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov5670_suspend, ov5670_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ov5670_acpi_ids[] = { + {"INT3479"}, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(acpi, ov5670_acpi_ids); +#endif + +static struct i2c_driver ov5670_i2c_driver = { + .driver = { + .name = "ov5670", + .pm = &ov5670_pm_ops, + .acpi_match_table = ACPI_PTR(ov5670_acpi_ids), + }, + .probe_new = ov5670_probe, + .remove = ov5670_remove, +}; + +module_i2c_driver(ov5670_i2c_driver); + +MODULE_AUTHOR("Rapolu, Chiranjeevi <chiranjeevi.rapolu@intel.com>"); +MODULE_AUTHOR("Yang, Hyungwoo <hyungwoo.yang@intel.com>"); +MODULE_DESCRIPTION("Omnivision ov5670 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov5675.c b/drivers/media/i2c/ov5675.c new file mode 100644 index 000000000..aa35a9546 --- /dev/null +++ b/drivers/media/i2c/ov5675.c @@ -0,0 +1,1254 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Intel Corporation. + +#include <asm/unaligned.h> +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define OV5675_REG_VALUE_08BIT 1 +#define OV5675_REG_VALUE_16BIT 2 +#define OV5675_REG_VALUE_24BIT 3 + +#define OV5675_LINK_FREQ_450MHZ 450000000ULL +#define OV5675_SCLK 90000000LL +#define OV5675_MCLK 19200000 +#define OV5675_DATA_LANES 2 +#define OV5675_RGB_DEPTH 10 + +#define OV5675_REG_CHIP_ID 0x300a +#define OV5675_CHIP_ID 0x5675 + +#define OV5675_REG_MODE_SELECT 0x0100 +#define OV5675_MODE_STANDBY 0x00 +#define OV5675_MODE_STREAMING 0x01 + +/* vertical-timings from sensor */ +#define OV5675_REG_VTS 0x380e +#define OV5675_VTS_30FPS 0x07e4 +#define OV5675_VTS_30FPS_MIN 0x07e4 +#define OV5675_VTS_MAX 0x7fff + +/* horizontal-timings from sensor */ +#define OV5675_REG_HTS 0x380c + +/* Exposure controls from sensor */ +#define OV5675_REG_EXPOSURE 0x3500 +#define OV5675_EXPOSURE_MIN 4 +#define OV5675_EXPOSURE_MAX_MARGIN 4 +#define OV5675_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define OV5675_REG_ANALOG_GAIN 0x3508 +#define OV5675_ANAL_GAIN_MIN 128 +#define OV5675_ANAL_GAIN_MAX 2047 +#define OV5675_ANAL_GAIN_STEP 1 + +/* Digital gain controls from sensor */ +#define OV5675_REG_MWB_R_GAIN 0x5019 +#define OV5675_REG_MWB_G_GAIN 0x501b +#define OV5675_REG_MWB_B_GAIN 0x501d +#define OV5675_DGTL_GAIN_MIN 0 +#define OV5675_DGTL_GAIN_MAX 4095 +#define OV5675_DGTL_GAIN_STEP 1 +#define OV5675_DGTL_GAIN_DEFAULT 1024 + +/* Test Pattern Control */ +#define OV5675_REG_TEST_PATTERN 0x4503 +#define OV5675_TEST_PATTERN_ENABLE BIT(7) +#define OV5675_TEST_PATTERN_BAR_SHIFT 2 + +/* Flip Mirror Controls from sensor */ +#define OV5675_REG_FORMAT1 0x3820 +#define OV5675_REG_FORMAT2 0x373d + +#define to_ov5675(_sd) container_of(_sd, struct ov5675, sd) + +enum { + OV5675_LINK_FREQ_900MBPS, +}; + +struct ov5675_reg { + u16 address; + u8 val; +}; + +struct ov5675_reg_list { + u32 num_of_regs; + const struct ov5675_reg *regs; +}; + +struct ov5675_link_freq_config { + const struct ov5675_reg_list reg_list; +}; + +struct ov5675_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timining size */ + u32 hts; + + /* Default vertical timining size */ + u32 vts_def; + + /* Min vertical timining size */ + u32 vts_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* Sensor register settings for this resolution */ + const struct ov5675_reg_list reg_list; +}; + +static const struct ov5675_reg mipi_data_rate_900mbps[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x0300, 0x04}, + {0x0302, 0x8d}, + {0x0303, 0x00}, + {0x030d, 0x26}, +}; + +static const struct ov5675_reg mode_2592x1944_regs[] = { + {0x3002, 0x21}, + {0x3107, 0x23}, + {0x3501, 0x20}, + {0x3503, 0x0c}, + {0x3508, 0x03}, + {0x3509, 0x00}, + {0x3600, 0x66}, + {0x3602, 0x30}, + {0x3610, 0xa5}, + {0x3612, 0x93}, + {0x3620, 0x80}, + {0x3642, 0x0e}, + {0x3661, 0x00}, + {0x3662, 0x10}, + {0x3664, 0xf3}, + {0x3665, 0x9e}, + {0x3667, 0xa5}, + {0x366e, 0x55}, + {0x366f, 0x55}, + {0x3670, 0x11}, + {0x3671, 0x11}, + {0x3672, 0x11}, + {0x3673, 0x11}, + {0x3714, 0x24}, + {0x371a, 0x3e}, + {0x3733, 0x10}, + {0x3734, 0x00}, + {0x373d, 0x24}, + {0x3764, 0x20}, + {0x3765, 0x20}, + {0x3766, 0x12}, + {0x37a1, 0x14}, + {0x37a8, 0x1c}, + {0x37ab, 0x0f}, + {0x37c2, 0x04}, + {0x37cb, 0x00}, + {0x37cc, 0x00}, + {0x37cd, 0x00}, + {0x37ce, 0x00}, + {0x37d8, 0x02}, + {0x37d9, 0x08}, + {0x37dc, 0x04}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x04}, + {0x3804, 0x0a}, + {0x3805, 0x3f}, + {0x3806, 0x07}, + {0x3807, 0xb3}, + {0x3808, 0x0a}, + {0x3809, 0x20}, + {0x380a, 0x07}, + {0x380b, 0x98}, + {0x380c, 0x02}, + {0x380d, 0xee}, + {0x380e, 0x07}, + {0x380f, 0xe4}, + {0x3811, 0x10}, + {0x3813, 0x0d}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x01}, + {0x3817, 0x01}, + {0x381e, 0x02}, + {0x3820, 0x88}, + {0x3821, 0x01}, + {0x3832, 0x04}, + {0x3c80, 0x01}, + {0x3c82, 0x00}, + {0x3c83, 0xc8}, + {0x3c8c, 0x0f}, + {0x3c8d, 0xa0}, + {0x3c90, 0x07}, + {0x3c91, 0x00}, + {0x3c92, 0x00}, + {0x3c93, 0x00}, + {0x3c94, 0xd0}, + {0x3c95, 0x50}, + {0x3c96, 0x35}, + {0x3c97, 0x00}, + {0x4001, 0xe0}, + {0x4008, 0x02}, + {0x4009, 0x0d}, + {0x400f, 0x80}, + {0x4013, 0x02}, + {0x4040, 0x00}, + {0x4041, 0x07}, + {0x404c, 0x50}, + {0x404e, 0x20}, + {0x4500, 0x06}, + {0x4503, 0x00}, + {0x450a, 0x04}, + {0x4809, 0x04}, + {0x480c, 0x12}, + {0x4819, 0x70}, + {0x4825, 0x32}, + {0x4826, 0x32}, + {0x482a, 0x06}, + {0x4833, 0x08}, + {0x4837, 0x0d}, + {0x5000, 0x77}, + {0x5b00, 0x01}, + {0x5b01, 0x10}, + {0x5b02, 0x01}, + {0x5b03, 0xdb}, + {0x5b05, 0x6c}, + {0x5e10, 0xfc}, + {0x3500, 0x00}, + {0x3501, 0x3E}, + {0x3502, 0x60}, + {0x3503, 0x08}, + {0x3508, 0x04}, + {0x3509, 0x00}, + {0x3832, 0x48}, + {0x5780, 0x3e}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x06}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x4003, 0x40}, + {0x3107, 0x01}, + {0x3c80, 0x08}, + {0x3c83, 0xb1}, + {0x3c8c, 0x10}, + {0x3c8d, 0x00}, + {0x3c90, 0x00}, + {0x3c94, 0x00}, + {0x3c95, 0x00}, + {0x3c96, 0x00}, + {0x37cb, 0x09}, + {0x37cc, 0x15}, + {0x37cd, 0x1f}, + {0x37ce, 0x1f}, +}; + +static const struct ov5675_reg mode_1296x972_regs[] = { + {0x3002, 0x21}, + {0x3107, 0x23}, + {0x3501, 0x20}, + {0x3503, 0x0c}, + {0x3508, 0x03}, + {0x3509, 0x00}, + {0x3600, 0x66}, + {0x3602, 0x30}, + {0x3610, 0xa5}, + {0x3612, 0x93}, + {0x3620, 0x80}, + {0x3642, 0x0e}, + {0x3661, 0x00}, + {0x3662, 0x08}, + {0x3664, 0xf3}, + {0x3665, 0x9e}, + {0x3667, 0xa5}, + {0x366e, 0x55}, + {0x366f, 0x55}, + {0x3670, 0x11}, + {0x3671, 0x11}, + {0x3672, 0x11}, + {0x3673, 0x11}, + {0x3714, 0x28}, + {0x371a, 0x3e}, + {0x3733, 0x10}, + {0x3734, 0x00}, + {0x373d, 0x24}, + {0x3764, 0x20}, + {0x3765, 0x20}, + {0x3766, 0x12}, + {0x37a1, 0x14}, + {0x37a8, 0x1c}, + {0x37ab, 0x0f}, + {0x37c2, 0x14}, + {0x37cb, 0x00}, + {0x37cc, 0x00}, + {0x37cd, 0x00}, + {0x37ce, 0x00}, + {0x37d8, 0x02}, + {0x37d9, 0x04}, + {0x37dc, 0x04}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x3f}, + {0x3806, 0x07}, + {0x3807, 0xb7}, + {0x3808, 0x05}, + {0x3809, 0x10}, + {0x380a, 0x03}, + {0x380b, 0xcc}, + {0x380c, 0x02}, + {0x380d, 0xee}, + {0x380e, 0x07}, + {0x380f, 0xd0}, + {0x3811, 0x08}, + {0x3813, 0x0d}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x03}, + {0x3817, 0x01}, + {0x381e, 0x02}, + {0x3820, 0x8b}, + {0x3821, 0x01}, + {0x3832, 0x04}, + {0x3c80, 0x01}, + {0x3c82, 0x00}, + {0x3c83, 0xc8}, + {0x3c8c, 0x0f}, + {0x3c8d, 0xa0}, + {0x3c90, 0x07}, + {0x3c91, 0x00}, + {0x3c92, 0x00}, + {0x3c93, 0x00}, + {0x3c94, 0xd0}, + {0x3c95, 0x50}, + {0x3c96, 0x35}, + {0x3c97, 0x00}, + {0x4001, 0xe0}, + {0x4008, 0x00}, + {0x4009, 0x07}, + {0x400f, 0x80}, + {0x4013, 0x02}, + {0x4040, 0x00}, + {0x4041, 0x03}, + {0x404c, 0x50}, + {0x404e, 0x20}, + {0x4500, 0x06}, + {0x4503, 0x00}, + {0x450a, 0x04}, + {0x4809, 0x04}, + {0x480c, 0x12}, + {0x4819, 0x70}, + {0x4825, 0x32}, + {0x4826, 0x32}, + {0x482a, 0x06}, + {0x4833, 0x08}, + {0x4837, 0x0d}, + {0x5000, 0x77}, + {0x5b00, 0x01}, + {0x5b01, 0x10}, + {0x5b02, 0x01}, + {0x5b03, 0xdb}, + {0x5b05, 0x6c}, + {0x5e10, 0xfc}, + {0x3500, 0x00}, + {0x3501, 0x1F}, + {0x3502, 0x20}, + {0x3503, 0x08}, + {0x3508, 0x04}, + {0x3509, 0x00}, + {0x3832, 0x48}, + {0x5780, 0x3e}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x06}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x4003, 0x40}, + {0x3107, 0x01}, + {0x3c80, 0x08}, + {0x3c83, 0xb1}, + {0x3c8c, 0x10}, + {0x3c8d, 0x00}, + {0x3c90, 0x00}, + {0x3c94, 0x00}, + {0x3c95, 0x00}, + {0x3c96, 0x00}, + {0x37cb, 0x09}, + {0x37cc, 0x15}, + {0x37cd, 0x1f}, + {0x37ce, 0x1f}, +}; + +static const char * const ov5675_test_pattern_menu[] = { + "Disabled", + "Standard Color Bar", + "Top-Bottom Darker Color Bar", + "Right-Left Darker Color Bar", + "Bottom-Top Darker Color Bar" +}; + +static const s64 link_freq_menu_items[] = { + OV5675_LINK_FREQ_450MHZ, +}; + +static const struct ov5675_link_freq_config link_freq_configs[] = { + [OV5675_LINK_FREQ_900MBPS] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_900mbps), + .regs = mipi_data_rate_900mbps, + } + } +}; + +static const struct ov5675_mode supported_modes[] = { + { + .width = 2592, + .height = 1944, + .hts = 1500, + .vts_def = OV5675_VTS_30FPS, + .vts_min = OV5675_VTS_30FPS_MIN, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2592x1944_regs), + .regs = mode_2592x1944_regs, + }, + .link_freq_index = OV5675_LINK_FREQ_900MBPS, + }, + { + .width = 1296, + .height = 972, + .hts = 1500, + .vts_def = OV5675_VTS_30FPS, + .vts_min = OV5675_VTS_30FPS_MIN, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x972_regs), + .regs = mode_1296x972_regs, + }, + .link_freq_index = OV5675_LINK_FREQ_900MBPS, + } +}; + +struct ov5675 { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct ov5675_mode *cur_mode; + + /* To serialize asynchronus callbacks */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static u64 to_pixel_rate(u32 f_index) +{ + u64 pixel_rate = link_freq_menu_items[f_index] * 2 * OV5675_DATA_LANES; + + do_div(pixel_rate, OV5675_RGB_DEPTH); + + return pixel_rate; +} + +static u64 to_pixels_per_line(u32 hts, u32 f_index) +{ + u64 ppl = hts * to_pixel_rate(f_index); + + do_div(ppl, OV5675_SCLK); + + return ppl; +} + +static int ov5675_read_reg(struct ov5675 *ov5675, u16 reg, u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5675->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = {0}; + int ret; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +static int ov5675_write_reg(struct ov5675 *ov5675, u16 reg, u16 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5675->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << 8 * (4 - len), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int ov5675_write_reg_list(struct ov5675 *ov5675, + const struct ov5675_reg_list *r_list) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5675->sd); + unsigned int i; + int ret; + + for (i = 0; i < r_list->num_of_regs; i++) { + ret = ov5675_write_reg(ov5675, r_list->regs[i].address, 1, + r_list->regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "failed to write reg 0x%4.4x. error = %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int ov5675_update_digital_gain(struct ov5675 *ov5675, u32 d_gain) +{ + int ret; + + ret = ov5675_write_reg(ov5675, OV5675_REG_MWB_R_GAIN, + OV5675_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = ov5675_write_reg(ov5675, OV5675_REG_MWB_G_GAIN, + OV5675_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + return ov5675_write_reg(ov5675, OV5675_REG_MWB_B_GAIN, + OV5675_REG_VALUE_16BIT, d_gain); +} + +static int ov5675_test_pattern(struct ov5675 *ov5675, u32 pattern) +{ + if (pattern) + pattern = (pattern - 1) << OV5675_TEST_PATTERN_BAR_SHIFT | + OV5675_TEST_PATTERN_ENABLE; + + return ov5675_write_reg(ov5675, OV5675_REG_TEST_PATTERN, + OV5675_REG_VALUE_08BIT, pattern); +} + +/* + * OV5675 supports keeping the pixel order by mirror and flip function + * The Bayer order isn't affected by the flip controls + */ +static int ov5675_set_ctrl_hflip(struct ov5675 *ov5675, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = ov5675_read_reg(ov5675, OV5675_REG_FORMAT1, + OV5675_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + return ov5675_write_reg(ov5675, OV5675_REG_FORMAT1, + OV5675_REG_VALUE_08BIT, + ctrl_val ? val & ~BIT(3) : val); +} + +static int ov5675_set_ctrl_vflip(struct ov5675 *ov5675, u8 ctrl_val) +{ + int ret; + u32 val; + + ret = ov5675_read_reg(ov5675, OV5675_REG_FORMAT1, + OV5675_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + ret = ov5675_write_reg(ov5675, OV5675_REG_FORMAT1, + OV5675_REG_VALUE_08BIT, + ctrl_val ? val | BIT(4) | BIT(5) : val); + + if (ret) + return ret; + + ret = ov5675_read_reg(ov5675, OV5675_REG_FORMAT2, + OV5675_REG_VALUE_08BIT, &val); + + if (ret) + return ret; + + return ov5675_write_reg(ov5675, OV5675_REG_FORMAT2, + OV5675_REG_VALUE_08BIT, + ctrl_val ? val | BIT(1) : val); +} + +static int ov5675_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov5675 *ov5675 = container_of(ctrl->handler, + struct ov5675, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov5675->sd); + s64 exposure_max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = ov5675->cur_mode->height + ctrl->val - + OV5675_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(ov5675->exposure, + ov5675->exposure->minimum, + exposure_max, ov5675->exposure->step, + exposure_max); + } + + /* V4L2 controls values will be applied only when power is already up */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ov5675_write_reg(ov5675, OV5675_REG_ANALOG_GAIN, + OV5675_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_DIGITAL_GAIN: + ret = ov5675_update_digital_gain(ov5675, ctrl->val); + break; + + case V4L2_CID_EXPOSURE: + /* 4 least significant bits of expsoure are fractional part + * val = val << 4 + * for ov5675, the unit of exposure is differnt from other + * OmniVision sensors, its exposure value is twice of the + * register value, the exposure should be divided by 2 before + * set register, e.g. val << 3. + */ + ret = ov5675_write_reg(ov5675, OV5675_REG_EXPOSURE, + OV5675_REG_VALUE_24BIT, ctrl->val << 3); + break; + + case V4L2_CID_VBLANK: + ret = ov5675_write_reg(ov5675, OV5675_REG_VTS, + OV5675_REG_VALUE_16BIT, + ov5675->cur_mode->height + ctrl->val + + 10); + break; + + case V4L2_CID_TEST_PATTERN: + ret = ov5675_test_pattern(ov5675, ctrl->val); + break; + + case V4L2_CID_HFLIP: + ov5675_set_ctrl_hflip(ov5675, ctrl->val); + break; + + case V4L2_CID_VFLIP: + ov5675_set_ctrl_vflip(ov5675, ctrl->val); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov5675_ctrl_ops = { + .s_ctrl = ov5675_set_ctrl, +}; + +static int ov5675_init_controls(struct ov5675 *ov5675) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max, h_blank; + int ret; + + ctrl_hdlr = &ov5675->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + ctrl_hdlr->lock = &ov5675->mutex; + ov5675->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &ov5675_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + if (ov5675->link_freq) + ov5675->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ov5675->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov5675_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + to_pixel_rate(OV5675_LINK_FREQ_900MBPS), + 1, + to_pixel_rate(OV5675_LINK_FREQ_900MBPS)); + ov5675->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov5675_ctrl_ops, + V4L2_CID_VBLANK, + ov5675->cur_mode->vts_min - ov5675->cur_mode->height, + OV5675_VTS_MAX - ov5675->cur_mode->height, 1, + ov5675->cur_mode->vts_def - ov5675->cur_mode->height); + h_blank = to_pixels_per_line(ov5675->cur_mode->hts, + ov5675->cur_mode->link_freq_index) - ov5675->cur_mode->width; + ov5675->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov5675_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (ov5675->hblank) + ov5675->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &ov5675_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV5675_ANAL_GAIN_MIN, OV5675_ANAL_GAIN_MAX, + OV5675_ANAL_GAIN_STEP, OV5675_ANAL_GAIN_MIN); + v4l2_ctrl_new_std(ctrl_hdlr, &ov5675_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV5675_DGTL_GAIN_MIN, OV5675_DGTL_GAIN_MAX, + OV5675_DGTL_GAIN_STEP, OV5675_DGTL_GAIN_DEFAULT); + exposure_max = (ov5675->cur_mode->vts_def - OV5675_EXPOSURE_MAX_MARGIN); + ov5675->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov5675_ctrl_ops, + V4L2_CID_EXPOSURE, + OV5675_EXPOSURE_MIN, exposure_max, + OV5675_EXPOSURE_STEP, + exposure_max); + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov5675_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov5675_test_pattern_menu) - 1, + 0, 0, ov5675_test_pattern_menu); + v4l2_ctrl_new_std(ctrl_hdlr, &ov5675_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(ctrl_hdlr, &ov5675_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (ctrl_hdlr->error) { + v4l2_ctrl_handler_free(ctrl_hdlr); + return ctrl_hdlr->error; + } + + ov5675->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static void ov5675_update_pad_format(const struct ov5675_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->field = V4L2_FIELD_NONE; +} + +static int ov5675_start_streaming(struct ov5675 *ov5675) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5675->sd); + const struct ov5675_reg_list *reg_list; + int link_freq_index, ret; + + link_freq_index = ov5675->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = ov5675_write_reg_list(ov5675, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set plls"); + return ret; + } + + reg_list = &ov5675->cur_mode->reg_list; + ret = ov5675_write_reg_list(ov5675, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set mode"); + return ret; + } + + ret = __v4l2_ctrl_handler_setup(ov5675->sd.ctrl_handler); + if (ret) + return ret; + + ret = ov5675_write_reg(ov5675, OV5675_REG_MODE_SELECT, + OV5675_REG_VALUE_08BIT, OV5675_MODE_STREAMING); + if (ret) { + dev_err(&client->dev, "failed to set stream"); + return ret; + } + + return 0; +} + +static void ov5675_stop_streaming(struct ov5675 *ov5675) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5675->sd); + + if (ov5675_write_reg(ov5675, OV5675_REG_MODE_SELECT, + OV5675_REG_VALUE_08BIT, OV5675_MODE_STANDBY)) + dev_err(&client->dev, "failed to set stream"); +} + +static int ov5675_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov5675 *ov5675 = to_ov5675(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + if (ov5675->streaming == enable) + return 0; + + mutex_lock(&ov5675->mutex); + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + mutex_unlock(&ov5675->mutex); + return ret; + } + + ret = ov5675_start_streaming(ov5675); + if (ret) { + enable = 0; + ov5675_stop_streaming(ov5675); + pm_runtime_put(&client->dev); + } + } else { + ov5675_stop_streaming(ov5675); + pm_runtime_put(&client->dev); + } + + ov5675->streaming = enable; + mutex_unlock(&ov5675->mutex); + + return ret; +} + +static int __maybe_unused ov5675_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5675 *ov5675 = to_ov5675(sd); + + mutex_lock(&ov5675->mutex); + if (ov5675->streaming) + ov5675_stop_streaming(ov5675); + + mutex_unlock(&ov5675->mutex); + + return 0; +} + +static int __maybe_unused ov5675_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5675 *ov5675 = to_ov5675(sd); + int ret; + + mutex_lock(&ov5675->mutex); + if (ov5675->streaming) { + ret = ov5675_start_streaming(ov5675); + if (ret) { + ov5675->streaming = false; + ov5675_stop_streaming(ov5675); + mutex_unlock(&ov5675->mutex); + return ret; + } + } + + mutex_unlock(&ov5675->mutex); + + return 0; +} + +static int ov5675_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov5675 *ov5675 = to_ov5675(sd); + const struct ov5675_mode *mode; + s32 vblank_def, h_blank; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, + height, fmt->format.width, + fmt->format.height); + + mutex_lock(&ov5675->mutex); + ov5675_update_pad_format(mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; + } else { + ov5675->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ov5675->link_freq, mode->link_freq_index); + __v4l2_ctrl_s_ctrl_int64(ov5675->pixel_rate, + to_pixel_rate(mode->link_freq_index)); + + /* Update limits and set FPS to default */ + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(ov5675->vblank, + mode->vts_min - mode->height, + OV5675_VTS_MAX - mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ov5675->vblank, vblank_def); + h_blank = to_pixels_per_line(mode->hts, mode->link_freq_index) - + mode->width; + __v4l2_ctrl_modify_range(ov5675->hblank, h_blank, h_blank, 1, + h_blank); + } + + mutex_unlock(&ov5675->mutex); + + return 0; +} + +static int ov5675_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov5675 *ov5675 = to_ov5675(sd); + + mutex_lock(&ov5675->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(&ov5675->sd, cfg, + fmt->pad); + else + ov5675_update_pad_format(ov5675->cur_mode, &fmt->format); + + mutex_unlock(&ov5675->mutex); + + return 0; +} + +static int ov5675_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int ov5675_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int ov5675_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov5675 *ov5675 = to_ov5675(sd); + + mutex_lock(&ov5675->mutex); + ov5675_update_pad_format(&supported_modes[0], + v4l2_subdev_get_try_format(sd, fh->pad, 0)); + mutex_unlock(&ov5675->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops ov5675_video_ops = { + .s_stream = ov5675_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ov5675_pad_ops = { + .set_fmt = ov5675_set_format, + .get_fmt = ov5675_get_format, + .enum_mbus_code = ov5675_enum_mbus_code, + .enum_frame_size = ov5675_enum_frame_size, +}; + +static const struct v4l2_subdev_ops ov5675_subdev_ops = { + .video = &ov5675_video_ops, + .pad = &ov5675_pad_ops, +}; + +static const struct media_entity_operations ov5675_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov5675_internal_ops = { + .open = ov5675_open, +}; + +static int ov5675_identify_module(struct ov5675 *ov5675) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5675->sd); + int ret; + u32 val; + + ret = ov5675_read_reg(ov5675, OV5675_REG_CHIP_ID, + OV5675_REG_VALUE_24BIT, &val); + if (ret) + return ret; + + if (val != OV5675_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x", + OV5675_CHIP_ID, val); + return -ENXIO; + } + + return 0; +} + +static int ov5675_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + u32 mclk; + int ret; + unsigned int i, j; + + if (!fwnode) + return -ENXIO; + + ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk); + + if (ret) { + dev_err(dev, "can't get clock frequency"); + return ret; + } + + if (mclk != OV5675_MCLK) { + dev_err(dev, "external clock %d is not supported", mclk); + return -EINVAL; + } + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -ENXIO; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV5675_DATA_LANES) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto check_hwcfg_error; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + ret = -EINVAL; + goto check_hwcfg_error; + } + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) { + if (link_freq_menu_items[i] == + bus_cfg.link_frequencies[j]) + break; + } + + if (j == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequency %lld supported", + link_freq_menu_items[i]); + ret = -EINVAL; + goto check_hwcfg_error; + } + } + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int ov5675_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5675 *ov5675 = to_ov5675(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(&client->dev); + mutex_destroy(&ov5675->mutex); + + return 0; +} + +static int ov5675_probe(struct i2c_client *client) +{ + struct ov5675 *ov5675; + int ret; + + ret = ov5675_check_hwcfg(&client->dev); + if (ret) { + dev_err(&client->dev, "failed to check HW configuration: %d", + ret); + return ret; + } + + ov5675 = devm_kzalloc(&client->dev, sizeof(*ov5675), GFP_KERNEL); + if (!ov5675) + return -ENOMEM; + + v4l2_i2c_subdev_init(&ov5675->sd, client, &ov5675_subdev_ops); + ret = ov5675_identify_module(ov5675); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d", ret); + return ret; + } + + mutex_init(&ov5675->mutex); + ov5675->cur_mode = &supported_modes[0]; + ret = ov5675_init_controls(ov5675); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ov5675->sd.internal_ops = &ov5675_internal_ops; + ov5675->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov5675->sd.entity.ops = &ov5675_subdev_entity_ops; + ov5675->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ov5675->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov5675->sd.entity, 1, &ov5675->pad); + if (ret) { + dev_err(&client->dev, "failed to init entity pads: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = v4l2_async_register_subdev_sensor_common(&ov5675->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %d", + ret); + goto probe_error_media_entity_cleanup; + } + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +probe_error_media_entity_cleanup: + media_entity_cleanup(&ov5675->sd.entity); + +probe_error_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(ov5675->sd.ctrl_handler); + mutex_destroy(&ov5675->mutex); + + return ret; +} + +static const struct dev_pm_ops ov5675_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov5675_suspend, ov5675_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ov5675_acpi_ids[] = { + {"OVTI5675"}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, ov5675_acpi_ids); +#endif + +static struct i2c_driver ov5675_i2c_driver = { + .driver = { + .name = "ov5675", + .pm = &ov5675_pm_ops, + .acpi_match_table = ACPI_PTR(ov5675_acpi_ids), + }, + .probe_new = ov5675_probe, + .remove = ov5675_remove, +}; + +module_i2c_driver(ov5675_i2c_driver); + +MODULE_AUTHOR("Shawn Tu <shawnx.tu@intel.com>"); +MODULE_DESCRIPTION("OmniVision OV5675 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov5695.c b/drivers/media/i2c/ov5695.c new file mode 100644 index 000000000..cc678d9d2 --- /dev/null +++ b/drivers/media/i2c/ov5695.c @@ -0,0 +1,1408 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ov5695 driver + * + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/sysfs.h> +#include <media/media-entity.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#ifndef V4L2_CID_DIGITAL_GAIN +#define V4L2_CID_DIGITAL_GAIN V4L2_CID_GAIN +#endif + +/* 45Mhz * 4 Binning */ +#define OV5695_PIXEL_RATE (45 * 1000 * 1000 * 4) +#define OV5695_XVCLK_FREQ 24000000 + +#define CHIP_ID 0x005695 +#define OV5695_REG_CHIP_ID 0x300a + +#define OV5695_REG_CTRL_MODE 0x0100 +#define OV5695_MODE_SW_STANDBY 0x0 +#define OV5695_MODE_STREAMING BIT(0) + +#define OV5695_REG_EXPOSURE 0x3500 +#define OV5695_EXPOSURE_MIN 4 +#define OV5695_EXPOSURE_STEP 1 +#define OV5695_VTS_MAX 0x7fff + +#define OV5695_REG_ANALOG_GAIN 0x3509 +#define ANALOG_GAIN_MIN 0x10 +#define ANALOG_GAIN_MAX 0xf8 +#define ANALOG_GAIN_STEP 1 +#define ANALOG_GAIN_DEFAULT 0xf8 + +#define OV5695_REG_DIGI_GAIN_H 0x350a +#define OV5695_REG_DIGI_GAIN_L 0x350b +#define OV5695_DIGI_GAIN_L_MASK 0x3f +#define OV5695_DIGI_GAIN_H_SHIFT 6 +#define OV5695_DIGI_GAIN_MIN 0 +#define OV5695_DIGI_GAIN_MAX (0x4000 - 1) +#define OV5695_DIGI_GAIN_STEP 1 +#define OV5695_DIGI_GAIN_DEFAULT 1024 + +#define OV5695_REG_TEST_PATTERN 0x4503 +#define OV5695_TEST_PATTERN_ENABLE 0x80 +#define OV5695_TEST_PATTERN_DISABLE 0x0 + +#define OV5695_REG_VTS 0x380e + +#define REG_NULL 0xFFFF + +#define OV5695_REG_VALUE_08BIT 1 +#define OV5695_REG_VALUE_16BIT 2 +#define OV5695_REG_VALUE_24BIT 3 + +#define OV5695_LANES 2 +#define OV5695_BITS_PER_SAMPLE 10 + +static const char * const ov5695_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +#define OV5695_NUM_SUPPLIES ARRAY_SIZE(ov5695_supply_names) + +struct regval { + u16 addr; + u8 val; +}; + +struct ov5695_mode { + u32 width; + u32 height; + u32 max_fps; + u32 hts_def; + u32 vts_def; + u32 exp_def; + const struct regval *reg_list; +}; + +struct ov5695 { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[OV5695_NUM_SUPPLIES]; + + struct v4l2_subdev subdev; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *anal_gain; + struct v4l2_ctrl *digi_gain; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *test_pattern; + struct mutex mutex; + bool streaming; + const struct ov5695_mode *cur_mode; +}; + +#define to_ov5695(sd) container_of(sd, struct ov5695, subdev) + +/* + * Xclk 24Mhz + * Pclk 45Mhz + * linelength 672(0x2a0) + * framelength 2232(0x8b8) + * grabwindow_width 1296 + * grabwindow_height 972 + * max_framerate 30fps + * mipi_datarate per lane 840Mbps + */ +static const struct regval ov5695_global_regs[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x0300, 0x04}, + {0x0301, 0x00}, + {0x0302, 0x69}, + {0x0303, 0x00}, + {0x0304, 0x00}, + {0x0305, 0x01}, + {0x0307, 0x00}, + {0x030b, 0x00}, + {0x030c, 0x00}, + {0x030d, 0x1e}, + {0x030e, 0x04}, + {0x030f, 0x03}, + {0x0312, 0x01}, + {0x3000, 0x00}, + {0x3002, 0xa1}, + {0x3008, 0x00}, + {0x3010, 0x00}, + {0x3022, 0x51}, + {0x3106, 0x15}, + {0x3107, 0x01}, + {0x3108, 0x05}, + {0x3500, 0x00}, + {0x3501, 0x45}, + {0x3502, 0x00}, + {0x3503, 0x08}, + {0x3504, 0x03}, + {0x3505, 0x8c}, + {0x3507, 0x03}, + {0x3508, 0x00}, + {0x3509, 0x10}, + {0x350c, 0x00}, + {0x350d, 0x80}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3601, 0x55}, + {0x3602, 0x58}, + {0x3614, 0x30}, + {0x3615, 0x77}, + {0x3621, 0x08}, + {0x3624, 0x40}, + {0x3633, 0x0c}, + {0x3634, 0x0c}, + {0x3635, 0x0c}, + {0x3636, 0x0c}, + {0x3638, 0x00}, + {0x3639, 0x00}, + {0x363a, 0x00}, + {0x363b, 0x00}, + {0x363c, 0xff}, + {0x363d, 0xfa}, + {0x3650, 0x44}, + {0x3651, 0x44}, + {0x3652, 0x44}, + {0x3653, 0x44}, + {0x3654, 0x44}, + {0x3655, 0x44}, + {0x3656, 0x44}, + {0x3657, 0x44}, + {0x3660, 0x00}, + {0x3661, 0x00}, + {0x3662, 0x00}, + {0x366a, 0x00}, + {0x366e, 0x0c}, + {0x3673, 0x04}, + {0x3700, 0x14}, + {0x3703, 0x0c}, + {0x3715, 0x01}, + {0x3733, 0x10}, + {0x3734, 0x40}, + {0x373f, 0xa0}, + {0x3765, 0x20}, + {0x37a1, 0x1d}, + {0x37a8, 0x26}, + {0x37ab, 0x14}, + {0x37c2, 0x04}, + {0x37cb, 0x09}, + {0x37cc, 0x13}, + {0x37cd, 0x1f}, + {0x37ce, 0x1f}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x3f}, + {0x3806, 0x07}, + {0x3807, 0xaf}, + {0x3808, 0x05}, + {0x3809, 0x10}, + {0x380a, 0x03}, + {0x380b, 0xcc}, + {0x380c, 0x02}, + {0x380d, 0xa0}, + {0x380e, 0x08}, + {0x380f, 0xb8}, + {0x3810, 0x00}, + {0x3811, 0x06}, + {0x3812, 0x00}, + {0x3813, 0x06}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x03}, + {0x3817, 0x01}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x381a, 0x00}, + {0x381b, 0x01}, + {0x3820, 0x8b}, + {0x3821, 0x01}, + {0x3c80, 0x08}, + {0x3c82, 0x00}, + {0x3c83, 0x00}, + {0x3c88, 0x00}, + {0x3d85, 0x14}, + {0x3f02, 0x08}, + {0x3f03, 0x10}, + {0x4008, 0x02}, + {0x4009, 0x09}, + {0x404e, 0x20}, + {0x4501, 0x00}, + {0x4502, 0x10}, + {0x4800, 0x00}, + {0x481f, 0x2a}, + {0x4837, 0x13}, + {0x5000, 0x17}, + {0x5780, 0x3e}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x06}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x5b00, 0x00}, + {0x5b01, 0x1c}, + {0x5b02, 0x00}, + {0x5b03, 0x7f}, + {0x5b05, 0x6c}, + {0x5e10, 0xfc}, + {0x4010, 0xf1}, + {0x3503, 0x08}, + {0x3505, 0x8c}, + {0x3507, 0x03}, + {0x3508, 0x00}, + {0x3509, 0xf8}, + {REG_NULL, 0x00}, +}; + +/* + * Xclk 24Mhz + * Pclk 45Mhz + * linelength 740(0x2e4) + * framelength 2024(0x7e8) + * grabwindow_width 2592 + * grabwindow_height 1944 + * max_framerate 30fps + * mipi_datarate per lane 840Mbps + */ +static const struct regval ov5695_2592x1944_regs[] = { + {0x3501, 0x7e}, + {0x366e, 0x18}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x04}, + {0x3804, 0x0a}, + {0x3805, 0x3f}, + {0x3806, 0x07}, + {0x3807, 0xab}, + {0x3808, 0x0a}, + {0x3809, 0x20}, + {0x380a, 0x07}, + {0x380b, 0x98}, + {0x380c, 0x02}, + {0x380d, 0xe4}, + {0x380e, 0x07}, + {0x380f, 0xe8}, + {0x3811, 0x06}, + {0x3813, 0x08}, + {0x3814, 0x01}, + {0x3816, 0x01}, + {0x3817, 0x01}, + {0x3820, 0x88}, + {0x3821, 0x00}, + {0x4501, 0x00}, + {0x4008, 0x04}, + {0x4009, 0x13}, + {REG_NULL, 0x00}, +}; + +/* + * Xclk 24Mhz + * Pclk 45Mhz + * linelength 672(0x2a0) + * framelength 2232(0x8b8) + * grabwindow_width 1920 + * grabwindow_height 1080 + * max_framerate 30fps + * mipi_datarate per lane 840Mbps + */ +static const struct regval ov5695_1920x1080_regs[] = { + {0x3501, 0x45}, + {0x366e, 0x18}, + {0x3800, 0x01}, + {0x3801, 0x50}, + {0x3802, 0x01}, + {0x3803, 0xb8}, + {0x3804, 0x08}, + {0x3805, 0xef}, + {0x3806, 0x05}, + {0x3807, 0xf7}, + {0x3808, 0x07}, + {0x3809, 0x80}, + {0x380a, 0x04}, + {0x380b, 0x38}, + {0x380c, 0x02}, + {0x380d, 0xa0}, + {0x380e, 0x08}, + {0x380f, 0xb8}, + {0x3811, 0x06}, + {0x3813, 0x04}, + {0x3814, 0x01}, + {0x3816, 0x01}, + {0x3817, 0x01}, + {0x3820, 0x88}, + {0x3821, 0x00}, + {0x4501, 0x00}, + {0x4008, 0x04}, + {0x4009, 0x13}, + {REG_NULL, 0x00} +}; + +/* + * Xclk 24Mhz + * Pclk 45Mhz + * linelength 740(0x02e4) + * framelength 1012(0x03f4) + * grabwindow_width 1296 + * grabwindow_height 972 + * max_framerate 60fps + * mipi_datarate per lane 840Mbps + */ +static const struct regval ov5695_1296x972_regs[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x0300, 0x04}, + {0x0301, 0x00}, + {0x0302, 0x69}, + {0x0303, 0x00}, + {0x0304, 0x00}, + {0x0305, 0x01}, + {0x0307, 0x00}, + {0x030b, 0x00}, + {0x030c, 0x00}, + {0x030d, 0x1e}, + {0x030e, 0x04}, + {0x030f, 0x03}, + {0x0312, 0x01}, + {0x3000, 0x00}, + {0x3002, 0xa1}, + {0x3008, 0x00}, + {0x3010, 0x00}, + {0x3016, 0x32}, + {0x3022, 0x51}, + {0x3106, 0x15}, + {0x3107, 0x01}, + {0x3108, 0x05}, + {0x3500, 0x00}, + {0x3501, 0x3e}, + {0x3502, 0x00}, + {0x3503, 0x08}, + {0x3504, 0x03}, + {0x3505, 0x8c}, + {0x3507, 0x03}, + {0x3508, 0x00}, + {0x3509, 0x10}, + {0x350c, 0x00}, + {0x350d, 0x80}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3601, 0x55}, + {0x3602, 0x58}, + {0x3611, 0x58}, + {0x3614, 0x30}, + {0x3615, 0x77}, + {0x3621, 0x08}, + {0x3624, 0x40}, + {0x3633, 0x0c}, + {0x3634, 0x0c}, + {0x3635, 0x0c}, + {0x3636, 0x0c}, + {0x3638, 0x00}, + {0x3639, 0x00}, + {0x363a, 0x00}, + {0x363b, 0x00}, + {0x363c, 0xff}, + {0x363d, 0xfa}, + {0x3650, 0x44}, + {0x3651, 0x44}, + {0x3652, 0x44}, + {0x3653, 0x44}, + {0x3654, 0x44}, + {0x3655, 0x44}, + {0x3656, 0x44}, + {0x3657, 0x44}, + {0x3660, 0x00}, + {0x3661, 0x00}, + {0x3662, 0x00}, + {0x366a, 0x00}, + {0x366e, 0x0c}, + {0x3673, 0x04}, + {0x3700, 0x14}, + {0x3703, 0x0c}, + {0x3706, 0x24}, + {0x3714, 0x27}, + {0x3715, 0x01}, + {0x3716, 0x00}, + {0x3717, 0x02}, + {0x3733, 0x10}, + {0x3734, 0x40}, + {0x373f, 0xa0}, + {0x3765, 0x20}, + {0x37a1, 0x1d}, + {0x37a8, 0x26}, + {0x37ab, 0x14}, + {0x37c2, 0x04}, + {0x37c3, 0xf0}, + {0x37cb, 0x09}, + {0x37cc, 0x13}, + {0x37cd, 0x1f}, + {0x37ce, 0x1f}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x3f}, + {0x3806, 0x07}, + {0x3807, 0xaf}, + {0x3808, 0x05}, + {0x3809, 0x10}, + {0x380a, 0x03}, + {0x380b, 0xcc}, + {0x380c, 0x02}, + {0x380d, 0xe4}, + {0x380e, 0x03}, + {0x380f, 0xf4}, + {0x3810, 0x00}, + {0x3811, 0x00}, + {0x3812, 0x00}, + {0x3813, 0x06}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x03}, + {0x3817, 0x01}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x381a, 0x00}, + {0x381b, 0x01}, + {0x3820, 0x8b}, + {0x3821, 0x01}, + {0x3c80, 0x08}, + {0x3c82, 0x00}, + {0x3c83, 0x00}, + {0x3c88, 0x00}, + {0x3d85, 0x14}, + {0x3f02, 0x08}, + {0x3f03, 0x10}, + {0x4008, 0x02}, + {0x4009, 0x09}, + {0x404e, 0x20}, + {0x4501, 0x00}, + {0x4502, 0x10}, + {0x4800, 0x00}, + {0x481f, 0x2a}, + {0x4837, 0x13}, + {0x5000, 0x13}, + {0x5780, 0x3e}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x06}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x5b00, 0x00}, + {0x5b01, 0x1c}, + {0x5b02, 0x00}, + {0x5b03, 0x7f}, + {0x5b05, 0x6c}, + {0x5e10, 0xfc}, + {0x4010, 0xf1}, + {0x3503, 0x08}, + {0x3505, 0x8c}, + {0x3507, 0x03}, + {0x3508, 0x00}, + {0x3509, 0xf8}, + {0x0100, 0x01}, + {REG_NULL, 0x00} +}; + +/* + * Xclk 24Mhz + * Pclk 45Mhz + * linelength 672(0x2a0) + * framelength 2232(0x8b8) + * grabwindow_width 1280 + * grabwindow_height 720 + * max_framerate 30fps + * mipi_datarate per lane 840Mbps + */ +static const struct regval ov5695_1280x720_regs[] = { + {0x3501, 0x45}, + {0x366e, 0x0c}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x01}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x3f}, + {0x3806, 0x06}, + {0x3807, 0xaf}, + {0x3808, 0x05}, + {0x3809, 0x00}, + {0x380a, 0x02}, + {0x380b, 0xd0}, + {0x380c, 0x02}, + {0x380d, 0xa0}, + {0x380e, 0x08}, + {0x380f, 0xb8}, + {0x3811, 0x06}, + {0x3813, 0x02}, + {0x3814, 0x03}, + {0x3816, 0x03}, + {0x3817, 0x01}, + {0x3820, 0x8b}, + {0x3821, 0x01}, + {0x4501, 0x00}, + {0x4008, 0x02}, + {0x4009, 0x09}, + {REG_NULL, 0x00} +}; + +/* + * Xclk 24Mhz + * Pclk 45Mhz + * linelength 672(0x2a0) + * framelength 558(0x22e) + * grabwindow_width 640 + * grabwindow_height 480 + * max_framerate 120fps + * mipi_datarate per lane 840Mbps + */ +static const struct regval ov5695_640x480_regs[] = { + {0x3501, 0x22}, + {0x366e, 0x0c}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x08}, + {0x3804, 0x0a}, + {0x3805, 0x3f}, + {0x3806, 0x07}, + {0x3807, 0xa7}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xe0}, + {0x380c, 0x02}, + {0x380d, 0xa0}, + {0x380e, 0x02}, + {0x380f, 0x2e}, + {0x3811, 0x06}, + {0x3813, 0x04}, + {0x3814, 0x07}, + {0x3816, 0x05}, + {0x3817, 0x03}, + {0x3820, 0x8d}, + {0x3821, 0x01}, + {0x4501, 0x00}, + {0x4008, 0x02}, + {0x4009, 0x09}, + {REG_NULL, 0x00} +}; + +static const struct ov5695_mode supported_modes[] = { + { + .width = 2592, + .height = 1944, + .max_fps = 30, + .exp_def = 0x0450, + .hts_def = 0x02e4 * 4, + .vts_def = 0x07e8, + .reg_list = ov5695_2592x1944_regs, + }, + { + .width = 1920, + .height = 1080, + .max_fps = 30, + .exp_def = 0x0450, + .hts_def = 0x02a0 * 4, + .vts_def = 0x08b8, + .reg_list = ov5695_1920x1080_regs, + }, + { + .width = 1296, + .height = 972, + .max_fps = 60, + .exp_def = 0x03e0, + .hts_def = 0x02e4 * 4, + .vts_def = 0x03f4, + .reg_list = ov5695_1296x972_regs, + }, + { + .width = 1280, + .height = 720, + .max_fps = 30, + .exp_def = 0x0450, + .hts_def = 0x02a0 * 4, + .vts_def = 0x08b8, + .reg_list = ov5695_1280x720_regs, + }, + { + .width = 640, + .height = 480, + .max_fps = 120, + .exp_def = 0x0450, + .hts_def = 0x02a0 * 4, + .vts_def = 0x022e, + .reg_list = ov5695_640x480_regs, + }, +}; + +#define OV5695_LINK_FREQ_420MHZ 420000000 +static const s64 link_freq_menu_items[] = { + OV5695_LINK_FREQ_420MHZ +}; + +static const char * const ov5695_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* Write registers up to 4 at a time */ +static int ov5695_write_reg(struct i2c_client *client, u16 reg, + u32 len, u32 val) +{ + u32 buf_i, val_i; + u8 buf[6]; + u8 *val_p; + __be32 val_be; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val_be = cpu_to_be32(val); + val_p = (u8 *)&val_be; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int ov5695_write_array(struct i2c_client *client, + const struct regval *regs) +{ + u32 i; + int ret = 0; + + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) + ret = ov5695_write_reg(client, regs[i].addr, + OV5695_REG_VALUE_08BIT, regs[i].val); + + return ret; +} + +/* Read registers up to 4 at a time */ +static int ov5695_read_reg(struct i2c_client *client, u16 reg, unsigned int len, + u32 *val) +{ + struct i2c_msg msgs[2]; + u8 *data_be_p; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + int ret; + + if (len > 4) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +static int ov5695_get_reso_dist(const struct ov5695_mode *mode, + struct v4l2_mbus_framefmt *framefmt) +{ + return abs(mode->width - framefmt->width) + + abs(mode->height - framefmt->height); +} + +static const struct ov5695_mode * +ov5695_find_best_fit(struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt = &fmt->format; + int dist; + int cur_best_fit = 0; + int cur_best_fit_dist = -1; + int i; + + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) { + dist = ov5695_get_reso_dist(&supported_modes[i], framefmt); + if (cur_best_fit_dist == -1 || dist < cur_best_fit_dist) { + cur_best_fit_dist = dist; + cur_best_fit = i; + } + } + + return &supported_modes[cur_best_fit]; +} + +static int ov5695_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov5695 *ov5695 = to_ov5695(sd); + const struct ov5695_mode *mode; + s64 h_blank, vblank_def; + + mutex_lock(&ov5695->mutex); + + mode = ov5695_find_best_fit(fmt); + fmt->format.code = MEDIA_BUS_FMT_SBGGR10_1X10; + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; +#endif + } else { + ov5695->cur_mode = mode; + h_blank = mode->hts_def - mode->width; + __v4l2_ctrl_modify_range(ov5695->hblank, h_blank, + h_blank, 1, h_blank); + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(ov5695->vblank, vblank_def, + OV5695_VTS_MAX - mode->height, + 1, vblank_def); + } + + mutex_unlock(&ov5695->mutex); + + return 0; +} + +static int ov5695_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov5695 *ov5695 = to_ov5695(sd); + const struct ov5695_mode *mode = ov5695->cur_mode; + + mutex_lock(&ov5695->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad); +#else + mutex_unlock(&ov5695->mutex); + return -EINVAL; +#endif + } else { + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = MEDIA_BUS_FMT_SBGGR10_1X10; + fmt->format.field = V4L2_FIELD_NONE; + } + mutex_unlock(&ov5695->mutex); + + return 0; +} + +static int ov5695_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + code->code = MEDIA_BUS_FMT_SBGGR10_1X10; + + return 0; +} + +static int ov5695_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = supported_modes[fse->index].width; + fse->max_height = supported_modes[fse->index].height; + fse->min_height = supported_modes[fse->index].height; + + return 0; +} + +static int ov5695_enable_test_pattern(struct ov5695 *ov5695, u32 pattern) +{ + u32 val; + + if (pattern) + val = (pattern - 1) | OV5695_TEST_PATTERN_ENABLE; + else + val = OV5695_TEST_PATTERN_DISABLE; + + return ov5695_write_reg(ov5695->client, OV5695_REG_TEST_PATTERN, + OV5695_REG_VALUE_08BIT, val); +} + +static int __ov5695_start_stream(struct ov5695 *ov5695) +{ + int ret; + + ret = ov5695_write_array(ov5695->client, ov5695_global_regs); + if (ret) + return ret; + ret = ov5695_write_array(ov5695->client, ov5695->cur_mode->reg_list); + if (ret) + return ret; + + /* In case these controls are set before streaming */ + ret = __v4l2_ctrl_handler_setup(&ov5695->ctrl_handler); + if (ret) + return ret; + + return ov5695_write_reg(ov5695->client, OV5695_REG_CTRL_MODE, + OV5695_REG_VALUE_08BIT, OV5695_MODE_STREAMING); +} + +static int __ov5695_stop_stream(struct ov5695 *ov5695) +{ + return ov5695_write_reg(ov5695->client, OV5695_REG_CTRL_MODE, + OV5695_REG_VALUE_08BIT, OV5695_MODE_SW_STANDBY); +} + +static int ov5695_s_stream(struct v4l2_subdev *sd, int on) +{ + struct ov5695 *ov5695 = to_ov5695(sd); + struct i2c_client *client = ov5695->client; + int ret = 0; + + mutex_lock(&ov5695->mutex); + on = !!on; + if (on == ov5695->streaming) + goto unlock_and_return; + + if (on) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + + ret = __ov5695_start_stream(ov5695); + if (ret) { + v4l2_err(sd, "start stream failed while write regs\n"); + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + } else { + __ov5695_stop_stream(ov5695); + pm_runtime_put(&client->dev); + } + + ov5695->streaming = on; + +unlock_and_return: + mutex_unlock(&ov5695->mutex); + + return ret; +} + +static int __ov5695_power_on(struct ov5695 *ov5695) +{ + int i, ret; + struct device *dev = &ov5695->client->dev; + + ret = clk_prepare_enable(ov5695->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + + gpiod_set_value_cansleep(ov5695->reset_gpio, 1); + + /* + * The hardware requires the regulators to be powered on in order, + * so enable them one by one. + */ + for (i = 0; i < OV5695_NUM_SUPPLIES; i++) { + ret = regulator_enable(ov5695->supplies[i].consumer); + if (ret) { + dev_err(dev, "Failed to enable %s: %d\n", + ov5695->supplies[i].supply, ret); + goto disable_reg_clk; + } + } + + gpiod_set_value_cansleep(ov5695->reset_gpio, 0); + + usleep_range(1000, 1200); + + return 0; + +disable_reg_clk: + for (--i; i >= 0; i--) + regulator_disable(ov5695->supplies[i].consumer); + clk_disable_unprepare(ov5695->xvclk); + + return ret; +} + +static void __ov5695_power_off(struct ov5695 *ov5695) +{ + struct device *dev = &ov5695->client->dev; + int i, ret; + + clk_disable_unprepare(ov5695->xvclk); + gpiod_set_value_cansleep(ov5695->reset_gpio, 1); + + /* + * The hardware requires the regulators to be powered off in order, + * so disable them one by one. + */ + for (i = OV5695_NUM_SUPPLIES - 1; i >= 0; i--) { + ret = regulator_disable(ov5695->supplies[i].consumer); + if (ret) + dev_err(dev, "Failed to disable %s: %d\n", + ov5695->supplies[i].supply, ret); + } +} + +static int __maybe_unused ov5695_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5695 *ov5695 = to_ov5695(sd); + + return __ov5695_power_on(ov5695); +} + +static int __maybe_unused ov5695_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5695 *ov5695 = to_ov5695(sd); + + __ov5695_power_off(ov5695); + + return 0; +} + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static int ov5695_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov5695 *ov5695 = to_ov5695(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + const struct ov5695_mode *def_mode = &supported_modes[0]; + + mutex_lock(&ov5695->mutex); + /* Initialize try_fmt */ + try_fmt->width = def_mode->width; + try_fmt->height = def_mode->height; + try_fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + + mutex_unlock(&ov5695->mutex); + /* No crop or compose */ + + return 0; +} +#endif + +static const struct dev_pm_ops ov5695_pm_ops = { + SET_RUNTIME_PM_OPS(ov5695_runtime_suspend, + ov5695_runtime_resume, NULL) +}; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static const struct v4l2_subdev_internal_ops ov5695_internal_ops = { + .open = ov5695_open, +}; +#endif + +static const struct v4l2_subdev_video_ops ov5695_video_ops = { + .s_stream = ov5695_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov5695_pad_ops = { + .enum_mbus_code = ov5695_enum_mbus_code, + .enum_frame_size = ov5695_enum_frame_sizes, + .get_fmt = ov5695_get_fmt, + .set_fmt = ov5695_set_fmt, +}; + +static const struct v4l2_subdev_ops ov5695_subdev_ops = { + .video = &ov5695_video_ops, + .pad = &ov5695_pad_ops, +}; + +static int ov5695_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov5695 *ov5695 = container_of(ctrl->handler, + struct ov5695, ctrl_handler); + struct i2c_client *client = ov5695->client; + s64 max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = ov5695->cur_mode->height + ctrl->val - 4; + __v4l2_ctrl_modify_range(ov5695->exposure, + ov5695->exposure->minimum, max, + ov5695->exposure->step, + ov5695->exposure->default_value); + break; + } + + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + /* 4 least significant bits of expsoure are fractional part */ + ret = ov5695_write_reg(ov5695->client, OV5695_REG_EXPOSURE, + OV5695_REG_VALUE_24BIT, ctrl->val << 4); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = ov5695_write_reg(ov5695->client, OV5695_REG_ANALOG_GAIN, + OV5695_REG_VALUE_08BIT, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = ov5695_write_reg(ov5695->client, OV5695_REG_DIGI_GAIN_L, + OV5695_REG_VALUE_08BIT, + ctrl->val & OV5695_DIGI_GAIN_L_MASK); + ret = ov5695_write_reg(ov5695->client, OV5695_REG_DIGI_GAIN_H, + OV5695_REG_VALUE_08BIT, + ctrl->val >> OV5695_DIGI_GAIN_H_SHIFT); + break; + case V4L2_CID_VBLANK: + ret = ov5695_write_reg(ov5695->client, OV5695_REG_VTS, + OV5695_REG_VALUE_16BIT, + ctrl->val + ov5695->cur_mode->height); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov5695_enable_test_pattern(ov5695, ctrl->val); + break; + default: + dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", + __func__, ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov5695_ctrl_ops = { + .s_ctrl = ov5695_set_ctrl, +}; + +static int ov5695_initialize_controls(struct ov5695 *ov5695) +{ + const struct ov5695_mode *mode; + struct v4l2_ctrl_handler *handler; + struct v4l2_ctrl *ctrl; + s64 exposure_max, vblank_def; + u32 h_blank; + int ret; + + handler = &ov5695->ctrl_handler; + mode = ov5695->cur_mode; + ret = v4l2_ctrl_handler_init(handler, 8); + if (ret) + return ret; + handler->lock = &ov5695->mutex; + + ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, + 0, 0, link_freq_menu_items); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, + 0, OV5695_PIXEL_RATE, 1, OV5695_PIXEL_RATE); + + h_blank = mode->hts_def - mode->width; + ov5695->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, + h_blank, h_blank, 1, h_blank); + if (ov5695->hblank) + ov5695->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank_def = mode->vts_def - mode->height; + ov5695->vblank = v4l2_ctrl_new_std(handler, &ov5695_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + OV5695_VTS_MAX - mode->height, + 1, vblank_def); + + exposure_max = mode->vts_def - 4; + ov5695->exposure = v4l2_ctrl_new_std(handler, &ov5695_ctrl_ops, + V4L2_CID_EXPOSURE, OV5695_EXPOSURE_MIN, + exposure_max, OV5695_EXPOSURE_STEP, + mode->exp_def); + + ov5695->anal_gain = v4l2_ctrl_new_std(handler, &ov5695_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, ANALOG_GAIN_MIN, + ANALOG_GAIN_MAX, ANALOG_GAIN_STEP, + ANALOG_GAIN_DEFAULT); + + /* Digital gain */ + ov5695->digi_gain = v4l2_ctrl_new_std(handler, &ov5695_ctrl_ops, + V4L2_CID_DIGITAL_GAIN, OV5695_DIGI_GAIN_MIN, + OV5695_DIGI_GAIN_MAX, OV5695_DIGI_GAIN_STEP, + OV5695_DIGI_GAIN_DEFAULT); + + ov5695->test_pattern = v4l2_ctrl_new_std_menu_items(handler, + &ov5695_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov5695_test_pattern_menu) - 1, + 0, 0, ov5695_test_pattern_menu); + + if (handler->error) { + ret = handler->error; + dev_err(&ov5695->client->dev, + "Failed to init controls(%d)\n", ret); + goto err_free_handler; + } + + ov5695->subdev.ctrl_handler = handler; + + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(handler); + + return ret; +} + +static int ov5695_check_sensor_id(struct ov5695 *ov5695, + struct i2c_client *client) +{ + struct device *dev = &ov5695->client->dev; + u32 id = 0; + int ret; + + ret = ov5695_read_reg(client, OV5695_REG_CHIP_ID, + OV5695_REG_VALUE_24BIT, &id); + if (id != CHIP_ID) { + dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret); + return ret; + } + + dev_info(dev, "Detected OV%06x sensor\n", CHIP_ID); + + return 0; +} + +static int ov5695_configure_regulators(struct ov5695 *ov5695) +{ + int i; + + for (i = 0; i < OV5695_NUM_SUPPLIES; i++) + ov5695->supplies[i].supply = ov5695_supply_names[i]; + + return devm_regulator_bulk_get(&ov5695->client->dev, + OV5695_NUM_SUPPLIES, + ov5695->supplies); +} + +static int ov5695_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ov5695 *ov5695; + struct v4l2_subdev *sd; + int ret; + + ov5695 = devm_kzalloc(dev, sizeof(*ov5695), GFP_KERNEL); + if (!ov5695) + return -ENOMEM; + + ov5695->client = client; + ov5695->cur_mode = &supported_modes[0]; + + ov5695->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(ov5695->xvclk)) { + dev_err(dev, "Failed to get xvclk\n"); + return -EINVAL; + } + ret = clk_set_rate(ov5695->xvclk, OV5695_XVCLK_FREQ); + if (ret < 0) { + dev_err(dev, "Failed to set xvclk rate (24MHz)\n"); + return ret; + } + if (clk_get_rate(ov5695->xvclk) != OV5695_XVCLK_FREQ) + dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n"); + + ov5695->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ov5695->reset_gpio)) { + dev_err(dev, "Failed to get reset-gpios\n"); + return -EINVAL; + } + + ret = ov5695_configure_regulators(ov5695); + if (ret) { + dev_err(dev, "Failed to get power regulators\n"); + return ret; + } + + mutex_init(&ov5695->mutex); + + sd = &ov5695->subdev; + v4l2_i2c_subdev_init(sd, client, &ov5695_subdev_ops); + ret = ov5695_initialize_controls(ov5695); + if (ret) + goto err_destroy_mutex; + + ret = __ov5695_power_on(ov5695); + if (ret) + goto err_free_handler; + + ret = ov5695_check_sensor_id(ov5695, client); + if (ret) + goto err_power_off; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + sd->internal_ops = &ov5695_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; +#endif +#if defined(CONFIG_MEDIA_CONTROLLER) + ov5695->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &ov5695->pad); + if (ret < 0) + goto err_power_off; +#endif + + ret = v4l2_async_register_subdev_sensor_common(sd); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_clean_entity; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +err_clean_entity: +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif +err_power_off: + __ov5695_power_off(ov5695); +err_free_handler: + v4l2_ctrl_handler_free(&ov5695->ctrl_handler); +err_destroy_mutex: + mutex_destroy(&ov5695->mutex); + + return ret; +} + +static int ov5695_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5695 *ov5695 = to_ov5695(sd); + + v4l2_async_unregister_subdev(sd); +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif + v4l2_ctrl_handler_free(&ov5695->ctrl_handler); + mutex_destroy(&ov5695->mutex); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + __ov5695_power_off(ov5695); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id ov5695_of_match[] = { + { .compatible = "ovti,ov5695" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ov5695_of_match); +#endif + +static struct i2c_driver ov5695_i2c_driver = { + .driver = { + .name = "ov5695", + .pm = &ov5695_pm_ops, + .of_match_table = of_match_ptr(ov5695_of_match), + }, + .probe = &ov5695_probe, + .remove = &ov5695_remove, +}; + +module_i2c_driver(ov5695_i2c_driver); + +MODULE_DESCRIPTION("OmniVision ov5695 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov6650.c b/drivers/media/i2c/ov6650.c new file mode 100644 index 000000000..d73f9f540 --- /dev/null +++ b/drivers/media/i2c/ov6650.c @@ -0,0 +1,1117 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * V4L2 subdevice driver for OmniVision OV6650 Camera Sensor + * + * Copyright (C) 2010 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl> + * + * Based on OmniVision OV96xx Camera Driver + * Copyright (C) 2009 Marek Vasut <marek.vasut@gmail.com> + * + * Based on ov772x camera driver: + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * Based on ov7670 and soc_camera_platform driver, + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> + * + * Hardware specific bits initially based on former work by Matt Callow + * drivers/media/video/omap/sensor_ov6650.c + * Copyright (C) 2006 Matt Callow + */ + +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/v4l2-mediabus.h> +#include <linux/module.h> + +#include <media/v4l2-clk.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +/* Register definitions */ +#define REG_GAIN 0x00 /* range 00 - 3F */ +#define REG_BLUE 0x01 +#define REG_RED 0x02 +#define REG_SAT 0x03 /* [7:4] saturation [0:3] reserved */ +#define REG_HUE 0x04 /* [7:6] rsrvd [5] hue en [4:0] hue */ + +#define REG_BRT 0x06 + +#define REG_PIDH 0x0a +#define REG_PIDL 0x0b + +#define REG_AECH 0x10 +#define REG_CLKRC 0x11 /* Data Format and Internal Clock */ + /* [7:6] Input system clock (MHz)*/ + /* 00=8, 01=12, 10=16, 11=24 */ + /* [5:0]: Internal Clock Pre-Scaler */ +#define REG_COMA 0x12 /* [7] Reset */ +#define REG_COMB 0x13 +#define REG_COMC 0x14 +#define REG_COMD 0x15 +#define REG_COML 0x16 +#define REG_HSTRT 0x17 +#define REG_HSTOP 0x18 +#define REG_VSTRT 0x19 +#define REG_VSTOP 0x1a +#define REG_PSHFT 0x1b +#define REG_MIDH 0x1c +#define REG_MIDL 0x1d +#define REG_HSYNS 0x1e +#define REG_HSYNE 0x1f +#define REG_COME 0x20 +#define REG_YOFF 0x21 +#define REG_UOFF 0x22 +#define REG_VOFF 0x23 +#define REG_AEW 0x24 +#define REG_AEB 0x25 +#define REG_COMF 0x26 +#define REG_COMG 0x27 +#define REG_COMH 0x28 +#define REG_COMI 0x29 + +#define REG_FRARL 0x2b +#define REG_COMJ 0x2c +#define REG_COMK 0x2d +#define REG_AVGY 0x2e +#define REG_REF0 0x2f +#define REG_REF1 0x30 +#define REG_REF2 0x31 +#define REG_FRAJH 0x32 +#define REG_FRAJL 0x33 +#define REG_FACT 0x34 +#define REG_L1AEC 0x35 +#define REG_AVGU 0x36 +#define REG_AVGV 0x37 + +#define REG_SPCB 0x60 +#define REG_SPCC 0x61 +#define REG_GAM1 0x62 +#define REG_GAM2 0x63 +#define REG_GAM3 0x64 +#define REG_SPCD 0x65 + +#define REG_SPCE 0x68 +#define REG_ADCL 0x69 + +#define REG_RMCO 0x6c +#define REG_GMCO 0x6d +#define REG_BMCO 0x6e + + +/* Register bits, values, etc. */ +#define OV6650_PIDH 0x66 /* high byte of product ID number */ +#define OV6650_PIDL 0x50 /* low byte of product ID number */ +#define OV6650_MIDH 0x7F /* high byte of mfg ID */ +#define OV6650_MIDL 0xA2 /* low byte of mfg ID */ + +#define DEF_GAIN 0x00 +#define DEF_BLUE 0x80 +#define DEF_RED 0x80 + +#define SAT_SHIFT 4 +#define SAT_MASK (0xf << SAT_SHIFT) +#define SET_SAT(x) (((x) << SAT_SHIFT) & SAT_MASK) + +#define HUE_EN BIT(5) +#define HUE_MASK 0x1f +#define DEF_HUE 0x10 +#define SET_HUE(x) (HUE_EN | ((x) & HUE_MASK)) + +#define DEF_AECH 0x4D + +#define CLKRC_8MHz 0x00 +#define CLKRC_12MHz 0x40 +#define CLKRC_16MHz 0x80 +#define CLKRC_24MHz 0xc0 +#define CLKRC_DIV_MASK 0x3f +#define GET_CLKRC_DIV(x) (((x) & CLKRC_DIV_MASK) + 1) +#define DEF_CLKRC 0x00 + +#define COMA_RESET BIT(7) +#define COMA_QCIF BIT(5) +#define COMA_RAW_RGB BIT(4) +#define COMA_RGB BIT(3) +#define COMA_BW BIT(2) +#define COMA_WORD_SWAP BIT(1) +#define COMA_BYTE_SWAP BIT(0) +#define DEF_COMA 0x00 + +#define COMB_FLIP_V BIT(7) +#define COMB_FLIP_H BIT(5) +#define COMB_BAND_FILTER BIT(4) +#define COMB_AWB BIT(2) +#define COMB_AGC BIT(1) +#define COMB_AEC BIT(0) +#define DEF_COMB 0x5f + +#define COML_ONE_CHANNEL BIT(7) + +#define DEF_HSTRT 0x24 +#define DEF_HSTOP 0xd4 +#define DEF_VSTRT 0x04 +#define DEF_VSTOP 0x94 + +#define COMF_HREF_LOW BIT(4) + +#define COMJ_PCLK_RISING BIT(4) +#define COMJ_VSYNC_HIGH BIT(0) + +/* supported resolutions */ +#define W_QCIF (DEF_HSTOP - DEF_HSTRT) +#define W_CIF (W_QCIF << 1) +#define H_QCIF (DEF_VSTOP - DEF_VSTRT) +#define H_CIF (H_QCIF << 1) + +#define FRAME_RATE_MAX 30 + + +struct ov6650_reg { + u8 reg; + u8 val; +}; + +struct ov6650 { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct { + /* exposure/autoexposure cluster */ + struct v4l2_ctrl *autoexposure; + struct v4l2_ctrl *exposure; + }; + struct { + /* gain/autogain cluster */ + struct v4l2_ctrl *autogain; + struct v4l2_ctrl *gain; + }; + struct { + /* blue/red/autowhitebalance cluster */ + struct v4l2_ctrl *autowb; + struct v4l2_ctrl *blue; + struct v4l2_ctrl *red; + }; + struct v4l2_clk *clk; + bool half_scale; /* scale down output by 2 */ + struct v4l2_rect rect; /* sensor cropping window */ + struct v4l2_fract tpf; /* as requested with s_frame_interval */ + u32 code; +}; + +struct ov6650_xclk { + unsigned long rate; + u8 clkrc; +}; + +static const struct ov6650_xclk ov6650_xclk[] = { +{ + .rate = 8000000, + .clkrc = CLKRC_8MHz, +}, +{ + .rate = 12000000, + .clkrc = CLKRC_12MHz, +}, +{ + .rate = 16000000, + .clkrc = CLKRC_16MHz, +}, +{ + .rate = 24000000, + .clkrc = CLKRC_24MHz, +}, +}; + +static u32 ov6650_codes[] = { + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_VYUY8_2X8, + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_Y8_1X8, +}; + +static const struct v4l2_mbus_framefmt ov6650_def_fmt = { + .width = W_CIF, + .height = H_CIF, + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .field = V4L2_FIELD_NONE, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + +/* read a register */ +static int ov6650_reg_read(struct i2c_client *client, u8 reg, u8 *val) +{ + int ret; + u8 data = reg; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &data, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + goto err; + + msg.flags = I2C_M_RD; + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + goto err; + + *val = data; + return 0; + +err: + dev_err(&client->dev, "Failed reading register 0x%02x!\n", reg); + return ret; +} + +/* write a register */ +static int ov6650_reg_write(struct i2c_client *client, u8 reg, u8 val) +{ + int ret; + unsigned char data[2] = { reg, val }; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = data, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + udelay(100); + + if (ret < 0) { + dev_err(&client->dev, "Failed writing register 0x%02x!\n", reg); + return ret; + } + return 0; +} + + +/* Read a register, alter its bits, write it back */ +static int ov6650_reg_rmw(struct i2c_client *client, u8 reg, u8 set, u8 mask) +{ + u8 val; + int ret; + + ret = ov6650_reg_read(client, reg, &val); + if (ret) { + dev_err(&client->dev, + "[Read]-Modify-Write of register 0x%02x failed!\n", + reg); + return ret; + } + + val &= ~mask; + val |= set; + + ret = ov6650_reg_write(client, reg, val); + if (ret) + dev_err(&client->dev, + "Read-Modify-[Write] of register 0x%02x failed!\n", + reg); + + return ret; +} + +static struct ov6650 *to_ov6650(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ov6650, subdev); +} + +/* Start/Stop streaming from the device */ +static int ov6650_s_stream(struct v4l2_subdev *sd, int enable) +{ + return 0; +} + +/* Get status of additional camera capabilities */ +static int ov6550_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov6650 *priv = container_of(ctrl->handler, struct ov6650, hdl); + struct v4l2_subdev *sd = &priv->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + uint8_t reg, reg2; + int ret; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + ret = ov6650_reg_read(client, REG_GAIN, ®); + if (!ret) + priv->gain->val = reg; + return ret; + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov6650_reg_read(client, REG_BLUE, ®); + if (!ret) + ret = ov6650_reg_read(client, REG_RED, ®2); + if (!ret) { + priv->blue->val = reg; + priv->red->val = reg2; + } + return ret; + case V4L2_CID_EXPOSURE_AUTO: + ret = ov6650_reg_read(client, REG_AECH, ®); + if (!ret) + priv->exposure->val = reg; + return ret; + } + return -EINVAL; +} + +/* Set status of additional camera capabilities */ +static int ov6550_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov6650 *priv = container_of(ctrl->handler, struct ov6650, hdl); + struct v4l2_subdev *sd = &priv->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + ret = ov6650_reg_rmw(client, REG_COMB, + ctrl->val ? COMB_AGC : 0, COMB_AGC); + if (!ret && !ctrl->val) + ret = ov6650_reg_write(client, REG_GAIN, priv->gain->val); + return ret; + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov6650_reg_rmw(client, REG_COMB, + ctrl->val ? COMB_AWB : 0, COMB_AWB); + if (!ret && !ctrl->val) { + ret = ov6650_reg_write(client, REG_BLUE, priv->blue->val); + if (!ret) + ret = ov6650_reg_write(client, REG_RED, + priv->red->val); + } + return ret; + case V4L2_CID_SATURATION: + return ov6650_reg_rmw(client, REG_SAT, SET_SAT(ctrl->val), + SAT_MASK); + case V4L2_CID_HUE: + return ov6650_reg_rmw(client, REG_HUE, SET_HUE(ctrl->val), + HUE_MASK); + case V4L2_CID_BRIGHTNESS: + return ov6650_reg_write(client, REG_BRT, ctrl->val); + case V4L2_CID_EXPOSURE_AUTO: + ret = ov6650_reg_rmw(client, REG_COMB, ctrl->val == + V4L2_EXPOSURE_AUTO ? COMB_AEC : 0, COMB_AEC); + if (!ret && ctrl->val == V4L2_EXPOSURE_MANUAL) + ret = ov6650_reg_write(client, REG_AECH, + priv->exposure->val); + return ret; + case V4L2_CID_GAMMA: + return ov6650_reg_write(client, REG_GAM1, ctrl->val); + case V4L2_CID_VFLIP: + return ov6650_reg_rmw(client, REG_COMB, + ctrl->val ? COMB_FLIP_V : 0, COMB_FLIP_V); + case V4L2_CID_HFLIP: + return ov6650_reg_rmw(client, REG_COMB, + ctrl->val ? COMB_FLIP_H : 0, COMB_FLIP_H); + } + + return -EINVAL; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov6650_get_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u8 val; + + if (reg->reg & ~0xff) + return -EINVAL; + + reg->size = 1; + + ret = ov6650_reg_read(client, reg->reg, &val); + if (!ret) + reg->val = (__u64)val; + + return ret; +} + +static int ov6650_set_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg & ~0xff || reg->val & ~0xff) + return -EINVAL; + + return ov6650_reg_write(client, reg->reg, reg->val); +} +#endif + +static int ov6650_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + int ret = 0; + + if (on) + ret = v4l2_clk_enable(priv->clk); + else + v4l2_clk_disable(priv->clk); + + return ret; +} + +static int ov6650_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = DEF_HSTRT << 1; + sel->r.top = DEF_VSTRT << 1; + sel->r.width = W_CIF; + sel->r.height = H_CIF; + return 0; + case V4L2_SEL_TGT_CROP: + sel->r = priv->rect; + return 0; + default: + return -EINVAL; + } +} + +static int ov6650_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + int ret; + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + v4l_bound_align_image(&sel->r.width, 2, W_CIF, 1, + &sel->r.height, 2, H_CIF, 1, 0); + v4l_bound_align_image(&sel->r.left, DEF_HSTRT << 1, + (DEF_HSTRT << 1) + W_CIF - (__s32)sel->r.width, 1, + &sel->r.top, DEF_VSTRT << 1, + (DEF_VSTRT << 1) + H_CIF - (__s32)sel->r.height, + 1, 0); + + ret = ov6650_reg_write(client, REG_HSTRT, sel->r.left >> 1); + if (!ret) { + priv->rect.width += priv->rect.left - sel->r.left; + priv->rect.left = sel->r.left; + ret = ov6650_reg_write(client, REG_HSTOP, + (sel->r.left + sel->r.width) >> 1); + } + if (!ret) { + priv->rect.width = sel->r.width; + ret = ov6650_reg_write(client, REG_VSTRT, sel->r.top >> 1); + } + if (!ret) { + priv->rect.height += priv->rect.top - sel->r.top; + priv->rect.top = sel->r.top; + ret = ov6650_reg_write(client, REG_VSTOP, + (sel->r.top + sel->r.height) >> 1); + } + if (!ret) + priv->rect.height = sel->r.height; + + return ret; +} + +static int ov6650_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + + if (format->pad) + return -EINVAL; + + /* initialize response with default media bus frame format */ + *mf = ov6650_def_fmt; + + /* update media bus format code and frame size */ + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + mf->width = cfg->try_fmt.width; + mf->height = cfg->try_fmt.height; + mf->code = cfg->try_fmt.code; + + } else { + mf->width = priv->rect.width >> priv->half_scale; + mf->height = priv->rect.height >> priv->half_scale; + mf->code = priv->code; + } + return 0; +} + +static bool is_unscaled_ok(int width, int height, struct v4l2_rect *rect) +{ + return width > rect->width >> 1 || height > rect->height >> 1; +} + +#define to_clkrc(div) ((div) - 1) + +/* set the format we will capture in */ +static int ov6650_s_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + bool half_scale = !is_unscaled_ok(mf->width, mf->height, &priv->rect); + struct v4l2_subdev_selection sel = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .target = V4L2_SEL_TGT_CROP, + .r.left = priv->rect.left + (priv->rect.width >> 1) - + (mf->width >> (1 - half_scale)), + .r.top = priv->rect.top + (priv->rect.height >> 1) - + (mf->height >> (1 - half_scale)), + .r.width = mf->width << half_scale, + .r.height = mf->height << half_scale, + }; + u32 code = mf->code; + u8 coma_set = 0, coma_mask = 0, coml_set, coml_mask; + int ret; + + /* select color matrix configuration for given color encoding */ + switch (code) { + case MEDIA_BUS_FMT_Y8_1X8: + dev_dbg(&client->dev, "pixel format GREY8_1X8\n"); + coma_mask |= COMA_RGB | COMA_WORD_SWAP | COMA_BYTE_SWAP; + coma_set |= COMA_BW; + break; + case MEDIA_BUS_FMT_YUYV8_2X8: + dev_dbg(&client->dev, "pixel format YUYV8_2X8_LE\n"); + coma_mask |= COMA_RGB | COMA_BW | COMA_BYTE_SWAP; + coma_set |= COMA_WORD_SWAP; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + dev_dbg(&client->dev, "pixel format YVYU8_2X8_LE (untested)\n"); + coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP | + COMA_BYTE_SWAP; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + dev_dbg(&client->dev, "pixel format YUYV8_2X8_BE\n"); + if (half_scale) { + coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP; + coma_set |= COMA_BYTE_SWAP; + } else { + coma_mask |= COMA_RGB | COMA_BW; + coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP; + } + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + dev_dbg(&client->dev, "pixel format YVYU8_2X8_BE (untested)\n"); + if (half_scale) { + coma_mask |= COMA_RGB | COMA_BW; + coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP; + } else { + coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP; + coma_set |= COMA_BYTE_SWAP; + } + break; + case MEDIA_BUS_FMT_SBGGR8_1X8: + dev_dbg(&client->dev, "pixel format SBGGR8_1X8 (untested)\n"); + coma_mask |= COMA_BW | COMA_BYTE_SWAP | COMA_WORD_SWAP; + coma_set |= COMA_RAW_RGB | COMA_RGB; + break; + default: + dev_err(&client->dev, "Pixel format not handled: 0x%x\n", code); + return -EINVAL; + } + + if (code == MEDIA_BUS_FMT_Y8_1X8 || + code == MEDIA_BUS_FMT_SBGGR8_1X8) { + coml_mask = COML_ONE_CHANNEL; + coml_set = 0; + } else { + coml_mask = 0; + coml_set = COML_ONE_CHANNEL; + } + + if (half_scale) { + dev_dbg(&client->dev, "max resolution: QCIF\n"); + coma_set |= COMA_QCIF; + } else { + dev_dbg(&client->dev, "max resolution: CIF\n"); + coma_mask |= COMA_QCIF; + } + + ret = ov6650_set_selection(sd, NULL, &sel); + if (!ret) + ret = ov6650_reg_rmw(client, REG_COMA, coma_set, coma_mask); + if (!ret) { + priv->half_scale = half_scale; + + ret = ov6650_reg_rmw(client, REG_COML, coml_set, coml_mask); + } + if (!ret) + priv->code = code; + + return ret; +} + +static int ov6650_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + + if (format->pad) + return -EINVAL; + + if (is_unscaled_ok(mf->width, mf->height, &priv->rect)) + v4l_bound_align_image(&mf->width, 2, W_CIF, 1, + &mf->height, 2, H_CIF, 1, 0); + + switch (mf->code) { + case MEDIA_BUS_FMT_Y10_1X10: + mf->code = MEDIA_BUS_FMT_Y8_1X8; + fallthrough; + case MEDIA_BUS_FMT_Y8_1X8: + case MEDIA_BUS_FMT_YVYU8_2X8: + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_VYUY8_2X8: + case MEDIA_BUS_FMT_UYVY8_2X8: + break; + default: + mf->code = MEDIA_BUS_FMT_SBGGR8_1X8; + fallthrough; + case MEDIA_BUS_FMT_SBGGR8_1X8: + break; + } + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + /* store media bus format code and frame size in pad config */ + cfg->try_fmt.width = mf->width; + cfg->try_fmt.height = mf->height; + cfg->try_fmt.code = mf->code; + + /* return default mbus frame format updated with pad config */ + *mf = ov6650_def_fmt; + mf->width = cfg->try_fmt.width; + mf->height = cfg->try_fmt.height; + mf->code = cfg->try_fmt.code; + + } else { + /* apply new media bus format code and frame size */ + int ret = ov6650_s_fmt(sd, mf); + + if (ret) + return ret; + + /* return default format updated with active size and code */ + *mf = ov6650_def_fmt; + mf->width = priv->rect.width >> priv->half_scale; + mf->height = priv->rect.height >> priv->half_scale; + mf->code = priv->code; + } + return 0; +} + +static int ov6650_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(ov6650_codes)) + return -EINVAL; + + code->code = ov6650_codes[code->index]; + return 0; +} + +static int ov6650_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + + ival->interval = priv->tpf; + + dev_dbg(&client->dev, "Frame interval: %u/%u s\n", + ival->interval.numerator, ival->interval.denominator); + + return 0; +} + +static int ov6650_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + struct v4l2_fract *tpf = &ival->interval; + int div, ret; + + if (tpf->numerator == 0 || tpf->denominator == 0) + div = 1; /* Reset to full rate */ + else + div = (tpf->numerator * FRAME_RATE_MAX) / tpf->denominator; + + if (div == 0) + div = 1; + else if (div > GET_CLKRC_DIV(CLKRC_DIV_MASK)) + div = GET_CLKRC_DIV(CLKRC_DIV_MASK); + + ret = ov6650_reg_rmw(client, REG_CLKRC, to_clkrc(div), CLKRC_DIV_MASK); + if (!ret) { + priv->tpf.numerator = div; + priv->tpf.denominator = FRAME_RATE_MAX; + + *tpf = priv->tpf; + } + + return ret; +} + +/* Soft reset the camera. This has nothing to do with the RESET pin! */ +static int ov6650_reset(struct i2c_client *client) +{ + int ret; + + dev_dbg(&client->dev, "reset\n"); + + ret = ov6650_reg_rmw(client, REG_COMA, COMA_RESET, 0); + if (ret) + dev_err(&client->dev, + "An error occurred while entering soft reset!\n"); + + return ret; +} + +/* program default register values */ +static int ov6650_prog_dflt(struct i2c_client *client, u8 clkrc) +{ + int ret; + + dev_dbg(&client->dev, "initializing\n"); + + ret = ov6650_reg_write(client, REG_COMA, 0); /* ~COMA_RESET */ + if (!ret) + ret = ov6650_reg_write(client, REG_CLKRC, clkrc); + if (!ret) + ret = ov6650_reg_rmw(client, REG_COMB, 0, COMB_BAND_FILTER); + + return ret; +} + +static int ov6650_video_probe(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + const struct ov6650_xclk *xclk = NULL; + unsigned long rate; + u8 pidh, pidl, midh, midl; + int i, ret = 0; + + priv->clk = v4l2_clk_get(&client->dev, NULL); + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + dev_err(&client->dev, "v4l2_clk request err: %d\n", ret); + return ret; + } + + rate = v4l2_clk_get_rate(priv->clk); + for (i = 0; rate && i < ARRAY_SIZE(ov6650_xclk); i++) { + if (rate != ov6650_xclk[i].rate) + continue; + + xclk = &ov6650_xclk[i]; + dev_info(&client->dev, "using host default clock rate %lukHz\n", + rate / 1000); + break; + } + for (i = 0; !xclk && i < ARRAY_SIZE(ov6650_xclk); i++) { + ret = v4l2_clk_set_rate(priv->clk, ov6650_xclk[i].rate); + if (ret || v4l2_clk_get_rate(priv->clk) != ov6650_xclk[i].rate) + continue; + + xclk = &ov6650_xclk[i]; + dev_info(&client->dev, "using negotiated clock rate %lukHz\n", + xclk->rate / 1000); + break; + } + if (!xclk) { + dev_err(&client->dev, "unable to get supported clock rate\n"); + if (!ret) + ret = -EINVAL; + goto eclkput; + } + + ret = ov6650_s_power(sd, 1); + if (ret < 0) + goto eclkput; + + msleep(20); + + /* + * check and show product ID and manufacturer ID + */ + ret = ov6650_reg_read(client, REG_PIDH, &pidh); + if (!ret) + ret = ov6650_reg_read(client, REG_PIDL, &pidl); + if (!ret) + ret = ov6650_reg_read(client, REG_MIDH, &midh); + if (!ret) + ret = ov6650_reg_read(client, REG_MIDL, &midl); + + if (ret) + goto done; + + if ((pidh != OV6650_PIDH) || (pidl != OV6650_PIDL)) { + dev_err(&client->dev, "Product ID error 0x%02x:0x%02x\n", + pidh, pidl); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, + "ov6650 Product ID 0x%02x:0x%02x Manufacturer ID 0x%02x:0x%02x\n", + pidh, pidl, midh, midl); + + ret = ov6650_reset(client); + if (!ret) + ret = ov6650_prog_dflt(client, xclk->clkrc); + if (!ret) { + struct v4l2_mbus_framefmt mf = ov6650_def_fmt; + + ret = ov6650_s_fmt(sd, &mf); + } + if (!ret) + ret = v4l2_ctrl_handler_setup(&priv->hdl); + +done: + ov6650_s_power(sd, 0); + if (!ret) + return 0; +eclkput: + v4l2_clk_put(priv->clk); + + return ret; +} + +static const struct v4l2_ctrl_ops ov6550_ctrl_ops = { + .g_volatile_ctrl = ov6550_g_volatile_ctrl, + .s_ctrl = ov6550_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops ov6650_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov6650_get_register, + .s_register = ov6650_set_register, +#endif + .s_power = ov6650_s_power, +}; + +/* Request bus settings on camera side */ +static int ov6650_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 comj, comf; + int ret; + + ret = ov6650_reg_read(client, REG_COMJ, &comj); + if (ret) + return ret; + + ret = ov6650_reg_read(client, REG_COMF, &comf); + if (ret) + return ret; + + cfg->flags = V4L2_MBUS_MASTER | V4L2_MBUS_DATA_ACTIVE_HIGH + | ((comj & COMJ_VSYNC_HIGH) ? V4L2_MBUS_VSYNC_ACTIVE_HIGH + : V4L2_MBUS_VSYNC_ACTIVE_LOW) + | ((comf & COMF_HREF_LOW) ? V4L2_MBUS_HSYNC_ACTIVE_LOW + : V4L2_MBUS_HSYNC_ACTIVE_HIGH) + | ((comj & COMJ_PCLK_RISING) ? V4L2_MBUS_PCLK_SAMPLE_RISING + : V4L2_MBUS_PCLK_SAMPLE_FALLING); + cfg->type = V4L2_MBUS_PARALLEL; + + return 0; +} + +/* Alter bus settings on camera side */ +static int ov6650_set_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + if (cfg->flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + ret = ov6650_reg_rmw(client, REG_COMJ, COMJ_PCLK_RISING, 0); + else if (cfg->flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + ret = ov6650_reg_rmw(client, REG_COMJ, 0, COMJ_PCLK_RISING); + if (ret) + return ret; + + if (cfg->flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + ret = ov6650_reg_rmw(client, REG_COMF, COMF_HREF_LOW, 0); + else if (cfg->flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + ret = ov6650_reg_rmw(client, REG_COMF, 0, COMF_HREF_LOW); + if (ret) + return ret; + + if (cfg->flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + ret = ov6650_reg_rmw(client, REG_COMJ, COMJ_VSYNC_HIGH, 0); + else if (cfg->flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + ret = ov6650_reg_rmw(client, REG_COMJ, 0, COMJ_VSYNC_HIGH); + if (ret) + return ret; + + /* + * Update the configuration to report what is actually applied to + * the hardware. + */ + return ov6650_get_mbus_config(sd, pad, cfg); +} + +static const struct v4l2_subdev_video_ops ov6650_video_ops = { + .s_stream = ov6650_s_stream, + .g_frame_interval = ov6650_g_frame_interval, + .s_frame_interval = ov6650_s_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops ov6650_pad_ops = { + .enum_mbus_code = ov6650_enum_mbus_code, + .get_selection = ov6650_get_selection, + .set_selection = ov6650_set_selection, + .get_fmt = ov6650_get_fmt, + .set_fmt = ov6650_set_fmt, + .get_mbus_config = ov6650_get_mbus_config, + .set_mbus_config = ov6650_set_mbus_config, +}; + +static const struct v4l2_subdev_ops ov6650_subdev_ops = { + .core = &ov6650_core_ops, + .video = &ov6650_video_ops, + .pad = &ov6650_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops ov6650_internal_ops = { + .registered = ov6650_video_probe, +}; + +/* + * i2c_driver function + */ +static int ov6650_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov6650 *priv; + int ret; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov6650_subdev_ops); + v4l2_ctrl_handler_init(&priv->hdl, 13); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + priv->autogain = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + priv->gain = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_GAIN, 0, 0x3f, 1, DEF_GAIN); + priv->autowb = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + priv->blue = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_BLUE_BALANCE, 0, 0xff, 1, DEF_BLUE); + priv->red = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_RED_BALANCE, 0, 0xff, 1, DEF_RED); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_SATURATION, 0, 0xf, 1, 0x8); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_HUE, 0, HUE_MASK, 1, DEF_HUE); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 0xff, 1, 0x80); + priv->autoexposure = v4l2_ctrl_new_std_menu(&priv->hdl, + &ov6550_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, V4L2_EXPOSURE_AUTO); + priv->exposure = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 0xff, 1, DEF_AECH); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_GAMMA, 0, 0xff, 1, 0x12); + + priv->subdev.ctrl_handler = &priv->hdl; + if (priv->hdl.error) { + ret = priv->hdl.error; + goto ectlhdlfree; + } + + v4l2_ctrl_auto_cluster(2, &priv->autogain, 0, true); + v4l2_ctrl_auto_cluster(3, &priv->autowb, 0, true); + v4l2_ctrl_auto_cluster(2, &priv->autoexposure, + V4L2_EXPOSURE_MANUAL, true); + + priv->rect.left = DEF_HSTRT << 1; + priv->rect.top = DEF_VSTRT << 1; + priv->rect.width = W_CIF; + priv->rect.height = H_CIF; + + /* Hardware default frame interval */ + priv->tpf.numerator = GET_CLKRC_DIV(DEF_CLKRC); + priv->tpf.denominator = FRAME_RATE_MAX; + + priv->subdev.internal_ops = &ov6650_internal_ops; + + ret = v4l2_async_register_subdev(&priv->subdev); + if (!ret) + return 0; +ectlhdlfree: + v4l2_ctrl_handler_free(&priv->hdl); + + return ret; +} + +static int ov6650_remove(struct i2c_client *client) +{ + struct ov6650 *priv = to_ov6650(client); + + v4l2_clk_put(priv->clk); + v4l2_async_unregister_subdev(&priv->subdev); + v4l2_ctrl_handler_free(&priv->hdl); + return 0; +} + +static const struct i2c_device_id ov6650_id[] = { + { "ov6650", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov6650_id); + +static struct i2c_driver ov6650_i2c_driver = { + .driver = { + .name = "ov6650", + }, + .probe = ov6650_probe, + .remove = ov6650_remove, + .id_table = ov6650_id, +}; + +module_i2c_driver(ov6650_i2c_driver); + +MODULE_DESCRIPTION("V4L2 subdevice driver for OmniVision OV6650 camera sensor"); +MODULE_AUTHOR("Janusz Krzysztofik <jmkrzyszt@gmail.com"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c new file mode 100644 index 000000000..0c10203f8 --- /dev/null +++ b/drivers/media/i2c/ov7251.c @@ -0,0 +1,1503 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the OV7251 camera sensor. + * + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2017-2018, Linaro Ltd. + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define OV7251_SC_MODE_SELECT 0x0100 +#define OV7251_SC_MODE_SELECT_SW_STANDBY 0x0 +#define OV7251_SC_MODE_SELECT_STREAMING 0x1 + +#define OV7251_CHIP_ID_HIGH 0x300a +#define OV7251_CHIP_ID_HIGH_BYTE 0x77 +#define OV7251_CHIP_ID_LOW 0x300b +#define OV7251_CHIP_ID_LOW_BYTE 0x50 +#define OV7251_SC_GP_IO_IN1 0x3029 +#define OV7251_AEC_EXPO_0 0x3500 +#define OV7251_AEC_EXPO_1 0x3501 +#define OV7251_AEC_EXPO_2 0x3502 +#define OV7251_AEC_AGC_ADJ_0 0x350a +#define OV7251_AEC_AGC_ADJ_1 0x350b +#define OV7251_TIMING_FORMAT1 0x3820 +#define OV7251_TIMING_FORMAT1_VFLIP BIT(2) +#define OV7251_TIMING_FORMAT2 0x3821 +#define OV7251_TIMING_FORMAT2_MIRROR BIT(2) +#define OV7251_PRE_ISP_00 0x5e00 +#define OV7251_PRE_ISP_00_TEST_PATTERN BIT(7) + +struct reg_value { + u16 reg; + u8 val; +}; + +struct ov7251_mode_info { + u32 width; + u32 height; + const struct reg_value *data; + u32 data_size; + u32 pixel_clock; + u32 link_freq; + u16 exposure_max; + u16 exposure_def; + struct v4l2_fract timeperframe; +}; + +struct ov7251 { + struct i2c_client *i2c_client; + struct device *dev; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_fwnode_endpoint ep; + struct v4l2_mbus_framefmt fmt; + struct v4l2_rect crop; + struct clk *xclk; + u32 xclk_freq; + + struct regulator *io_regulator; + struct regulator *core_regulator; + struct regulator *analog_regulator; + + const struct ov7251_mode_info *current_mode; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *pixel_clock; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; + + /* Cached register values */ + u8 aec_pk_manual; + u8 pre_isp_00; + u8 timing_format1; + u8 timing_format2; + + struct mutex lock; /* lock to protect power state, ctrls and mode */ + bool power_on; + + struct gpio_desc *enable_gpio; +}; + +static inline struct ov7251 *to_ov7251(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov7251, sd); +} + +static const struct reg_value ov7251_global_init_setting[] = { + { 0x0103, 0x01 }, + { 0x303b, 0x02 }, +}; + +static const struct reg_value ov7251_setting_vga_30fps[] = { + { 0x3005, 0x00 }, + { 0x3012, 0xc0 }, + { 0x3013, 0xd2 }, + { 0x3014, 0x04 }, + { 0x3016, 0xf0 }, + { 0x3017, 0xf0 }, + { 0x3018, 0xf0 }, + { 0x301a, 0xf0 }, + { 0x301b, 0xf0 }, + { 0x301c, 0xf0 }, + { 0x3023, 0x05 }, + { 0x3037, 0xf0 }, + { 0x3098, 0x04 }, /* pll2 pre divider */ + { 0x3099, 0x28 }, /* pll2 multiplier */ + { 0x309a, 0x05 }, /* pll2 sys divider */ + { 0x309b, 0x04 }, /* pll2 adc divider */ + { 0x309d, 0x00 }, /* pll2 divider */ + { 0x30b0, 0x0a }, /* pll1 pix divider */ + { 0x30b1, 0x01 }, /* pll1 divider */ + { 0x30b3, 0x64 }, /* pll1 multiplier */ + { 0x30b4, 0x03 }, /* pll1 pre divider */ + { 0x30b5, 0x05 }, /* pll1 mipi divider */ + { 0x3106, 0xda }, + { 0x3503, 0x07 }, + { 0x3509, 0x10 }, + { 0x3600, 0x1c }, + { 0x3602, 0x62 }, + { 0x3620, 0xb7 }, + { 0x3622, 0x04 }, + { 0x3626, 0x21 }, + { 0x3627, 0x30 }, + { 0x3630, 0x44 }, + { 0x3631, 0x35 }, + { 0x3634, 0x60 }, + { 0x3636, 0x00 }, + { 0x3662, 0x01 }, + { 0x3663, 0x70 }, + { 0x3664, 0x50 }, + { 0x3666, 0x0a }, + { 0x3669, 0x1a }, + { 0x366a, 0x00 }, + { 0x366b, 0x50 }, + { 0x3673, 0x01 }, + { 0x3674, 0xff }, + { 0x3675, 0x03 }, + { 0x3705, 0xc1 }, + { 0x3709, 0x40 }, + { 0x373c, 0x08 }, + { 0x3742, 0x00 }, + { 0x3757, 0xb3 }, + { 0x3788, 0x00 }, + { 0x37a8, 0x01 }, + { 0x37a9, 0xc0 }, + { 0x3800, 0x00 }, + { 0x3801, 0x04 }, + { 0x3802, 0x00 }, + { 0x3803, 0x04 }, + { 0x3804, 0x02 }, + { 0x3805, 0x8b }, + { 0x3806, 0x01 }, + { 0x3807, 0xeb }, + { 0x3808, 0x02 }, /* width high */ + { 0x3809, 0x80 }, /* width low */ + { 0x380a, 0x01 }, /* height high */ + { 0x380b, 0xe0 }, /* height low */ + { 0x380c, 0x03 }, /* total horiz timing high */ + { 0x380d, 0xa0 }, /* total horiz timing low */ + { 0x380e, 0x06 }, /* total vertical timing high */ + { 0x380f, 0xbc }, /* total vertical timing low */ + { 0x3810, 0x00 }, + { 0x3811, 0x04 }, + { 0x3812, 0x00 }, + { 0x3813, 0x05 }, + { 0x3814, 0x11 }, + { 0x3815, 0x11 }, + { 0x3820, 0x40 }, + { 0x3821, 0x00 }, + { 0x382f, 0x0e }, + { 0x3832, 0x00 }, + { 0x3833, 0x05 }, + { 0x3834, 0x00 }, + { 0x3835, 0x0c }, + { 0x3837, 0x00 }, + { 0x3b80, 0x00 }, + { 0x3b81, 0xa5 }, + { 0x3b82, 0x10 }, + { 0x3b83, 0x00 }, + { 0x3b84, 0x08 }, + { 0x3b85, 0x00 }, + { 0x3b86, 0x01 }, + { 0x3b87, 0x00 }, + { 0x3b88, 0x00 }, + { 0x3b89, 0x00 }, + { 0x3b8a, 0x00 }, + { 0x3b8b, 0x05 }, + { 0x3b8c, 0x00 }, + { 0x3b8d, 0x00 }, + { 0x3b8e, 0x00 }, + { 0x3b8f, 0x1a }, + { 0x3b94, 0x05 }, + { 0x3b95, 0xf2 }, + { 0x3b96, 0x40 }, + { 0x3c00, 0x89 }, + { 0x3c01, 0x63 }, + { 0x3c02, 0x01 }, + { 0x3c03, 0x00 }, + { 0x3c04, 0x00 }, + { 0x3c05, 0x03 }, + { 0x3c06, 0x00 }, + { 0x3c07, 0x06 }, + { 0x3c0c, 0x01 }, + { 0x3c0d, 0xd0 }, + { 0x3c0e, 0x02 }, + { 0x3c0f, 0x0a }, + { 0x4001, 0x42 }, + { 0x4004, 0x04 }, + { 0x4005, 0x00 }, + { 0x404e, 0x01 }, + { 0x4300, 0xff }, + { 0x4301, 0x00 }, + { 0x4315, 0x00 }, + { 0x4501, 0x48 }, + { 0x4600, 0x00 }, + { 0x4601, 0x4e }, + { 0x4801, 0x0f }, + { 0x4806, 0x0f }, + { 0x4819, 0xaa }, + { 0x4823, 0x3e }, + { 0x4837, 0x19 }, + { 0x4a0d, 0x00 }, + { 0x4a47, 0x7f }, + { 0x4a49, 0xf0 }, + { 0x4a4b, 0x30 }, + { 0x5000, 0x85 }, + { 0x5001, 0x80 }, +}; + +static const struct reg_value ov7251_setting_vga_60fps[] = { + { 0x3005, 0x00 }, + { 0x3012, 0xc0 }, + { 0x3013, 0xd2 }, + { 0x3014, 0x04 }, + { 0x3016, 0x10 }, + { 0x3017, 0x00 }, + { 0x3018, 0x00 }, + { 0x301a, 0x00 }, + { 0x301b, 0x00 }, + { 0x301c, 0x00 }, + { 0x3023, 0x05 }, + { 0x3037, 0xf0 }, + { 0x3098, 0x04 }, /* pll2 pre divider */ + { 0x3099, 0x28 }, /* pll2 multiplier */ + { 0x309a, 0x05 }, /* pll2 sys divider */ + { 0x309b, 0x04 }, /* pll2 adc divider */ + { 0x309d, 0x00 }, /* pll2 divider */ + { 0x30b0, 0x0a }, /* pll1 pix divider */ + { 0x30b1, 0x01 }, /* pll1 divider */ + { 0x30b3, 0x64 }, /* pll1 multiplier */ + { 0x30b4, 0x03 }, /* pll1 pre divider */ + { 0x30b5, 0x05 }, /* pll1 mipi divider */ + { 0x3106, 0xda }, + { 0x3503, 0x07 }, + { 0x3509, 0x10 }, + { 0x3600, 0x1c }, + { 0x3602, 0x62 }, + { 0x3620, 0xb7 }, + { 0x3622, 0x04 }, + { 0x3626, 0x21 }, + { 0x3627, 0x30 }, + { 0x3630, 0x44 }, + { 0x3631, 0x35 }, + { 0x3634, 0x60 }, + { 0x3636, 0x00 }, + { 0x3662, 0x01 }, + { 0x3663, 0x70 }, + { 0x3664, 0x50 }, + { 0x3666, 0x0a }, + { 0x3669, 0x1a }, + { 0x366a, 0x00 }, + { 0x366b, 0x50 }, + { 0x3673, 0x01 }, + { 0x3674, 0xff }, + { 0x3675, 0x03 }, + { 0x3705, 0xc1 }, + { 0x3709, 0x40 }, + { 0x373c, 0x08 }, + { 0x3742, 0x00 }, + { 0x3757, 0xb3 }, + { 0x3788, 0x00 }, + { 0x37a8, 0x01 }, + { 0x37a9, 0xc0 }, + { 0x3800, 0x00 }, + { 0x3801, 0x04 }, + { 0x3802, 0x00 }, + { 0x3803, 0x04 }, + { 0x3804, 0x02 }, + { 0x3805, 0x8b }, + { 0x3806, 0x01 }, + { 0x3807, 0xeb }, + { 0x3808, 0x02 }, /* width high */ + { 0x3809, 0x80 }, /* width low */ + { 0x380a, 0x01 }, /* height high */ + { 0x380b, 0xe0 }, /* height low */ + { 0x380c, 0x03 }, /* total horiz timing high */ + { 0x380d, 0xa0 }, /* total horiz timing low */ + { 0x380e, 0x03 }, /* total vertical timing high */ + { 0x380f, 0x5c }, /* total vertical timing low */ + { 0x3810, 0x00 }, + { 0x3811, 0x04 }, + { 0x3812, 0x00 }, + { 0x3813, 0x05 }, + { 0x3814, 0x11 }, + { 0x3815, 0x11 }, + { 0x3820, 0x40 }, + { 0x3821, 0x00 }, + { 0x382f, 0x0e }, + { 0x3832, 0x00 }, + { 0x3833, 0x05 }, + { 0x3834, 0x00 }, + { 0x3835, 0x0c }, + { 0x3837, 0x00 }, + { 0x3b80, 0x00 }, + { 0x3b81, 0xa5 }, + { 0x3b82, 0x10 }, + { 0x3b83, 0x00 }, + { 0x3b84, 0x08 }, + { 0x3b85, 0x00 }, + { 0x3b86, 0x01 }, + { 0x3b87, 0x00 }, + { 0x3b88, 0x00 }, + { 0x3b89, 0x00 }, + { 0x3b8a, 0x00 }, + { 0x3b8b, 0x05 }, + { 0x3b8c, 0x00 }, + { 0x3b8d, 0x00 }, + { 0x3b8e, 0x00 }, + { 0x3b8f, 0x1a }, + { 0x3b94, 0x05 }, + { 0x3b95, 0xf2 }, + { 0x3b96, 0x40 }, + { 0x3c00, 0x89 }, + { 0x3c01, 0x63 }, + { 0x3c02, 0x01 }, + { 0x3c03, 0x00 }, + { 0x3c04, 0x00 }, + { 0x3c05, 0x03 }, + { 0x3c06, 0x00 }, + { 0x3c07, 0x06 }, + { 0x3c0c, 0x01 }, + { 0x3c0d, 0xd0 }, + { 0x3c0e, 0x02 }, + { 0x3c0f, 0x0a }, + { 0x4001, 0x42 }, + { 0x4004, 0x04 }, + { 0x4005, 0x00 }, + { 0x404e, 0x01 }, + { 0x4300, 0xff }, + { 0x4301, 0x00 }, + { 0x4315, 0x00 }, + { 0x4501, 0x48 }, + { 0x4600, 0x00 }, + { 0x4601, 0x4e }, + { 0x4801, 0x0f }, + { 0x4806, 0x0f }, + { 0x4819, 0xaa }, + { 0x4823, 0x3e }, + { 0x4837, 0x19 }, + { 0x4a0d, 0x00 }, + { 0x4a47, 0x7f }, + { 0x4a49, 0xf0 }, + { 0x4a4b, 0x30 }, + { 0x5000, 0x85 }, + { 0x5001, 0x80 }, +}; + +static const struct reg_value ov7251_setting_vga_90fps[] = { + { 0x3005, 0x00 }, + { 0x3012, 0xc0 }, + { 0x3013, 0xd2 }, + { 0x3014, 0x04 }, + { 0x3016, 0x10 }, + { 0x3017, 0x00 }, + { 0x3018, 0x00 }, + { 0x301a, 0x00 }, + { 0x301b, 0x00 }, + { 0x301c, 0x00 }, + { 0x3023, 0x05 }, + { 0x3037, 0xf0 }, + { 0x3098, 0x04 }, /* pll2 pre divider */ + { 0x3099, 0x28 }, /* pll2 multiplier */ + { 0x309a, 0x05 }, /* pll2 sys divider */ + { 0x309b, 0x04 }, /* pll2 adc divider */ + { 0x309d, 0x00 }, /* pll2 divider */ + { 0x30b0, 0x0a }, /* pll1 pix divider */ + { 0x30b1, 0x01 }, /* pll1 divider */ + { 0x30b3, 0x64 }, /* pll1 multiplier */ + { 0x30b4, 0x03 }, /* pll1 pre divider */ + { 0x30b5, 0x05 }, /* pll1 mipi divider */ + { 0x3106, 0xda }, + { 0x3503, 0x07 }, + { 0x3509, 0x10 }, + { 0x3600, 0x1c }, + { 0x3602, 0x62 }, + { 0x3620, 0xb7 }, + { 0x3622, 0x04 }, + { 0x3626, 0x21 }, + { 0x3627, 0x30 }, + { 0x3630, 0x44 }, + { 0x3631, 0x35 }, + { 0x3634, 0x60 }, + { 0x3636, 0x00 }, + { 0x3662, 0x01 }, + { 0x3663, 0x70 }, + { 0x3664, 0x50 }, + { 0x3666, 0x0a }, + { 0x3669, 0x1a }, + { 0x366a, 0x00 }, + { 0x366b, 0x50 }, + { 0x3673, 0x01 }, + { 0x3674, 0xff }, + { 0x3675, 0x03 }, + { 0x3705, 0xc1 }, + { 0x3709, 0x40 }, + { 0x373c, 0x08 }, + { 0x3742, 0x00 }, + { 0x3757, 0xb3 }, + { 0x3788, 0x00 }, + { 0x37a8, 0x01 }, + { 0x37a9, 0xc0 }, + { 0x3800, 0x00 }, + { 0x3801, 0x04 }, + { 0x3802, 0x00 }, + { 0x3803, 0x04 }, + { 0x3804, 0x02 }, + { 0x3805, 0x8b }, + { 0x3806, 0x01 }, + { 0x3807, 0xeb }, + { 0x3808, 0x02 }, /* width high */ + { 0x3809, 0x80 }, /* width low */ + { 0x380a, 0x01 }, /* height high */ + { 0x380b, 0xe0 }, /* height low */ + { 0x380c, 0x03 }, /* total horiz timing high */ + { 0x380d, 0xa0 }, /* total horiz timing low */ + { 0x380e, 0x02 }, /* total vertical timing high */ + { 0x380f, 0x3c }, /* total vertical timing low */ + { 0x3810, 0x00 }, + { 0x3811, 0x04 }, + { 0x3812, 0x00 }, + { 0x3813, 0x05 }, + { 0x3814, 0x11 }, + { 0x3815, 0x11 }, + { 0x3820, 0x40 }, + { 0x3821, 0x00 }, + { 0x382f, 0x0e }, + { 0x3832, 0x00 }, + { 0x3833, 0x05 }, + { 0x3834, 0x00 }, + { 0x3835, 0x0c }, + { 0x3837, 0x00 }, + { 0x3b80, 0x00 }, + { 0x3b81, 0xa5 }, + { 0x3b82, 0x10 }, + { 0x3b83, 0x00 }, + { 0x3b84, 0x08 }, + { 0x3b85, 0x00 }, + { 0x3b86, 0x01 }, + { 0x3b87, 0x00 }, + { 0x3b88, 0x00 }, + { 0x3b89, 0x00 }, + { 0x3b8a, 0x00 }, + { 0x3b8b, 0x05 }, + { 0x3b8c, 0x00 }, + { 0x3b8d, 0x00 }, + { 0x3b8e, 0x00 }, + { 0x3b8f, 0x1a }, + { 0x3b94, 0x05 }, + { 0x3b95, 0xf2 }, + { 0x3b96, 0x40 }, + { 0x3c00, 0x89 }, + { 0x3c01, 0x63 }, + { 0x3c02, 0x01 }, + { 0x3c03, 0x00 }, + { 0x3c04, 0x00 }, + { 0x3c05, 0x03 }, + { 0x3c06, 0x00 }, + { 0x3c07, 0x06 }, + { 0x3c0c, 0x01 }, + { 0x3c0d, 0xd0 }, + { 0x3c0e, 0x02 }, + { 0x3c0f, 0x0a }, + { 0x4001, 0x42 }, + { 0x4004, 0x04 }, + { 0x4005, 0x00 }, + { 0x404e, 0x01 }, + { 0x4300, 0xff }, + { 0x4301, 0x00 }, + { 0x4315, 0x00 }, + { 0x4501, 0x48 }, + { 0x4600, 0x00 }, + { 0x4601, 0x4e }, + { 0x4801, 0x0f }, + { 0x4806, 0x0f }, + { 0x4819, 0xaa }, + { 0x4823, 0x3e }, + { 0x4837, 0x19 }, + { 0x4a0d, 0x00 }, + { 0x4a47, 0x7f }, + { 0x4a49, 0xf0 }, + { 0x4a4b, 0x30 }, + { 0x5000, 0x85 }, + { 0x5001, 0x80 }, +}; + +static const s64 link_freq[] = { + 240000000, +}; + +static const struct ov7251_mode_info ov7251_mode_info_data[] = { + { + .width = 640, + .height = 480, + .data = ov7251_setting_vga_30fps, + .data_size = ARRAY_SIZE(ov7251_setting_vga_30fps), + .pixel_clock = 48000000, + .link_freq = 0, /* an index in link_freq[] */ + .exposure_max = 1704, + .exposure_def = 504, + .timeperframe = { + .numerator = 100, + .denominator = 3000 + } + }, + { + .width = 640, + .height = 480, + .data = ov7251_setting_vga_60fps, + .data_size = ARRAY_SIZE(ov7251_setting_vga_60fps), + .pixel_clock = 48000000, + .link_freq = 0, /* an index in link_freq[] */ + .exposure_max = 840, + .exposure_def = 504, + .timeperframe = { + .numerator = 100, + .denominator = 6014 + } + }, + { + .width = 640, + .height = 480, + .data = ov7251_setting_vga_90fps, + .data_size = ARRAY_SIZE(ov7251_setting_vga_90fps), + .pixel_clock = 48000000, + .link_freq = 0, /* an index in link_freq[] */ + .exposure_max = 552, + .exposure_def = 504, + .timeperframe = { + .numerator = 100, + .denominator = 9043 + } + }, +}; + +static int ov7251_regulators_enable(struct ov7251 *ov7251) +{ + int ret; + + /* OV7251 power up sequence requires core regulator + * to be enabled not earlier than io regulator + */ + + ret = regulator_enable(ov7251->io_regulator); + if (ret < 0) { + dev_err(ov7251->dev, "set io voltage failed\n"); + return ret; + } + + ret = regulator_enable(ov7251->analog_regulator); + if (ret) { + dev_err(ov7251->dev, "set analog voltage failed\n"); + goto err_disable_io; + } + + ret = regulator_enable(ov7251->core_regulator); + if (ret) { + dev_err(ov7251->dev, "set core voltage failed\n"); + goto err_disable_analog; + } + + return 0; + +err_disable_analog: + regulator_disable(ov7251->analog_regulator); + +err_disable_io: + regulator_disable(ov7251->io_regulator); + + return ret; +} + +static void ov7251_regulators_disable(struct ov7251 *ov7251) +{ + int ret; + + ret = regulator_disable(ov7251->core_regulator); + if (ret < 0) + dev_err(ov7251->dev, "core regulator disable failed\n"); + + ret = regulator_disable(ov7251->analog_regulator); + if (ret < 0) + dev_err(ov7251->dev, "analog regulator disable failed\n"); + + ret = regulator_disable(ov7251->io_regulator); + if (ret < 0) + dev_err(ov7251->dev, "io regulator disable failed\n"); +} + +static int ov7251_write_reg(struct ov7251 *ov7251, u16 reg, u8 val) +{ + u8 regbuf[3]; + int ret; + + regbuf[0] = reg >> 8; + regbuf[1] = reg & 0xff; + regbuf[2] = val; + + ret = i2c_master_send(ov7251->i2c_client, regbuf, 3); + if (ret < 0) { + dev_err(ov7251->dev, "%s: write reg error %d: reg=%x, val=%x\n", + __func__, ret, reg, val); + return ret; + } + + return 0; +} + +static int ov7251_write_seq_regs(struct ov7251 *ov7251, u16 reg, u8 *val, + u8 num) +{ + u8 regbuf[5]; + u8 nregbuf = sizeof(reg) + num * sizeof(*val); + int ret = 0; + + if (nregbuf > sizeof(regbuf)) + return -EINVAL; + + regbuf[0] = reg >> 8; + regbuf[1] = reg & 0xff; + + memcpy(regbuf + 2, val, num); + + ret = i2c_master_send(ov7251->i2c_client, regbuf, nregbuf); + if (ret < 0) { + dev_err(ov7251->dev, + "%s: write seq regs error %d: first reg=%x\n", + __func__, ret, reg); + return ret; + } + + return 0; +} + +static int ov7251_read_reg(struct ov7251 *ov7251, u16 reg, u8 *val) +{ + u8 regbuf[2]; + int ret; + + regbuf[0] = reg >> 8; + regbuf[1] = reg & 0xff; + + ret = i2c_master_send(ov7251->i2c_client, regbuf, 2); + if (ret < 0) { + dev_err(ov7251->dev, "%s: write reg error %d: reg=%x\n", + __func__, ret, reg); + return ret; + } + + ret = i2c_master_recv(ov7251->i2c_client, val, 1); + if (ret < 0) { + dev_err(ov7251->dev, "%s: read reg error %d: reg=%x\n", + __func__, ret, reg); + return ret; + } + + return 0; +} + +static int ov7251_set_exposure(struct ov7251 *ov7251, s32 exposure) +{ + u16 reg; + u8 val[3]; + + reg = OV7251_AEC_EXPO_0; + val[0] = (exposure & 0xf000) >> 12; /* goes to OV7251_AEC_EXPO_0 */ + val[1] = (exposure & 0x0ff0) >> 4; /* goes to OV7251_AEC_EXPO_1 */ + val[2] = (exposure & 0x000f) << 4; /* goes to OV7251_AEC_EXPO_2 */ + + return ov7251_write_seq_regs(ov7251, reg, val, 3); +} + +static int ov7251_set_gain(struct ov7251 *ov7251, s32 gain) +{ + u16 reg; + u8 val[2]; + + reg = OV7251_AEC_AGC_ADJ_0; + val[0] = (gain & 0x0300) >> 8; /* goes to OV7251_AEC_AGC_ADJ_0 */ + val[1] = gain & 0xff; /* goes to OV7251_AEC_AGC_ADJ_1 */ + + return ov7251_write_seq_regs(ov7251, reg, val, 2); +} + +static int ov7251_set_register_array(struct ov7251 *ov7251, + const struct reg_value *settings, + unsigned int num_settings) +{ + unsigned int i; + int ret; + + for (i = 0; i < num_settings; ++i, ++settings) { + ret = ov7251_write_reg(ov7251, settings->reg, settings->val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int ov7251_set_power_on(struct ov7251 *ov7251) +{ + int ret; + u32 wait_us; + + ret = ov7251_regulators_enable(ov7251); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(ov7251->xclk); + if (ret < 0) { + dev_err(ov7251->dev, "clk prepare enable failed\n"); + ov7251_regulators_disable(ov7251); + return ret; + } + + gpiod_set_value_cansleep(ov7251->enable_gpio, 1); + + /* wait at least 65536 external clock cycles */ + wait_us = DIV_ROUND_UP(65536 * 1000, + DIV_ROUND_UP(ov7251->xclk_freq, 1000)); + usleep_range(wait_us, wait_us + 1000); + + return 0; +} + +static void ov7251_set_power_off(struct ov7251 *ov7251) +{ + clk_disable_unprepare(ov7251->xclk); + gpiod_set_value_cansleep(ov7251->enable_gpio, 0); + ov7251_regulators_disable(ov7251); +} + +static int ov7251_s_power(struct v4l2_subdev *sd, int on) +{ + struct ov7251 *ov7251 = to_ov7251(sd); + int ret = 0; + + mutex_lock(&ov7251->lock); + + /* If the power state is not modified - no work to do. */ + if (ov7251->power_on == !!on) + goto exit; + + if (on) { + ret = ov7251_set_power_on(ov7251); + if (ret < 0) + goto exit; + + ret = ov7251_set_register_array(ov7251, + ov7251_global_init_setting, + ARRAY_SIZE(ov7251_global_init_setting)); + if (ret < 0) { + dev_err(ov7251->dev, "could not set init registers\n"); + ov7251_set_power_off(ov7251); + goto exit; + } + + ov7251->power_on = true; + } else { + ov7251_set_power_off(ov7251); + ov7251->power_on = false; + } + +exit: + mutex_unlock(&ov7251->lock); + + return ret; +} + +static int ov7251_set_hflip(struct ov7251 *ov7251, s32 value) +{ + u8 val = ov7251->timing_format2; + int ret; + + if (value) + val |= OV7251_TIMING_FORMAT2_MIRROR; + else + val &= ~OV7251_TIMING_FORMAT2_MIRROR; + + ret = ov7251_write_reg(ov7251, OV7251_TIMING_FORMAT2, val); + if (!ret) + ov7251->timing_format2 = val; + + return ret; +} + +static int ov7251_set_vflip(struct ov7251 *ov7251, s32 value) +{ + u8 val = ov7251->timing_format1; + int ret; + + if (value) + val |= OV7251_TIMING_FORMAT1_VFLIP; + else + val &= ~OV7251_TIMING_FORMAT1_VFLIP; + + ret = ov7251_write_reg(ov7251, OV7251_TIMING_FORMAT1, val); + if (!ret) + ov7251->timing_format1 = val; + + return ret; +} + +static int ov7251_set_test_pattern(struct ov7251 *ov7251, s32 value) +{ + u8 val = ov7251->pre_isp_00; + int ret; + + if (value) + val |= OV7251_PRE_ISP_00_TEST_PATTERN; + else + val &= ~OV7251_PRE_ISP_00_TEST_PATTERN; + + ret = ov7251_write_reg(ov7251, OV7251_PRE_ISP_00, val); + if (!ret) + ov7251->pre_isp_00 = val; + + return ret; +} + +static const char * const ov7251_test_pattern_menu[] = { + "Disabled", + "Vertical Pattern Bars", +}; + +static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov7251 *ov7251 = container_of(ctrl->handler, + struct ov7251, ctrls); + int ret; + + /* v4l2_ctrl_lock() locks our mutex */ + + if (!ov7251->power_on) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; + case V4L2_CID_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov7251_set_test_pattern(ov7251, ctrl->val); + break; + case V4L2_CID_HFLIP: + ret = ov7251_set_hflip(ov7251, ctrl->val); + break; + case V4L2_CID_VFLIP: + ret = ov7251_set_vflip(ov7251, ctrl->val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops ov7251_ctrl_ops = { + .s_ctrl = ov7251_s_ctrl, +}; + +static int ov7251_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_Y10_1X10; + + return 0; +} + +static int ov7251_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->code != MEDIA_BUS_FMT_Y10_1X10) + return -EINVAL; + + if (fse->index >= ARRAY_SIZE(ov7251_mode_info_data)) + return -EINVAL; + + fse->min_width = ov7251_mode_info_data[fse->index].width; + fse->max_width = ov7251_mode_info_data[fse->index].width; + fse->min_height = ov7251_mode_info_data[fse->index].height; + fse->max_height = ov7251_mode_info_data[fse->index].height; + + return 0; +} + +static int ov7251_enum_frame_ival(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + unsigned int index = fie->index; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ov7251_mode_info_data); i++) { + if (fie->width != ov7251_mode_info_data[i].width || + fie->height != ov7251_mode_info_data[i].height) + continue; + + if (index-- == 0) { + fie->interval = ov7251_mode_info_data[i].timeperframe; + return 0; + } + } + + return -EINVAL; +} + +static struct v4l2_mbus_framefmt * +__ov7251_get_pad_format(struct ov7251 *ov7251, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&ov7251->sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &ov7251->fmt; + default: + return NULL; + } +} + +static int ov7251_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov7251 *ov7251 = to_ov7251(sd); + + mutex_lock(&ov7251->lock); + format->format = *__ov7251_get_pad_format(ov7251, cfg, format->pad, + format->which); + mutex_unlock(&ov7251->lock); + + return 0; +} + +static struct v4l2_rect * +__ov7251_get_pad_crop(struct ov7251 *ov7251, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&ov7251->sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &ov7251->crop; + default: + return NULL; + } +} + +static inline u32 avg_fps(const struct v4l2_fract *t) +{ + return (t->denominator + (t->numerator >> 1)) / t->numerator; +} + +static const struct ov7251_mode_info * +ov7251_find_mode_by_ival(struct ov7251 *ov7251, struct v4l2_fract *timeperframe) +{ + const struct ov7251_mode_info *mode = ov7251->current_mode; + unsigned int fps_req = avg_fps(timeperframe); + unsigned int max_dist_match = (unsigned int) -1; + unsigned int i, n = 0; + + for (i = 0; i < ARRAY_SIZE(ov7251_mode_info_data); i++) { + unsigned int dist; + unsigned int fps_tmp; + + if (mode->width != ov7251_mode_info_data[i].width || + mode->height != ov7251_mode_info_data[i].height) + continue; + + fps_tmp = avg_fps(&ov7251_mode_info_data[i].timeperframe); + + dist = abs(fps_req - fps_tmp); + + if (dist < max_dist_match) { + n = i; + max_dist_match = dist; + } + } + + return &ov7251_mode_info_data[n]; +} + +static int ov7251_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov7251 *ov7251 = to_ov7251(sd); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + const struct ov7251_mode_info *new_mode; + int ret = 0; + + mutex_lock(&ov7251->lock); + + __crop = __ov7251_get_pad_crop(ov7251, cfg, format->pad, format->which); + + new_mode = v4l2_find_nearest_size(ov7251_mode_info_data, + ARRAY_SIZE(ov7251_mode_info_data), + width, height, + format->format.width, format->format.height); + + __crop->width = new_mode->width; + __crop->height = new_mode->height; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ret = __v4l2_ctrl_s_ctrl_int64(ov7251->pixel_clock, + new_mode->pixel_clock); + if (ret < 0) + goto exit; + + ret = __v4l2_ctrl_s_ctrl(ov7251->link_freq, + new_mode->link_freq); + if (ret < 0) + goto exit; + + ret = __v4l2_ctrl_modify_range(ov7251->exposure, + 1, new_mode->exposure_max, + 1, new_mode->exposure_def); + if (ret < 0) + goto exit; + + ret = __v4l2_ctrl_s_ctrl(ov7251->exposure, + new_mode->exposure_def); + if (ret < 0) + goto exit; + + ret = __v4l2_ctrl_s_ctrl(ov7251->gain, 16); + if (ret < 0) + goto exit; + + ov7251->current_mode = new_mode; + } + + __format = __ov7251_get_pad_format(ov7251, cfg, format->pad, + format->which); + __format->width = __crop->width; + __format->height = __crop->height; + __format->code = MEDIA_BUS_FMT_Y10_1X10; + __format->field = V4L2_FIELD_NONE; + __format->colorspace = V4L2_COLORSPACE_SRGB; + __format->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(__format->colorspace); + __format->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + __format->colorspace, __format->ycbcr_enc); + __format->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(__format->colorspace); + + format->format = *__format; + +exit: + mutex_unlock(&ov7251->lock); + + return ret; +} + +static int ov7251_entity_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg) +{ + struct v4l2_subdev_format fmt = { + .which = cfg ? V4L2_SUBDEV_FORMAT_TRY + : V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .width = 640, + .height = 480 + } + }; + + ov7251_set_format(subdev, cfg, &fmt); + + return 0; +} + +static int ov7251_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct ov7251 *ov7251 = to_ov7251(sd); + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + mutex_lock(&ov7251->lock); + sel->r = *__ov7251_get_pad_crop(ov7251, cfg, sel->pad, + sel->which); + mutex_unlock(&ov7251->lock); + + return 0; +} + +static int ov7251_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct ov7251 *ov7251 = to_ov7251(subdev); + int ret; + + mutex_lock(&ov7251->lock); + + if (enable) { + ret = ov7251_set_register_array(ov7251, + ov7251->current_mode->data, + ov7251->current_mode->data_size); + if (ret < 0) { + dev_err(ov7251->dev, "could not set mode %dx%d\n", + ov7251->current_mode->width, + ov7251->current_mode->height); + goto exit; + } + ret = __v4l2_ctrl_handler_setup(&ov7251->ctrls); + if (ret < 0) { + dev_err(ov7251->dev, "could not sync v4l2 controls\n"); + goto exit; + } + ret = ov7251_write_reg(ov7251, OV7251_SC_MODE_SELECT, + OV7251_SC_MODE_SELECT_STREAMING); + } else { + ret = ov7251_write_reg(ov7251, OV7251_SC_MODE_SELECT, + OV7251_SC_MODE_SELECT_SW_STANDBY); + } + +exit: + mutex_unlock(&ov7251->lock); + + return ret; +} + +static int ov7251_get_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fi) +{ + struct ov7251 *ov7251 = to_ov7251(subdev); + + mutex_lock(&ov7251->lock); + fi->interval = ov7251->current_mode->timeperframe; + mutex_unlock(&ov7251->lock); + + return 0; +} + +static int ov7251_set_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fi) +{ + struct ov7251 *ov7251 = to_ov7251(subdev); + const struct ov7251_mode_info *new_mode; + int ret = 0; + + mutex_lock(&ov7251->lock); + new_mode = ov7251_find_mode_by_ival(ov7251, &fi->interval); + + if (new_mode != ov7251->current_mode) { + ret = __v4l2_ctrl_s_ctrl_int64(ov7251->pixel_clock, + new_mode->pixel_clock); + if (ret < 0) + goto exit; + + ret = __v4l2_ctrl_s_ctrl(ov7251->link_freq, + new_mode->link_freq); + if (ret < 0) + goto exit; + + ret = __v4l2_ctrl_modify_range(ov7251->exposure, + 1, new_mode->exposure_max, + 1, new_mode->exposure_def); + if (ret < 0) + goto exit; + + ret = __v4l2_ctrl_s_ctrl(ov7251->exposure, + new_mode->exposure_def); + if (ret < 0) + goto exit; + + ret = __v4l2_ctrl_s_ctrl(ov7251->gain, 16); + if (ret < 0) + goto exit; + + ov7251->current_mode = new_mode; + } + + fi->interval = ov7251->current_mode->timeperframe; + +exit: + mutex_unlock(&ov7251->lock); + + return ret; +} + +static const struct v4l2_subdev_core_ops ov7251_core_ops = { + .s_power = ov7251_s_power, +}; + +static const struct v4l2_subdev_video_ops ov7251_video_ops = { + .s_stream = ov7251_s_stream, + .g_frame_interval = ov7251_get_frame_interval, + .s_frame_interval = ov7251_set_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops ov7251_subdev_pad_ops = { + .init_cfg = ov7251_entity_init_cfg, + .enum_mbus_code = ov7251_enum_mbus_code, + .enum_frame_size = ov7251_enum_frame_size, + .enum_frame_interval = ov7251_enum_frame_ival, + .get_fmt = ov7251_get_format, + .set_fmt = ov7251_set_format, + .get_selection = ov7251_get_selection, +}; + +static const struct v4l2_subdev_ops ov7251_subdev_ops = { + .core = &ov7251_core_ops, + .video = &ov7251_video_ops, + .pad = &ov7251_subdev_pad_ops, +}; + +static int ov7251_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct fwnode_handle *endpoint; + struct ov7251 *ov7251; + u8 chip_id_high, chip_id_low, chip_rev; + int ret; + + ov7251 = devm_kzalloc(dev, sizeof(struct ov7251), GFP_KERNEL); + if (!ov7251) + return -ENOMEM; + + ov7251->i2c_client = client; + ov7251->dev = dev; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(endpoint, &ov7251->ep); + fwnode_handle_put(endpoint); + if (ret < 0) { + dev_err(dev, "parsing endpoint node failed\n"); + return ret; + } + + if (ov7251->ep.bus_type != V4L2_MBUS_CSI2_DPHY) { + dev_err(dev, "invalid bus type (%u), must be CSI2 (%u)\n", + ov7251->ep.bus_type, V4L2_MBUS_CSI2_DPHY); + return -EINVAL; + } + + /* get system clock (xclk) */ + ov7251->xclk = devm_clk_get(dev, "xclk"); + if (IS_ERR(ov7251->xclk)) { + dev_err(dev, "could not get xclk"); + return PTR_ERR(ov7251->xclk); + } + + ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &ov7251->xclk_freq); + if (ret) { + dev_err(dev, "could not get xclk frequency\n"); + return ret; + } + + /* external clock must be 24MHz, allow 1% tolerance */ + if (ov7251->xclk_freq < 23760000 || ov7251->xclk_freq > 24240000) { + dev_err(dev, "external clock frequency %u is not supported\n", + ov7251->xclk_freq); + return -EINVAL; + } + + ret = clk_set_rate(ov7251->xclk, ov7251->xclk_freq); + if (ret) { + dev_err(dev, "could not set xclk frequency\n"); + return ret; + } + + ov7251->io_regulator = devm_regulator_get(dev, "vdddo"); + if (IS_ERR(ov7251->io_regulator)) { + dev_err(dev, "cannot get io regulator\n"); + return PTR_ERR(ov7251->io_regulator); + } + + ov7251->core_regulator = devm_regulator_get(dev, "vddd"); + if (IS_ERR(ov7251->core_regulator)) { + dev_err(dev, "cannot get core regulator\n"); + return PTR_ERR(ov7251->core_regulator); + } + + ov7251->analog_regulator = devm_regulator_get(dev, "vdda"); + if (IS_ERR(ov7251->analog_regulator)) { + dev_err(dev, "cannot get analog regulator\n"); + return PTR_ERR(ov7251->analog_regulator); + } + + ov7251->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(ov7251->enable_gpio)) { + dev_err(dev, "cannot get enable gpio\n"); + return PTR_ERR(ov7251->enable_gpio); + } + + mutex_init(&ov7251->lock); + + v4l2_ctrl_handler_init(&ov7251->ctrls, 7); + ov7251->ctrls.lock = &ov7251->lock; + + v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, + 0, 0, ov7251_test_pattern_menu); + ov7251->pixel_clock = v4l2_ctrl_new_std(&ov7251->ctrls, + &ov7251_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 1, INT_MAX, 1, 1); + ov7251->link_freq = v4l2_ctrl_new_int_menu(&ov7251->ctrls, + &ov7251_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq) - 1, + 0, link_freq); + if (ov7251->link_freq) + ov7251->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ov7251->sd.ctrl_handler = &ov7251->ctrls; + + if (ov7251->ctrls.error) { + dev_err(dev, "%s: control initialization error %d\n", + __func__, ov7251->ctrls.error); + ret = ov7251->ctrls.error; + goto free_ctrl; + } + + v4l2_i2c_subdev_init(&ov7251->sd, client, &ov7251_subdev_ops); + ov7251->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov7251->pad.flags = MEDIA_PAD_FL_SOURCE; + ov7251->sd.dev = &client->dev; + ov7251->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&ov7251->sd.entity, 1, &ov7251->pad); + if (ret < 0) { + dev_err(dev, "could not register media entity\n"); + goto free_ctrl; + } + + ret = ov7251_s_power(&ov7251->sd, true); + if (ret < 0) { + dev_err(dev, "could not power up OV7251\n"); + goto free_entity; + } + + ret = ov7251_read_reg(ov7251, OV7251_CHIP_ID_HIGH, &chip_id_high); + if (ret < 0 || chip_id_high != OV7251_CHIP_ID_HIGH_BYTE) { + dev_err(dev, "could not read ID high\n"); + ret = -ENODEV; + goto power_down; + } + ret = ov7251_read_reg(ov7251, OV7251_CHIP_ID_LOW, &chip_id_low); + if (ret < 0 || chip_id_low != OV7251_CHIP_ID_LOW_BYTE) { + dev_err(dev, "could not read ID low\n"); + ret = -ENODEV; + goto power_down; + } + + ret = ov7251_read_reg(ov7251, OV7251_SC_GP_IO_IN1, &chip_rev); + if (ret < 0) { + dev_err(dev, "could not read revision\n"); + ret = -ENODEV; + goto power_down; + } + chip_rev >>= 4; + + dev_info(dev, "OV7251 revision %x (%s) detected at address 0x%02x\n", + chip_rev, + chip_rev == 0x4 ? "1A / 1B" : + chip_rev == 0x5 ? "1C / 1D" : + chip_rev == 0x6 ? "1E" : + chip_rev == 0x7 ? "1F" : "unknown", + client->addr); + + ret = ov7251_read_reg(ov7251, OV7251_PRE_ISP_00, + &ov7251->pre_isp_00); + if (ret < 0) { + dev_err(dev, "could not read test pattern value\n"); + ret = -ENODEV; + goto power_down; + } + + ret = ov7251_read_reg(ov7251, OV7251_TIMING_FORMAT1, + &ov7251->timing_format1); + if (ret < 0) { + dev_err(dev, "could not read vflip value\n"); + ret = -ENODEV; + goto power_down; + } + + ret = ov7251_read_reg(ov7251, OV7251_TIMING_FORMAT2, + &ov7251->timing_format2); + if (ret < 0) { + dev_err(dev, "could not read hflip value\n"); + ret = -ENODEV; + goto power_down; + } + + ov7251_s_power(&ov7251->sd, false); + + ret = v4l2_async_register_subdev(&ov7251->sd); + if (ret < 0) { + dev_err(dev, "could not register v4l2 device\n"); + goto free_entity; + } + + ov7251_entity_init_cfg(&ov7251->sd, NULL); + + return 0; + +power_down: + ov7251_s_power(&ov7251->sd, false); +free_entity: + media_entity_cleanup(&ov7251->sd.entity); +free_ctrl: + v4l2_ctrl_handler_free(&ov7251->ctrls); + mutex_destroy(&ov7251->lock); + + return ret; +} + +static int ov7251_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov7251 *ov7251 = to_ov7251(sd); + + v4l2_async_unregister_subdev(&ov7251->sd); + media_entity_cleanup(&ov7251->sd.entity); + v4l2_ctrl_handler_free(&ov7251->ctrls); + mutex_destroy(&ov7251->lock); + + return 0; +} + +static const struct of_device_id ov7251_of_match[] = { + { .compatible = "ovti,ov7251" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ov7251_of_match); + +static struct i2c_driver ov7251_i2c_driver = { + .driver = { + .of_match_table = ov7251_of_match, + .name = "ov7251", + }, + .probe_new = ov7251_probe, + .remove = ov7251_remove, +}; + +module_i2c_driver(ov7251_i2c_driver); + +MODULE_DESCRIPTION("Omnivision OV7251 Camera Driver"); +MODULE_AUTHOR("Todor Tomov <todor.tomov@linaro.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov7640.c b/drivers/media/i2c/ov7640.c new file mode 100644 index 000000000..010803d58 --- /dev/null +++ b/drivers/media/i2c/ov7640.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <linux/slab.h> + +MODULE_DESCRIPTION("OmniVision ov7640 sensor driver"); +MODULE_LICENSE("GPL v2"); + +static const u8 initial_registers[] = { + 0x12, 0x80, + 0x12, 0x54, + 0x14, 0x24, + 0x15, 0x01, + 0x28, 0x20, + 0x75, 0x82, + 0xFF, 0xFF, /* Terminator (reg 0xFF is unused) */ +}; + +static int write_regs(struct i2c_client *client, const u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0xFF; i += 2) + if (i2c_smbus_write_byte_data(client, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_ops ov7640_ops; + +static int ov7640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct v4l2_subdev *sd; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + sd = devm_kzalloc(&client->dev, sizeof(*sd), GFP_KERNEL); + if (sd == NULL) + return -ENOMEM; + v4l2_i2c_subdev_init(sd, client, &ov7640_ops); + + client->flags = I2C_CLIENT_SCCB; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + if (write_regs(client, initial_registers) < 0) { + v4l_err(client, "error initializing OV7640\n"); + return -ENODEV; + } + + return 0; +} + + +static int ov7640_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + + return 0; +} + +static const struct i2c_device_id ov7640_id[] = { + { "ov7640", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov7640_id); + +static struct i2c_driver ov7640_driver = { + .driver = { + .name = "ov7640", + }, + .probe = ov7640_probe, + .remove = ov7640_remove, + .id_table = ov7640_id, +}; +module_i2c_driver(ov7640_driver); diff --git a/drivers/media/i2c/ov7670.c b/drivers/media/i2c/ov7670.c new file mode 100644 index 000000000..e47800cb6 --- /dev/null +++ b/drivers/media/i2c/ov7670.c @@ -0,0 +1,2031 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * A V4L2 driver for OmniVision OV7670 cameras. + * + * Copyright 2006 One Laptop Per Child Association, Inc. Written + * by Jonathan Corbet with substantial inspiration from Mark + * McClelland's ovcamchip code. + * + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> + */ +#include <linux/clk.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mediabus.h> +#include <media/v4l2-image-sizes.h> +#include <media/i2c/ov7670.h> + +MODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>"); +MODULE_DESCRIPTION("A low-level driver for OmniVision ov7670 sensors"); +MODULE_LICENSE("GPL"); + +static bool debug; +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +/* + * The 7670 sits on i2c with ID 0x42 + */ +#define OV7670_I2C_ADDR 0x42 + +#define PLL_FACTOR 4 + +/* Registers */ +#define REG_GAIN 0x00 /* Gain lower 8 bits (rest in vref) */ +#define REG_BLUE 0x01 /* blue gain */ +#define REG_RED 0x02 /* red gain */ +#define REG_VREF 0x03 /* Pieces of GAIN, VSTART, VSTOP */ +#define REG_COM1 0x04 /* Control 1 */ +#define COM1_CCIR656 0x40 /* CCIR656 enable */ +#define REG_BAVE 0x05 /* U/B Average level */ +#define REG_GbAVE 0x06 /* Y/Gb Average level */ +#define REG_AECHH 0x07 /* AEC MS 5 bits */ +#define REG_RAVE 0x08 /* V/R Average level */ +#define REG_COM2 0x09 /* Control 2 */ +#define COM2_SSLEEP 0x10 /* Soft sleep mode */ +#define REG_PID 0x0a /* Product ID MSB */ +#define REG_VER 0x0b /* Product ID LSB */ +#define REG_COM3 0x0c /* Control 3 */ +#define COM3_SWAP 0x40 /* Byte swap */ +#define COM3_SCALEEN 0x08 /* Enable scaling */ +#define COM3_DCWEN 0x04 /* Enable downsamp/crop/window */ +#define REG_COM4 0x0d /* Control 4 */ +#define REG_COM5 0x0e /* All "reserved" */ +#define REG_COM6 0x0f /* Control 6 */ +#define REG_AECH 0x10 /* More bits of AEC value */ +#define REG_CLKRC 0x11 /* Clocl control */ +#define CLK_EXT 0x40 /* Use external clock directly */ +#define CLK_SCALE 0x3f /* Mask for internal clock scale */ +#define REG_COM7 0x12 /* Control 7 */ +#define COM7_RESET 0x80 /* Register reset */ +#define COM7_FMT_MASK 0x38 +#define COM7_FMT_VGA 0x00 +#define COM7_FMT_CIF 0x20 /* CIF format */ +#define COM7_FMT_QVGA 0x10 /* QVGA format */ +#define COM7_FMT_QCIF 0x08 /* QCIF format */ +#define COM7_RGB 0x04 /* bits 0 and 2 - RGB format */ +#define COM7_YUV 0x00 /* YUV */ +#define COM7_BAYER 0x01 /* Bayer format */ +#define COM7_PBAYER 0x05 /* "Processed bayer" */ +#define REG_COM8 0x13 /* Control 8 */ +#define COM8_FASTAEC 0x80 /* Enable fast AGC/AEC */ +#define COM8_AECSTEP 0x40 /* Unlimited AEC step size */ +#define COM8_BFILT 0x20 /* Band filter enable */ +#define COM8_AGC 0x04 /* Auto gain enable */ +#define COM8_AWB 0x02 /* White balance enable */ +#define COM8_AEC 0x01 /* Auto exposure enable */ +#define REG_COM9 0x14 /* Control 9 - gain ceiling */ +#define REG_COM10 0x15 /* Control 10 */ +#define COM10_HSYNC 0x40 /* HSYNC instead of HREF */ +#define COM10_PCLK_HB 0x20 /* Suppress PCLK on horiz blank */ +#define COM10_HREF_REV 0x08 /* Reverse HREF */ +#define COM10_VS_LEAD 0x04 /* VSYNC on clock leading edge */ +#define COM10_VS_NEG 0x02 /* VSYNC negative */ +#define COM10_HS_NEG 0x01 /* HSYNC negative */ +#define REG_HSTART 0x17 /* Horiz start high bits */ +#define REG_HSTOP 0x18 /* Horiz stop high bits */ +#define REG_VSTART 0x19 /* Vert start high bits */ +#define REG_VSTOP 0x1a /* Vert stop high bits */ +#define REG_PSHFT 0x1b /* Pixel delay after HREF */ +#define REG_MIDH 0x1c /* Manuf. ID high */ +#define REG_MIDL 0x1d /* Manuf. ID low */ +#define REG_MVFP 0x1e /* Mirror / vflip */ +#define MVFP_MIRROR 0x20 /* Mirror image */ +#define MVFP_FLIP 0x10 /* Vertical flip */ + +#define REG_AEW 0x24 /* AGC upper limit */ +#define REG_AEB 0x25 /* AGC lower limit */ +#define REG_VPT 0x26 /* AGC/AEC fast mode op region */ +#define REG_HSYST 0x30 /* HSYNC rising edge delay */ +#define REG_HSYEN 0x31 /* HSYNC falling edge delay */ +#define REG_HREF 0x32 /* HREF pieces */ +#define REG_TSLB 0x3a /* lots of stuff */ +#define TSLB_YLAST 0x04 /* UYVY or VYUY - see com13 */ +#define REG_COM11 0x3b /* Control 11 */ +#define COM11_NIGHT 0x80 /* NIght mode enable */ +#define COM11_NMFR 0x60 /* Two bit NM frame rate */ +#define COM11_HZAUTO 0x10 /* Auto detect 50/60 Hz */ +#define COM11_50HZ 0x08 /* Manual 50Hz select */ +#define COM11_EXP 0x02 +#define REG_COM12 0x3c /* Control 12 */ +#define COM12_HREF 0x80 /* HREF always */ +#define REG_COM13 0x3d /* Control 13 */ +#define COM13_GAMMA 0x80 /* Gamma enable */ +#define COM13_UVSAT 0x40 /* UV saturation auto adjustment */ +#define COM13_UVSWAP 0x01 /* V before U - w/TSLB */ +#define REG_COM14 0x3e /* Control 14 */ +#define COM14_DCWEN 0x10 /* DCW/PCLK-scale enable */ +#define REG_EDGE 0x3f /* Edge enhancement factor */ +#define REG_COM15 0x40 /* Control 15 */ +#define COM15_R10F0 0x00 /* Data range 10 to F0 */ +#define COM15_R01FE 0x80 /* 01 to FE */ +#define COM15_R00FF 0xc0 /* 00 to FF */ +#define COM15_RGB565 0x10 /* RGB565 output */ +#define COM15_RGB555 0x30 /* RGB555 output */ +#define REG_COM16 0x41 /* Control 16 */ +#define COM16_AWBGAIN 0x08 /* AWB gain enable */ +#define REG_COM17 0x42 /* Control 17 */ +#define COM17_AECWIN 0xc0 /* AEC window - must match COM4 */ +#define COM17_CBAR 0x08 /* DSP Color bar */ + +/* + * This matrix defines how the colors are generated, must be + * tweaked to adjust hue and saturation. + * + * Order: v-red, v-green, v-blue, u-red, u-green, u-blue + * + * They are nine-bit signed quantities, with the sign bit + * stored in 0x58. Sign for v-red is bit 0, and up from there. + */ +#define REG_CMATRIX_BASE 0x4f +#define CMATRIX_LEN 6 +#define REG_CMATRIX_SIGN 0x58 + + +#define REG_BRIGHT 0x55 /* Brightness */ +#define REG_CONTRAS 0x56 /* Contrast control */ + +#define REG_GFIX 0x69 /* Fix gain control */ + +#define REG_DBLV 0x6b /* PLL control an debugging */ +#define DBLV_BYPASS 0x0a /* Bypass PLL */ +#define DBLV_X4 0x4a /* clock x4 */ +#define DBLV_X6 0x8a /* clock x6 */ +#define DBLV_X8 0xca /* clock x8 */ + +#define REG_SCALING_XSC 0x70 /* Test pattern and horizontal scale factor */ +#define TEST_PATTTERN_0 0x80 +#define REG_SCALING_YSC 0x71 /* Test pattern and vertical scale factor */ +#define TEST_PATTTERN_1 0x80 + +#define REG_REG76 0x76 /* OV's name */ +#define R76_BLKPCOR 0x80 /* Black pixel correction enable */ +#define R76_WHTPCOR 0x40 /* White pixel correction enable */ + +#define REG_RGB444 0x8c /* RGB 444 control */ +#define R444_ENABLE 0x02 /* Turn on RGB444, overrides 5x5 */ +#define R444_RGBX 0x01 /* Empty nibble at end */ + +#define REG_HAECC1 0x9f /* Hist AEC/AGC control 1 */ +#define REG_HAECC2 0xa0 /* Hist AEC/AGC control 2 */ + +#define REG_BD50MAX 0xa5 /* 50hz banding step limit */ +#define REG_HAECC3 0xa6 /* Hist AEC/AGC control 3 */ +#define REG_HAECC4 0xa7 /* Hist AEC/AGC control 4 */ +#define REG_HAECC5 0xa8 /* Hist AEC/AGC control 5 */ +#define REG_HAECC6 0xa9 /* Hist AEC/AGC control 6 */ +#define REG_HAECC7 0xaa /* Hist AEC/AGC control 7 */ +#define REG_BD60MAX 0xab /* 60hz banding step limit */ + +enum ov7670_model { + MODEL_OV7670 = 0, + MODEL_OV7675, +}; + +struct ov7670_win_size { + int width; + int height; + unsigned char com7_bit; + int hstart; /* Start/stop values for the camera. Note */ + int hstop; /* that they do not always make complete */ + int vstart; /* sense to humans, but evidently the sensor */ + int vstop; /* will do the right thing... */ + struct regval_list *regs; /* Regs to tweak */ +}; + +struct ov7670_devtype { + /* formats supported for each model */ + struct ov7670_win_size *win_sizes; + unsigned int n_win_sizes; + /* callbacks for frame rate control */ + int (*set_framerate)(struct v4l2_subdev *, struct v4l2_fract *); + void (*get_framerate)(struct v4l2_subdev *, struct v4l2_fract *); +}; + +/* + * Information we maintain about a known sensor. + */ +struct ov7670_format_struct; /* coming later */ +struct ov7670_info { + struct v4l2_subdev sd; +#if defined(CONFIG_MEDIA_CONTROLLER) + struct media_pad pad; +#endif + struct v4l2_ctrl_handler hdl; + struct { + /* gain cluster */ + struct v4l2_ctrl *auto_gain; + struct v4l2_ctrl *gain; + }; + struct { + /* exposure cluster */ + struct v4l2_ctrl *auto_exposure; + struct v4l2_ctrl *exposure; + }; + struct { + /* saturation/hue cluster */ + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *hue; + }; + struct v4l2_mbus_framefmt format; + struct ov7670_format_struct *fmt; /* Current format */ + struct ov7670_win_size *wsize; + struct clk *clk; + int on; + struct gpio_desc *resetb_gpio; + struct gpio_desc *pwdn_gpio; + unsigned int mbus_config; /* Media bus configuration flags */ + int min_width; /* Filter out smaller sizes */ + int min_height; /* Filter out smaller sizes */ + int clock_speed; /* External clock speed (MHz) */ + u8 clkrc; /* Clock divider value */ + bool use_smbus; /* Use smbus I/O instead of I2C */ + bool pll_bypass; + bool pclk_hb_disable; + const struct ov7670_devtype *devtype; /* Device specifics */ +}; + +static inline struct ov7670_info *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov7670_info, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ov7670_info, hdl)->sd; +} + + + +/* + * The default register settings, as obtained from OmniVision. There + * is really no making sense of most of these - lots of "reserved" values + * and such. + * + * These settings give VGA YUYV. + */ + +struct regval_list { + unsigned char reg_num; + unsigned char value; +}; + +static struct regval_list ov7670_default_regs[] = { + { REG_COM7, COM7_RESET }, +/* + * Clock scale: 3 = 15fps + * 2 = 20fps + * 1 = 30fps + */ + { REG_CLKRC, 0x1 }, /* OV: clock scale (30 fps) */ + { REG_TSLB, 0x04 }, /* OV */ + { REG_COM7, 0 }, /* VGA */ + /* + * Set the hardware window. These values from OV don't entirely + * make sense - hstop is less than hstart. But they work... + */ + { REG_HSTART, 0x13 }, { REG_HSTOP, 0x01 }, + { REG_HREF, 0xb6 }, { REG_VSTART, 0x02 }, + { REG_VSTOP, 0x7a }, { REG_VREF, 0x0a }, + + { REG_COM3, 0 }, { REG_COM14, 0 }, + /* Mystery scaling numbers */ + { REG_SCALING_XSC, 0x3a }, + { REG_SCALING_YSC, 0x35 }, + { 0x72, 0x11 }, { 0x73, 0xf0 }, + { 0xa2, 0x02 }, { REG_COM10, 0x0 }, + + /* Gamma curve values */ + { 0x7a, 0x20 }, { 0x7b, 0x10 }, + { 0x7c, 0x1e }, { 0x7d, 0x35 }, + { 0x7e, 0x5a }, { 0x7f, 0x69 }, + { 0x80, 0x76 }, { 0x81, 0x80 }, + { 0x82, 0x88 }, { 0x83, 0x8f }, + { 0x84, 0x96 }, { 0x85, 0xa3 }, + { 0x86, 0xaf }, { 0x87, 0xc4 }, + { 0x88, 0xd7 }, { 0x89, 0xe8 }, + + /* AGC and AEC parameters. Note we start by disabling those features, + then turn them only after tweaking the values. */ + { REG_COM8, COM8_FASTAEC | COM8_AECSTEP | COM8_BFILT }, + { REG_GAIN, 0 }, { REG_AECH, 0 }, + { REG_COM4, 0x40 }, /* magic reserved bit */ + { REG_COM9, 0x18 }, /* 4x gain + magic rsvd bit */ + { REG_BD50MAX, 0x05 }, { REG_BD60MAX, 0x07 }, + { REG_AEW, 0x95 }, { REG_AEB, 0x33 }, + { REG_VPT, 0xe3 }, { REG_HAECC1, 0x78 }, + { REG_HAECC2, 0x68 }, { 0xa1, 0x03 }, /* magic */ + { REG_HAECC3, 0xd8 }, { REG_HAECC4, 0xd8 }, + { REG_HAECC5, 0xf0 }, { REG_HAECC6, 0x90 }, + { REG_HAECC7, 0x94 }, + { REG_COM8, COM8_FASTAEC|COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC }, + + /* Almost all of these are magic "reserved" values. */ + { REG_COM5, 0x61 }, { REG_COM6, 0x4b }, + { 0x16, 0x02 }, { REG_MVFP, 0x07 }, + { 0x21, 0x02 }, { 0x22, 0x91 }, + { 0x29, 0x07 }, { 0x33, 0x0b }, + { 0x35, 0x0b }, { 0x37, 0x1d }, + { 0x38, 0x71 }, { 0x39, 0x2a }, + { REG_COM12, 0x78 }, { 0x4d, 0x40 }, + { 0x4e, 0x20 }, { REG_GFIX, 0 }, + { 0x6b, 0x4a }, { 0x74, 0x10 }, + { 0x8d, 0x4f }, { 0x8e, 0 }, + { 0x8f, 0 }, { 0x90, 0 }, + { 0x91, 0 }, { 0x96, 0 }, + { 0x9a, 0 }, { 0xb0, 0x84 }, + { 0xb1, 0x0c }, { 0xb2, 0x0e }, + { 0xb3, 0x82 }, { 0xb8, 0x0a }, + + /* More reserved magic, some of which tweaks white balance */ + { 0x43, 0x0a }, { 0x44, 0xf0 }, + { 0x45, 0x34 }, { 0x46, 0x58 }, + { 0x47, 0x28 }, { 0x48, 0x3a }, + { 0x59, 0x88 }, { 0x5a, 0x88 }, + { 0x5b, 0x44 }, { 0x5c, 0x67 }, + { 0x5d, 0x49 }, { 0x5e, 0x0e }, + { 0x6c, 0x0a }, { 0x6d, 0x55 }, + { 0x6e, 0x11 }, { 0x6f, 0x9f }, /* "9e for advance AWB" */ + { 0x6a, 0x40 }, { REG_BLUE, 0x40 }, + { REG_RED, 0x60 }, + { REG_COM8, COM8_FASTAEC|COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC|COM8_AWB }, + + /* Matrix coefficients */ + { 0x4f, 0x80 }, { 0x50, 0x80 }, + { 0x51, 0 }, { 0x52, 0x22 }, + { 0x53, 0x5e }, { 0x54, 0x80 }, + { 0x58, 0x9e }, + + { REG_COM16, COM16_AWBGAIN }, { REG_EDGE, 0 }, + { 0x75, 0x05 }, { 0x76, 0xe1 }, + { 0x4c, 0 }, { 0x77, 0x01 }, + { REG_COM13, 0xc3 }, { 0x4b, 0x09 }, + { 0xc9, 0x60 }, { REG_COM16, 0x38 }, + { 0x56, 0x40 }, + + { 0x34, 0x11 }, { REG_COM11, COM11_EXP|COM11_HZAUTO }, + { 0xa4, 0x88 }, { 0x96, 0 }, + { 0x97, 0x30 }, { 0x98, 0x20 }, + { 0x99, 0x30 }, { 0x9a, 0x84 }, + { 0x9b, 0x29 }, { 0x9c, 0x03 }, + { 0x9d, 0x4c }, { 0x9e, 0x3f }, + { 0x78, 0x04 }, + + /* Extra-weird stuff. Some sort of multiplexor register */ + { 0x79, 0x01 }, { 0xc8, 0xf0 }, + { 0x79, 0x0f }, { 0xc8, 0x00 }, + { 0x79, 0x10 }, { 0xc8, 0x7e }, + { 0x79, 0x0a }, { 0xc8, 0x80 }, + { 0x79, 0x0b }, { 0xc8, 0x01 }, + { 0x79, 0x0c }, { 0xc8, 0x0f }, + { 0x79, 0x0d }, { 0xc8, 0x20 }, + { 0x79, 0x09 }, { 0xc8, 0x80 }, + { 0x79, 0x02 }, { 0xc8, 0xc0 }, + { 0x79, 0x03 }, { 0xc8, 0x40 }, + { 0x79, 0x05 }, { 0xc8, 0x30 }, + { 0x79, 0x26 }, + + { 0xff, 0xff }, /* END MARKER */ +}; + + +/* + * Here we'll try to encapsulate the changes for just the output + * video format. + * + * RGB656 and YUV422 come from OV; RGB444 is homebrewed. + * + * IMPORTANT RULE: the first entry must be for COM7, see ov7670_s_fmt for why. + */ + + +static struct regval_list ov7670_fmt_yuv422[] = { + { REG_COM7, 0x0 }, /* Selects YUV mode */ + { REG_RGB444, 0 }, /* No RGB444 please */ + { REG_COM1, 0 }, /* CCIR601 */ + { REG_COM15, COM15_R00FF }, + { REG_COM9, 0x48 }, /* 32x gain ceiling; 0x8 is reserved bit */ + { 0x4f, 0x80 }, /* "matrix coefficient 1" */ + { 0x50, 0x80 }, /* "matrix coefficient 2" */ + { 0x51, 0 }, /* vb */ + { 0x52, 0x22 }, /* "matrix coefficient 4" */ + { 0x53, 0x5e }, /* "matrix coefficient 5" */ + { 0x54, 0x80 }, /* "matrix coefficient 6" */ + { REG_COM13, COM13_GAMMA|COM13_UVSAT }, + { 0xff, 0xff }, +}; + +static struct regval_list ov7670_fmt_rgb565[] = { + { REG_COM7, COM7_RGB }, /* Selects RGB mode */ + { REG_RGB444, 0 }, /* No RGB444 please */ + { REG_COM1, 0x0 }, /* CCIR601 */ + { REG_COM15, COM15_RGB565 }, + { REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */ + { 0x4f, 0xb3 }, /* "matrix coefficient 1" */ + { 0x50, 0xb3 }, /* "matrix coefficient 2" */ + { 0x51, 0 }, /* vb */ + { 0x52, 0x3d }, /* "matrix coefficient 4" */ + { 0x53, 0xa7 }, /* "matrix coefficient 5" */ + { 0x54, 0xe4 }, /* "matrix coefficient 6" */ + { REG_COM13, COM13_GAMMA|COM13_UVSAT }, + { 0xff, 0xff }, +}; + +static struct regval_list ov7670_fmt_rgb444[] = { + { REG_COM7, COM7_RGB }, /* Selects RGB mode */ + { REG_RGB444, R444_ENABLE }, /* Enable xxxxrrrr ggggbbbb */ + { REG_COM1, 0x0 }, /* CCIR601 */ + { REG_COM15, COM15_R01FE|COM15_RGB565 }, /* Data range needed? */ + { REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */ + { 0x4f, 0xb3 }, /* "matrix coefficient 1" */ + { 0x50, 0xb3 }, /* "matrix coefficient 2" */ + { 0x51, 0 }, /* vb */ + { 0x52, 0x3d }, /* "matrix coefficient 4" */ + { 0x53, 0xa7 }, /* "matrix coefficient 5" */ + { 0x54, 0xe4 }, /* "matrix coefficient 6" */ + { REG_COM13, COM13_GAMMA|COM13_UVSAT|0x2 }, /* Magic rsvd bit */ + { 0xff, 0xff }, +}; + +static struct regval_list ov7670_fmt_raw[] = { + { REG_COM7, COM7_BAYER }, + { REG_COM13, 0x08 }, /* No gamma, magic rsvd bit */ + { REG_COM16, 0x3d }, /* Edge enhancement, denoise */ + { REG_REG76, 0xe1 }, /* Pix correction, magic rsvd */ + { 0xff, 0xff }, +}; + + + +/* + * Low-level register I/O. + * + * Note that there are two versions of these. On the XO 1, the + * i2c controller only does SMBUS, so that's what we use. The + * ov7670 is not really an SMBUS device, though, so the communication + * is not always entirely reliable. + */ +static int ov7670_read_smbus(struct v4l2_subdev *sd, unsigned char reg, + unsigned char *value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret >= 0) { + *value = (unsigned char)ret; + ret = 0; + } + return ret; +} + + +static int ov7670_write_smbus(struct v4l2_subdev *sd, unsigned char reg, + unsigned char value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = i2c_smbus_write_byte_data(client, reg, value); + + if (reg == REG_COM7 && (value & COM7_RESET)) + msleep(5); /* Wait for reset to run */ + return ret; +} + +/* + * On most platforms, we'd rather do straight i2c I/O. + */ +static int ov7670_read_i2c(struct v4l2_subdev *sd, unsigned char reg, + unsigned char *value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 data = reg; + struct i2c_msg msg; + int ret; + + /* + * Send out the register address... + */ + msg.addr = client->addr; + msg.flags = 0; + msg.len = 1; + msg.buf = &data; + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + printk(KERN_ERR "Error %d on register write\n", ret); + return ret; + } + /* + * ...then read back the result. + */ + msg.flags = I2C_M_RD; + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret >= 0) { + *value = data; + ret = 0; + } + return ret; +} + + +static int ov7670_write_i2c(struct v4l2_subdev *sd, unsigned char reg, + unsigned char value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct i2c_msg msg; + unsigned char data[2] = { reg, value }; + int ret; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = data; + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret > 0) + ret = 0; + if (reg == REG_COM7 && (value & COM7_RESET)) + msleep(5); /* Wait for reset to run */ + return ret; +} + +static int ov7670_read(struct v4l2_subdev *sd, unsigned char reg, + unsigned char *value) +{ + struct ov7670_info *info = to_state(sd); + if (info->use_smbus) + return ov7670_read_smbus(sd, reg, value); + else + return ov7670_read_i2c(sd, reg, value); +} + +static int ov7670_write(struct v4l2_subdev *sd, unsigned char reg, + unsigned char value) +{ + struct ov7670_info *info = to_state(sd); + if (info->use_smbus) + return ov7670_write_smbus(sd, reg, value); + else + return ov7670_write_i2c(sd, reg, value); +} + +static int ov7670_update_bits(struct v4l2_subdev *sd, unsigned char reg, + unsigned char mask, unsigned char value) +{ + unsigned char orig; + int ret; + + ret = ov7670_read(sd, reg, &orig); + if (ret) + return ret; + + return ov7670_write(sd, reg, (orig & ~mask) | (value & mask)); +} + +/* + * Write a list of register settings; ff/ff stops the process. + */ +static int ov7670_write_array(struct v4l2_subdev *sd, struct regval_list *vals) +{ + while (vals->reg_num != 0xff || vals->value != 0xff) { + int ret = ov7670_write(sd, vals->reg_num, vals->value); + if (ret < 0) + return ret; + vals++; + } + return 0; +} + + +/* + * Stuff that knows about the sensor. + */ +static int ov7670_reset(struct v4l2_subdev *sd, u32 val) +{ + ov7670_write(sd, REG_COM7, COM7_RESET); + msleep(1); + return 0; +} + + +static int ov7670_init(struct v4l2_subdev *sd, u32 val) +{ + return ov7670_write_array(sd, ov7670_default_regs); +} + +static int ov7670_detect(struct v4l2_subdev *sd) +{ + unsigned char v; + int ret; + + ret = ov7670_init(sd, 0); + if (ret < 0) + return ret; + ret = ov7670_read(sd, REG_MIDH, &v); + if (ret < 0) + return ret; + if (v != 0x7f) /* OV manuf. id. */ + return -ENODEV; + ret = ov7670_read(sd, REG_MIDL, &v); + if (ret < 0) + return ret; + if (v != 0xa2) + return -ENODEV; + /* + * OK, we know we have an OmniVision chip...but which one? + */ + ret = ov7670_read(sd, REG_PID, &v); + if (ret < 0) + return ret; + if (v != 0x76) /* PID + VER = 0x76 / 0x73 */ + return -ENODEV; + ret = ov7670_read(sd, REG_VER, &v); + if (ret < 0) + return ret; + if (v != 0x73) /* PID + VER = 0x76 / 0x73 */ + return -ENODEV; + return 0; +} + + +/* + * Store information about the video data format. The color matrix + * is deeply tied into the format, so keep the relevant values here. + * The magic matrix numbers come from OmniVision. + */ +static struct ov7670_format_struct { + u32 mbus_code; + enum v4l2_colorspace colorspace; + struct regval_list *regs; + int cmatrix[CMATRIX_LEN]; +} ov7670_formats[] = { + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .regs = ov7670_fmt_yuv422, + .cmatrix = { 128, -128, 0, -34, -94, 128 }, + }, + { + .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + .regs = ov7670_fmt_rgb444, + .cmatrix = { 179, -179, 0, -61, -176, 228 }, + }, + { + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + .regs = ov7670_fmt_rgb565, + .cmatrix = { 179, -179, 0, -61, -176, 228 }, + }, + { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .regs = ov7670_fmt_raw, + .cmatrix = { 0, 0, 0, 0, 0, 0 }, + }, +}; +#define N_OV7670_FMTS ARRAY_SIZE(ov7670_formats) + + +/* + * Then there is the issue of window sizes. Try to capture the info here. + */ + +/* + * QCIF mode is done (by OV) in a very strange way - it actually looks like + * VGA with weird scaling options - they do *not* use the canned QCIF mode + * which is allegedly provided by the sensor. So here's the weird register + * settings. + */ +static struct regval_list ov7670_qcif_regs[] = { + { REG_COM3, COM3_SCALEEN|COM3_DCWEN }, + { REG_COM3, COM3_DCWEN }, + { REG_COM14, COM14_DCWEN | 0x01}, + { 0x73, 0xf1 }, + { 0xa2, 0x52 }, + { 0x7b, 0x1c }, + { 0x7c, 0x28 }, + { 0x7d, 0x3c }, + { 0x7f, 0x69 }, + { REG_COM9, 0x38 }, + { 0xa1, 0x0b }, + { 0x74, 0x19 }, + { 0x9a, 0x80 }, + { 0x43, 0x14 }, + { REG_COM13, 0xc0 }, + { 0xff, 0xff }, +}; + +static struct ov7670_win_size ov7670_win_sizes[] = { + /* VGA */ + { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .com7_bit = COM7_FMT_VGA, + .hstart = 158, /* These values from */ + .hstop = 14, /* Omnivision */ + .vstart = 10, + .vstop = 490, + .regs = NULL, + }, + /* CIF */ + { + .width = CIF_WIDTH, + .height = CIF_HEIGHT, + .com7_bit = COM7_FMT_CIF, + .hstart = 170, /* Empirically determined */ + .hstop = 90, + .vstart = 14, + .vstop = 494, + .regs = NULL, + }, + /* QVGA */ + { + .width = QVGA_WIDTH, + .height = QVGA_HEIGHT, + .com7_bit = COM7_FMT_QVGA, + .hstart = 168, /* Empirically determined */ + .hstop = 24, + .vstart = 12, + .vstop = 492, + .regs = NULL, + }, + /* QCIF */ + { + .width = QCIF_WIDTH, + .height = QCIF_HEIGHT, + .com7_bit = COM7_FMT_VGA, /* see comment above */ + .hstart = 456, /* Empirically determined */ + .hstop = 24, + .vstart = 14, + .vstop = 494, + .regs = ov7670_qcif_regs, + } +}; + +static struct ov7670_win_size ov7675_win_sizes[] = { + /* + * Currently, only VGA is supported. Theoretically it could be possible + * to support CIF, QVGA and QCIF too. Taking values for ov7670 as a + * base and tweak them empirically could be required. + */ + { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .com7_bit = COM7_FMT_VGA, + .hstart = 158, /* These values from */ + .hstop = 14, /* Omnivision */ + .vstart = 14, /* Empirically determined */ + .vstop = 494, + .regs = NULL, + } +}; + +static void ov7675_get_framerate(struct v4l2_subdev *sd, + struct v4l2_fract *tpf) +{ + struct ov7670_info *info = to_state(sd); + u32 clkrc = info->clkrc; + int pll_factor; + + if (info->pll_bypass) + pll_factor = 1; + else + pll_factor = PLL_FACTOR; + + clkrc++; + if (info->fmt->mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8) + clkrc = (clkrc >> 1); + + tpf->numerator = 1; + tpf->denominator = (5 * pll_factor * info->clock_speed) / + (4 * clkrc); +} + +static int ov7675_apply_framerate(struct v4l2_subdev *sd) +{ + struct ov7670_info *info = to_state(sd); + int ret; + + ret = ov7670_write(sd, REG_CLKRC, info->clkrc); + if (ret < 0) + return ret; + + return ov7670_write(sd, REG_DBLV, + info->pll_bypass ? DBLV_BYPASS : DBLV_X4); +} + +static int ov7675_set_framerate(struct v4l2_subdev *sd, + struct v4l2_fract *tpf) +{ + struct ov7670_info *info = to_state(sd); + u32 clkrc; + int pll_factor; + + /* + * The formula is fps = 5/4*pixclk for YUV/RGB and + * fps = 5/2*pixclk for RAW. + * + * pixclk = clock_speed / (clkrc + 1) * PLLfactor + * + */ + if (tpf->numerator == 0 || tpf->denominator == 0) { + clkrc = 0; + } else { + pll_factor = info->pll_bypass ? 1 : PLL_FACTOR; + clkrc = (5 * pll_factor * info->clock_speed * tpf->numerator) / + (4 * tpf->denominator); + if (info->fmt->mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8) + clkrc = (clkrc << 1); + clkrc--; + } + + /* + * The datasheet claims that clkrc = 0 will divide the input clock by 1 + * but we've checked with an oscilloscope that it divides by 2 instead. + * So, if clkrc = 0 just bypass the divider. + */ + if (clkrc <= 0) + clkrc = CLK_EXT; + else if (clkrc > CLK_SCALE) + clkrc = CLK_SCALE; + info->clkrc = clkrc; + + /* Recalculate frame rate */ + ov7675_get_framerate(sd, tpf); + + /* + * If the device is not powered up by the host driver do + * not apply any changes to H/W at this time. Instead + * the framerate will be restored right after power-up. + */ + if (info->on) + return ov7675_apply_framerate(sd); + + return 0; +} + +static void ov7670_get_framerate_legacy(struct v4l2_subdev *sd, + struct v4l2_fract *tpf) +{ + struct ov7670_info *info = to_state(sd); + + tpf->numerator = 1; + tpf->denominator = info->clock_speed; + if ((info->clkrc & CLK_EXT) == 0 && (info->clkrc & CLK_SCALE) > 1) + tpf->denominator /= (info->clkrc & CLK_SCALE); +} + +static int ov7670_set_framerate_legacy(struct v4l2_subdev *sd, + struct v4l2_fract *tpf) +{ + struct ov7670_info *info = to_state(sd); + int div; + + if (tpf->numerator == 0 || tpf->denominator == 0) + div = 1; /* Reset to full rate */ + else + div = (tpf->numerator * info->clock_speed) / tpf->denominator; + if (div == 0) + div = 1; + else if (div > CLK_SCALE) + div = CLK_SCALE; + info->clkrc = (info->clkrc & 0x80) | div; + tpf->numerator = 1; + tpf->denominator = info->clock_speed / div; + + /* + * If the device is not powered up by the host driver do + * not apply any changes to H/W at this time. Instead + * the framerate will be restored right after power-up. + */ + if (info->on) + return ov7670_write(sd, REG_CLKRC, info->clkrc); + + return 0; +} + +/* + * Store a set of start/stop values into the camera. + */ +static int ov7670_set_hw(struct v4l2_subdev *sd, int hstart, int hstop, + int vstart, int vstop) +{ + int ret; + unsigned char v; +/* + * Horizontal: 11 bits, top 8 live in hstart and hstop. Bottom 3 of + * hstart are in href[2:0], bottom 3 of hstop in href[5:3]. There is + * a mystery "edge offset" value in the top two bits of href. + */ + ret = ov7670_write(sd, REG_HSTART, (hstart >> 3) & 0xff); + ret += ov7670_write(sd, REG_HSTOP, (hstop >> 3) & 0xff); + ret += ov7670_read(sd, REG_HREF, &v); + v = (v & 0xc0) | ((hstop & 0x7) << 3) | (hstart & 0x7); + msleep(10); + ret += ov7670_write(sd, REG_HREF, v); +/* + * Vertical: similar arrangement, but only 10 bits. + */ + ret += ov7670_write(sd, REG_VSTART, (vstart >> 2) & 0xff); + ret += ov7670_write(sd, REG_VSTOP, (vstop >> 2) & 0xff); + ret += ov7670_read(sd, REG_VREF, &v); + v = (v & 0xf0) | ((vstop & 0x3) << 2) | (vstart & 0x3); + msleep(10); + ret += ov7670_write(sd, REG_VREF, v); + return ret; +} + + +static int ov7670_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= N_OV7670_FMTS) + return -EINVAL; + + code->code = ov7670_formats[code->index].mbus_code; + return 0; +} + +static int ov7670_try_fmt_internal(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt, + struct ov7670_format_struct **ret_fmt, + struct ov7670_win_size **ret_wsize) +{ + int index, i; + struct ov7670_win_size *wsize; + struct ov7670_info *info = to_state(sd); + unsigned int n_win_sizes = info->devtype->n_win_sizes; + unsigned int win_sizes_limit = n_win_sizes; + + for (index = 0; index < N_OV7670_FMTS; index++) + if (ov7670_formats[index].mbus_code == fmt->code) + break; + if (index >= N_OV7670_FMTS) { + /* default to first format */ + index = 0; + fmt->code = ov7670_formats[0].mbus_code; + } + if (ret_fmt != NULL) + *ret_fmt = ov7670_formats + index; + /* + * Fields: the OV devices claim to be progressive. + */ + fmt->field = V4L2_FIELD_NONE; + + /* + * Don't consider values that don't match min_height and min_width + * constraints. + */ + if (info->min_width || info->min_height) + for (i = 0; i < n_win_sizes; i++) { + wsize = info->devtype->win_sizes + i; + + if (wsize->width < info->min_width || + wsize->height < info->min_height) { + win_sizes_limit = i; + break; + } + } + /* + * Round requested image size down to the nearest + * we support, but not below the smallest. + */ + for (wsize = info->devtype->win_sizes; + wsize < info->devtype->win_sizes + win_sizes_limit; wsize++) + if (fmt->width >= wsize->width && fmt->height >= wsize->height) + break; + if (wsize >= info->devtype->win_sizes + win_sizes_limit) + wsize--; /* Take the smallest one */ + if (ret_wsize != NULL) + *ret_wsize = wsize; + /* + * Note the size we'll actually handle. + */ + fmt->width = wsize->width; + fmt->height = wsize->height; + fmt->colorspace = ov7670_formats[index].colorspace; + + info->format = *fmt; + + return 0; +} + +static int ov7670_apply_fmt(struct v4l2_subdev *sd) +{ + struct ov7670_info *info = to_state(sd); + struct ov7670_win_size *wsize = info->wsize; + unsigned char com7, com10 = 0; + int ret; + + /* + * COM7 is a pain in the ass, it doesn't like to be read then + * quickly written afterward. But we have everything we need + * to set it absolutely here, as long as the format-specific + * register sets list it first. + */ + com7 = info->fmt->regs[0].value; + com7 |= wsize->com7_bit; + ret = ov7670_write(sd, REG_COM7, com7); + if (ret) + return ret; + + /* + * Configure the media bus through COM10 register + */ + if (info->mbus_config & V4L2_MBUS_VSYNC_ACTIVE_LOW) + com10 |= COM10_VS_NEG; + if (info->mbus_config & V4L2_MBUS_HSYNC_ACTIVE_LOW) + com10 |= COM10_HREF_REV; + if (info->pclk_hb_disable) + com10 |= COM10_PCLK_HB; + ret = ov7670_write(sd, REG_COM10, com10); + if (ret) + return ret; + + /* + * Now write the rest of the array. Also store start/stops + */ + ret = ov7670_write_array(sd, info->fmt->regs + 1); + if (ret) + return ret; + + ret = ov7670_set_hw(sd, wsize->hstart, wsize->hstop, wsize->vstart, + wsize->vstop); + if (ret) + return ret; + + if (wsize->regs) { + ret = ov7670_write_array(sd, wsize->regs); + if (ret) + return ret; + } + + /* + * If we're running RGB565, we must rewrite clkrc after setting + * the other parameters or the image looks poor. If we're *not* + * doing RGB565, we must not rewrite clkrc or the image looks + * *really* poor. + * + * (Update) Now that we retain clkrc state, we should be able + * to write it unconditionally, and that will make the frame + * rate persistent too. + */ + ret = ov7670_write(sd, REG_CLKRC, info->clkrc); + if (ret) + return ret; + + return 0; +} + +/* + * Set a format. + */ +static int ov7670_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov7670_info *info = to_state(sd); +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + struct v4l2_mbus_framefmt *mbus_fmt; +#endif + int ret; + + if (format->pad) + return -EINVAL; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + ret = ov7670_try_fmt_internal(sd, &format->format, NULL, NULL); + if (ret) + return ret; +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + mbus_fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + *mbus_fmt = format->format; +#endif + return 0; + } + + ret = ov7670_try_fmt_internal(sd, &format->format, &info->fmt, &info->wsize); + if (ret) + return ret; + + /* + * If the device is not powered up by the host driver do + * not apply any changes to H/W at this time. Instead + * the frame format will be restored right after power-up. + */ + if (info->on) + return ov7670_apply_fmt(sd); + + return 0; +} + +static int ov7670_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov7670_info *info = to_state(sd); +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + struct v4l2_mbus_framefmt *mbus_fmt; +#endif + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + mbus_fmt = v4l2_subdev_get_try_format(sd, cfg, 0); + format->format = *mbus_fmt; + return 0; +#else + return -EINVAL; +#endif + } else { + format->format = info->format; + } + + return 0; +} + +/* + * Implement G/S_PARM. There is a "high quality" mode we could try + * to do someday; for now, we just do the frame rate tweak. + */ +static int ov7670_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct ov7670_info *info = to_state(sd); + + + info->devtype->get_framerate(sd, &ival->interval); + + return 0; +} + +static int ov7670_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct v4l2_fract *tpf = &ival->interval; + struct ov7670_info *info = to_state(sd); + + + return info->devtype->set_framerate(sd, tpf); +} + + +/* + * Frame intervals. Since frame rates are controlled with the clock + * divider, we can only do 30/n for integer n values. So no continuous + * or stepwise options. Here we just pick a handful of logical values. + */ + +static int ov7670_frame_rates[] = { 30, 15, 10, 5, 1 }; + +static int ov7670_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct ov7670_info *info = to_state(sd); + unsigned int n_win_sizes = info->devtype->n_win_sizes; + int i; + + if (fie->pad) + return -EINVAL; + if (fie->index >= ARRAY_SIZE(ov7670_frame_rates)) + return -EINVAL; + + /* + * Check if the width/height is valid. + * + * If a minimum width/height was requested, filter out the capture + * windows that fall outside that. + */ + for (i = 0; i < n_win_sizes; i++) { + struct ov7670_win_size *win = &info->devtype->win_sizes[i]; + + if (info->min_width && win->width < info->min_width) + continue; + if (info->min_height && win->height < info->min_height) + continue; + if (fie->width == win->width && fie->height == win->height) + break; + } + if (i == n_win_sizes) + return -EINVAL; + fie->interval.numerator = 1; + fie->interval.denominator = ov7670_frame_rates[fie->index]; + return 0; +} + +/* + * Frame size enumeration + */ +static int ov7670_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct ov7670_info *info = to_state(sd); + int i; + int num_valid = -1; + __u32 index = fse->index; + unsigned int n_win_sizes = info->devtype->n_win_sizes; + + if (fse->pad) + return -EINVAL; + + /* + * If a minimum width/height was requested, filter out the capture + * windows that fall outside that. + */ + for (i = 0; i < n_win_sizes; i++) { + struct ov7670_win_size *win = &info->devtype->win_sizes[i]; + if (info->min_width && win->width < info->min_width) + continue; + if (info->min_height && win->height < info->min_height) + continue; + if (index == ++num_valid) { + fse->min_width = fse->max_width = win->width; + fse->min_height = fse->max_height = win->height; + return 0; + } + } + + return -EINVAL; +} + +/* + * Code for dealing with controls. + */ + +static int ov7670_store_cmatrix(struct v4l2_subdev *sd, + int matrix[CMATRIX_LEN]) +{ + int i, ret; + unsigned char signbits = 0; + + /* + * Weird crap seems to exist in the upper part of + * the sign bits register, so let's preserve it. + */ + ret = ov7670_read(sd, REG_CMATRIX_SIGN, &signbits); + signbits &= 0xc0; + + for (i = 0; i < CMATRIX_LEN; i++) { + unsigned char raw; + + if (matrix[i] < 0) { + signbits |= (1 << i); + if (matrix[i] < -255) + raw = 0xff; + else + raw = (-1 * matrix[i]) & 0xff; + } + else { + if (matrix[i] > 255) + raw = 0xff; + else + raw = matrix[i] & 0xff; + } + ret += ov7670_write(sd, REG_CMATRIX_BASE + i, raw); + } + ret += ov7670_write(sd, REG_CMATRIX_SIGN, signbits); + return ret; +} + + +/* + * Hue also requires messing with the color matrix. It also requires + * trig functions, which tend not to be well supported in the kernel. + * So here is a simple table of sine values, 0-90 degrees, in steps + * of five degrees. Values are multiplied by 1000. + * + * The following naive approximate trig functions require an argument + * carefully limited to -180 <= theta <= 180. + */ +#define SIN_STEP 5 +static const int ov7670_sin_table[] = { + 0, 87, 173, 258, 342, 422, + 499, 573, 642, 707, 766, 819, + 866, 906, 939, 965, 984, 996, + 1000 +}; + +static int ov7670_sine(int theta) +{ + int chs = 1; + int sine; + + if (theta < 0) { + theta = -theta; + chs = -1; + } + if (theta <= 90) + sine = ov7670_sin_table[theta/SIN_STEP]; + else { + theta -= 90; + sine = 1000 - ov7670_sin_table[theta/SIN_STEP]; + } + return sine*chs; +} + +static int ov7670_cosine(int theta) +{ + theta = 90 - theta; + if (theta > 180) + theta -= 360; + else if (theta < -180) + theta += 360; + return ov7670_sine(theta); +} + + + + +static void ov7670_calc_cmatrix(struct ov7670_info *info, + int matrix[CMATRIX_LEN], int sat, int hue) +{ + int i; + /* + * Apply the current saturation setting first. + */ + for (i = 0; i < CMATRIX_LEN; i++) + matrix[i] = (info->fmt->cmatrix[i] * sat) >> 7; + /* + * Then, if need be, rotate the hue value. + */ + if (hue != 0) { + int sinth, costh, tmpmatrix[CMATRIX_LEN]; + + memcpy(tmpmatrix, matrix, CMATRIX_LEN*sizeof(int)); + sinth = ov7670_sine(hue); + costh = ov7670_cosine(hue); + + matrix[0] = (matrix[3]*sinth + matrix[0]*costh)/1000; + matrix[1] = (matrix[4]*sinth + matrix[1]*costh)/1000; + matrix[2] = (matrix[5]*sinth + matrix[2]*costh)/1000; + matrix[3] = (matrix[3]*costh - matrix[0]*sinth)/1000; + matrix[4] = (matrix[4]*costh - matrix[1]*sinth)/1000; + matrix[5] = (matrix[5]*costh - matrix[2]*sinth)/1000; + } +} + + + +static int ov7670_s_sat_hue(struct v4l2_subdev *sd, int sat, int hue) +{ + struct ov7670_info *info = to_state(sd); + int matrix[CMATRIX_LEN]; + int ret; + + ov7670_calc_cmatrix(info, matrix, sat, hue); + ret = ov7670_store_cmatrix(sd, matrix); + return ret; +} + + +/* + * Some weird registers seem to store values in a sign/magnitude format! + */ + +static unsigned char ov7670_abs_to_sm(unsigned char v) +{ + if (v > 127) + return v & 0x7f; + return (128 - v) | 0x80; +} + +static int ov7670_s_brightness(struct v4l2_subdev *sd, int value) +{ + unsigned char com8 = 0, v; + int ret; + + ov7670_read(sd, REG_COM8, &com8); + com8 &= ~COM8_AEC; + ov7670_write(sd, REG_COM8, com8); + v = ov7670_abs_to_sm(value); + ret = ov7670_write(sd, REG_BRIGHT, v); + return ret; +} + +static int ov7670_s_contrast(struct v4l2_subdev *sd, int value) +{ + return ov7670_write(sd, REG_CONTRAS, (unsigned char) value); +} + +static int ov7670_s_hflip(struct v4l2_subdev *sd, int value) +{ + unsigned char v = 0; + int ret; + + ret = ov7670_read(sd, REG_MVFP, &v); + if (value) + v |= MVFP_MIRROR; + else + v &= ~MVFP_MIRROR; + msleep(10); /* FIXME */ + ret += ov7670_write(sd, REG_MVFP, v); + return ret; +} + +static int ov7670_s_vflip(struct v4l2_subdev *sd, int value) +{ + unsigned char v = 0; + int ret; + + ret = ov7670_read(sd, REG_MVFP, &v); + if (value) + v |= MVFP_FLIP; + else + v &= ~MVFP_FLIP; + msleep(10); /* FIXME */ + ret += ov7670_write(sd, REG_MVFP, v); + return ret; +} + +/* + * GAIN is split between REG_GAIN and REG_VREF[7:6]. If one believes + * the data sheet, the VREF parts should be the most significant, but + * experience shows otherwise. There seems to be little value in + * messing with the VREF bits, so we leave them alone. + */ +static int ov7670_g_gain(struct v4l2_subdev *sd, __s32 *value) +{ + int ret; + unsigned char gain; + + ret = ov7670_read(sd, REG_GAIN, &gain); + *value = gain; + return ret; +} + +static int ov7670_s_gain(struct v4l2_subdev *sd, int value) +{ + int ret; + unsigned char com8; + + ret = ov7670_write(sd, REG_GAIN, value & 0xff); + /* Have to turn off AGC as well */ + if (ret == 0) { + ret = ov7670_read(sd, REG_COM8, &com8); + ret = ov7670_write(sd, REG_COM8, com8 & ~COM8_AGC); + } + return ret; +} + +/* + * Tweak autogain. + */ +static int ov7670_s_autogain(struct v4l2_subdev *sd, int value) +{ + int ret; + unsigned char com8; + + ret = ov7670_read(sd, REG_COM8, &com8); + if (ret == 0) { + if (value) + com8 |= COM8_AGC; + else + com8 &= ~COM8_AGC; + ret = ov7670_write(sd, REG_COM8, com8); + } + return ret; +} + +static int ov7670_s_exp(struct v4l2_subdev *sd, int value) +{ + int ret; + unsigned char com1, com8, aech, aechh; + + ret = ov7670_read(sd, REG_COM1, &com1) + + ov7670_read(sd, REG_COM8, &com8) + + ov7670_read(sd, REG_AECHH, &aechh); + if (ret) + return ret; + + com1 = (com1 & 0xfc) | (value & 0x03); + aech = (value >> 2) & 0xff; + aechh = (aechh & 0xc0) | ((value >> 10) & 0x3f); + ret = ov7670_write(sd, REG_COM1, com1) + + ov7670_write(sd, REG_AECH, aech) + + ov7670_write(sd, REG_AECHH, aechh); + /* Have to turn off AEC as well */ + if (ret == 0) + ret = ov7670_write(sd, REG_COM8, com8 & ~COM8_AEC); + return ret; +} + +/* + * Tweak autoexposure. + */ +static int ov7670_s_autoexp(struct v4l2_subdev *sd, + enum v4l2_exposure_auto_type value) +{ + int ret; + unsigned char com8; + + ret = ov7670_read(sd, REG_COM8, &com8); + if (ret == 0) { + if (value == V4L2_EXPOSURE_AUTO) + com8 |= COM8_AEC; + else + com8 &= ~COM8_AEC; + ret = ov7670_write(sd, REG_COM8, com8); + } + return ret; +} + +static const char * const ov7670_test_pattern_menu[] = { + "No test output", + "Shifting \"1\"", + "8-bar color bar", + "Fade to gray color bar", +}; + +static int ov7670_s_test_pattern(struct v4l2_subdev *sd, int value) +{ + int ret; + + ret = ov7670_update_bits(sd, REG_SCALING_XSC, TEST_PATTTERN_0, + value & BIT(0) ? TEST_PATTTERN_0 : 0); + if (ret) + return ret; + + return ov7670_update_bits(sd, REG_SCALING_YSC, TEST_PATTTERN_1, + value & BIT(1) ? TEST_PATTTERN_1 : 0); +} + +static int ov7670_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct ov7670_info *info = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + return ov7670_g_gain(sd, &info->gain->val); + } + return -EINVAL; +} + +static int ov7670_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct ov7670_info *info = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + return ov7670_s_brightness(sd, ctrl->val); + case V4L2_CID_CONTRAST: + return ov7670_s_contrast(sd, ctrl->val); + case V4L2_CID_SATURATION: + return ov7670_s_sat_hue(sd, + info->saturation->val, info->hue->val); + case V4L2_CID_VFLIP: + return ov7670_s_vflip(sd, ctrl->val); + case V4L2_CID_HFLIP: + return ov7670_s_hflip(sd, ctrl->val); + case V4L2_CID_AUTOGAIN: + /* Only set manual gain if auto gain is not explicitly + turned on. */ + if (!ctrl->val) { + /* ov7670_s_gain turns off auto gain */ + return ov7670_s_gain(sd, info->gain->val); + } + return ov7670_s_autogain(sd, ctrl->val); + case V4L2_CID_EXPOSURE_AUTO: + /* Only set manual exposure if auto exposure is not explicitly + turned on. */ + if (ctrl->val == V4L2_EXPOSURE_MANUAL) { + /* ov7670_s_exp turns off auto exposure */ + return ov7670_s_exp(sd, info->exposure->val); + } + return ov7670_s_autoexp(sd, ctrl->val); + case V4L2_CID_TEST_PATTERN: + return ov7670_s_test_pattern(sd, ctrl->val); + } + return -EINVAL; +} + +static const struct v4l2_ctrl_ops ov7670_ctrl_ops = { + .s_ctrl = ov7670_s_ctrl, + .g_volatile_ctrl = ov7670_g_volatile_ctrl, +}; + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov7670_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + unsigned char val = 0; + int ret; + + ret = ov7670_read(sd, reg->reg & 0xff, &val); + reg->val = val; + reg->size = 1; + return ret; +} + +static int ov7670_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + ov7670_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static void ov7670_power_on(struct v4l2_subdev *sd) +{ + struct ov7670_info *info = to_state(sd); + + if (info->on) + return; + + clk_prepare_enable(info->clk); + + if (info->pwdn_gpio) + gpiod_set_value(info->pwdn_gpio, 0); + if (info->resetb_gpio) { + gpiod_set_value(info->resetb_gpio, 1); + usleep_range(500, 1000); + gpiod_set_value(info->resetb_gpio, 0); + } + if (info->pwdn_gpio || info->resetb_gpio || info->clk) + usleep_range(3000, 5000); + + info->on = true; +} + +static void ov7670_power_off(struct v4l2_subdev *sd) +{ + struct ov7670_info *info = to_state(sd); + + if (!info->on) + return; + + clk_disable_unprepare(info->clk); + + if (info->pwdn_gpio) + gpiod_set_value(info->pwdn_gpio, 1); + + info->on = false; +} + +static int ov7670_s_power(struct v4l2_subdev *sd, int on) +{ + struct ov7670_info *info = to_state(sd); + + if (info->on == on) + return 0; + + if (on) { + ov7670_power_on (sd); + ov7670_init(sd, 0); + ov7670_apply_fmt(sd); + ov7675_apply_framerate(sd); + v4l2_ctrl_handler_setup(&info->hdl); + } else { + ov7670_power_off (sd); + } + + return 0; +} + +static void ov7670_get_default_format(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *format) +{ + struct ov7670_info *info = to_state(sd); + + format->width = info->devtype->win_sizes[0].width; + format->height = info->devtype->win_sizes[0].height; + format->colorspace = info->fmt->colorspace; + format->code = info->fmt->mbus_code; + format->field = V4L2_FIELD_NONE; +} + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static int ov7670_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + + ov7670_get_default_format(sd, format); + + return 0; +} +#endif + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops ov7670_core_ops = { + .reset = ov7670_reset, + .init = ov7670_init, + .s_power = ov7670_s_power, + .log_status = v4l2_ctrl_subdev_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov7670_g_register, + .s_register = ov7670_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops ov7670_video_ops = { + .s_frame_interval = ov7670_s_frame_interval, + .g_frame_interval = ov7670_g_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops ov7670_pad_ops = { + .enum_frame_interval = ov7670_enum_frame_interval, + .enum_frame_size = ov7670_enum_frame_size, + .enum_mbus_code = ov7670_enum_mbus_code, + .get_fmt = ov7670_get_fmt, + .set_fmt = ov7670_set_fmt, +}; + +static const struct v4l2_subdev_ops ov7670_ops = { + .core = &ov7670_core_ops, + .video = &ov7670_video_ops, + .pad = &ov7670_pad_ops, +}; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static const struct v4l2_subdev_internal_ops ov7670_subdev_internal_ops = { + .open = ov7670_open, +}; +#endif + +/* ----------------------------------------------------------------------- */ + +static const struct ov7670_devtype ov7670_devdata[] = { + [MODEL_OV7670] = { + .win_sizes = ov7670_win_sizes, + .n_win_sizes = ARRAY_SIZE(ov7670_win_sizes), + .set_framerate = ov7670_set_framerate_legacy, + .get_framerate = ov7670_get_framerate_legacy, + }, + [MODEL_OV7675] = { + .win_sizes = ov7675_win_sizes, + .n_win_sizes = ARRAY_SIZE(ov7675_win_sizes), + .set_framerate = ov7675_set_framerate, + .get_framerate = ov7675_get_framerate, + }, +}; + +static int ov7670_init_gpio(struct i2c_client *client, struct ov7670_info *info) +{ + info->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(info->pwdn_gpio)) { + dev_info(&client->dev, "can't get %s GPIO\n", "powerdown"); + return PTR_ERR(info->pwdn_gpio); + } + + info->resetb_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(info->resetb_gpio)) { + dev_info(&client->dev, "can't get %s GPIO\n", "reset"); + return PTR_ERR(info->resetb_gpio); + } + + usleep_range(3000, 5000); + + return 0; +} + +/* + * ov7670_parse_dt() - Parse device tree to collect mbus configuration + * properties + */ +static int ov7670_parse_dt(struct device *dev, + struct ov7670_info *info) +{ + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + struct fwnode_handle *ep; + int ret; + + if (!fwnode) + return -EINVAL; + + info->pclk_hb_disable = false; + if (fwnode_property_present(fwnode, "ov7670,pclk-hb-disable")) + info->pclk_hb_disable = true; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus_type != V4L2_MBUS_PARALLEL) { + dev_err(dev, "Unsupported media bus type\n"); + return -EINVAL; + } + info->mbus_config = bus_cfg.bus.parallel.flags; + + return 0; +} + +static int ov7670_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct v4l2_fract tpf; + struct v4l2_subdev *sd; + struct ov7670_info *info; + int ret; + + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (info == NULL) + return -ENOMEM; + sd = &info->sd; + v4l2_i2c_subdev_init(sd, client, &ov7670_ops); + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + sd->internal_ops = &ov7670_subdev_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; +#endif + + info->clock_speed = 30; /* default: a guess */ + + if (dev_fwnode(&client->dev)) { + ret = ov7670_parse_dt(&client->dev, info); + if (ret) + return ret; + + } else if (client->dev.platform_data) { + struct ov7670_config *config = client->dev.platform_data; + + /* + * Must apply configuration before initializing device, because it + * selects I/O method. + */ + info->min_width = config->min_width; + info->min_height = config->min_height; + info->use_smbus = config->use_smbus; + + if (config->clock_speed) + info->clock_speed = config->clock_speed; + + if (config->pll_bypass) + info->pll_bypass = true; + + if (config->pclk_hb_disable) + info->pclk_hb_disable = true; + } + + info->clk = devm_clk_get(&client->dev, "xclk"); /* optional */ + if (IS_ERR(info->clk)) { + ret = PTR_ERR(info->clk); + if (ret == -ENOENT) + info->clk = NULL; + else + return ret; + } + + ret = ov7670_init_gpio(client, info); + if (ret) + return ret; + + ov7670_power_on(sd); + + if (info->clk) { + info->clock_speed = clk_get_rate(info->clk) / 1000000; + if (info->clock_speed < 10 || info->clock_speed > 48) { + ret = -EINVAL; + goto power_off; + } + } + + /* Make sure it's an ov7670 */ + ret = ov7670_detect(sd); + if (ret) { + v4l_dbg(1, debug, client, + "chip found @ 0x%x (%s) is not an ov7670 chip.\n", + client->addr << 1, client->adapter->name); + goto power_off; + } + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + info->devtype = &ov7670_devdata[id->driver_data]; + info->fmt = &ov7670_formats[0]; + info->wsize = &info->devtype->win_sizes[0]; + + ov7670_get_default_format(sd, &info->format); + + info->clkrc = 0; + + /* Set default frame rate to 30 fps */ + tpf.numerator = 1; + tpf.denominator = 30; + info->devtype->set_framerate(sd, &tpf); + + v4l2_ctrl_handler_init(&info->hdl, 10); + v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + info->saturation = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_SATURATION, 0, 256, 1, 128); + info->hue = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_HUE, -180, 180, 5, 0); + info->gain = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_GAIN, 0, 255, 1, 128); + info->auto_gain = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + info->exposure = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 65535, 1, 500); + info->auto_exposure = v4l2_ctrl_new_std_menu(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, 0, + V4L2_EXPOSURE_AUTO); + v4l2_ctrl_new_std_menu_items(&info->hdl, &ov7670_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7670_test_pattern_menu) - 1, 0, 0, + ov7670_test_pattern_menu); + sd->ctrl_handler = &info->hdl; + if (info->hdl.error) { + ret = info->hdl.error; + + goto hdl_free; + } + /* + * We have checked empirically that hw allows to read back the gain + * value chosen by auto gain but that's not the case for auto exposure. + */ + v4l2_ctrl_auto_cluster(2, &info->auto_gain, 0, true); + v4l2_ctrl_auto_cluster(2, &info->auto_exposure, + V4L2_EXPOSURE_MANUAL, false); + v4l2_ctrl_cluster(2, &info->saturation); + +#if defined(CONFIG_MEDIA_CONTROLLER) + info->pad.flags = MEDIA_PAD_FL_SOURCE; + info->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&info->sd.entity, 1, &info->pad); + if (ret < 0) + goto hdl_free; +#endif + + v4l2_ctrl_handler_setup(&info->hdl); + + ret = v4l2_async_register_subdev(&info->sd); + if (ret < 0) + goto entity_cleanup; + + ov7670_power_off(sd); + return 0; + +entity_cleanup: + media_entity_cleanup(&info->sd.entity); +hdl_free: + v4l2_ctrl_handler_free(&info->hdl); +power_off: + ov7670_power_off(sd); + return ret; +} + +static int ov7670_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov7670_info *info = to_state(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_ctrl_handler_free(&info->hdl); + media_entity_cleanup(&info->sd.entity); + return 0; +} + +static const struct i2c_device_id ov7670_id[] = { + { "ov7670", MODEL_OV7670 }, + { "ov7675", MODEL_OV7675 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov7670_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id ov7670_of_match[] = { + { .compatible = "ovti,ov7670", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ov7670_of_match); +#endif + +static struct i2c_driver ov7670_driver = { + .driver = { + .name = "ov7670", + .of_match_table = of_match_ptr(ov7670_of_match), + }, + .probe = ov7670_probe, + .remove = ov7670_remove, + .id_table = ov7670_id, +}; + +module_i2c_driver(ov7670_driver); diff --git a/drivers/media/i2c/ov772x.c b/drivers/media/i2c/ov772x.c new file mode 100644 index 000000000..5033950a4 --- /dev/null +++ b/drivers/media/i2c/ov772x.c @@ -0,0 +1,1496 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ov772x Camera Driver + * + * Copyright (C) 2017 Jacopo Mondi <jacopo+renesas@jmondi.org> + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * Based on ov7670 and soc_camera_platform driver, + * + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/v4l2-mediabus.h> +#include <linux/videodev2.h> + +#include <media/i2c/ov772x.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-subdev.h> + +/* + * register offset + */ +#define GAIN 0x00 /* AGC - Gain control gain setting */ +#define BLUE 0x01 /* AWB - Blue channel gain setting */ +#define RED 0x02 /* AWB - Red channel gain setting */ +#define GREEN 0x03 /* AWB - Green channel gain setting */ +#define COM1 0x04 /* Common control 1 */ +#define BAVG 0x05 /* U/B Average Level */ +#define GAVG 0x06 /* Y/Gb Average Level */ +#define RAVG 0x07 /* V/R Average Level */ +#define AECH 0x08 /* Exposure Value - AEC MSBs */ +#define COM2 0x09 /* Common control 2 */ +#define PID 0x0A /* Product ID Number MSB */ +#define VER 0x0B /* Product ID Number LSB */ +#define COM3 0x0C /* Common control 3 */ +#define COM4 0x0D /* Common control 4 */ +#define COM5 0x0E /* Common control 5 */ +#define COM6 0x0F /* Common control 6 */ +#define AEC 0x10 /* Exposure Value */ +#define CLKRC 0x11 /* Internal clock */ +#define COM7 0x12 /* Common control 7 */ +#define COM8 0x13 /* Common control 8 */ +#define COM9 0x14 /* Common control 9 */ +#define COM10 0x15 /* Common control 10 */ +#define REG16 0x16 /* Register 16 */ +#define HSTART 0x17 /* Horizontal sensor size */ +#define HSIZE 0x18 /* Horizontal frame (HREF column) end high 8-bit */ +#define VSTART 0x19 /* Vertical frame (row) start high 8-bit */ +#define VSIZE 0x1A /* Vertical sensor size */ +#define PSHFT 0x1B /* Data format - pixel delay select */ +#define MIDH 0x1C /* Manufacturer ID byte - high */ +#define MIDL 0x1D /* Manufacturer ID byte - low */ +#define LAEC 0x1F /* Fine AEC value */ +#define COM11 0x20 /* Common control 11 */ +#define BDBASE 0x22 /* Banding filter Minimum AEC value */ +#define DBSTEP 0x23 /* Banding filter Maximum Setp */ +#define AEW 0x24 /* AGC/AEC - Stable operating region (upper limit) */ +#define AEB 0x25 /* AGC/AEC - Stable operating region (lower limit) */ +#define VPT 0x26 /* AGC/AEC Fast mode operating region */ +#define REG28 0x28 /* Register 28 */ +#define HOUTSIZE 0x29 /* Horizontal data output size MSBs */ +#define EXHCH 0x2A /* Dummy pixel insert MSB */ +#define EXHCL 0x2B /* Dummy pixel insert LSB */ +#define VOUTSIZE 0x2C /* Vertical data output size MSBs */ +#define ADVFL 0x2D /* LSB of insert dummy lines in Vertical direction */ +#define ADVFH 0x2E /* MSG of insert dummy lines in Vertical direction */ +#define YAVE 0x2F /* Y/G Channel Average value */ +#define LUMHTH 0x30 /* Histogram AEC/AGC Luminance high level threshold */ +#define LUMLTH 0x31 /* Histogram AEC/AGC Luminance low level threshold */ +#define HREF 0x32 /* Image start and size control */ +#define DM_LNL 0x33 /* Dummy line low 8 bits */ +#define DM_LNH 0x34 /* Dummy line high 8 bits */ +#define ADOFF_B 0x35 /* AD offset compensation value for B channel */ +#define ADOFF_R 0x36 /* AD offset compensation value for R channel */ +#define ADOFF_GB 0x37 /* AD offset compensation value for Gb channel */ +#define ADOFF_GR 0x38 /* AD offset compensation value for Gr channel */ +#define OFF_B 0x39 /* Analog process B channel offset value */ +#define OFF_R 0x3A /* Analog process R channel offset value */ +#define OFF_GB 0x3B /* Analog process Gb channel offset value */ +#define OFF_GR 0x3C /* Analog process Gr channel offset value */ +#define COM12 0x3D /* Common control 12 */ +#define COM13 0x3E /* Common control 13 */ +#define COM14 0x3F /* Common control 14 */ +#define COM15 0x40 /* Common control 15*/ +#define COM16 0x41 /* Common control 16 */ +#define TGT_B 0x42 /* BLC blue channel target value */ +#define TGT_R 0x43 /* BLC red channel target value */ +#define TGT_GB 0x44 /* BLC Gb channel target value */ +#define TGT_GR 0x45 /* BLC Gr channel target value */ +/* for ov7720 */ +#define LCC0 0x46 /* Lens correction control 0 */ +#define LCC1 0x47 /* Lens correction option 1 - X coordinate */ +#define LCC2 0x48 /* Lens correction option 2 - Y coordinate */ +#define LCC3 0x49 /* Lens correction option 3 */ +#define LCC4 0x4A /* Lens correction option 4 - radius of the circular */ +#define LCC5 0x4B /* Lens correction option 5 */ +#define LCC6 0x4C /* Lens correction option 6 */ +/* for ov7725 */ +#define LC_CTR 0x46 /* Lens correction control */ +#define LC_XC 0x47 /* X coordinate of lens correction center relative */ +#define LC_YC 0x48 /* Y coordinate of lens correction center relative */ +#define LC_COEF 0x49 /* Lens correction coefficient */ +#define LC_RADI 0x4A /* Lens correction radius */ +#define LC_COEFB 0x4B /* Lens B channel compensation coefficient */ +#define LC_COEFR 0x4C /* Lens R channel compensation coefficient */ + +#define FIXGAIN 0x4D /* Analog fix gain amplifer */ +#define AREF0 0x4E /* Sensor reference control */ +#define AREF1 0x4F /* Sensor reference current control */ +#define AREF2 0x50 /* Analog reference control */ +#define AREF3 0x51 /* ADC reference control */ +#define AREF4 0x52 /* ADC reference control */ +#define AREF5 0x53 /* ADC reference control */ +#define AREF6 0x54 /* Analog reference control */ +#define AREF7 0x55 /* Analog reference control */ +#define UFIX 0x60 /* U channel fixed value output */ +#define VFIX 0x61 /* V channel fixed value output */ +#define AWBB_BLK 0x62 /* AWB option for advanced AWB */ +#define AWB_CTRL0 0x63 /* AWB control byte 0 */ +#define DSP_CTRL1 0x64 /* DSP control byte 1 */ +#define DSP_CTRL2 0x65 /* DSP control byte 2 */ +#define DSP_CTRL3 0x66 /* DSP control byte 3 */ +#define DSP_CTRL4 0x67 /* DSP control byte 4 */ +#define AWB_BIAS 0x68 /* AWB BLC level clip */ +#define AWB_CTRL1 0x69 /* AWB control 1 */ +#define AWB_CTRL2 0x6A /* AWB control 2 */ +#define AWB_CTRL3 0x6B /* AWB control 3 */ +#define AWB_CTRL4 0x6C /* AWB control 4 */ +#define AWB_CTRL5 0x6D /* AWB control 5 */ +#define AWB_CTRL6 0x6E /* AWB control 6 */ +#define AWB_CTRL7 0x6F /* AWB control 7 */ +#define AWB_CTRL8 0x70 /* AWB control 8 */ +#define AWB_CTRL9 0x71 /* AWB control 9 */ +#define AWB_CTRL10 0x72 /* AWB control 10 */ +#define AWB_CTRL11 0x73 /* AWB control 11 */ +#define AWB_CTRL12 0x74 /* AWB control 12 */ +#define AWB_CTRL13 0x75 /* AWB control 13 */ +#define AWB_CTRL14 0x76 /* AWB control 14 */ +#define AWB_CTRL15 0x77 /* AWB control 15 */ +#define AWB_CTRL16 0x78 /* AWB control 16 */ +#define AWB_CTRL17 0x79 /* AWB control 17 */ +#define AWB_CTRL18 0x7A /* AWB control 18 */ +#define AWB_CTRL19 0x7B /* AWB control 19 */ +#define AWB_CTRL20 0x7C /* AWB control 20 */ +#define AWB_CTRL21 0x7D /* AWB control 21 */ +#define GAM1 0x7E /* Gamma Curve 1st segment input end point */ +#define GAM2 0x7F /* Gamma Curve 2nd segment input end point */ +#define GAM3 0x80 /* Gamma Curve 3rd segment input end point */ +#define GAM4 0x81 /* Gamma Curve 4th segment input end point */ +#define GAM5 0x82 /* Gamma Curve 5th segment input end point */ +#define GAM6 0x83 /* Gamma Curve 6th segment input end point */ +#define GAM7 0x84 /* Gamma Curve 7th segment input end point */ +#define GAM8 0x85 /* Gamma Curve 8th segment input end point */ +#define GAM9 0x86 /* Gamma Curve 9th segment input end point */ +#define GAM10 0x87 /* Gamma Curve 10th segment input end point */ +#define GAM11 0x88 /* Gamma Curve 11th segment input end point */ +#define GAM12 0x89 /* Gamma Curve 12th segment input end point */ +#define GAM13 0x8A /* Gamma Curve 13th segment input end point */ +#define GAM14 0x8B /* Gamma Curve 14th segment input end point */ +#define GAM15 0x8C /* Gamma Curve 15th segment input end point */ +#define SLOP 0x8D /* Gamma curve highest segment slope */ +#define DNSTH 0x8E /* De-noise threshold */ +#define EDGE_STRNGT 0x8F /* Edge strength control when manual mode */ +#define EDGE_TRSHLD 0x90 /* Edge threshold control when manual mode */ +#define DNSOFF 0x91 /* Auto De-noise threshold control */ +#define EDGE_UPPER 0x92 /* Edge strength upper limit when Auto mode */ +#define EDGE_LOWER 0x93 /* Edge strength lower limit when Auto mode */ +#define MTX1 0x94 /* Matrix coefficient 1 */ +#define MTX2 0x95 /* Matrix coefficient 2 */ +#define MTX3 0x96 /* Matrix coefficient 3 */ +#define MTX4 0x97 /* Matrix coefficient 4 */ +#define MTX5 0x98 /* Matrix coefficient 5 */ +#define MTX6 0x99 /* Matrix coefficient 6 */ +#define MTX_CTRL 0x9A /* Matrix control */ +#define BRIGHT 0x9B /* Brightness control */ +#define CNTRST 0x9C /* Contrast contrast */ +#define CNTRST_CTRL 0x9D /* Contrast contrast center */ +#define UVAD_J0 0x9E /* Auto UV adjust contrast 0 */ +#define UVAD_J1 0x9F /* Auto UV adjust contrast 1 */ +#define SCAL0 0xA0 /* Scaling control 0 */ +#define SCAL1 0xA1 /* Scaling control 1 */ +#define SCAL2 0xA2 /* Scaling control 2 */ +#define FIFODLYM 0xA3 /* FIFO manual mode delay control */ +#define FIFODLYA 0xA4 /* FIFO auto mode delay control */ +#define SDE 0xA6 /* Special digital effect control */ +#define USAT 0xA7 /* U component saturation control */ +#define VSAT 0xA8 /* V component saturation control */ +/* for ov7720 */ +#define HUE0 0xA9 /* Hue control 0 */ +#define HUE1 0xAA /* Hue control 1 */ +/* for ov7725 */ +#define HUECOS 0xA9 /* Cosine value */ +#define HUESIN 0xAA /* Sine value */ + +#define SIGN 0xAB /* Sign bit for Hue and contrast */ +#define DSPAUTO 0xAC /* DSP auto function ON/OFF control */ + +/* + * register detail + */ + +/* COM2 */ +#define SOFT_SLEEP_MODE 0x10 /* Soft sleep mode */ + /* Output drive capability */ +#define OCAP_1x 0x00 /* 1x */ +#define OCAP_2x 0x01 /* 2x */ +#define OCAP_3x 0x02 /* 3x */ +#define OCAP_4x 0x03 /* 4x */ + +/* COM3 */ +#define SWAP_MASK (SWAP_RGB | SWAP_YUV | SWAP_ML) +#define IMG_MASK (VFLIP_IMG | HFLIP_IMG) + +#define VFLIP_IMG 0x80 /* Vertical flip image ON/OFF selection */ +#define HFLIP_IMG 0x40 /* Horizontal mirror image ON/OFF selection */ +#define SWAP_RGB 0x20 /* Swap B/R output sequence in RGB mode */ +#define SWAP_YUV 0x10 /* Swap Y/UV output sequence in YUV mode */ +#define SWAP_ML 0x08 /* Swap output MSB/LSB */ + /* Tri-state option for output clock */ +#define NOTRI_CLOCK 0x04 /* 0: Tri-state at this period */ + /* 1: No tri-state at this period */ + /* Tri-state option for output data */ +#define NOTRI_DATA 0x02 /* 0: Tri-state at this period */ + /* 1: No tri-state at this period */ +#define SCOLOR_TEST 0x01 /* Sensor color bar test pattern */ + +/* COM4 */ + /* PLL frequency control */ +#define PLL_BYPASS 0x00 /* 00: Bypass PLL */ +#define PLL_4x 0x40 /* 01: PLL 4x */ +#define PLL_6x 0x80 /* 10: PLL 6x */ +#define PLL_8x 0xc0 /* 11: PLL 8x */ + /* AEC evaluate window */ +#define AEC_FULL 0x00 /* 00: Full window */ +#define AEC_1p2 0x10 /* 01: 1/2 window */ +#define AEC_1p4 0x20 /* 10: 1/4 window */ +#define AEC_2p3 0x30 /* 11: Low 2/3 window */ +#define COM4_RESERVED 0x01 /* Reserved bit */ + +/* COM5 */ +#define AFR_ON_OFF 0x80 /* Auto frame rate control ON/OFF selection */ +#define AFR_SPPED 0x40 /* Auto frame rate control speed selection */ + /* Auto frame rate max rate control */ +#define AFR_NO_RATE 0x00 /* No reduction of frame rate */ +#define AFR_1p2 0x10 /* Max reduction to 1/2 frame rate */ +#define AFR_1p4 0x20 /* Max reduction to 1/4 frame rate */ +#define AFR_1p8 0x30 /* Max reduction to 1/8 frame rate */ + /* Auto frame rate active point control */ +#define AF_2x 0x00 /* Add frame when AGC reaches 2x gain */ +#define AF_4x 0x04 /* Add frame when AGC reaches 4x gain */ +#define AF_8x 0x08 /* Add frame when AGC reaches 8x gain */ +#define AF_16x 0x0c /* Add frame when AGC reaches 16x gain */ + /* AEC max step control */ +#define AEC_NO_LIMIT 0x01 /* 0 : AEC incease step has limit */ + /* 1 : No limit to AEC increase step */ +/* CLKRC */ + /* Input clock divider register */ +#define CLKRC_RESERVED 0x80 /* Reserved bit */ +#define CLKRC_DIV(n) ((n) - 1) + +/* COM7 */ + /* SCCB Register Reset */ +#define SCCB_RESET 0x80 /* 0 : No change */ + /* 1 : Resets all registers to default */ + /* Resolution selection */ +#define SLCT_MASK 0x40 /* Mask of VGA or QVGA */ +#define SLCT_VGA 0x00 /* 0 : VGA */ +#define SLCT_QVGA 0x40 /* 1 : QVGA */ +#define ITU656_ON_OFF 0x20 /* ITU656 protocol ON/OFF selection */ +#define SENSOR_RAW 0x10 /* Sensor RAW */ + /* RGB output format control */ +#define FMT_MASK 0x0c /* Mask of color format */ +#define FMT_GBR422 0x00 /* 00 : GBR 4:2:2 */ +#define FMT_RGB565 0x04 /* 01 : RGB 565 */ +#define FMT_RGB555 0x08 /* 10 : RGB 555 */ +#define FMT_RGB444 0x0c /* 11 : RGB 444 */ + /* Output format control */ +#define OFMT_MASK 0x03 /* Mask of output format */ +#define OFMT_YUV 0x00 /* 00 : YUV */ +#define OFMT_P_BRAW 0x01 /* 01 : Processed Bayer RAW */ +#define OFMT_RGB 0x02 /* 10 : RGB */ +#define OFMT_BRAW 0x03 /* 11 : Bayer RAW */ + +/* COM8 */ +#define FAST_ALGO 0x80 /* Enable fast AGC/AEC algorithm */ + /* AEC Setp size limit */ +#define UNLMT_STEP 0x40 /* 0 : Step size is limited */ + /* 1 : Unlimited step size */ +#define BNDF_ON_OFF 0x20 /* Banding filter ON/OFF */ +#define AEC_BND 0x10 /* Enable AEC below banding value */ +#define AEC_ON_OFF 0x08 /* Fine AEC ON/OFF control */ +#define AGC_ON 0x04 /* AGC Enable */ +#define AWB_ON 0x02 /* AWB Enable */ +#define AEC_ON 0x01 /* AEC Enable */ + +/* COM9 */ +#define BASE_AECAGC 0x80 /* Histogram or average based AEC/AGC */ + /* Automatic gain ceiling - maximum AGC value */ +#define GAIN_2x 0x00 /* 000 : 2x */ +#define GAIN_4x 0x10 /* 001 : 4x */ +#define GAIN_8x 0x20 /* 010 : 8x */ +#define GAIN_16x 0x30 /* 011 : 16x */ +#define GAIN_32x 0x40 /* 100 : 32x */ +#define GAIN_64x 0x50 /* 101 : 64x */ +#define GAIN_128x 0x60 /* 110 : 128x */ +#define DROP_VSYNC 0x04 /* Drop VSYNC output of corrupt frame */ +#define DROP_HREF 0x02 /* Drop HREF output of corrupt frame */ + +/* COM11 */ +#define SGLF_ON_OFF 0x02 /* Single frame ON/OFF selection */ +#define SGLF_TRIG 0x01 /* Single frame transfer trigger */ + +/* HREF */ +#define HREF_VSTART_SHIFT 6 /* VSTART LSB */ +#define HREF_HSTART_SHIFT 4 /* HSTART 2 LSBs */ +#define HREF_VSIZE_SHIFT 2 /* VSIZE LSB */ +#define HREF_HSIZE_SHIFT 0 /* HSIZE 2 LSBs */ + +/* EXHCH */ +#define EXHCH_VSIZE_SHIFT 2 /* VOUTSIZE LSB */ +#define EXHCH_HSIZE_SHIFT 0 /* HOUTSIZE 2 LSBs */ + +/* DSP_CTRL1 */ +#define FIFO_ON 0x80 /* FIFO enable/disable selection */ +#define UV_ON_OFF 0x40 /* UV adjust function ON/OFF selection */ +#define YUV444_2_422 0x20 /* YUV444 to 422 UV channel option selection */ +#define CLR_MTRX_ON_OFF 0x10 /* Color matrix ON/OFF selection */ +#define INTPLT_ON_OFF 0x08 /* Interpolation ON/OFF selection */ +#define GMM_ON_OFF 0x04 /* Gamma function ON/OFF selection */ +#define AUTO_BLK_ON_OFF 0x02 /* Black defect auto correction ON/OFF */ +#define AUTO_WHT_ON_OFF 0x01 /* White define auto correction ON/OFF */ + +/* DSP_CTRL3 */ +#define UV_MASK 0x80 /* UV output sequence option */ +#define UV_ON 0x80 /* ON */ +#define UV_OFF 0x00 /* OFF */ +#define CBAR_MASK 0x20 /* DSP Color bar mask */ +#define CBAR_ON 0x20 /* ON */ +#define CBAR_OFF 0x00 /* OFF */ + +/* DSP_CTRL4 */ +#define DSP_OFMT_YUV 0x00 +#define DSP_OFMT_RGB 0x00 +#define DSP_OFMT_RAW8 0x02 +#define DSP_OFMT_RAW10 0x03 + +/* DSPAUTO (DSP Auto Function ON/OFF Control) */ +#define AWB_ACTRL 0x80 /* AWB auto threshold control */ +#define DENOISE_ACTRL 0x40 /* De-noise auto threshold control */ +#define EDGE_ACTRL 0x20 /* Edge enhancement auto strength control */ +#define UV_ACTRL 0x10 /* UV adjust auto slope control */ +#define SCAL0_ACTRL 0x08 /* Auto scaling factor control */ +#define SCAL1_2_ACTRL 0x04 /* Auto scaling factor control */ + +#define OV772X_MAX_WIDTH VGA_WIDTH +#define OV772X_MAX_HEIGHT VGA_HEIGHT + +/* + * ID + */ +#define OV7720 0x7720 +#define OV7725 0x7721 +#define VERSION(pid, ver) ((pid << 8) | (ver & 0xFF)) + +/* + * PLL multipliers + */ +static struct { + unsigned int mult; + u8 com4; +} ov772x_pll[] = { + { 1, PLL_BYPASS, }, + { 4, PLL_4x, }, + { 6, PLL_6x, }, + { 8, PLL_8x, }, +}; + +/* + * struct + */ + +struct ov772x_color_format { + u32 code; + enum v4l2_colorspace colorspace; + u8 dsp3; + u8 dsp4; + u8 com3; + u8 com7; +}; + +struct ov772x_win_size { + char *name; + unsigned char com7_bit; + unsigned int sizeimage; + struct v4l2_rect rect; +}; + +struct ov772x_priv { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct clk *clk; + struct regmap *regmap; + struct ov772x_camera_info *info; + struct gpio_desc *pwdn_gpio; + struct gpio_desc *rstb_gpio; + const struct ov772x_color_format *cfmt; + const struct ov772x_win_size *win; + struct v4l2_ctrl *vflip_ctrl; + struct v4l2_ctrl *hflip_ctrl; + /* band_filter = COM8[5] ? 256 - BDBASE : 0 */ + struct v4l2_ctrl *band_filter_ctrl; + unsigned int fps; + /* lock to protect power_count and streaming */ + struct mutex lock; + int power_count; + int streaming; +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_pad pad; +#endif +}; + +/* + * supported color format list + */ +static const struct ov772x_color_format ov772x_cfmts[] = { + { + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = SWAP_YUV, + .com7 = OFMT_YUV, + }, + { + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = UV_ON, + .dsp4 = DSP_OFMT_YUV, + .com3 = SWAP_YUV, + .com7 = OFMT_YUV, + }, + { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = 0x0, + .com7 = OFMT_YUV, + }, + { + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = SWAP_RGB, + .com7 = FMT_RGB555 | OFMT_RGB, + }, + { + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = 0x0, + .com7 = FMT_RGB555 | OFMT_RGB, + }, + { + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = SWAP_RGB, + .com7 = FMT_RGB565 | OFMT_RGB, + }, + { + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = 0x0, + .com7 = FMT_RGB565 | OFMT_RGB, + }, + { + /* Setting DSP4 to DSP_OFMT_RAW8 still gives 10-bit output, + * regardless of the COM7 value. We can thus only support 10-bit + * Bayer until someone figures it out. + */ + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_RAW10, + .com3 = 0x0, + .com7 = SENSOR_RAW | OFMT_BRAW, + }, +}; + +/* + * window size list + */ + +static const struct ov772x_win_size ov772x_win_sizes[] = { + { + .name = "VGA", + .com7_bit = SLCT_VGA, + .sizeimage = 510 * 748, + .rect = { + .left = 140, + .top = 14, + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + }, + }, { + .name = "QVGA", + .com7_bit = SLCT_QVGA, + .sizeimage = 278 * 576, + .rect = { + .left = 252, + .top = 6, + .width = QVGA_WIDTH, + .height = QVGA_HEIGHT, + }, + }, +}; + +/* + * frame rate settings lists + */ +static const unsigned int ov772x_frame_intervals[] = { 5, 10, 15, 20, 30, 60 }; + +/* + * general function + */ + +static struct ov772x_priv *to_ov772x(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov772x_priv, subdev); +} + +static int ov772x_reset(struct ov772x_priv *priv) +{ + int ret; + + ret = regmap_write(priv->regmap, COM7, SCCB_RESET); + if (ret < 0) + return ret; + + usleep_range(1000, 5000); + + return regmap_update_bits(priv->regmap, COM2, SOFT_SLEEP_MODE, + SOFT_SLEEP_MODE); +} + +/* + * subdev ops + */ + +static int ov772x_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov772x_priv *priv = to_ov772x(sd); + int ret = 0; + + mutex_lock(&priv->lock); + + if (priv->streaming == enable) + goto done; + + ret = regmap_update_bits(priv->regmap, COM2, SOFT_SLEEP_MODE, + enable ? 0 : SOFT_SLEEP_MODE); + if (ret) + goto done; + + if (enable) { + dev_dbg(&client->dev, "format %d, win %s\n", + priv->cfmt->code, priv->win->name); + } + priv->streaming = enable; + +done: + mutex_unlock(&priv->lock); + + return ret; +} + +static unsigned int ov772x_select_fps(struct ov772x_priv *priv, + struct v4l2_fract *tpf) +{ + unsigned int fps = tpf->numerator ? + tpf->denominator / tpf->numerator : + tpf->denominator; + unsigned int best_diff; + unsigned int diff; + unsigned int idx; + unsigned int i; + + /* Approximate to the closest supported frame interval. */ + best_diff = ~0L; + for (i = 0, idx = 0; i < ARRAY_SIZE(ov772x_frame_intervals); i++) { + diff = abs(fps - ov772x_frame_intervals[i]); + if (diff < best_diff) { + idx = i; + best_diff = diff; + } + } + + return ov772x_frame_intervals[idx]; +} + +static int ov772x_set_frame_rate(struct ov772x_priv *priv, + unsigned int fps, + const struct ov772x_color_format *cfmt, + const struct ov772x_win_size *win) +{ + unsigned long fin = clk_get_rate(priv->clk); + unsigned int best_diff; + unsigned int fsize; + unsigned int pclk; + unsigned int diff; + unsigned int i; + u8 clkrc = 0; + u8 com4 = 0; + int ret; + + /* Use image size (with blankings) to calculate desired pixel clock. */ + switch (cfmt->com7 & OFMT_MASK) { + case OFMT_BRAW: + fsize = win->sizeimage; + break; + case OFMT_RGB: + case OFMT_YUV: + default: + fsize = win->sizeimage * 2; + break; + } + + pclk = fps * fsize; + + /* + * Pixel clock generation circuit is pretty simple: + * + * Fin -> [ / CLKRC_div] -> [ * PLL_mult] -> pclk + * + * Try to approximate the desired pixel clock testing all available + * PLL multipliers (1x, 4x, 6x, 8x) and calculate corresponding + * divisor with: + * + * div = PLL_mult * Fin / pclk + * + * and re-calculate the pixel clock using it: + * + * pclk = Fin * PLL_mult / CLKRC_div + * + * Choose the PLL_mult and CLKRC_div pair that gives a pixel clock + * closer to the desired one. + * + * The desired pixel clock is calculated using a known frame size + * (blanking included) and FPS. + */ + best_diff = ~0L; + for (i = 0; i < ARRAY_SIZE(ov772x_pll); i++) { + unsigned int pll_mult = ov772x_pll[i].mult; + unsigned int pll_out = pll_mult * fin; + unsigned int t_pclk; + unsigned int div; + + if (pll_out < pclk) + continue; + + div = DIV_ROUND_CLOSEST(pll_out, pclk); + t_pclk = DIV_ROUND_CLOSEST(fin * pll_mult, div); + diff = abs(pclk - t_pclk); + if (diff < best_diff) { + best_diff = diff; + clkrc = CLKRC_DIV(div); + com4 = ov772x_pll[i].com4; + } + } + + ret = regmap_write(priv->regmap, COM4, com4 | COM4_RESERVED); + if (ret < 0) + return ret; + + ret = regmap_write(priv->regmap, CLKRC, clkrc | CLKRC_RESERVED); + if (ret < 0) + return ret; + + return 0; +} + +static int ov772x_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct ov772x_priv *priv = to_ov772x(sd); + struct v4l2_fract *tpf = &ival->interval; + + tpf->numerator = 1; + tpf->denominator = priv->fps; + + return 0; +} + +static int ov772x_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct ov772x_priv *priv = to_ov772x(sd); + struct v4l2_fract *tpf = &ival->interval; + unsigned int fps; + int ret = 0; + + mutex_lock(&priv->lock); + + if (priv->streaming) { + ret = -EBUSY; + goto error; + } + + fps = ov772x_select_fps(priv, tpf); + + /* + * If the device is not powered up by the host driver do + * not apply any changes to H/W at this time. Instead + * the frame rate will be restored right after power-up. + */ + if (priv->power_count > 0) { + ret = ov772x_set_frame_rate(priv, fps, priv->cfmt, priv->win); + if (ret) + goto error; + } + + tpf->numerator = 1; + tpf->denominator = fps; + priv->fps = fps; + +error: + mutex_unlock(&priv->lock); + + return ret; +} + +static int ov772x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov772x_priv *priv = container_of(ctrl->handler, + struct ov772x_priv, hdl); + struct regmap *regmap = priv->regmap; + int ret = 0; + u8 val; + + /* v4l2_ctrl_lock() locks our own mutex */ + + /* + * If the device is not powered up by the host driver do + * not apply any controls to H/W at this time. Instead + * the controls will be restored right after power-up. + */ + if (priv->power_count == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + val = ctrl->val ? VFLIP_IMG : 0x00; + if (priv->info && (priv->info->flags & OV772X_FLAG_VFLIP)) + val ^= VFLIP_IMG; + return regmap_update_bits(regmap, COM3, VFLIP_IMG, val); + case V4L2_CID_HFLIP: + val = ctrl->val ? HFLIP_IMG : 0x00; + if (priv->info && (priv->info->flags & OV772X_FLAG_HFLIP)) + val ^= HFLIP_IMG; + return regmap_update_bits(regmap, COM3, HFLIP_IMG, val); + case V4L2_CID_BAND_STOP_FILTER: + if (!ctrl->val) { + /* Switch the filter off, it is on now */ + ret = regmap_update_bits(regmap, BDBASE, 0xff, 0xff); + if (!ret) + ret = regmap_update_bits(regmap, COM8, + BNDF_ON_OFF, 0); + } else { + /* Switch the filter on, set AEC low limit */ + val = 256 - ctrl->val; + ret = regmap_update_bits(regmap, COM8, + BNDF_ON_OFF, BNDF_ON_OFF); + if (!ret) + ret = regmap_update_bits(regmap, BDBASE, + 0xff, val); + } + + return ret; + } + + return -EINVAL; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov772x_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct ov772x_priv *priv = to_ov772x(sd); + int ret; + unsigned int val; + + reg->size = 1; + if (reg->reg > 0xff) + return -EINVAL; + + ret = regmap_read(priv->regmap, reg->reg, &val); + if (ret < 0) + return ret; + + reg->val = (__u64)val; + + return 0; +} + +static int ov772x_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct ov772x_priv *priv = to_ov772x(sd); + + if (reg->reg > 0xff || + reg->val > 0xff) + return -EINVAL; + + return regmap_write(priv->regmap, reg->reg, reg->val); +} +#endif + +static int ov772x_power_on(struct ov772x_priv *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev); + int ret; + + if (priv->clk) { + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + } + + if (priv->pwdn_gpio) { + gpiod_set_value(priv->pwdn_gpio, 1); + usleep_range(500, 1000); + } + + /* + * FIXME: The reset signal is connected to a shared GPIO on some + * platforms (namely the SuperH Migo-R). Until a framework becomes + * available to handle this cleanly, request the GPIO temporarily + * to avoid conflicts. + */ + priv->rstb_gpio = gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(priv->rstb_gpio)) { + dev_info(&client->dev, "Unable to get GPIO \"reset\""); + clk_disable_unprepare(priv->clk); + return PTR_ERR(priv->rstb_gpio); + } + + if (priv->rstb_gpio) { + gpiod_set_value(priv->rstb_gpio, 1); + usleep_range(500, 1000); + gpiod_set_value(priv->rstb_gpio, 0); + usleep_range(500, 1000); + + gpiod_put(priv->rstb_gpio); + } + + return 0; +} + +static int ov772x_power_off(struct ov772x_priv *priv) +{ + clk_disable_unprepare(priv->clk); + + if (priv->pwdn_gpio) { + gpiod_set_value(priv->pwdn_gpio, 0); + usleep_range(500, 1000); + } + + return 0; +} + +static int ov772x_set_params(struct ov772x_priv *priv, + const struct ov772x_color_format *cfmt, + const struct ov772x_win_size *win); + +static int ov772x_s_power(struct v4l2_subdev *sd, int on) +{ + struct ov772x_priv *priv = to_ov772x(sd); + int ret = 0; + + mutex_lock(&priv->lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (priv->power_count == !on) { + if (on) { + ret = ov772x_power_on(priv); + /* + * Restore the format, the frame rate, and + * the controls + */ + if (!ret) + ret = ov772x_set_params(priv, priv->cfmt, + priv->win); + } else { + ret = ov772x_power_off(priv); + } + } + + if (!ret) { + /* Update the power count. */ + priv->power_count += on ? 1 : -1; + WARN(priv->power_count < 0, "Unbalanced power count\n"); + WARN(priv->power_count > 1, "Duplicated s_power call\n"); + } + + mutex_unlock(&priv->lock); + + return ret; +} + +static const struct ov772x_win_size *ov772x_select_win(u32 width, u32 height) +{ + const struct ov772x_win_size *win = &ov772x_win_sizes[0]; + u32 best_diff = UINT_MAX; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ov772x_win_sizes); ++i) { + u32 diff = abs(width - ov772x_win_sizes[i].rect.width) + + abs(height - ov772x_win_sizes[i].rect.height); + if (diff < best_diff) { + best_diff = diff; + win = &ov772x_win_sizes[i]; + } + } + + return win; +} + +static void ov772x_select_params(const struct v4l2_mbus_framefmt *mf, + const struct ov772x_color_format **cfmt, + const struct ov772x_win_size **win) +{ + unsigned int i; + + /* Select a format. */ + *cfmt = &ov772x_cfmts[0]; + + for (i = 0; i < ARRAY_SIZE(ov772x_cfmts); i++) { + if (mf->code == ov772x_cfmts[i].code) { + *cfmt = &ov772x_cfmts[i]; + break; + } + } + + /* Select a window size. */ + *win = ov772x_select_win(mf->width, mf->height); +} + +static int ov772x_edgectrl(struct ov772x_priv *priv) +{ + struct regmap *regmap = priv->regmap; + int ret; + + if (!priv->info) + return 0; + + if (priv->info->edgectrl.strength & OV772X_MANUAL_EDGE_CTRL) { + /* + * Manual Edge Control Mode. + * + * Edge auto strength bit is set by default. + * Remove it when manual mode. + */ + + ret = regmap_update_bits(regmap, DSPAUTO, EDGE_ACTRL, 0x00); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, EDGE_TRSHLD, + OV772X_EDGE_THRESHOLD_MASK, + priv->info->edgectrl.threshold); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, EDGE_STRNGT, + OV772X_EDGE_STRENGTH_MASK, + priv->info->edgectrl.strength); + if (ret < 0) + return ret; + + } else if (priv->info->edgectrl.upper > priv->info->edgectrl.lower) { + /* + * Auto Edge Control Mode. + * + * Set upper and lower limit. + */ + ret = regmap_update_bits(regmap, EDGE_UPPER, + OV772X_EDGE_UPPER_MASK, + priv->info->edgectrl.upper); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, EDGE_LOWER, + OV772X_EDGE_LOWER_MASK, + priv->info->edgectrl.lower); + if (ret < 0) + return ret; + } + + return 0; +} + +static int ov772x_set_params(struct ov772x_priv *priv, + const struct ov772x_color_format *cfmt, + const struct ov772x_win_size *win) +{ + int ret; + u8 val; + + /* Reset hardware. */ + ov772x_reset(priv); + + /* Edge Ctrl. */ + ret = ov772x_edgectrl(priv); + if (ret < 0) + return ret; + + /* Format and window size. */ + ret = regmap_write(priv->regmap, HSTART, win->rect.left >> 2); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = regmap_write(priv->regmap, HSIZE, win->rect.width >> 2); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = regmap_write(priv->regmap, VSTART, win->rect.top >> 1); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = regmap_write(priv->regmap, VSIZE, win->rect.height >> 1); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = regmap_write(priv->regmap, HOUTSIZE, win->rect.width >> 2); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = regmap_write(priv->regmap, VOUTSIZE, win->rect.height >> 1); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = regmap_write(priv->regmap, HREF, + ((win->rect.top & 1) << HREF_VSTART_SHIFT) | + ((win->rect.left & 3) << HREF_HSTART_SHIFT) | + ((win->rect.height & 1) << HREF_VSIZE_SHIFT) | + ((win->rect.width & 3) << HREF_HSIZE_SHIFT)); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = regmap_write(priv->regmap, EXHCH, + ((win->rect.height & 1) << EXHCH_VSIZE_SHIFT) | + ((win->rect.width & 3) << EXHCH_HSIZE_SHIFT)); + if (ret < 0) + goto ov772x_set_fmt_error; + + /* Set DSP_CTRL3. */ + val = cfmt->dsp3; + if (val) { + ret = regmap_update_bits(priv->regmap, DSP_CTRL3, UV_MASK, val); + if (ret < 0) + goto ov772x_set_fmt_error; + } + + /* DSP_CTRL4: AEC reference point and DSP output format. */ + if (cfmt->dsp4) { + ret = regmap_write(priv->regmap, DSP_CTRL4, cfmt->dsp4); + if (ret < 0) + goto ov772x_set_fmt_error; + } + + /* Set COM3. */ + val = cfmt->com3; + if (priv->info && (priv->info->flags & OV772X_FLAG_VFLIP)) + val |= VFLIP_IMG; + if (priv->info && (priv->info->flags & OV772X_FLAG_HFLIP)) + val |= HFLIP_IMG; + if (priv->vflip_ctrl->val) + val ^= VFLIP_IMG; + if (priv->hflip_ctrl->val) + val ^= HFLIP_IMG; + + ret = regmap_update_bits(priv->regmap, COM3, SWAP_MASK | IMG_MASK, val); + if (ret < 0) + goto ov772x_set_fmt_error; + + /* COM7: Sensor resolution and output format control. */ + ret = regmap_write(priv->regmap, COM7, win->com7_bit | cfmt->com7); + if (ret < 0) + goto ov772x_set_fmt_error; + + /* COM4, CLKRC: Set pixel clock and framerate. */ + ret = ov772x_set_frame_rate(priv, priv->fps, cfmt, win); + if (ret < 0) + goto ov772x_set_fmt_error; + + /* Set COM8. */ + if (priv->band_filter_ctrl->val) { + unsigned short band_filter = priv->band_filter_ctrl->val; + + ret = regmap_update_bits(priv->regmap, COM8, + BNDF_ON_OFF, BNDF_ON_OFF); + if (!ret) + ret = regmap_update_bits(priv->regmap, BDBASE, + 0xff, 256 - band_filter); + if (ret < 0) + goto ov772x_set_fmt_error; + } + + return ret; + +ov772x_set_fmt_error: + + ov772x_reset(priv); + + return ret; +} + +static int ov772x_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct ov772x_priv *priv = to_ov772x(sd); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + sel->r.left = 0; + sel->r.top = 0; + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP: + sel->r.width = priv->win->rect.width; + sel->r.height = priv->win->rect.height; + return 0; + default: + return -EINVAL; + } +} + +static int ov772x_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct ov772x_priv *priv = to_ov772x(sd); + + if (format->pad) + return -EINVAL; + + mf->width = priv->win->rect.width; + mf->height = priv->win->rect.height; + mf->code = priv->cfmt->code; + mf->colorspace = priv->cfmt->colorspace; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int ov772x_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov772x_priv *priv = to_ov772x(sd); + struct v4l2_mbus_framefmt *mf = &format->format; + const struct ov772x_color_format *cfmt; + const struct ov772x_win_size *win; + int ret = 0; + + if (format->pad) + return -EINVAL; + + ov772x_select_params(mf, &cfmt, &win); + + mf->code = cfmt->code; + mf->width = win->rect.width; + mf->height = win->rect.height; + mf->field = V4L2_FIELD_NONE; + mf->colorspace = cfmt->colorspace; + mf->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + mf->quantization = V4L2_QUANTIZATION_DEFAULT; + mf->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + cfg->try_fmt = *mf; + return 0; + } + + mutex_lock(&priv->lock); + + if (priv->streaming) { + ret = -EBUSY; + goto error; + } + + /* + * If the device is not powered up by the host driver do + * not apply any changes to H/W at this time. Instead + * the format will be restored right after power-up. + */ + if (priv->power_count > 0) { + ret = ov772x_set_params(priv, cfmt, win); + if (ret < 0) + goto error; + } + priv->win = win; + priv->cfmt = cfmt; + +error: + mutex_unlock(&priv->lock); + + return ret; +} + +static int ov772x_video_probe(struct ov772x_priv *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev); + int pid, ver, midh, midl; + const char *devname; + int ret; + + ret = ov772x_power_on(priv); + if (ret < 0) + return ret; + + /* Check and show product ID and manufacturer ID. */ + ret = regmap_read(priv->regmap, PID, &pid); + if (ret < 0) + return ret; + ret = regmap_read(priv->regmap, VER, &ver); + if (ret < 0) + return ret; + + switch (VERSION(pid, ver)) { + case OV7720: + devname = "ov7720"; + break; + case OV7725: + devname = "ov7725"; + break; + default: + dev_err(&client->dev, + "Product ID error %x:%x\n", pid, ver); + ret = -ENODEV; + goto done; + } + + ret = regmap_read(priv->regmap, MIDH, &midh); + if (ret < 0) + return ret; + ret = regmap_read(priv->regmap, MIDL, &midl); + if (ret < 0) + return ret; + + dev_info(&client->dev, + "%s Product ID %0x:%0x Manufacturer ID %x:%x\n", + devname, pid, ver, midh, midl); + + ret = v4l2_ctrl_handler_setup(&priv->hdl); + +done: + ov772x_power_off(priv); + + return ret; +} + +static const struct v4l2_ctrl_ops ov772x_ctrl_ops = { + .s_ctrl = ov772x_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops ov772x_subdev_core_ops = { + .log_status = v4l2_ctrl_subdev_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov772x_g_register, + .s_register = ov772x_s_register, +#endif + .s_power = ov772x_s_power, +}; + +static int ov772x_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + if (fie->pad || fie->index >= ARRAY_SIZE(ov772x_frame_intervals)) + return -EINVAL; + + if (fie->width != VGA_WIDTH && fie->width != QVGA_WIDTH) + return -EINVAL; + if (fie->height != VGA_HEIGHT && fie->height != QVGA_HEIGHT) + return -EINVAL; + + fie->interval.numerator = 1; + fie->interval.denominator = ov772x_frame_intervals[fie->index]; + + return 0; +} + +static int ov772x_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(ov772x_cfmts)) + return -EINVAL; + + code->code = ov772x_cfmts[code->index].code; + + return 0; +} + +static const struct v4l2_subdev_video_ops ov772x_subdev_video_ops = { + .s_stream = ov772x_s_stream, + .s_frame_interval = ov772x_s_frame_interval, + .g_frame_interval = ov772x_g_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops ov772x_subdev_pad_ops = { + .enum_frame_interval = ov772x_enum_frame_interval, + .enum_mbus_code = ov772x_enum_mbus_code, + .get_selection = ov772x_get_selection, + .get_fmt = ov772x_get_fmt, + .set_fmt = ov772x_set_fmt, +}; + +static const struct v4l2_subdev_ops ov772x_subdev_ops = { + .core = &ov772x_subdev_core_ops, + .video = &ov772x_subdev_video_ops, + .pad = &ov772x_subdev_pad_ops, +}; + +/* + * i2c_driver function + */ + +static int ov772x_probe(struct i2c_client *client) +{ + struct ov772x_priv *priv; + int ret; + static const struct regmap_config ov772x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = DSPAUTO, + }; + + if (!client->dev.of_node && !client->dev.platform_data) { + dev_err(&client->dev, + "Missing ov772x platform data for non-DT device\n"); + return -EINVAL; + } + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = devm_regmap_init_sccb(client, &ov772x_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&client->dev, "Failed to allocate register map\n"); + return PTR_ERR(priv->regmap); + } + + priv->info = client->dev.platform_data; + mutex_init(&priv->lock); + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov772x_subdev_ops); + priv->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + v4l2_ctrl_handler_init(&priv->hdl, 3); + /* Use our mutex for the controls */ + priv->hdl.lock = &priv->lock; + priv->vflip_ctrl = v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + priv->hflip_ctrl = v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + priv->band_filter_ctrl = v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops, + V4L2_CID_BAND_STOP_FILTER, + 0, 256, 1, 0); + priv->subdev.ctrl_handler = &priv->hdl; + if (priv->hdl.error) { + ret = priv->hdl.error; + goto error_ctrl_free; + } + + priv->clk = clk_get(&client->dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(&client->dev, "Unable to get xclk clock\n"); + ret = PTR_ERR(priv->clk); + goto error_ctrl_free; + } + + priv->pwdn_gpio = gpiod_get_optional(&client->dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(priv->pwdn_gpio)) { + dev_info(&client->dev, "Unable to get GPIO \"powerdown\""); + ret = PTR_ERR(priv->pwdn_gpio); + goto error_clk_put; + } + + ret = ov772x_video_probe(priv); + if (ret < 0) + goto error_gpio_put; + +#ifdef CONFIG_MEDIA_CONTROLLER + priv->pad.flags = MEDIA_PAD_FL_SOURCE; + priv->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&priv->subdev.entity, 1, &priv->pad); + if (ret < 0) + goto error_gpio_put; +#endif + + priv->cfmt = &ov772x_cfmts[0]; + priv->win = &ov772x_win_sizes[0]; + priv->fps = 15; + + ret = v4l2_async_register_subdev(&priv->subdev); + if (ret) + goto error_entity_cleanup; + + return 0; + +error_entity_cleanup: + media_entity_cleanup(&priv->subdev.entity); +error_gpio_put: + if (priv->pwdn_gpio) + gpiod_put(priv->pwdn_gpio); +error_clk_put: + clk_put(priv->clk); +error_ctrl_free: + v4l2_ctrl_handler_free(&priv->hdl); + mutex_destroy(&priv->lock); + + return ret; +} + +static int ov772x_remove(struct i2c_client *client) +{ + struct ov772x_priv *priv = to_ov772x(i2c_get_clientdata(client)); + + media_entity_cleanup(&priv->subdev.entity); + clk_put(priv->clk); + if (priv->pwdn_gpio) + gpiod_put(priv->pwdn_gpio); + v4l2_async_unregister_subdev(&priv->subdev); + v4l2_ctrl_handler_free(&priv->hdl); + mutex_destroy(&priv->lock); + + return 0; +} + +static const struct i2c_device_id ov772x_id[] = { + { "ov772x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov772x_id); + +static const struct of_device_id ov772x_of_match[] = { + { .compatible = "ovti,ov7725", }, + { .compatible = "ovti,ov7720", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ov772x_of_match); + +static struct i2c_driver ov772x_i2c_driver = { + .driver = { + .name = "ov772x", + .of_match_table = ov772x_of_match, + }, + .probe_new = ov772x_probe, + .remove = ov772x_remove, + .id_table = ov772x_id, +}; + +module_i2c_driver(ov772x_i2c_driver); + +MODULE_DESCRIPTION("V4L2 driver for OV772x image sensor"); +MODULE_AUTHOR("Kuninori Morimoto"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov7740.c b/drivers/media/i2c/ov7740.c new file mode 100644 index 000000000..5832461c0 --- /dev/null +++ b/drivers/media/i2c/ov7740.c @@ -0,0 +1,1227 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017 Microchip Corporation. + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-subdev.h> + +#define REG_OUTSIZE_LSB 0x34 + +/* OV7740 register tables */ +#define REG_GAIN 0x00 /* Gain lower 8 bits (rest in vref) */ +#define REG_BGAIN 0x01 /* blue gain */ +#define REG_RGAIN 0x02 /* red gain */ +#define REG_GGAIN 0x03 /* green gain */ +#define REG_REG04 0x04 /* analog setting, don't change*/ +#define REG_BAVG 0x05 /* b channel average */ +#define REG_GAVG 0x06 /* g channel average */ +#define REG_RAVG 0x07 /* r channel average */ + +#define REG_REG0C 0x0C /* filp enable */ +#define REG0C_IMG_FLIP 0x80 +#define REG0C_IMG_MIRROR 0x40 + +#define REG_REG0E 0x0E /* blc line */ +#define REG_HAEC 0x0F /* auto exposure cntrl */ +#define REG_AEC 0x10 /* auto exposure cntrl */ + +#define REG_CLK 0x11 /* Clock control */ +#define REG_REG55 0x55 /* Clock PLL DIV/PreDiv */ + +#define REG_REG12 0x12 + +#define REG_REG13 0x13 /* auto/manual AGC, AEC, Write Balance*/ +#define REG13_AEC_EN 0x01 +#define REG13_AGC_EN 0x04 + +#define REG_REG14 0x14 +#define REG_CTRL15 0x15 +#define REG15_GAIN_MSB 0x03 + +#define REG_REG16 0x16 + +#define REG_MIDH 0x1C /* manufacture id byte */ +#define REG_MIDL 0x1D /* manufacture id byre */ +#define REG_PIDH 0x0A /* Product ID MSB */ +#define REG_PIDL 0x0B /* Product ID LSB */ + +#define REG_84 0x84 /* lots of stuff */ +#define REG_REG38 0x38 /* sub-addr */ + +#define REG_AHSTART 0x17 /* Horiz start high bits */ +#define REG_AHSIZE 0x18 +#define REG_AVSTART 0x19 /* Vert start high bits */ +#define REG_AVSIZE 0x1A +#define REG_PSHFT 0x1b /* Pixel delay after HREF */ + +#define REG_HOUTSIZE 0x31 +#define REG_VOUTSIZE 0x32 +#define REG_HVSIZEOFF 0x33 +#define REG_REG34 0x34 /* DSP output size H/V LSB*/ + +#define REG_ISP_CTRL00 0x80 +#define ISPCTRL00_AWB_EN 0x10 +#define ISPCTRL00_AWB_GAIN_EN 0x04 + +#define REG_YGAIN 0xE2 /* ygain for contrast control */ + +#define REG_YBRIGHT 0xE3 +#define REG_SGNSET 0xE4 +#define SGNSET_YBRIGHT_MASK 0x08 + +#define REG_USAT 0xDD +#define REG_VSAT 0xDE + + +struct ov7740 { + struct v4l2_subdev subdev; +#if defined(CONFIG_MEDIA_CONTROLLER) + struct media_pad pad; +#endif + struct v4l2_mbus_framefmt format; + const struct ov7740_pixfmt *fmt; /* Current format */ + const struct ov7740_framesize *frmsize; + struct regmap *regmap; + struct clk *xvclk; + struct v4l2_ctrl_handler ctrl_handler; + struct { + /* gain cluster */ + struct v4l2_ctrl *auto_gain; + struct v4l2_ctrl *gain; + }; + struct { + struct v4l2_ctrl *auto_wb; + struct v4l2_ctrl *blue_balance; + struct v4l2_ctrl *red_balance; + }; + struct { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + struct { + /* exposure cluster */ + struct v4l2_ctrl *auto_exposure; + struct v4l2_ctrl *exposure; + }; + struct { + /* saturation/hue cluster */ + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *hue; + }; + struct v4l2_ctrl *brightness; + struct v4l2_ctrl *contrast; + + struct mutex mutex; /* To serialize asynchronus callbacks */ + bool streaming; /* Streaming on/off */ + + struct gpio_desc *resetb_gpio; + struct gpio_desc *pwdn_gpio; +}; + +struct ov7740_pixfmt { + u32 mbus_code; + enum v4l2_colorspace colorspace; + const struct reg_sequence *regs; + u32 reg_num; +}; + +struct ov7740_framesize { + u16 width; + u16 height; + const struct reg_sequence *regs; + u32 reg_num; +}; + +static const struct reg_sequence ov7740_vga[] = { + {0x55, 0x40}, + {0x11, 0x02}, + + {0xd5, 0x10}, + {0x0c, 0x12}, + {0x0d, 0x34}, + {0x17, 0x25}, + {0x18, 0xa0}, + {0x19, 0x03}, + {0x1a, 0xf0}, + {0x1b, 0x89}, + {0x22, 0x03}, + {0x29, 0x18}, + {0x2b, 0xf8}, + {0x2c, 0x01}, + {REG_HOUTSIZE, 0xa0}, + {REG_VOUTSIZE, 0xf0}, + {0x33, 0xc4}, + {REG_OUTSIZE_LSB, 0x0}, + {0x35, 0x05}, + {0x04, 0x60}, + {0x27, 0x80}, + {0x3d, 0x0f}, + {0x3e, 0x80}, + {0x3f, 0x40}, + {0x40, 0x7f}, + {0x41, 0x6a}, + {0x42, 0x29}, + {0x44, 0x22}, + {0x45, 0x41}, + {0x47, 0x02}, + {0x49, 0x64}, + {0x4a, 0xa1}, + {0x4b, 0x40}, + {0x4c, 0x1a}, + {0x4d, 0x50}, + {0x4e, 0x13}, + {0x64, 0x00}, + {0x67, 0x88}, + {0x68, 0x1a}, + + {0x14, 0x28}, + {0x24, 0x3c}, + {0x25, 0x30}, + {0x26, 0x72}, + {0x50, 0x97}, + {0x51, 0x1f}, + {0x52, 0x00}, + {0x53, 0x00}, + {0x20, 0x00}, + {0x21, 0xcf}, + {0x50, 0x4b}, + {0x38, 0x14}, + {0xe9, 0x00}, + {0x56, 0x55}, + {0x57, 0xff}, + {0x58, 0xff}, + {0x59, 0xff}, + {0x5f, 0x04}, + {0xec, 0x00}, + {0x13, 0xff}, + + {0x81, 0x3f}, + {0x82, 0x32}, + {0x38, 0x11}, + {0x84, 0x70}, + {0x85, 0x00}, + {0x86, 0x03}, + {0x87, 0x01}, + {0x88, 0x05}, + {0x89, 0x30}, + {0x8d, 0x30}, + {0x8f, 0x85}, + {0x93, 0x30}, + {0x95, 0x85}, + {0x99, 0x30}, + {0x9b, 0x85}, + + {0x9c, 0x08}, + {0x9d, 0x12}, + {0x9e, 0x23}, + {0x9f, 0x45}, + {0xa0, 0x55}, + {0xa1, 0x64}, + {0xa2, 0x72}, + {0xa3, 0x7f}, + {0xa4, 0x8b}, + {0xa5, 0x95}, + {0xa6, 0xa7}, + {0xa7, 0xb5}, + {0xa8, 0xcb}, + {0xa9, 0xdd}, + {0xaa, 0xec}, + {0xab, 0x1a}, + + {0xce, 0x78}, + {0xcf, 0x6e}, + {0xd0, 0x0a}, + {0xd1, 0x0c}, + {0xd2, 0x84}, + {0xd3, 0x90}, + {0xd4, 0x1e}, + + {0x5a, 0x24}, + {0x5b, 0x1f}, + {0x5c, 0x88}, + {0x5d, 0x60}, + + {0xac, 0x6e}, + {0xbe, 0xff}, + {0xbf, 0x00}, + + {0x0f, 0x1d}, + {0x0f, 0x1f}, +}; + +static const struct ov7740_framesize ov7740_framesizes[] = { + { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .regs = ov7740_vga, + .reg_num = ARRAY_SIZE(ov7740_vga), + }, +}; + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov7740_get_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); + struct regmap *regmap = ov7740->regmap; + unsigned int val = 0; + int ret; + + ret = regmap_read(regmap, reg->reg & 0xff, &val); + reg->val = val; + reg->size = 1; + + return ret; +} + +static int ov7740_set_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); + struct regmap *regmap = ov7740->regmap; + + regmap_write(regmap, reg->reg & 0xff, reg->val & 0xff); + + return 0; +} +#endif + +static int ov7740_set_power(struct ov7740 *ov7740, int on) +{ + int ret; + + if (on) { + ret = clk_prepare_enable(ov7740->xvclk); + if (ret) + return ret; + + if (ov7740->pwdn_gpio) + gpiod_direction_output(ov7740->pwdn_gpio, 0); + + if (ov7740->resetb_gpio) { + gpiod_set_value(ov7740->resetb_gpio, 1); + usleep_range(500, 1000); + gpiod_set_value(ov7740->resetb_gpio, 0); + usleep_range(3000, 5000); + } + } else { + clk_disable_unprepare(ov7740->xvclk); + + if (ov7740->pwdn_gpio) + gpiod_direction_output(ov7740->pwdn_gpio, 0); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops ov7740_subdev_core_ops = { + .log_status = v4l2_ctrl_subdev_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov7740_get_register, + .s_register = ov7740_set_register, +#endif + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static int ov7740_set_white_balance(struct ov7740 *ov7740, int awb) +{ + struct regmap *regmap = ov7740->regmap; + unsigned int value; + int ret; + + ret = regmap_read(regmap, REG_ISP_CTRL00, &value); + if (!ret) { + if (awb) + value |= (ISPCTRL00_AWB_EN | ISPCTRL00_AWB_GAIN_EN); + else + value &= ~(ISPCTRL00_AWB_EN | ISPCTRL00_AWB_GAIN_EN); + ret = regmap_write(regmap, REG_ISP_CTRL00, value); + if (ret) + return ret; + } + + if (!awb) { + ret = regmap_write(regmap, REG_BGAIN, + ov7740->blue_balance->val); + if (ret) + return ret; + + ret = regmap_write(regmap, REG_RGAIN, ov7740->red_balance->val); + if (ret) + return ret; + } + + return 0; +} + +static int ov7740_set_saturation(struct regmap *regmap, int value) +{ + int ret; + + ret = regmap_write(regmap, REG_USAT, (unsigned char)value); + if (ret) + return ret; + + return regmap_write(regmap, REG_VSAT, (unsigned char)value); +} + +static int ov7740_set_gain(struct regmap *regmap, int value) +{ + int ret; + + ret = regmap_write(regmap, REG_GAIN, value & 0xff); + if (ret) + return ret; + + ret = regmap_update_bits(regmap, REG_CTRL15, + REG15_GAIN_MSB, (value >> 8) & 0x3); + if (!ret) + ret = regmap_update_bits(regmap, REG_REG13, REG13_AGC_EN, 0); + + return ret; +} + +static int ov7740_set_autogain(struct regmap *regmap, int value) +{ + unsigned int reg; + int ret; + + ret = regmap_read(regmap, REG_REG13, ®); + if (ret) + return ret; + if (value) + reg |= REG13_AGC_EN; + else + reg &= ~REG13_AGC_EN; + return regmap_write(regmap, REG_REG13, reg); +} + +static int ov7740_set_brightness(struct regmap *regmap, int value) +{ + /* Turn off AEC/AGC */ + regmap_update_bits(regmap, REG_REG13, REG13_AEC_EN, 0); + regmap_update_bits(regmap, REG_REG13, REG13_AGC_EN, 0); + + if (value >= 0) { + regmap_write(regmap, REG_YBRIGHT, (unsigned char)value); + regmap_update_bits(regmap, REG_SGNSET, SGNSET_YBRIGHT_MASK, 0); + } else{ + regmap_write(regmap, REG_YBRIGHT, (unsigned char)(-value)); + regmap_update_bits(regmap, REG_SGNSET, SGNSET_YBRIGHT_MASK, 1); + } + + return 0; +} + +static int ov7740_set_contrast(struct regmap *regmap, int value) +{ + return regmap_write(regmap, REG_YGAIN, (unsigned char)value); +} + +static int ov7740_get_gain(struct ov7740 *ov7740, struct v4l2_ctrl *ctrl) +{ + struct regmap *regmap = ov7740->regmap; + unsigned int value0, value1; + int ret; + + if (!ctrl->val) + return 0; + + ret = regmap_read(regmap, REG_GAIN, &value0); + if (ret) + return ret; + ret = regmap_read(regmap, REG_CTRL15, &value1); + if (ret) + return ret; + + ov7740->gain->val = (value1 << 8) | (value0 & 0xff); + + return 0; +} + +static int ov7740_get_exp(struct ov7740 *ov7740, struct v4l2_ctrl *ctrl) +{ + struct regmap *regmap = ov7740->regmap; + unsigned int value0, value1; + int ret; + + if (ctrl->val == V4L2_EXPOSURE_MANUAL) + return 0; + + ret = regmap_read(regmap, REG_AEC, &value0); + if (ret) + return ret; + ret = regmap_read(regmap, REG_HAEC, &value1); + if (ret) + return ret; + + ov7740->exposure->val = (value1 << 8) | (value0 & 0xff); + + return 0; +} + +static int ov7740_set_exp(struct regmap *regmap, int value) +{ + int ret; + + /* Turn off AEC/AGC */ + ret = regmap_update_bits(regmap, REG_REG13, + REG13_AEC_EN | REG13_AGC_EN, 0); + if (ret) + return ret; + + ret = regmap_write(regmap, REG_AEC, (unsigned char)value); + if (ret) + return ret; + + return regmap_write(regmap, REG_HAEC, (unsigned char)(value >> 8)); +} + +static int ov7740_set_autoexp(struct regmap *regmap, + enum v4l2_exposure_auto_type value) +{ + unsigned int reg; + int ret; + + ret = regmap_read(regmap, REG_REG13, ®); + if (!ret) { + if (value == V4L2_EXPOSURE_AUTO) + reg |= (REG13_AEC_EN | REG13_AGC_EN); + else + reg &= ~(REG13_AEC_EN | REG13_AGC_EN); + ret = regmap_write(regmap, REG_REG13, reg); + } + + return ret; +} + + +static int ov7740_get_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov7740 *ov7740 = container_of(ctrl->handler, + struct ov7740, ctrl_handler); + int ret; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + ret = ov7740_get_gain(ov7740, ctrl); + break; + case V4L2_CID_EXPOSURE_AUTO: + ret = ov7740_get_exp(ov7740, ctrl); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int ov7740_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov7740 *ov7740 = container_of(ctrl->handler, + struct ov7740, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov7740->subdev); + struct regmap *regmap = ov7740->regmap; + int ret; + u8 val; + + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov7740_set_white_balance(ov7740, ctrl->val); + break; + case V4L2_CID_SATURATION: + ret = ov7740_set_saturation(regmap, ctrl->val); + break; + case V4L2_CID_BRIGHTNESS: + ret = ov7740_set_brightness(regmap, ctrl->val); + break; + case V4L2_CID_CONTRAST: + ret = ov7740_set_contrast(regmap, ctrl->val); + break; + case V4L2_CID_VFLIP: + val = ctrl->val ? REG0C_IMG_FLIP : 0x00; + ret = regmap_update_bits(regmap, REG_REG0C, + REG0C_IMG_FLIP, val); + break; + case V4L2_CID_HFLIP: + val = ctrl->val ? REG0C_IMG_MIRROR : 0x00; + ret = regmap_update_bits(regmap, REG_REG0C, + REG0C_IMG_MIRROR, val); + break; + case V4L2_CID_AUTOGAIN: + if (!ctrl->val) + ret = ov7740_set_gain(regmap, ov7740->gain->val); + else + ret = ov7740_set_autogain(regmap, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + if (ctrl->val == V4L2_EXPOSURE_MANUAL) + ret = ov7740_set_exp(regmap, ov7740->exposure->val); + else + ret = ov7740_set_autoexp(regmap, ctrl->val); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov7740_ctrl_ops = { + .g_volatile_ctrl = ov7740_get_volatile_ctrl, + .s_ctrl = ov7740_set_ctrl, +}; + +static int ov7740_start_streaming(struct ov7740 *ov7740) +{ + int ret; + + if (ov7740->fmt) { + ret = regmap_multi_reg_write(ov7740->regmap, + ov7740->fmt->regs, + ov7740->fmt->reg_num); + if (ret) + return ret; + } + + if (ov7740->frmsize) { + ret = regmap_multi_reg_write(ov7740->regmap, + ov7740->frmsize->regs, + ov7740->frmsize->reg_num); + if (ret) + return ret; + } + + return __v4l2_ctrl_handler_setup(ov7740->subdev.ctrl_handler); +} + +static int ov7740_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&ov7740->mutex); + if (ov7740->streaming == enable) { + mutex_unlock(&ov7740->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + ret = ov7740_start_streaming(ov7740); + if (ret) + goto err_rpm_put; + } else { + pm_runtime_put(&client->dev); + } + + ov7740->streaming = enable; + + mutex_unlock(&ov7740->mutex); + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&ov7740->mutex); + return ret; +} + +static int ov7740_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct v4l2_fract *tpf = &ival->interval; + + + tpf->numerator = 1; + tpf->denominator = 60; + + return 0; +} + +static int ov7740_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct v4l2_fract *tpf = &ival->interval; + + + tpf->numerator = 1; + tpf->denominator = 60; + + return 0; +} + +static const struct v4l2_subdev_video_ops ov7740_subdev_video_ops = { + .s_stream = ov7740_set_stream, + .s_frame_interval = ov7740_s_frame_interval, + .g_frame_interval = ov7740_g_frame_interval, +}; + +static const struct reg_sequence ov7740_format_yuyv[] = { + {0x12, 0x00}, + {0x36, 0x3f}, + {0x80, 0x7f}, + {0x83, 0x01}, +}; + +static const struct reg_sequence ov7740_format_bggr8[] = { + {0x36, 0x2f}, + {0x80, 0x01}, + {0x83, 0x04}, +}; + +static const struct ov7740_pixfmt ov7740_formats[] = { + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .regs = ov7740_format_yuyv, + .reg_num = ARRAY_SIZE(ov7740_format_yuyv), + }, + { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .regs = ov7740_format_bggr8, + .reg_num = ARRAY_SIZE(ov7740_format_bggr8), + } +}; +#define N_OV7740_FMTS ARRAY_SIZE(ov7740_formats) + +static int ov7740_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= N_OV7740_FMTS) + return -EINVAL; + + code->code = ov7740_formats[code->index].mbus_code; + + return 0; +} + +static int ov7740_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + if (fie->pad) + return -EINVAL; + + if (fie->index >= 1) + return -EINVAL; + + if ((fie->width != VGA_WIDTH) || (fie->height != VGA_HEIGHT)) + return -EINVAL; + + fie->interval.numerator = 1; + fie->interval.denominator = 60; + + return 0; +} + +static int ov7740_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->pad) + return -EINVAL; + + if (fse->index > 0) + return -EINVAL; + + fse->min_width = fse->max_width = VGA_WIDTH; + fse->min_height = fse->max_height = VGA_HEIGHT; + + return 0; +} + +static int ov7740_try_fmt_internal(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt, + const struct ov7740_pixfmt **ret_fmt, + const struct ov7740_framesize **ret_frmsize) +{ + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); + const struct ov7740_framesize *fsize = &ov7740_framesizes[0]; + int index, i; + + for (index = 0; index < N_OV7740_FMTS; index++) { + if (ov7740_formats[index].mbus_code == fmt->code) + break; + } + if (index >= N_OV7740_FMTS) { + /* default to first format */ + index = 0; + fmt->code = ov7740_formats[0].mbus_code; + } + if (ret_fmt != NULL) + *ret_fmt = ov7740_formats + index; + + for (i = 0; i < ARRAY_SIZE(ov7740_framesizes); i++) { + if ((fsize->width >= fmt->width) && + (fsize->height >= fmt->height)) { + fmt->width = fsize->width; + fmt->height = fsize->height; + break; + } + + fsize++; + } + if (i >= ARRAY_SIZE(ov7740_framesizes)) { + fsize = &ov7740_framesizes[0]; + fmt->width = fsize->width; + fmt->height = fsize->height; + } + if (ret_frmsize != NULL) + *ret_frmsize = fsize; + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = ov7740_formats[index].colorspace; + + ov7740->format = *fmt; + + return 0; +} + +static int ov7740_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); + const struct ov7740_pixfmt *ovfmt; + const struct ov7740_framesize *fsize; +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + struct v4l2_mbus_framefmt *mbus_fmt; +#endif + int ret; + + mutex_lock(&ov7740->mutex); + if (format->pad) { + ret = -EINVAL; + goto error; + } + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + ret = ov7740_try_fmt_internal(sd, &format->format, NULL, NULL); + if (ret) + goto error; +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + mbus_fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + *mbus_fmt = format->format; +#endif + mutex_unlock(&ov7740->mutex); + return 0; + } + + ret = ov7740_try_fmt_internal(sd, &format->format, &ovfmt, &fsize); + if (ret) + goto error; + + ov7740->fmt = ovfmt; + ov7740->frmsize = fsize; + + mutex_unlock(&ov7740->mutex); + return 0; + +error: + mutex_unlock(&ov7740->mutex); + return ret; +} + +static int ov7740_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + struct v4l2_mbus_framefmt *mbus_fmt; +#endif + int ret = 0; + + mutex_lock(&ov7740->mutex); + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + mbus_fmt = v4l2_subdev_get_try_format(sd, cfg, 0); + format->format = *mbus_fmt; + ret = 0; +#else + ret = -EINVAL; +#endif + } else { + format->format = ov7740->format; + } + mutex_unlock(&ov7740->mutex); + + return ret; +} + +static const struct v4l2_subdev_pad_ops ov7740_subdev_pad_ops = { + .enum_frame_interval = ov7740_enum_frame_interval, + .enum_frame_size = ov7740_enum_frame_size, + .enum_mbus_code = ov7740_enum_mbus_code, + .get_fmt = ov7740_get_fmt, + .set_fmt = ov7740_set_fmt, +}; + +static const struct v4l2_subdev_ops ov7740_subdev_ops = { + .core = &ov7740_subdev_core_ops, + .video = &ov7740_subdev_video_ops, + .pad = &ov7740_subdev_pad_ops, +}; + +static void ov7740_get_default_format(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *format) +{ + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); + + format->width = ov7740->frmsize->width; + format->height = ov7740->frmsize->height; + format->colorspace = ov7740->fmt->colorspace; + format->code = ov7740->fmt->mbus_code; + format->field = V4L2_FIELD_NONE; +} + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static int ov7740_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); + struct v4l2_mbus_framefmt *format = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + + mutex_lock(&ov7740->mutex); + ov7740_get_default_format(sd, format); + mutex_unlock(&ov7740->mutex); + + return 0; +} + +static const struct v4l2_subdev_internal_ops ov7740_subdev_internal_ops = { + .open = ov7740_open, +}; +#endif + +static int ov7740_probe_dt(struct i2c_client *client, + struct ov7740 *ov7740) +{ + ov7740->resetb_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ov7740->resetb_gpio)) { + dev_info(&client->dev, "can't get %s GPIO\n", "reset"); + return PTR_ERR(ov7740->resetb_gpio); + } + + ov7740->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(ov7740->pwdn_gpio)) { + dev_info(&client->dev, "can't get %s GPIO\n", "powerdown"); + return PTR_ERR(ov7740->pwdn_gpio); + } + + return 0; +} + +static int ov7740_detect(struct ov7740 *ov7740) +{ + struct regmap *regmap = ov7740->regmap; + unsigned int midh, midl, pidh, pidl; + int ret; + + ret = regmap_read(regmap, REG_MIDH, &midh); + if (ret) + return ret; + if (midh != 0x7f) + return -ENODEV; + + ret = regmap_read(regmap, REG_MIDL, &midl); + if (ret) + return ret; + if (midl != 0xa2) + return -ENODEV; + + ret = regmap_read(regmap, REG_PIDH, &pidh); + if (ret) + return ret; + if (pidh != 0x77) + return -ENODEV; + + ret = regmap_read(regmap, REG_PIDL, &pidl); + if (ret) + return ret; + if ((pidl != 0x40) && (pidl != 0x41) && (pidl != 0x42)) + return -ENODEV; + + return 0; +} + +static int ov7740_init_controls(struct ov7740 *ov7740) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov7740->subdev); + struct v4l2_ctrl_handler *ctrl_hdlr = &ov7740->ctrl_handler; + int ret; + + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 12); + if (ret < 0) + return ret; + + ctrl_hdlr->lock = &ov7740->mutex; + ov7740->auto_wb = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + ov7740->blue_balance = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_BLUE_BALANCE, + 0, 0xff, 1, 0x80); + ov7740->red_balance = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_RED_BALANCE, + 0, 0xff, 1, 0x80); + + ov7740->brightness = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_BRIGHTNESS, + -255, 255, 1, 0); + ov7740->contrast = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_CONTRAST, + 0, 127, 1, 0x20); + ov7740->saturation = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_SATURATION, 0, 256, 1, 0x80); + ov7740->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + ov7740->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + ov7740->gain = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_GAIN, 0, 1023, 1, 500); + + ov7740->auto_gain = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + + ov7740->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov7740_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 65535, 1, 500); + + ov7740->auto_exposure = v4l2_ctrl_new_std_menu(ctrl_hdlr, + &ov7740_ctrl_ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, + V4L2_EXPOSURE_AUTO); + + v4l2_ctrl_auto_cluster(3, &ov7740->auto_wb, 0, false); + v4l2_ctrl_auto_cluster(2, &ov7740->auto_gain, 0, true); + v4l2_ctrl_auto_cluster(2, &ov7740->auto_exposure, + V4L2_EXPOSURE_MANUAL, true); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "controls initialisation failed (%d)\n", + ret); + goto error; + } + + ret = v4l2_ctrl_handler_setup(ctrl_hdlr); + if (ret) { + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ov7740->subdev.ctrl_handler = ctrl_hdlr; + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&ov7740->mutex); + return ret; +} + +static void ov7740_free_controls(struct ov7740 *ov7740) +{ + v4l2_ctrl_handler_free(ov7740->subdev.ctrl_handler); + mutex_destroy(&ov7740->mutex); +} + +#define OV7740_MAX_REGISTER 0xff +static const struct regmap_config ov7740_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = OV7740_MAX_REGISTER, +}; + +static int ov7740_probe(struct i2c_client *client) +{ + struct ov7740 *ov7740; + struct v4l2_subdev *sd; + int ret; + + ov7740 = devm_kzalloc(&client->dev, sizeof(*ov7740), GFP_KERNEL); + if (!ov7740) + return -ENOMEM; + + ov7740->xvclk = devm_clk_get(&client->dev, "xvclk"); + if (IS_ERR(ov7740->xvclk)) { + ret = PTR_ERR(ov7740->xvclk); + dev_err(&client->dev, + "OV7740: fail to get xvclk: %d\n", ret); + return ret; + } + + ret = ov7740_probe_dt(client, ov7740); + if (ret) + return ret; + + ov7740->regmap = devm_regmap_init_sccb(client, &ov7740_regmap_config); + if (IS_ERR(ov7740->regmap)) { + ret = PTR_ERR(ov7740->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + sd = &ov7740->subdev; + v4l2_i2c_subdev_init(sd, client, &ov7740_subdev_ops); + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + sd->internal_ops = &ov7740_subdev_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; +#endif + +#if defined(CONFIG_MEDIA_CONTROLLER) + ov7740->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &ov7740->pad); + if (ret) + return ret; +#endif + + ret = ov7740_set_power(ov7740, 1); + if (ret) + return ret; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + + ret = ov7740_detect(ov7740); + if (ret) + goto error_detect; + + mutex_init(&ov7740->mutex); + + ret = ov7740_init_controls(ov7740); + if (ret) + goto error_init_controls; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + ov7740->fmt = &ov7740_formats[0]; + ov7740->frmsize = &ov7740_framesizes[0]; + + ov7740_get_default_format(sd, &ov7740->format); + + ret = v4l2_async_register_subdev(sd); + if (ret) + goto error_async_register; + + pm_runtime_idle(&client->dev); + + return 0; + +error_async_register: + v4l2_ctrl_handler_free(ov7740->subdev.ctrl_handler); +error_init_controls: + ov7740_free_controls(ov7740); +error_detect: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + ov7740_set_power(ov7740, 0); + media_entity_cleanup(&ov7740->subdev.entity); + + return ret; +} + +static int ov7740_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); + + mutex_destroy(&ov7740->mutex); + v4l2_ctrl_handler_free(ov7740->subdev.ctrl_handler); + media_entity_cleanup(&ov7740->subdev.entity); + v4l2_async_unregister_subdev(sd); + ov7740_free_controls(ov7740); + + pm_runtime_get_sync(&client->dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + ov7740_set_power(ov7740, 0); + return 0; +} + +static int __maybe_unused ov7740_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); + + ov7740_set_power(ov7740, 0); + + return 0; +} + +static int __maybe_unused ov7740_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov7740 *ov7740 = container_of(sd, struct ov7740, subdev); + + return ov7740_set_power(ov7740, 1); +} + +static const struct i2c_device_id ov7740_id[] = { + { "ov7740", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, ov7740_id); + +static const struct dev_pm_ops ov7740_pm_ops = { + SET_RUNTIME_PM_OPS(ov7740_runtime_suspend, ov7740_runtime_resume, NULL) +}; + +static const struct of_device_id ov7740_of_match[] = { + {.compatible = "ovti,ov7740", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ov7740_of_match); + +static struct i2c_driver ov7740_i2c_driver = { + .driver = { + .name = "ov7740", + .pm = &ov7740_pm_ops, + .of_match_table = of_match_ptr(ov7740_of_match), + }, + .probe_new = ov7740_probe, + .remove = ov7740_remove, + .id_table = ov7740_id, +}; +module_i2c_driver(ov7740_i2c_driver); + +MODULE_DESCRIPTION("The V4L2 driver for Omnivision 7740 sensor"); +MODULE_AUTHOR("Songjun Wu <songjun.wu@atmel.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov8856.c b/drivers/media/i2c/ov8856.c new file mode 100644 index 000000000..2f4ceaa80 --- /dev/null +++ b/drivers/media/i2c/ov8856.c @@ -0,0 +1,1864 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Intel Corporation. + +#include <asm/unaligned.h> +#include <linux/acpi.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define OV8856_REG_VALUE_08BIT 1 +#define OV8856_REG_VALUE_16BIT 2 +#define OV8856_REG_VALUE_24BIT 3 + +#define OV8856_LINK_FREQ_360MHZ 360000000ULL +#define OV8856_LINK_FREQ_180MHZ 180000000ULL +#define OV8856_SCLK 144000000ULL +#define OV8856_XVCLK_19_2 19200000 +#define OV8856_DATA_LANES 4 +#define OV8856_RGB_DEPTH 10 + +#define OV8856_REG_CHIP_ID 0x300a +#define OV8856_CHIP_ID 0x00885a + +#define OV8856_REG_MODE_SELECT 0x0100 +#define OV8856_MODE_STANDBY 0x00 +#define OV8856_MODE_STREAMING 0x01 + +/* module revisions */ +#define OV8856_2A_MODULE 0x01 +#define OV8856_1B_MODULE 0x02 + +/* the OTP read-out buffer is at 0x7000 and 0xf is the offset + * of the byte in the OTP that means the module revision + */ +#define OV8856_MODULE_REVISION 0x700f +#define OV8856_OTP_MODE_CTRL 0x3d84 +#define OV8856_OTP_LOAD_CTRL 0x3d81 +#define OV8856_OTP_MODE_AUTO 0x00 +#define OV8856_OTP_LOAD_CTRL_ENABLE BIT(0) + +/* vertical-timings from sensor */ +#define OV8856_REG_VTS 0x380e +#define OV8856_VTS_MAX 0x7fff + +/* horizontal-timings from sensor */ +#define OV8856_REG_HTS 0x380c + +/* Exposure controls from sensor */ +#define OV8856_REG_EXPOSURE 0x3500 +#define OV8856_EXPOSURE_MIN 6 +#define OV8856_EXPOSURE_MAX_MARGIN 6 +#define OV8856_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define OV8856_REG_ANALOG_GAIN 0x3508 +#define OV8856_ANAL_GAIN_MIN 128 +#define OV8856_ANAL_GAIN_MAX 2047 +#define OV8856_ANAL_GAIN_STEP 1 + +/* Digital gain controls from sensor */ +#define OV8856_REG_MWB_R_GAIN 0x5019 +#define OV8856_REG_MWB_G_GAIN 0x501b +#define OV8856_REG_MWB_B_GAIN 0x501d +#define OV8856_DGTL_GAIN_MIN 0 +#define OV8856_DGTL_GAIN_MAX 4095 +#define OV8856_DGTL_GAIN_STEP 1 +#define OV8856_DGTL_GAIN_DEFAULT 1024 + +/* Test Pattern Control */ +#define OV8856_REG_TEST_PATTERN 0x5e00 +#define OV8856_TEST_PATTERN_ENABLE BIT(7) +#define OV8856_TEST_PATTERN_BAR_SHIFT 2 + +#define to_ov8856(_sd) container_of(_sd, struct ov8856, sd) + +static const char * const ov8856_supply_names[] = { + "dovdd", /* Digital I/O power */ + "avdd", /* Analog power */ + "dvdd", /* Digital core power */ +}; + +enum { + OV8856_LINK_FREQ_720MBPS, + OV8856_LINK_FREQ_360MBPS, +}; + +struct ov8856_reg { + u16 address; + u8 val; +}; + +struct ov8856_reg_list { + u32 num_of_regs; + const struct ov8856_reg *regs; +}; + +struct ov8856_link_freq_config { + const struct ov8856_reg_list reg_list; +}; + +struct ov8856_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timining size */ + u32 hts; + + /* Default vertical timining size */ + u32 vts_def; + + /* Min vertical timining size */ + u32 vts_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* Sensor register settings for this resolution */ + const struct ov8856_reg_list reg_list; +}; + +static const struct ov8856_reg mipi_data_rate_720mbps[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x0302, 0x4b}, + {0x0303, 0x01}, + {0x030b, 0x02}, + {0x030d, 0x4b}, + {0x031e, 0x0c}, +}; + +static const struct ov8856_reg mipi_data_rate_360mbps[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x0302, 0x4b}, + {0x0303, 0x03}, + {0x030b, 0x02}, + {0x030d, 0x4b}, + {0x031e, 0x0c}, +}; + +static const struct ov8856_reg mode_3280x2464_regs[] = { + {0x3000, 0x20}, + {0x3003, 0x08}, + {0x300e, 0x20}, + {0x3010, 0x00}, + {0x3015, 0x84}, + {0x3018, 0x72}, + {0x3021, 0x23}, + {0x3033, 0x24}, + {0x3500, 0x00}, + {0x3501, 0x9a}, + {0x3502, 0x20}, + {0x3503, 0x08}, + {0x3505, 0x83}, + {0x3508, 0x01}, + {0x3509, 0x80}, + {0x350c, 0x00}, + {0x350d, 0x80}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x72}, + {0x3601, 0x40}, + {0x3602, 0x30}, + {0x3610, 0xc5}, + {0x3611, 0x58}, + {0x3612, 0x5c}, + {0x3613, 0xca}, + {0x3614, 0x20}, + {0x3628, 0xff}, + {0x3629, 0xff}, + {0x362a, 0xff}, + {0x3633, 0x10}, + {0x3634, 0x10}, + {0x3635, 0x10}, + {0x3636, 0x10}, + {0x3663, 0x08}, + {0x3669, 0x34}, + {0x366e, 0x10}, + {0x3706, 0x86}, + {0x370b, 0x7e}, + {0x3714, 0x23}, + {0x3730, 0x12}, + {0x3733, 0x10}, + {0x3764, 0x00}, + {0x3765, 0x00}, + {0x3769, 0x62}, + {0x376a, 0x2a}, + {0x376b, 0x30}, + {0x3780, 0x00}, + {0x3781, 0x24}, + {0x3782, 0x00}, + {0x3783, 0x23}, + {0x3798, 0x2f}, + {0x37a1, 0x60}, + {0x37a8, 0x6a}, + {0x37ab, 0x3f}, + {0x37c2, 0x04}, + {0x37c3, 0xf1}, + {0x37c9, 0x80}, + {0x37cb, 0x16}, + {0x37cc, 0x16}, + {0x37cd, 0x16}, + {0x37ce, 0x16}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x06}, + {0x3804, 0x0c}, + {0x3805, 0xdf}, + {0x3806, 0x09}, + {0x3807, 0xa7}, + {0x3808, 0x0c}, + {0x3809, 0xd0}, + {0x380a, 0x09}, + {0x380b, 0xa0}, + {0x380c, 0x07}, + {0x380d, 0x88}, + {0x380e, 0x09}, + {0x380f, 0xb8}, + {0x3810, 0x00}, + {0x3811, 0x00}, + {0x3812, 0x00}, + {0x3813, 0x01}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x00}, + {0x3817, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x10}, + {0x3820, 0x80}, + {0x3821, 0x46}, + {0x382a, 0x01}, + {0x382b, 0x01}, + {0x3830, 0x06}, + {0x3836, 0x02}, + {0x3862, 0x04}, + {0x3863, 0x08}, + {0x3cc0, 0x33}, + {0x3d85, 0x17}, + {0x3d8c, 0x73}, + {0x3d8d, 0xde}, + {0x4001, 0xe0}, + {0x4003, 0x40}, + {0x4008, 0x00}, + {0x4009, 0x0b}, + {0x400a, 0x00}, + {0x400b, 0x84}, + {0x400f, 0x80}, + {0x4010, 0xf0}, + {0x4011, 0xff}, + {0x4012, 0x02}, + {0x4013, 0x01}, + {0x4014, 0x01}, + {0x4015, 0x01}, + {0x4042, 0x00}, + {0x4043, 0x80}, + {0x4044, 0x00}, + {0x4045, 0x80}, + {0x4046, 0x00}, + {0x4047, 0x80}, + {0x4048, 0x00}, + {0x4049, 0x80}, + {0x4041, 0x03}, + {0x404c, 0x20}, + {0x404d, 0x00}, + {0x404e, 0x20}, + {0x4203, 0x80}, + {0x4307, 0x30}, + {0x4317, 0x00}, + {0x4503, 0x08}, + {0x4601, 0x80}, + {0x4800, 0x44}, + {0x4816, 0x53}, + {0x481b, 0x58}, + {0x481f, 0x27}, + {0x4837, 0x16}, + {0x483c, 0x0f}, + {0x484b, 0x05}, + {0x5000, 0x57}, + {0x5001, 0x0a}, + {0x5004, 0x04}, + {0x502e, 0x03}, + {0x5030, 0x41}, + {0x5780, 0x14}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x5795, 0x02}, + {0x5796, 0x20}, + {0x5797, 0x20}, + {0x5798, 0xd5}, + {0x5799, 0xd5}, + {0x579a, 0x00}, + {0x579b, 0x50}, + {0x579c, 0x00}, + {0x579d, 0x2c}, + {0x579e, 0x0c}, + {0x579f, 0x40}, + {0x57a0, 0x09}, + {0x57a1, 0x40}, + {0x59f8, 0x3d}, + {0x5a08, 0x02}, + {0x5b00, 0x02}, + {0x5b01, 0x10}, + {0x5b02, 0x03}, + {0x5b03, 0xcf}, + {0x5b05, 0x6c}, + {0x5e00, 0x00} +}; + +static const struct ov8856_reg mode_3264x2448_regs[] = { + {0x0103, 0x01}, + {0x0302, 0x3c}, + {0x0303, 0x01}, + {0x031e, 0x0c}, + {0x3000, 0x20}, + {0x3003, 0x08}, + {0x300e, 0x20}, + {0x3010, 0x00}, + {0x3015, 0x84}, + {0x3018, 0x72}, + {0x3021, 0x23}, + {0x3033, 0x24}, + {0x3500, 0x00}, + {0x3501, 0x9a}, + {0x3502, 0x20}, + {0x3503, 0x08}, + {0x3505, 0x83}, + {0x3508, 0x01}, + {0x3509, 0x80}, + {0x350c, 0x00}, + {0x350d, 0x80}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x72}, + {0x3601, 0x40}, + {0x3602, 0x30}, + {0x3610, 0xc5}, + {0x3611, 0x58}, + {0x3612, 0x5c}, + {0x3613, 0xca}, + {0x3614, 0x60}, + {0x3628, 0xff}, + {0x3629, 0xff}, + {0x362a, 0xff}, + {0x3633, 0x10}, + {0x3634, 0x10}, + {0x3635, 0x10}, + {0x3636, 0x10}, + {0x3663, 0x08}, + {0x3669, 0x34}, + {0x366d, 0x00}, + {0x366e, 0x10}, + {0x3706, 0x86}, + {0x370b, 0x7e}, + {0x3714, 0x23}, + {0x3730, 0x12}, + {0x3733, 0x10}, + {0x3764, 0x00}, + {0x3765, 0x00}, + {0x3769, 0x62}, + {0x376a, 0x2a}, + {0x376b, 0x30}, + {0x3780, 0x00}, + {0x3781, 0x24}, + {0x3782, 0x00}, + {0x3783, 0x23}, + {0x3798, 0x2f}, + {0x37a1, 0x60}, + {0x37a8, 0x6a}, + {0x37ab, 0x3f}, + {0x37c2, 0x04}, + {0x37c3, 0xf1}, + {0x37c9, 0x80}, + {0x37cb, 0x16}, + {0x37cc, 0x16}, + {0x37cd, 0x16}, + {0x37ce, 0x16}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x0c}, + {0x3804, 0x0c}, + {0x3805, 0xdf}, + {0x3806, 0x09}, + {0x3807, 0xa3}, + {0x3808, 0x0c}, + {0x3809, 0xc0}, + {0x380a, 0x09}, + {0x380b, 0x90}, + {0x380c, 0x07}, + {0x380d, 0x8c}, + {0x380e, 0x09}, + {0x380f, 0xb2}, + {0x3810, 0x00}, + {0x3811, 0x04}, + {0x3812, 0x00}, + {0x3813, 0x02}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x00}, + {0x3817, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x10}, + {0x3820, 0x80}, + {0x3821, 0x46}, + {0x382a, 0x01}, + {0x382b, 0x01}, + {0x3830, 0x06}, + {0x3836, 0x02}, + {0x3862, 0x04}, + {0x3863, 0x08}, + {0x3cc0, 0x33}, + {0x3d85, 0x17}, + {0x3d8c, 0x73}, + {0x3d8d, 0xde}, + {0x4001, 0xe0}, + {0x4003, 0x40}, + {0x4008, 0x00}, + {0x4009, 0x0b}, + {0x400a, 0x00}, + {0x400b, 0x84}, + {0x400f, 0x80}, + {0x4010, 0xf0}, + {0x4011, 0xff}, + {0x4012, 0x02}, + {0x4013, 0x01}, + {0x4014, 0x01}, + {0x4015, 0x01}, + {0x4042, 0x00}, + {0x4043, 0x80}, + {0x4044, 0x00}, + {0x4045, 0x80}, + {0x4046, 0x00}, + {0x4047, 0x80}, + {0x4048, 0x00}, + {0x4049, 0x80}, + {0x4041, 0x03}, + {0x404c, 0x20}, + {0x404d, 0x00}, + {0x404e, 0x20}, + {0x4203, 0x80}, + {0x4307, 0x30}, + {0x4317, 0x00}, + {0x4502, 0x50}, + {0x4503, 0x08}, + {0x4601, 0x80}, + {0x4800, 0x44}, + {0x4816, 0x53}, + {0x481b, 0x50}, + {0x481f, 0x27}, + {0x4823, 0x3c}, + {0x482b, 0x00}, + {0x4831, 0x66}, + {0x4837, 0x16}, + {0x483c, 0x0f}, + {0x484b, 0x05}, + {0x5000, 0x77}, + {0x5001, 0x0a}, + {0x5003, 0xc8}, + {0x5004, 0x04}, + {0x5006, 0x00}, + {0x5007, 0x00}, + {0x502e, 0x03}, + {0x5030, 0x41}, + {0x5780, 0x14}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x5795, 0x02}, + {0x5796, 0x20}, + {0x5797, 0x20}, + {0x5798, 0xd5}, + {0x5799, 0xd5}, + {0x579a, 0x00}, + {0x579b, 0x50}, + {0x579c, 0x00}, + {0x579d, 0x2c}, + {0x579e, 0x0c}, + {0x579f, 0x40}, + {0x57a0, 0x09}, + {0x57a1, 0x40}, + {0x59f8, 0x3d}, + {0x5a08, 0x02}, + {0x5b00, 0x02}, + {0x5b01, 0x10}, + {0x5b02, 0x03}, + {0x5b03, 0xcf}, + {0x5b05, 0x6c}, + {0x5e00, 0x00}, + {0x5e10, 0xfc} +}; + +static const struct ov8856_reg mode_1640x1232_regs[] = { + {0x3000, 0x20}, + {0x3003, 0x08}, + {0x300e, 0x20}, + {0x3010, 0x00}, + {0x3015, 0x84}, + {0x3018, 0x72}, + {0x3021, 0x23}, + {0x3033, 0x24}, + {0x3500, 0x00}, + {0x3501, 0x4c}, + {0x3502, 0xe0}, + {0x3503, 0x08}, + {0x3505, 0x83}, + {0x3508, 0x01}, + {0x3509, 0x80}, + {0x350c, 0x00}, + {0x350d, 0x80}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x72}, + {0x3601, 0x40}, + {0x3602, 0x30}, + {0x3610, 0xc5}, + {0x3611, 0x58}, + {0x3612, 0x5c}, + {0x3613, 0xca}, + {0x3614, 0x20}, + {0x3628, 0xff}, + {0x3629, 0xff}, + {0x362a, 0xff}, + {0x3633, 0x10}, + {0x3634, 0x10}, + {0x3635, 0x10}, + {0x3636, 0x10}, + {0x3663, 0x08}, + {0x3669, 0x34}, + {0x366e, 0x08}, + {0x3706, 0x86}, + {0x370b, 0x7e}, + {0x3714, 0x27}, + {0x3730, 0x12}, + {0x3733, 0x10}, + {0x3764, 0x00}, + {0x3765, 0x00}, + {0x3769, 0x62}, + {0x376a, 0x2a}, + {0x376b, 0x30}, + {0x3780, 0x00}, + {0x3781, 0x24}, + {0x3782, 0x00}, + {0x3783, 0x23}, + {0x3798, 0x2f}, + {0x37a1, 0x60}, + {0x37a8, 0x6a}, + {0x37ab, 0x3f}, + {0x37c2, 0x14}, + {0x37c3, 0xf1}, + {0x37c9, 0x80}, + {0x37cb, 0x16}, + {0x37cc, 0x16}, + {0x37cd, 0x16}, + {0x37ce, 0x16}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x06}, + {0x3804, 0x0c}, + {0x3805, 0xdf}, + {0x3806, 0x09}, + {0x3807, 0xa7}, + {0x3808, 0x06}, + {0x3809, 0x68}, + {0x380a, 0x04}, + {0x380b, 0xd0}, + {0x380c, 0x0e}, + {0x380d, 0xec}, + {0x380e, 0x04}, + {0x380f, 0xe8}, + {0x3810, 0x00}, + {0x3811, 0x00}, + {0x3812, 0x00}, + {0x3813, 0x01}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x00}, + {0x3817, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x10}, + {0x3820, 0x90}, + {0x3821, 0x67}, + {0x382a, 0x03}, + {0x382b, 0x01}, + {0x3830, 0x06}, + {0x3836, 0x02}, + {0x3862, 0x04}, + {0x3863, 0x08}, + {0x3cc0, 0x33}, + {0x3d85, 0x17}, + {0x3d8c, 0x73}, + {0x3d8d, 0xde}, + {0x4001, 0xe0}, + {0x4003, 0x40}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x400a, 0x00}, + {0x400b, 0x84}, + {0x400f, 0x80}, + {0x4010, 0xf0}, + {0x4011, 0xff}, + {0x4012, 0x02}, + {0x4013, 0x01}, + {0x4014, 0x01}, + {0x4015, 0x01}, + {0x4042, 0x00}, + {0x4043, 0x80}, + {0x4044, 0x00}, + {0x4045, 0x80}, + {0x4046, 0x00}, + {0x4047, 0x80}, + {0x4048, 0x00}, + {0x4049, 0x80}, + {0x4041, 0x03}, + {0x404c, 0x20}, + {0x404d, 0x00}, + {0x404e, 0x20}, + {0x4203, 0x80}, + {0x4307, 0x30}, + {0x4317, 0x00}, + {0x4503, 0x08}, + {0x4601, 0x80}, + {0x4800, 0x44}, + {0x4816, 0x53}, + {0x481b, 0x58}, + {0x481f, 0x27}, + {0x4837, 0x16}, + {0x483c, 0x0f}, + {0x484b, 0x05}, + {0x5000, 0x57}, + {0x5001, 0x0a}, + {0x5004, 0x04}, + {0x502e, 0x03}, + {0x5030, 0x41}, + {0x5780, 0x14}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x5795, 0x00}, + {0x5796, 0x10}, + {0x5797, 0x10}, + {0x5798, 0x73}, + {0x5799, 0x73}, + {0x579a, 0x00}, + {0x579b, 0x28}, + {0x579c, 0x00}, + {0x579d, 0x16}, + {0x579e, 0x06}, + {0x579f, 0x20}, + {0x57a0, 0x04}, + {0x57a1, 0xa0}, + {0x59f8, 0x3d}, + {0x5a08, 0x02}, + {0x5b00, 0x02}, + {0x5b01, 0x10}, + {0x5b02, 0x03}, + {0x5b03, 0xcf}, + {0x5b05, 0x6c}, + {0x5e00, 0x00} +}; + +static const struct ov8856_reg mode_1632x1224_regs[] = { + {0x0103, 0x01}, + {0x0302, 0x3c}, + {0x0303, 0x01}, + {0x031e, 0x0c}, + {0x3000, 0x20}, + {0x3003, 0x08}, + {0x300e, 0x20}, + {0x3010, 0x00}, + {0x3015, 0x84}, + {0x3018, 0x72}, + {0x3021, 0x23}, + {0x3033, 0x24}, + {0x3500, 0x00}, + {0x3501, 0x4c}, + {0x3502, 0xe0}, + {0x3503, 0x08}, + {0x3505, 0x83}, + {0x3508, 0x01}, + {0x3509, 0x80}, + {0x350c, 0x00}, + {0x350d, 0x80}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x72}, + {0x3601, 0x40}, + {0x3602, 0x30}, + {0x3610, 0xc5}, + {0x3611, 0x58}, + {0x3612, 0x5c}, + {0x3613, 0xca}, + {0x3614, 0x60}, + {0x3628, 0xff}, + {0x3629, 0xff}, + {0x362a, 0xff}, + {0x3633, 0x10}, + {0x3634, 0x10}, + {0x3635, 0x10}, + {0x3636, 0x10}, + {0x3663, 0x08}, + {0x3669, 0x34}, + {0x366d, 0x00}, + {0x366e, 0x08}, + {0x3706, 0x86}, + {0x370b, 0x7e}, + {0x3714, 0x27}, + {0x3730, 0x12}, + {0x3733, 0x10}, + {0x3764, 0x00}, + {0x3765, 0x00}, + {0x3769, 0x62}, + {0x376a, 0x2a}, + {0x376b, 0x30}, + {0x3780, 0x00}, + {0x3781, 0x24}, + {0x3782, 0x00}, + {0x3783, 0x23}, + {0x3798, 0x2f}, + {0x37a1, 0x60}, + {0x37a8, 0x6a}, + {0x37ab, 0x3f}, + {0x37c2, 0x14}, + {0x37c3, 0xf1}, + {0x37c9, 0x80}, + {0x37cb, 0x16}, + {0x37cc, 0x16}, + {0x37cd, 0x16}, + {0x37ce, 0x16}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x0c}, + {0x3804, 0x0c}, + {0x3805, 0xdf}, + {0x3806, 0x09}, + {0x3807, 0xa3}, + {0x3808, 0x06}, + {0x3809, 0x60}, + {0x380a, 0x04}, + {0x380b, 0xc8}, + {0x380c, 0x07}, + {0x380d, 0x8c}, + {0x380e, 0x09}, + {0x380f, 0xb2}, + {0x3810, 0x00}, + {0x3811, 0x02}, + {0x3812, 0x00}, + {0x3813, 0x02}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x00}, + {0x3817, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x10}, + {0x3820, 0x80}, + {0x3821, 0x47}, + {0x382a, 0x03}, + {0x382b, 0x01}, + {0x3830, 0x06}, + {0x3836, 0x02}, + {0x3862, 0x04}, + {0x3863, 0x08}, + {0x3cc0, 0x33}, + {0x3d85, 0x17}, + {0x3d8c, 0x73}, + {0x3d8d, 0xde}, + {0x4001, 0xe0}, + {0x4003, 0x40}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x400a, 0x00}, + {0x400b, 0x84}, + {0x400f, 0x80}, + {0x4010, 0xf0}, + {0x4011, 0xff}, + {0x4012, 0x02}, + {0x4013, 0x01}, + {0x4014, 0x01}, + {0x4015, 0x01}, + {0x4042, 0x00}, + {0x4043, 0x80}, + {0x4044, 0x00}, + {0x4045, 0x80}, + {0x4046, 0x00}, + {0x4047, 0x80}, + {0x4048, 0x00}, + {0x4049, 0x80}, + {0x4041, 0x03}, + {0x404c, 0x20}, + {0x404d, 0x00}, + {0x404e, 0x20}, + {0x4203, 0x80}, + {0x4307, 0x30}, + {0x4317, 0x00}, + {0x4502, 0x50}, + {0x4503, 0x08}, + {0x4601, 0x80}, + {0x4800, 0x44}, + {0x4816, 0x53}, + {0x481b, 0x50}, + {0x481f, 0x27}, + {0x4823, 0x3c}, + {0x482b, 0x00}, + {0x4831, 0x66}, + {0x4837, 0x16}, + {0x483c, 0x0f}, + {0x484b, 0x05}, + {0x5000, 0x77}, + {0x5001, 0x0a}, + {0x5003, 0xc8}, + {0x5004, 0x04}, + {0x5006, 0x00}, + {0x5007, 0x00}, + {0x502e, 0x03}, + {0x5030, 0x41}, + {0x5795, 0x00}, + {0x5796, 0x10}, + {0x5797, 0x10}, + {0x5798, 0x73}, + {0x5799, 0x73}, + {0x579a, 0x00}, + {0x579b, 0x28}, + {0x579c, 0x00}, + {0x579d, 0x16}, + {0x579e, 0x06}, + {0x579f, 0x20}, + {0x57a0, 0x04}, + {0x57a1, 0xa0}, + {0x5780, 0x14}, + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x01}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, + {0x59f8, 0x3d}, + {0x5a08, 0x02}, + {0x5b00, 0x02}, + {0x5b01, 0x10}, + {0x5b02, 0x03}, + {0x5b03, 0xcf}, + {0x5b05, 0x6c}, + {0x5e00, 0x00}, + {0x5e10, 0xfc} +}; + +static const char * const ov8856_test_pattern_menu[] = { + "Disabled", + "Standard Color Bar", + "Top-Bottom Darker Color Bar", + "Right-Left Darker Color Bar", + "Bottom-Top Darker Color Bar" +}; + +static const s64 link_freq_menu_items[] = { + OV8856_LINK_FREQ_360MHZ, + OV8856_LINK_FREQ_180MHZ +}; + +static const struct ov8856_link_freq_config link_freq_configs[] = { + [OV8856_LINK_FREQ_720MBPS] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_720mbps), + .regs = mipi_data_rate_720mbps, + } + }, + [OV8856_LINK_FREQ_360MBPS] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_360mbps), + .regs = mipi_data_rate_360mbps, + } + } +}; + +static const struct ov8856_mode supported_modes[] = { + { + .width = 3280, + .height = 2464, + .hts = 1928, + .vts_def = 2488, + .vts_min = 2488, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs), + .regs = mode_3280x2464_regs, + }, + .link_freq_index = OV8856_LINK_FREQ_720MBPS, + }, + { + .width = 3264, + .height = 2448, + .hts = 1932, + .vts_def = 2482, + .vts_min = 2482, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3264x2448_regs), + .regs = mode_3264x2448_regs, + }, + .link_freq_index = OV8856_LINK_FREQ_720MBPS, + }, + { + .width = 1640, + .height = 1232, + .hts = 3820, + .vts_def = 1256, + .vts_min = 1256, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x1232_regs), + .regs = mode_1640x1232_regs, + }, + .link_freq_index = OV8856_LINK_FREQ_360MBPS, + }, + { + .width = 1632, + .height = 1224, + .hts = 1932, + .vts_def = 2482, + .vts_min = 2482, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1632x1224_regs), + .regs = mode_1632x1224_regs, + }, + .link_freq_index = OV8856_LINK_FREQ_360MBPS, + } +}; + +struct ov8856 { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(ov8856_supply_names)]; + + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct ov8856_mode *cur_mode; + + /* To serialize asynchronus callbacks */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static u64 to_pixel_rate(u32 f_index) +{ + u64 pixel_rate = link_freq_menu_items[f_index] * 2 * OV8856_DATA_LANES; + + do_div(pixel_rate, OV8856_RGB_DEPTH); + + return pixel_rate; +} + +static u64 to_pixels_per_line(u32 hts, u32 f_index) +{ + u64 ppl = hts * to_pixel_rate(f_index); + + do_div(ppl, OV8856_SCLK); + + return ppl; +} + +static int ov8856_read_reg(struct ov8856 *ov8856, u16 reg, u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = {0}; + int ret; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +static int ov8856_write_reg(struct ov8856 *ov8856, u16 reg, u16 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << 8 * (4 - len), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int ov8856_write_reg_list(struct ov8856 *ov8856, + const struct ov8856_reg_list *r_list) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + unsigned int i; + int ret; + + for (i = 0; i < r_list->num_of_regs; i++) { + ret = ov8856_write_reg(ov8856, r_list->regs[i].address, 1, + r_list->regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "failed to write reg 0x%4.4x. error = %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int ov8856_update_digital_gain(struct ov8856 *ov8856, u32 d_gain) +{ + int ret; + + ret = ov8856_write_reg(ov8856, OV8856_REG_MWB_R_GAIN, + OV8856_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = ov8856_write_reg(ov8856, OV8856_REG_MWB_G_GAIN, + OV8856_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + return ov8856_write_reg(ov8856, OV8856_REG_MWB_B_GAIN, + OV8856_REG_VALUE_16BIT, d_gain); +} + +static int ov8856_test_pattern(struct ov8856 *ov8856, u32 pattern) +{ + if (pattern) + pattern = (pattern - 1) << OV8856_TEST_PATTERN_BAR_SHIFT | + OV8856_TEST_PATTERN_ENABLE; + + return ov8856_write_reg(ov8856, OV8856_REG_TEST_PATTERN, + OV8856_REG_VALUE_08BIT, pattern); +} + +static int ov8856_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov8856 *ov8856 = container_of(ctrl->handler, + struct ov8856, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + s64 exposure_max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = ov8856->cur_mode->height + ctrl->val - + OV8856_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(ov8856->exposure, + ov8856->exposure->minimum, + exposure_max, ov8856->exposure->step, + exposure_max); + } + + /* V4L2 controls values will be applied only when power is already up */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ov8856_write_reg(ov8856, OV8856_REG_ANALOG_GAIN, + OV8856_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_DIGITAL_GAIN: + ret = ov8856_update_digital_gain(ov8856, ctrl->val); + break; + + case V4L2_CID_EXPOSURE: + /* 4 least significant bits of expsoure are fractional part */ + ret = ov8856_write_reg(ov8856, OV8856_REG_EXPOSURE, + OV8856_REG_VALUE_24BIT, ctrl->val << 4); + break; + + case V4L2_CID_VBLANK: + ret = ov8856_write_reg(ov8856, OV8856_REG_VTS, + OV8856_REG_VALUE_16BIT, + ov8856->cur_mode->height + ctrl->val); + break; + + case V4L2_CID_TEST_PATTERN: + ret = ov8856_test_pattern(ov8856, ctrl->val); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov8856_ctrl_ops = { + .s_ctrl = ov8856_set_ctrl, +}; + +static int ov8856_init_controls(struct ov8856 *ov8856) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max, h_blank; + int ret; + + ctrl_hdlr = &ov8856->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + ctrl_hdlr->lock = &ov8856->mutex; + ov8856->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &ov8856_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + if (ov8856->link_freq) + ov8856->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ov8856->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov8856_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + to_pixel_rate(OV8856_LINK_FREQ_720MBPS), + 1, + to_pixel_rate(OV8856_LINK_FREQ_720MBPS)); + ov8856->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov8856_ctrl_ops, + V4L2_CID_VBLANK, + ov8856->cur_mode->vts_min - ov8856->cur_mode->height, + OV8856_VTS_MAX - ov8856->cur_mode->height, 1, + ov8856->cur_mode->vts_def - ov8856->cur_mode->height); + h_blank = to_pixels_per_line(ov8856->cur_mode->hts, + ov8856->cur_mode->link_freq_index) - ov8856->cur_mode->width; + ov8856->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov8856_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (ov8856->hblank) + ov8856->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &ov8856_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV8856_ANAL_GAIN_MIN, OV8856_ANAL_GAIN_MAX, + OV8856_ANAL_GAIN_STEP, OV8856_ANAL_GAIN_MIN); + v4l2_ctrl_new_std(ctrl_hdlr, &ov8856_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV8856_DGTL_GAIN_MIN, OV8856_DGTL_GAIN_MAX, + OV8856_DGTL_GAIN_STEP, OV8856_DGTL_GAIN_DEFAULT); + exposure_max = ov8856->cur_mode->vts_def - OV8856_EXPOSURE_MAX_MARGIN; + ov8856->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov8856_ctrl_ops, + V4L2_CID_EXPOSURE, + OV8856_EXPOSURE_MIN, exposure_max, + OV8856_EXPOSURE_STEP, + exposure_max); + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov8856_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov8856_test_pattern_menu) - 1, + 0, 0, ov8856_test_pattern_menu); + if (ctrl_hdlr->error) + return ctrl_hdlr->error; + + ov8856->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static void ov8856_update_pad_format(const struct ov8856_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->field = V4L2_FIELD_NONE; +} + +static int ov8856_start_streaming(struct ov8856 *ov8856) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + const struct ov8856_reg_list *reg_list; + int link_freq_index, ret; + + link_freq_index = ov8856->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = ov8856_write_reg_list(ov8856, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set plls"); + return ret; + } + + reg_list = &ov8856->cur_mode->reg_list; + ret = ov8856_write_reg_list(ov8856, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set mode"); + return ret; + } + + ret = __v4l2_ctrl_handler_setup(ov8856->sd.ctrl_handler); + if (ret) + return ret; + + ret = ov8856_write_reg(ov8856, OV8856_REG_MODE_SELECT, + OV8856_REG_VALUE_08BIT, OV8856_MODE_STREAMING); + if (ret) { + dev_err(&client->dev, "failed to set stream"); + return ret; + } + + return 0; +} + +static void ov8856_stop_streaming(struct ov8856 *ov8856) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + + if (ov8856_write_reg(ov8856, OV8856_REG_MODE_SELECT, + OV8856_REG_VALUE_08BIT, OV8856_MODE_STANDBY)) + dev_err(&client->dev, "failed to set stream"); +} + +static int ov8856_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov8856 *ov8856 = to_ov8856(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + if (ov8856->streaming == enable) + return 0; + + mutex_lock(&ov8856->mutex); + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + mutex_unlock(&ov8856->mutex); + return ret; + } + + ret = ov8856_start_streaming(ov8856); + if (ret) { + enable = 0; + ov8856_stop_streaming(ov8856); + pm_runtime_put(&client->dev); + } + } else { + ov8856_stop_streaming(ov8856); + pm_runtime_put(&client->dev); + } + + ov8856->streaming = enable; + mutex_unlock(&ov8856->mutex); + + return ret; +} + +static int __ov8856_power_on(struct ov8856 *ov8856) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + int ret; + + if (is_acpi_node(dev_fwnode(&client->dev))) + return 0; + + ret = clk_prepare_enable(ov8856->xvclk); + if (ret < 0) { + dev_err(&client->dev, "failed to enable xvclk\n"); + return ret; + } + + if (ov8856->reset_gpio) { + gpiod_set_value_cansleep(ov8856->reset_gpio, 1); + usleep_range(1000, 2000); + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ov8856_supply_names), + ov8856->supplies); + if (ret < 0) { + dev_err(&client->dev, "failed to enable regulators\n"); + goto disable_clk; + } + + gpiod_set_value_cansleep(ov8856->reset_gpio, 0); + usleep_range(1500, 1800); + + return 0; + +disable_clk: + gpiod_set_value_cansleep(ov8856->reset_gpio, 1); + clk_disable_unprepare(ov8856->xvclk); + + return ret; +} + +static void __ov8856_power_off(struct ov8856 *ov8856) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + + if (is_acpi_node(dev_fwnode(&client->dev))) + return; + + gpiod_set_value_cansleep(ov8856->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ov8856_supply_names), + ov8856->supplies); + clk_disable_unprepare(ov8856->xvclk); +} + +static int __maybe_unused ov8856_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov8856 *ov8856 = to_ov8856(sd); + + mutex_lock(&ov8856->mutex); + if (ov8856->streaming) + ov8856_stop_streaming(ov8856); + + __ov8856_power_off(ov8856); + mutex_unlock(&ov8856->mutex); + + return 0; +} + +static int __maybe_unused ov8856_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov8856 *ov8856 = to_ov8856(sd); + int ret; + + mutex_lock(&ov8856->mutex); + + __ov8856_power_on(ov8856); + if (ov8856->streaming) { + ret = ov8856_start_streaming(ov8856); + if (ret) { + ov8856->streaming = false; + ov8856_stop_streaming(ov8856); + mutex_unlock(&ov8856->mutex); + return ret; + } + } + + mutex_unlock(&ov8856->mutex); + + return 0; +} + +static int ov8856_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov8856 *ov8856 = to_ov8856(sd); + const struct ov8856_mode *mode; + s32 vblank_def, h_blank; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, + height, fmt->format.width, + fmt->format.height); + + mutex_lock(&ov8856->mutex); + ov8856_update_pad_format(mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; + } else { + ov8856->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ov8856->link_freq, mode->link_freq_index); + __v4l2_ctrl_s_ctrl_int64(ov8856->pixel_rate, + to_pixel_rate(mode->link_freq_index)); + + /* Update limits and set FPS to default */ + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(ov8856->vblank, + mode->vts_min - mode->height, + OV8856_VTS_MAX - mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ov8856->vblank, vblank_def); + h_blank = to_pixels_per_line(mode->hts, mode->link_freq_index) - + mode->width; + __v4l2_ctrl_modify_range(ov8856->hblank, h_blank, h_blank, 1, + h_blank); + } + + mutex_unlock(&ov8856->mutex); + + return 0; +} + +static int ov8856_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov8856 *ov8856 = to_ov8856(sd); + + mutex_lock(&ov8856->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(&ov8856->sd, cfg, + fmt->pad); + else + ov8856_update_pad_format(ov8856->cur_mode, &fmt->format); + + mutex_unlock(&ov8856->mutex); + + return 0; +} + +static int ov8856_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Only one bayer order GRBG is supported */ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int ov8856_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int ov8856_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov8856 *ov8856 = to_ov8856(sd); + + mutex_lock(&ov8856->mutex); + ov8856_update_pad_format(&supported_modes[0], + v4l2_subdev_get_try_format(sd, fh->pad, 0)); + mutex_unlock(&ov8856->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops ov8856_video_ops = { + .s_stream = ov8856_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ov8856_pad_ops = { + .set_fmt = ov8856_set_format, + .get_fmt = ov8856_get_format, + .enum_mbus_code = ov8856_enum_mbus_code, + .enum_frame_size = ov8856_enum_frame_size, +}; + +static const struct v4l2_subdev_ops ov8856_subdev_ops = { + .video = &ov8856_video_ops, + .pad = &ov8856_pad_ops, +}; + +static const struct media_entity_operations ov8856_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov8856_internal_ops = { + .open = ov8856_open, +}; + +static int ov8856_identify_module(struct ov8856 *ov8856) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + int ret; + u32 val; + + ret = ov8856_read_reg(ov8856, OV8856_REG_CHIP_ID, + OV8856_REG_VALUE_24BIT, &val); + if (ret) + return ret; + + if (val != OV8856_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x", + OV8856_CHIP_ID, val); + return -ENXIO; + } + + ret = ov8856_write_reg(ov8856, OV8856_REG_MODE_SELECT, + OV8856_REG_VALUE_08BIT, OV8856_MODE_STREAMING); + if (ret) + return ret; + + ret = ov8856_write_reg(ov8856, OV8856_OTP_MODE_CTRL, + OV8856_REG_VALUE_08BIT, OV8856_OTP_MODE_AUTO); + if (ret) { + dev_err(&client->dev, "failed to set otp mode"); + return ret; + } + + ret = ov8856_write_reg(ov8856, OV8856_OTP_LOAD_CTRL, + OV8856_REG_VALUE_08BIT, + OV8856_OTP_LOAD_CTRL_ENABLE); + if (ret) { + dev_err(&client->dev, "failed to enable load control"); + return ret; + } + + ret = ov8856_read_reg(ov8856, OV8856_MODULE_REVISION, + OV8856_REG_VALUE_08BIT, &val); + if (ret) { + dev_err(&client->dev, "failed to read module revision"); + return ret; + } + + dev_info(&client->dev, "OV8856 revision %x (%s) at address 0x%02x\n", + val, + val == OV8856_2A_MODULE ? "2A" : + val == OV8856_1B_MODULE ? "1B" : "unknown revision", + client->addr); + + ret = ov8856_write_reg(ov8856, OV8856_REG_MODE_SELECT, + OV8856_REG_VALUE_08BIT, OV8856_MODE_STANDBY); + if (ret) { + dev_err(&client->dev, "failed to exit streaming mode"); + return ret; + } + + return 0; +} + +static int ov8856_get_hwcfg(struct ov8856 *ov8856, struct device *dev) +{ + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + u32 xvclk_rate; + int ret; + unsigned int i, j; + + if (!fwnode) + return -ENXIO; + + ret = fwnode_property_read_u32(fwnode, "clock-frequency", &xvclk_rate); + if (ret) + return ret; + + if (!is_acpi_node(fwnode)) { + ov8856->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(ov8856->xvclk)) { + dev_err(dev, "could not get xvclk clock (%pe)\n", + ov8856->xvclk); + return PTR_ERR(ov8856->xvclk); + } + + clk_set_rate(ov8856->xvclk, xvclk_rate); + xvclk_rate = clk_get_rate(ov8856->xvclk); + } + + if (xvclk_rate != OV8856_XVCLK_19_2) + dev_warn(dev, "external clock rate %u is unsupported", + xvclk_rate); + + ov8856->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ov8856->reset_gpio)) + return PTR_ERR(ov8856->reset_gpio); + + for (i = 0; i < ARRAY_SIZE(ov8856_supply_names); i++) + ov8856->supplies[i].supply = ov8856_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ov8856_supply_names), + ov8856->supplies); + if (ret) + return ret; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -ENXIO; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV8856_DATA_LANES) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto check_hwcfg_error; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + ret = -EINVAL; + goto check_hwcfg_error; + } + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) { + if (link_freq_menu_items[i] == + bus_cfg.link_frequencies[j]) + break; + } + + if (j == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequency %lld supported", + link_freq_menu_items[i]); + ret = -EINVAL; + goto check_hwcfg_error; + } + } + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int ov8856_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov8856 *ov8856 = to_ov8856(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(&client->dev); + mutex_destroy(&ov8856->mutex); + + __ov8856_power_off(ov8856); + + return 0; +} + +static int ov8856_probe(struct i2c_client *client) +{ + struct ov8856 *ov8856; + int ret; + + ov8856 = devm_kzalloc(&client->dev, sizeof(*ov8856), GFP_KERNEL); + if (!ov8856) + return -ENOMEM; + + ret = ov8856_get_hwcfg(ov8856, &client->dev); + if (ret) { + dev_err(&client->dev, "failed to get HW configuration: %d", + ret); + return ret; + } + + v4l2_i2c_subdev_init(&ov8856->sd, client, &ov8856_subdev_ops); + + ret = __ov8856_power_on(ov8856); + if (ret) { + dev_err(&client->dev, "failed to power on\n"); + return ret; + } + + ret = ov8856_identify_module(ov8856); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d", ret); + goto probe_power_off; + } + + mutex_init(&ov8856->mutex); + ov8856->cur_mode = &supported_modes[0]; + ret = ov8856_init_controls(ov8856); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ov8856->sd.internal_ops = &ov8856_internal_ops; + ov8856->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov8856->sd.entity.ops = &ov8856_subdev_entity_ops; + ov8856->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ov8856->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov8856->sd.entity, 1, &ov8856->pad); + if (ret) { + dev_err(&client->dev, "failed to init entity pads: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = v4l2_async_register_subdev_sensor_common(&ov8856->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %d", + ret); + goto probe_error_media_entity_cleanup; + } + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +probe_error_media_entity_cleanup: + media_entity_cleanup(&ov8856->sd.entity); + +probe_error_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(ov8856->sd.ctrl_handler); + mutex_destroy(&ov8856->mutex); + +probe_power_off: + __ov8856_power_off(ov8856); + + return ret; +} + +static const struct dev_pm_ops ov8856_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov8856_suspend, ov8856_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ov8856_acpi_ids[] = { + {"OVTI8856"}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, ov8856_acpi_ids); +#endif + +static const struct of_device_id ov8856_of_match[] = { + { .compatible = "ovti,ov8856" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ov8856_of_match); + +static struct i2c_driver ov8856_i2c_driver = { + .driver = { + .name = "ov8856", + .pm = &ov8856_pm_ops, + .acpi_match_table = ACPI_PTR(ov8856_acpi_ids), + .of_match_table = ov8856_of_match, + }, + .probe_new = ov8856_probe, + .remove = ov8856_remove, +}; + +module_i2c_driver(ov8856_i2c_driver); + +MODULE_AUTHOR("Ben Kao <ben.kao@intel.com>"); +MODULE_DESCRIPTION("OmniVision OV8856 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov9640.c b/drivers/media/i2c/ov9640.c new file mode 100644 index 000000000..e2a25240f --- /dev/null +++ b/drivers/media/i2c/ov9640.c @@ -0,0 +1,778 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * OmniVision OV96xx Camera Driver + * + * Copyright (C) 2009 Marek Vasut <marek.vasut@gmail.com> + * + * Based on ov772x camera driver: + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * Based on ov7670 and soc_camera_platform driver, + * transition from soc_camera to pxa_camera based on mt9m111 + * + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/v4l2-mediabus.h> +#include <linux/videodev2.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-clk.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> + +#include <linux/gpio/consumer.h> + +#include "ov9640.h" + +#define to_ov9640_sensor(sd) container_of(sd, struct ov9640_priv, subdev) + +/* default register setup */ +static const struct ov9640_reg ov9640_regs_dflt[] = { + { OV9640_COM5, OV9640_COM5_SYSCLK | OV9640_COM5_LONGEXP }, + { OV9640_COM6, OV9640_COM6_OPT_BLC | OV9640_COM6_ADBLC_BIAS | + OV9640_COM6_FMT_RST | OV9640_COM6_ADBLC_OPTEN }, + { OV9640_PSHFT, OV9640_PSHFT_VAL(0x01) }, + { OV9640_ACOM, OV9640_ACOM_2X_ANALOG | OV9640_ACOM_RSVD }, + { OV9640_TSLB, OV9640_TSLB_YUYV_UYVY }, + { OV9640_COM16, OV9640_COM16_RB_AVG }, + + /* Gamma curve P */ + { 0x6c, 0x40 }, { 0x6d, 0x30 }, { 0x6e, 0x4b }, { 0x6f, 0x60 }, + { 0x70, 0x70 }, { 0x71, 0x70 }, { 0x72, 0x70 }, { 0x73, 0x70 }, + { 0x74, 0x60 }, { 0x75, 0x60 }, { 0x76, 0x50 }, { 0x77, 0x48 }, + { 0x78, 0x3a }, { 0x79, 0x2e }, { 0x7a, 0x28 }, { 0x7b, 0x22 }, + + /* Gamma curve T */ + { 0x7c, 0x04 }, { 0x7d, 0x07 }, { 0x7e, 0x10 }, { 0x7f, 0x28 }, + { 0x80, 0x36 }, { 0x81, 0x44 }, { 0x82, 0x52 }, { 0x83, 0x60 }, + { 0x84, 0x6c }, { 0x85, 0x78 }, { 0x86, 0x8c }, { 0x87, 0x9e }, + { 0x88, 0xbb }, { 0x89, 0xd2 }, { 0x8a, 0xe6 }, +}; + +/* Configurations + * NOTE: for YUV, alter the following registers: + * COM12 |= OV9640_COM12_YUV_AVG + * + * for RGB, alter the following registers: + * COM7 |= OV9640_COM7_RGB + * COM13 |= OV9640_COM13_RGB_AVG + * COM15 |= proper RGB color encoding mode + */ +static const struct ov9640_reg ov9640_regs_qqcif[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x0f) }, + { OV9640_COM1, OV9640_COM1_QQFMT | OV9640_COM1_HREF_2SKIP }, + { OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD }, + { OV9640_COM7, OV9640_COM7_QCIF }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_qqvga[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x07) }, + { OV9640_COM1, OV9640_COM1_QQFMT | OV9640_COM1_HREF_2SKIP }, + { OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD }, + { OV9640_COM7, OV9640_COM7_QVGA }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_qcif[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x07) }, + { OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD }, + { OV9640_COM7, OV9640_COM7_QCIF }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_qvga[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x03) }, + { OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD }, + { OV9640_COM7, OV9640_COM7_QVGA }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_cif[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x03) }, + { OV9640_COM3, OV9640_COM3_VP }, + { OV9640_COM7, OV9640_COM7_CIF }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_vga[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x01) }, + { OV9640_COM3, OV9640_COM3_VP }, + { OV9640_COM7, OV9640_COM7_VGA }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_sxga[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x01) }, + { OV9640_COM3, OV9640_COM3_VP }, + { OV9640_COM7, 0 }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_yuv[] = { + { OV9640_MTX1, 0x58 }, + { OV9640_MTX2, 0x48 }, + { OV9640_MTX3, 0x10 }, + { OV9640_MTX4, 0x28 }, + { OV9640_MTX5, 0x48 }, + { OV9640_MTX6, 0x70 }, + { OV9640_MTX7, 0x40 }, + { OV9640_MTX8, 0x40 }, + { OV9640_MTX9, 0x40 }, + { OV9640_MTXS, 0x0f }, +}; + +static const struct ov9640_reg ov9640_regs_rgb[] = { + { OV9640_MTX1, 0x71 }, + { OV9640_MTX2, 0x3e }, + { OV9640_MTX3, 0x0c }, + { OV9640_MTX4, 0x33 }, + { OV9640_MTX5, 0x72 }, + { OV9640_MTX6, 0x00 }, + { OV9640_MTX7, 0x2b }, + { OV9640_MTX8, 0x66 }, + { OV9640_MTX9, 0xd2 }, + { OV9640_MTXS, 0x65 }, +}; + +static const u32 ov9640_codes[] = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, + MEDIA_BUS_FMT_RGB565_2X8_LE, +}; + +/* read a register */ +static int ov9640_reg_read(struct i2c_client *client, u8 reg, u8 *val) +{ + int ret; + u8 data = reg; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &data, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + goto err; + + msg.flags = I2C_M_RD; + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + goto err; + + *val = data; + return 0; + +err: + dev_err(&client->dev, "Failed reading register 0x%02x!\n", reg); + return ret; +} + +/* write a register */ +static int ov9640_reg_write(struct i2c_client *client, u8 reg, u8 val) +{ + int ret; + u8 _val; + unsigned char data[2] = { reg, val }; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = data, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_err(&client->dev, "Failed writing register 0x%02x!\n", reg); + return ret; + } + + /* we have to read the register back ... no idea why, maybe HW bug */ + ret = ov9640_reg_read(client, reg, &_val); + if (ret) + dev_err(&client->dev, + "Failed reading back register 0x%02x!\n", reg); + + return 0; +} + + +/* Read a register, alter its bits, write it back */ +static int ov9640_reg_rmw(struct i2c_client *client, u8 reg, u8 set, u8 unset) +{ + u8 val; + int ret; + + ret = ov9640_reg_read(client, reg, &val); + if (ret) { + dev_err(&client->dev, + "[Read]-Modify-Write of register %02x failed!\n", reg); + return ret; + } + + val |= set; + val &= ~unset; + + ret = ov9640_reg_write(client, reg, val); + if (ret) + dev_err(&client->dev, + "Read-Modify-[Write] of register %02x failed!\n", reg); + + return ret; +} + +/* Soft reset the camera. This has nothing to do with the RESET pin! */ +static int ov9640_reset(struct i2c_client *client) +{ + int ret; + + ret = ov9640_reg_write(client, OV9640_COM7, OV9640_COM7_SCCB_RESET); + if (ret) + dev_err(&client->dev, + "An error occurred while entering soft reset!\n"); + + return ret; +} + +/* Start/Stop streaming from the device */ +static int ov9640_s_stream(struct v4l2_subdev *sd, int enable) +{ + return 0; +} + +/* Set status of additional camera capabilities */ +static int ov9640_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov9640_priv *priv = container_of(ctrl->handler, + struct ov9640_priv, hdl); + struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev); + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (ctrl->val) + return ov9640_reg_rmw(client, OV9640_MVFP, + OV9640_MVFP_V, 0); + return ov9640_reg_rmw(client, OV9640_MVFP, 0, OV9640_MVFP_V); + case V4L2_CID_HFLIP: + if (ctrl->val) + return ov9640_reg_rmw(client, OV9640_MVFP, + OV9640_MVFP_H, 0); + return ov9640_reg_rmw(client, OV9640_MVFP, 0, OV9640_MVFP_H); + } + + return -EINVAL; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov9640_get_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u8 val; + + if (reg->reg & ~0xff) + return -EINVAL; + + reg->size = 1; + + ret = ov9640_reg_read(client, reg->reg, &val); + if (ret) + return ret; + + reg->val = (__u64)val; + + return 0; +} + +static int ov9640_set_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg & ~0xff || reg->val & ~0xff) + return -EINVAL; + + return ov9640_reg_write(client, reg->reg, reg->val); +} +#endif + +static int ov9640_s_power(struct v4l2_subdev *sd, int on) +{ + struct ov9640_priv *priv = to_ov9640_sensor(sd); + int ret = 0; + + if (on) { + gpiod_set_value(priv->gpio_power, 1); + usleep_range(1000, 2000); + ret = v4l2_clk_enable(priv->clk); + usleep_range(1000, 2000); + gpiod_set_value(priv->gpio_reset, 0); + } else { + gpiod_set_value(priv->gpio_reset, 1); + usleep_range(1000, 2000); + v4l2_clk_disable(priv->clk); + usleep_range(1000, 2000); + gpiod_set_value(priv->gpio_power, 0); + } + + return ret; +} + +/* select nearest higher resolution for capture */ +static void ov9640_res_roundup(u32 *width, u32 *height) +{ + unsigned int i; + enum { QQCIF, QQVGA, QCIF, QVGA, CIF, VGA, SXGA }; + static const u32 res_x[] = { 88, 160, 176, 320, 352, 640, 1280 }; + static const u32 res_y[] = { 72, 120, 144, 240, 288, 480, 960 }; + + for (i = 0; i < ARRAY_SIZE(res_x); i++) { + if (res_x[i] >= *width && res_y[i] >= *height) { + *width = res_x[i]; + *height = res_y[i]; + return; + } + } + + *width = res_x[SXGA]; + *height = res_y[SXGA]; +} + +/* Prepare necessary register changes depending on color encoding */ +static void ov9640_alter_regs(u32 code, + struct ov9640_reg_alt *alt) +{ + switch (code) { + default: + case MEDIA_BUS_FMT_UYVY8_2X8: + alt->com12 = OV9640_COM12_YUV_AVG; + alt->com13 = OV9640_COM13_Y_DELAY_EN | + OV9640_COM13_YUV_DLY(0x01); + break; + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: + alt->com7 = OV9640_COM7_RGB; + alt->com13 = OV9640_COM13_RGB_AVG; + alt->com15 = OV9640_COM15_RGB_555; + break; + case MEDIA_BUS_FMT_RGB565_2X8_LE: + alt->com7 = OV9640_COM7_RGB; + alt->com13 = OV9640_COM13_RGB_AVG; + alt->com15 = OV9640_COM15_RGB_565; + break; + } +} + +/* Setup registers according to resolution and color encoding */ +static int ov9640_write_regs(struct i2c_client *client, u32 width, + u32 code, struct ov9640_reg_alt *alts) +{ + const struct ov9640_reg *ov9640_regs, *matrix_regs; + unsigned int ov9640_regs_len, matrix_regs_len; + unsigned int i; + int ret; + u8 val; + + /* select register configuration for given resolution */ + switch (width) { + case W_QQCIF: + ov9640_regs = ov9640_regs_qqcif; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qqcif); + break; + case W_QQVGA: + ov9640_regs = ov9640_regs_qqvga; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qqvga); + break; + case W_QCIF: + ov9640_regs = ov9640_regs_qcif; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qcif); + break; + case W_QVGA: + ov9640_regs = ov9640_regs_qvga; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qvga); + break; + case W_CIF: + ov9640_regs = ov9640_regs_cif; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_cif); + break; + case W_VGA: + ov9640_regs = ov9640_regs_vga; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_vga); + break; + case W_SXGA: + ov9640_regs = ov9640_regs_sxga; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_sxga); + break; + default: + dev_err(&client->dev, "Failed to select resolution!\n"); + return -EINVAL; + } + + /* select color matrix configuration for given color encoding */ + if (code == MEDIA_BUS_FMT_UYVY8_2X8) { + matrix_regs = ov9640_regs_yuv; + matrix_regs_len = ARRAY_SIZE(ov9640_regs_yuv); + } else { + matrix_regs = ov9640_regs_rgb; + matrix_regs_len = ARRAY_SIZE(ov9640_regs_rgb); + } + + /* write register settings into the module */ + for (i = 0; i < ov9640_regs_len; i++) { + val = ov9640_regs[i].val; + + switch (ov9640_regs[i].reg) { + case OV9640_COM7: + val |= alts->com7; + break; + case OV9640_COM12: + val |= alts->com12; + break; + case OV9640_COM13: + val |= alts->com13; + break; + case OV9640_COM15: + val |= alts->com15; + break; + } + + ret = ov9640_reg_write(client, ov9640_regs[i].reg, val); + if (ret) + return ret; + } + + /* write color matrix configuration into the module */ + for (i = 0; i < matrix_regs_len; i++) { + ret = ov9640_reg_write(client, matrix_regs[i].reg, + matrix_regs[i].val); + if (ret) + return ret; + } + + return 0; +} + +/* program default register values */ +static int ov9640_prog_dflt(struct i2c_client *client) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(ov9640_regs_dflt); i++) { + ret = ov9640_reg_write(client, ov9640_regs_dflt[i].reg, + ov9640_regs_dflt[i].val); + if (ret) + return ret; + } + + /* wait for the changes to actually happen, 140ms are not enough yet */ + msleep(150); + + return 0; +} + +/* set the format we will capture in */ +static int ov9640_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov9640_reg_alt alts = {0}; + int ret; + + ov9640_alter_regs(mf->code, &alts); + + ov9640_reset(client); + + ret = ov9640_prog_dflt(client); + if (ret) + return ret; + + return ov9640_write_regs(client, mf->width, mf->code, &alts); +} + +static int ov9640_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + + if (format->pad) + return -EINVAL; + + ov9640_res_roundup(&mf->width, &mf->height); + + mf->field = V4L2_FIELD_NONE; + + switch (mf->code) { + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: + case MEDIA_BUS_FMT_RGB565_2X8_LE: + mf->colorspace = V4L2_COLORSPACE_SRGB; + break; + default: + mf->code = MEDIA_BUS_FMT_UYVY8_2X8; + fallthrough; + case MEDIA_BUS_FMT_UYVY8_2X8: + mf->colorspace = V4L2_COLORSPACE_JPEG; + break; + } + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return ov9640_s_fmt(sd, mf); + + cfg->try_fmt = *mf; + + return 0; +} + +static int ov9640_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(ov9640_codes)) + return -EINVAL; + + code->code = ov9640_codes[code->index]; + + return 0; +} + +static int ov9640_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + sel->r.left = 0; + sel->r.top = 0; + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP: + sel->r.width = W_SXGA; + sel->r.height = H_SXGA; + return 0; + default: + return -EINVAL; + } +} + +static int ov9640_video_probe(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov9640_priv *priv = to_ov9640_sensor(sd); + u8 pid, ver, midh, midl; + const char *devname; + int ret; + + ret = ov9640_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* + * check and show product ID and manufacturer ID + */ + + ret = ov9640_reg_read(client, OV9640_PID, &pid); + if (!ret) + ret = ov9640_reg_read(client, OV9640_VER, &ver); + if (!ret) + ret = ov9640_reg_read(client, OV9640_MIDH, &midh); + if (!ret) + ret = ov9640_reg_read(client, OV9640_MIDL, &midl); + if (ret) + goto done; + + switch (VERSION(pid, ver)) { + case OV9640_V2: + devname = "ov9640"; + priv->revision = 2; + break; + case OV9640_V3: + devname = "ov9640"; + priv->revision = 3; + break; + default: + dev_err(&client->dev, "Product ID error %x:%x\n", pid, ver); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, "%s Product ID %0x:%0x Manufacturer ID %x:%x\n", + devname, pid, ver, midh, midl); + + ret = v4l2_ctrl_handler_setup(&priv->hdl); + +done: + ov9640_s_power(&priv->subdev, 0); + return ret; +} + +static const struct v4l2_ctrl_ops ov9640_ctrl_ops = { + .s_ctrl = ov9640_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops ov9640_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov9640_get_register, + .s_register = ov9640_set_register, +#endif + .s_power = ov9640_s_power, +}; + +/* Request bus settings on camera side */ +static int ov9640_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER | + V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_HIGH | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + + return 0; +} + +static const struct v4l2_subdev_video_ops ov9640_video_ops = { + .s_stream = ov9640_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov9640_pad_ops = { + .enum_mbus_code = ov9640_enum_mbus_code, + .get_selection = ov9640_get_selection, + .set_fmt = ov9640_set_fmt, + .get_mbus_config = ov9640_get_mbus_config, +}; + +static const struct v4l2_subdev_ops ov9640_subdev_ops = { + .core = &ov9640_core_ops, + .video = &ov9640_video_ops, + .pad = &ov9640_pad_ops, +}; + +/* + * i2c_driver function + */ +static int ov9640_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov9640_priv *priv; + int ret; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->gpio_power = devm_gpiod_get(&client->dev, "Camera power", + GPIOD_OUT_LOW); + if (IS_ERR(priv->gpio_power)) { + ret = PTR_ERR(priv->gpio_power); + return ret; + } + + priv->gpio_reset = devm_gpiod_get(&client->dev, "Camera reset", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpio_reset)) { + ret = PTR_ERR(priv->gpio_reset); + return ret; + } + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov9640_subdev_ops); + + v4l2_ctrl_handler_init(&priv->hdl, 2); + v4l2_ctrl_new_std(&priv->hdl, &ov9640_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov9640_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + if (priv->hdl.error) { + ret = priv->hdl.error; + goto ectrlinit; + } + + priv->subdev.ctrl_handler = &priv->hdl; + + priv->clk = v4l2_clk_get(&client->dev, "mclk"); + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + goto ectrlinit; + } + + ret = ov9640_video_probe(client); + if (ret) + goto eprobe; + + priv->subdev.dev = &client->dev; + ret = v4l2_async_register_subdev(&priv->subdev); + if (ret) + goto eprobe; + + return 0; + +eprobe: + v4l2_clk_put(priv->clk); +ectrlinit: + v4l2_ctrl_handler_free(&priv->hdl); + + return ret; +} + +static int ov9640_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov9640_priv *priv = to_ov9640_sensor(sd); + + v4l2_clk_put(priv->clk); + v4l2_async_unregister_subdev(&priv->subdev); + v4l2_ctrl_handler_free(&priv->hdl); + + return 0; +} + +static const struct i2c_device_id ov9640_id[] = { + { "ov9640", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov9640_id); + +static struct i2c_driver ov9640_i2c_driver = { + .driver = { + .name = "ov9640", + }, + .probe = ov9640_probe, + .remove = ov9640_remove, + .id_table = ov9640_id, +}; + +module_i2c_driver(ov9640_i2c_driver); + +MODULE_DESCRIPTION("OmniVision OV96xx CMOS Image Sensor driver"); +MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov9640.h b/drivers/media/i2c/ov9640.h new file mode 100644 index 000000000..a8ed6992c --- /dev/null +++ b/drivers/media/i2c/ov9640.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * OmniVision OV96xx Camera Header File + * + * Copyright (C) 2009 Marek Vasut <marek.vasut@gmail.com> + */ + +#ifndef __DRIVERS_MEDIA_VIDEO_OV9640_H__ +#define __DRIVERS_MEDIA_VIDEO_OV9640_H__ + +/* Register definitions */ +#define OV9640_GAIN 0x00 +#define OV9640_BLUE 0x01 +#define OV9640_RED 0x02 +#define OV9640_VFER 0x03 +#define OV9640_COM1 0x04 +#define OV9640_BAVE 0x05 +#define OV9640_GEAVE 0x06 +#define OV9640_RSID 0x07 +#define OV9640_RAVE 0x08 +#define OV9640_COM2 0x09 +#define OV9640_PID 0x0a +#define OV9640_VER 0x0b +#define OV9640_COM3 0x0c +#define OV9640_COM4 0x0d +#define OV9640_COM5 0x0e +#define OV9640_COM6 0x0f +#define OV9640_AECH 0x10 +#define OV9640_CLKRC 0x11 +#define OV9640_COM7 0x12 +#define OV9640_COM8 0x13 +#define OV9640_COM9 0x14 +#define OV9640_COM10 0x15 +/* 0x16 - RESERVED */ +#define OV9640_HSTART 0x17 +#define OV9640_HSTOP 0x18 +#define OV9640_VSTART 0x19 +#define OV9640_VSTOP 0x1a +#define OV9640_PSHFT 0x1b +#define OV9640_MIDH 0x1c +#define OV9640_MIDL 0x1d +#define OV9640_MVFP 0x1e +#define OV9640_LAEC 0x1f +#define OV9640_BOS 0x20 +#define OV9640_GBOS 0x21 +#define OV9640_GROS 0x22 +#define OV9640_ROS 0x23 +#define OV9640_AEW 0x24 +#define OV9640_AEB 0x25 +#define OV9640_VPT 0x26 +#define OV9640_BBIAS 0x27 +#define OV9640_GBBIAS 0x28 +/* 0x29 - RESERVED */ +#define OV9640_EXHCH 0x2a +#define OV9640_EXHCL 0x2b +#define OV9640_RBIAS 0x2c +#define OV9640_ADVFL 0x2d +#define OV9640_ADVFH 0x2e +#define OV9640_YAVE 0x2f +#define OV9640_HSYST 0x30 +#define OV9640_HSYEN 0x31 +#define OV9640_HREF 0x32 +#define OV9640_CHLF 0x33 +#define OV9640_ARBLM 0x34 +/* 0x35..0x36 - RESERVED */ +#define OV9640_ADC 0x37 +#define OV9640_ACOM 0x38 +#define OV9640_OFON 0x39 +#define OV9640_TSLB 0x3a +#define OV9640_COM11 0x3b +#define OV9640_COM12 0x3c +#define OV9640_COM13 0x3d +#define OV9640_COM14 0x3e +#define OV9640_EDGE 0x3f +#define OV9640_COM15 0x40 +#define OV9640_COM16 0x41 +#define OV9640_COM17 0x42 +/* 0x43..0x4e - RESERVED */ +#define OV9640_MTX1 0x4f +#define OV9640_MTX2 0x50 +#define OV9640_MTX3 0x51 +#define OV9640_MTX4 0x52 +#define OV9640_MTX5 0x53 +#define OV9640_MTX6 0x54 +#define OV9640_MTX7 0x55 +#define OV9640_MTX8 0x56 +#define OV9640_MTX9 0x57 +#define OV9640_MTXS 0x58 +/* 0x59..0x61 - RESERVED */ +#define OV9640_LCC1 0x62 +#define OV9640_LCC2 0x63 +#define OV9640_LCC3 0x64 +#define OV9640_LCC4 0x65 +#define OV9640_LCC5 0x66 +#define OV9640_MANU 0x67 +#define OV9640_MANV 0x68 +#define OV9640_HV 0x69 +#define OV9640_MBD 0x6a +#define OV9640_DBLV 0x6b +#define OV9640_GSP 0x6c /* ... till 0x7b */ +#define OV9640_GST 0x7c /* ... till 0x8a */ + +#define OV9640_CLKRC_DPLL_EN 0x80 +#define OV9640_CLKRC_DIRECT 0x40 +#define OV9640_CLKRC_DIV(x) ((x) & 0x3f) + +#define OV9640_PSHFT_VAL(x) ((x) & 0xff) + +#define OV9640_ACOM_2X_ANALOG 0x80 +#define OV9640_ACOM_RSVD 0x12 + +#define OV9640_MVFP_V 0x10 +#define OV9640_MVFP_H 0x20 + +#define OV9640_COM1_HREF_NOSKIP 0x00 +#define OV9640_COM1_HREF_2SKIP 0x04 +#define OV9640_COM1_HREF_3SKIP 0x08 +#define OV9640_COM1_QQFMT 0x20 + +#define OV9640_COM2_SSM 0x10 + +#define OV9640_COM3_VP 0x04 + +#define OV9640_COM4_QQ_VP 0x80 +#define OV9640_COM4_RSVD 0x40 + +#define OV9640_COM5_SYSCLK 0x80 +#define OV9640_COM5_LONGEXP 0x01 + +#define OV9640_COM6_OPT_BLC 0x40 +#define OV9640_COM6_ADBLC_BIAS 0x08 +#define OV9640_COM6_FMT_RST 0x82 +#define OV9640_COM6_ADBLC_OPTEN 0x01 + +#define OV9640_COM7_RAW_RGB 0x01 +#define OV9640_COM7_RGB 0x04 +#define OV9640_COM7_QCIF 0x08 +#define OV9640_COM7_QVGA 0x10 +#define OV9640_COM7_CIF 0x20 +#define OV9640_COM7_VGA 0x40 +#define OV9640_COM7_SCCB_RESET 0x80 + +#define OV9640_TSLB_YVYU_YUYV 0x04 +#define OV9640_TSLB_YUYV_UYVY 0x08 + +#define OV9640_COM12_YUV_AVG 0x04 +#define OV9640_COM12_RSVD 0x40 + +#define OV9640_COM13_GAMMA_NONE 0x00 +#define OV9640_COM13_GAMMA_Y 0x40 +#define OV9640_COM13_GAMMA_RAW 0x80 +#define OV9640_COM13_RGB_AVG 0x20 +#define OV9640_COM13_MATRIX_EN 0x10 +#define OV9640_COM13_Y_DELAY_EN 0x08 +#define OV9640_COM13_YUV_DLY(x) ((x) & 0x07) + +#define OV9640_COM15_OR_00FF 0x00 +#define OV9640_COM15_OR_01FE 0x40 +#define OV9640_COM15_OR_10F0 0xc0 +#define OV9640_COM15_RGB_NORM 0x00 +#define OV9640_COM15_RGB_565 0x10 +#define OV9640_COM15_RGB_555 0x30 + +#define OV9640_COM16_RB_AVG 0x01 + +/* IDs */ +#define OV9640_V2 0x9648 +#define OV9640_V3 0x9649 +#define VERSION(pid, ver) (((pid) << 8) | ((ver) & 0xFF)) + +/* supported resolutions */ +enum { + W_QQCIF = 88, + W_QQVGA = 160, + W_QCIF = 176, + W_QVGA = 320, + W_CIF = 352, + W_VGA = 640, + W_SXGA = 1280 +}; +#define H_SXGA 960 + +/* Misc. structures */ +struct ov9640_reg_alt { + u8 com7; + u8 com12; + u8 com13; + u8 com15; +}; + +struct ov9640_reg { + u8 reg; + u8 val; +}; + +struct ov9640_priv { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct v4l2_clk *clk; + struct gpio_desc *gpio_power; + struct gpio_desc *gpio_reset; + + int model; + int revision; +}; + +#endif /* __DRIVERS_MEDIA_VIDEO_OV9640_H__ */ diff --git a/drivers/media/i2c/ov9650.c b/drivers/media/i2c/ov9650.c new file mode 100644 index 000000000..4fe68aa55 --- /dev/null +++ b/drivers/media/i2c/ov9650.c @@ -0,0 +1,1629 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Omnivision OV9650/OV9652 CMOS Image Sensor driver + * + * Copyright (C) 2013, Sylwester Nawrocki <sylvester.nawrocki@gmail.com> + * + * Register definitions and initial settings based on a driver written + * by Vladimir Fonov. + * Copyright (c) 2010, Vladimir Fonov + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/media.h> +#include <linux/module.h> +#include <linux/ratelimit.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/videodev2.h> + +#include <media/media-entity.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-mediabus.h> +#include <media/i2c/ov9650.h> + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +#define DRIVER_NAME "OV9650" + +/* + * OV9650/OV9652 register definitions + */ +#define REG_GAIN 0x00 /* Gain control, AGC[7:0] */ +#define REG_BLUE 0x01 /* AWB - Blue channel gain */ +#define REG_RED 0x02 /* AWB - Red channel gain */ +#define REG_VREF 0x03 /* [7:6] - AGC[9:8], [5:3]/[2:0] */ +#define VREF_GAIN_MASK 0xc0 /* - VREF end/start low 3 bits */ +#define REG_COM1 0x04 +#define COM1_CCIR656 0x40 +#define REG_B_AVE 0x05 +#define REG_GB_AVE 0x06 +#define REG_GR_AVE 0x07 +#define REG_R_AVE 0x08 +#define REG_COM2 0x09 +#define REG_PID 0x0a /* Product ID MSB */ +#define REG_VER 0x0b /* Product ID LSB */ +#define REG_COM3 0x0c +#define COM3_SWAP 0x40 +#define COM3_VARIOPIXEL1 0x04 +#define REG_COM4 0x0d /* Vario Pixels */ +#define COM4_VARIOPIXEL2 0x80 +#define REG_COM5 0x0e /* System clock options */ +#define COM5_SLAVE_MODE 0x10 +#define COM5_SYSTEMCLOCK48MHZ 0x80 +#define REG_COM6 0x0f /* HREF & ADBLC options */ +#define REG_AECH 0x10 /* Exposure value, AEC[9:2] */ +#define REG_CLKRC 0x11 /* Clock control */ +#define CLK_EXT 0x40 /* Use external clock directly */ +#define CLK_SCALE 0x3f /* Mask for internal clock scale */ +#define REG_COM7 0x12 /* SCCB reset, output format */ +#define COM7_RESET 0x80 +#define COM7_FMT_MASK 0x38 +#define COM7_FMT_VGA 0x40 +#define COM7_FMT_CIF 0x20 +#define COM7_FMT_QVGA 0x10 +#define COM7_FMT_QCIF 0x08 +#define COM7_RGB 0x04 +#define COM7_YUV 0x00 +#define COM7_BAYER 0x01 +#define COM7_PBAYER 0x05 +#define REG_COM8 0x13 /* AGC/AEC options */ +#define COM8_FASTAEC 0x80 /* Enable fast AGC/AEC */ +#define COM8_AECSTEP 0x40 /* Unlimited AEC step size */ +#define COM8_BFILT 0x20 /* Band filter enable */ +#define COM8_AGC 0x04 /* Auto gain enable */ +#define COM8_AWB 0x02 /* White balance enable */ +#define COM8_AEC 0x01 /* Auto exposure enable */ +#define REG_COM9 0x14 /* Gain ceiling */ +#define COM9_GAIN_CEIL_MASK 0x70 /* */ +#define REG_COM10 0x15 /* PCLK, HREF, HSYNC signals polarity */ +#define COM10_HSYNC 0x40 /* HSYNC instead of HREF */ +#define COM10_PCLK_HB 0x20 /* Suppress PCLK on horiz blank */ +#define COM10_HREF_REV 0x08 /* Reverse HREF */ +#define COM10_VS_LEAD 0x04 /* VSYNC on clock leading edge */ +#define COM10_VS_NEG 0x02 /* VSYNC negative */ +#define COM10_HS_NEG 0x01 /* HSYNC negative */ +#define REG_HSTART 0x17 /* Horiz start high bits */ +#define REG_HSTOP 0x18 /* Horiz stop high bits */ +#define REG_VSTART 0x19 /* Vert start high bits */ +#define REG_VSTOP 0x1a /* Vert stop high bits */ +#define REG_PSHFT 0x1b /* Pixel delay after HREF */ +#define REG_MIDH 0x1c /* Manufacturer ID MSB */ +#define REG_MIDL 0x1d /* Manufufacturer ID LSB */ +#define REG_MVFP 0x1e /* Image mirror/flip */ +#define MVFP_MIRROR 0x20 /* Mirror image */ +#define MVFP_FLIP 0x10 /* Vertical flip */ +#define REG_BOS 0x20 /* B channel Offset */ +#define REG_GBOS 0x21 /* Gb channel Offset */ +#define REG_GROS 0x22 /* Gr channel Offset */ +#define REG_ROS 0x23 /* R channel Offset */ +#define REG_AEW 0x24 /* AGC upper limit */ +#define REG_AEB 0x25 /* AGC lower limit */ +#define REG_VPT 0x26 /* AGC/AEC fast mode op region */ +#define REG_BBIAS 0x27 /* B channel output bias */ +#define REG_GBBIAS 0x28 /* Gb channel output bias */ +#define REG_GRCOM 0x29 /* Analog BLC & regulator */ +#define REG_EXHCH 0x2a /* Dummy pixel insert MSB */ +#define REG_EXHCL 0x2b /* Dummy pixel insert LSB */ +#define REG_RBIAS 0x2c /* R channel output bias */ +#define REG_ADVFL 0x2d /* LSB of dummy line insert */ +#define REG_ADVFH 0x2e /* MSB of dummy line insert */ +#define REG_YAVE 0x2f /* Y/G channel average value */ +#define REG_HSYST 0x30 /* HSYNC rising edge delay LSB*/ +#define REG_HSYEN 0x31 /* HSYNC falling edge delay LSB*/ +#define REG_HREF 0x32 /* HREF pieces */ +#define REG_CHLF 0x33 /* reserved */ +#define REG_ADC 0x37 /* reserved */ +#define REG_ACOM 0x38 /* reserved */ +#define REG_OFON 0x39 /* Power down register */ +#define OFON_PWRDN 0x08 /* Power down bit */ +#define REG_TSLB 0x3a /* YUVU format */ +#define TSLB_YUYV_MASK 0x0c /* UYVY or VYUY - see com13 */ +#define REG_COM11 0x3b /* Night mode, banding filter enable */ +#define COM11_NIGHT 0x80 /* Night mode enable */ +#define COM11_NMFR 0x60 /* Two bit NM frame rate */ +#define COM11_BANDING 0x01 /* Banding filter */ +#define COM11_AEC_REF_MASK 0x18 /* AEC reference area selection */ +#define REG_COM12 0x3c /* HREF option, UV average */ +#define COM12_HREF 0x80 /* HREF always */ +#define REG_COM13 0x3d /* Gamma selection, Color matrix en. */ +#define COM13_GAMMA 0x80 /* Gamma enable */ +#define COM13_UVSAT 0x40 /* UV saturation auto adjustment */ +#define COM13_UVSWAP 0x01 /* V before U - w/TSLB */ +#define REG_COM14 0x3e /* Edge enhancement options */ +#define COM14_EDGE_EN 0x02 +#define COM14_EEF_X2 0x01 +#define REG_EDGE 0x3f /* Edge enhancement factor */ +#define EDGE_FACTOR_MASK 0x0f +#define REG_COM15 0x40 /* Output range, RGB 555/565 */ +#define COM15_R10F0 0x00 /* Data range 10 to F0 */ +#define COM15_R01FE 0x80 /* 01 to FE */ +#define COM15_R00FF 0xc0 /* 00 to FF */ +#define COM15_RGB565 0x10 /* RGB565 output */ +#define COM15_RGB555 0x30 /* RGB555 output */ +#define COM15_SWAPRB 0x04 /* Swap R&B */ +#define REG_COM16 0x41 /* Color matrix coeff options */ +#define REG_COM17 0x42 /* Single frame out, banding filter */ +/* n = 1...9, 0x4f..0x57 */ +#define REG_MTX(__n) (0x4f + (__n) - 1) +#define REG_MTXS 0x58 +/* Lens Correction Option 1...5, __n = 0...5 */ +#define REG_LCC(__n) (0x62 + (__n) - 1) +#define LCC5_LCC_ENABLE 0x01 /* LCC5, enable lens correction */ +#define LCC5_LCC_COLOR 0x04 +#define REG_MANU 0x67 /* Manual U value */ +#define REG_MANV 0x68 /* Manual V value */ +#define REG_HV 0x69 /* Manual banding filter MSB */ +#define REG_MBD 0x6a /* Manual banding filter value */ +#define REG_DBLV 0x6b /* reserved */ +#define REG_GSP 0x6c /* Gamma curve */ +#define GSP_LEN 15 +#define REG_GST 0x7c /* Gamma curve */ +#define GST_LEN 15 +#define REG_COM21 0x8b +#define REG_COM22 0x8c /* Edge enhancement, denoising */ +#define COM22_WHTPCOR 0x02 /* White pixel correction enable */ +#define COM22_WHTPCOROPT 0x01 /* White pixel correction option */ +#define COM22_DENOISE 0x10 /* White pixel correction option */ +#define REG_COM23 0x8d /* Color bar test, color gain */ +#define COM23_TEST_MODE 0x10 +#define REG_DBLC1 0x8f /* Digital BLC */ +#define REG_DBLC_B 0x90 /* Digital BLC B channel offset */ +#define REG_DBLC_R 0x91 /* Digital BLC R channel offset */ +#define REG_DM_LNL 0x92 /* Dummy line low 8 bits */ +#define REG_DM_LNH 0x93 /* Dummy line high 8 bits */ +#define REG_LCCFB 0x9d /* Lens Correction B channel */ +#define REG_LCCFR 0x9e /* Lens Correction R channel */ +#define REG_DBLC_GB 0x9f /* Digital BLC GB chan offset */ +#define REG_DBLC_GR 0xa0 /* Digital BLC GR chan offset */ +#define REG_AECHM 0xa1 /* Exposure value - bits AEC[15:10] */ +#define REG_BD50ST 0xa2 /* Banding filter value for 50Hz */ +#define REG_BD60ST 0xa3 /* Banding filter value for 60Hz */ +#define REG_NULL 0xff /* Array end token */ + +#define DEF_CLKRC 0x80 + +#define OV965X_ID(_msb, _lsb) ((_msb) << 8 | (_lsb)) +#define OV9650_ID 0x9650 +#define OV9652_ID 0x9652 + +struct ov965x_ctrls { + struct v4l2_ctrl_handler handler; + struct { + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *exposure; + }; + struct { + struct v4l2_ctrl *auto_wb; + struct v4l2_ctrl *blue_balance; + struct v4l2_ctrl *red_balance; + }; + struct { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + struct { + struct v4l2_ctrl *auto_gain; + struct v4l2_ctrl *gain; + }; + struct v4l2_ctrl *brightness; + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *sharpness; + struct v4l2_ctrl *light_freq; + u8 update; +}; + +struct ov965x_framesize { + u16 width; + u16 height; + u16 max_exp_lines; + const u8 *regs; +}; + +struct ov965x_interval { + struct v4l2_fract interval; + /* Maximum resolution for this interval */ + struct v4l2_frmsize_discrete size; + u8 clkrc_div; +}; + +enum gpio_id { + GPIO_PWDN, + GPIO_RST, + NUM_GPIOS, +}; + +struct ov965x { + struct v4l2_subdev sd; + struct media_pad pad; + enum v4l2_mbus_type bus_type; + struct gpio_desc *gpios[NUM_GPIOS]; + /* External master clock frequency */ + unsigned long mclk_frequency; + struct clk *clk; + + /* Protects the struct fields below */ + struct mutex lock; + + struct regmap *regmap; + + /* Exposure row interval in us */ + unsigned int exp_row_interval; + + unsigned short id; + const struct ov965x_framesize *frame_size; + /* YUYV sequence (pixel format) control register */ + u8 tslb_reg; + struct v4l2_mbus_framefmt format; + + struct ov965x_ctrls ctrls; + /* Pointer to frame rate control data structure */ + const struct ov965x_interval *fiv; + + int streaming; + int power; + + u8 apply_frame_fmt; +}; + +struct i2c_rv { + u8 addr; + u8 value; +}; + +static const struct i2c_rv ov965x_init_regs[] = { + { REG_COM2, 0x10 }, /* Set soft sleep mode */ + { REG_COM5, 0x00 }, /* System clock options */ + { REG_COM2, 0x01 }, /* Output drive, soft sleep mode */ + { REG_COM10, 0x00 }, /* Slave mode, HREF vs HSYNC, signals negate */ + { REG_EDGE, 0xa6 }, /* Edge enhancement treshhold and factor */ + { REG_COM16, 0x02 }, /* Color matrix coeff double option */ + { REG_COM17, 0x08 }, /* Single frame out, banding filter */ + { 0x16, 0x06 }, + { REG_CHLF, 0xc0 }, /* Reserved */ + { 0x34, 0xbf }, + { 0xa8, 0x80 }, + { 0x96, 0x04 }, + { 0x8e, 0x00 }, + { REG_COM12, 0x77 }, /* HREF option, UV average */ + { 0x8b, 0x06 }, + { 0x35, 0x91 }, + { 0x94, 0x88 }, + { 0x95, 0x88 }, + { REG_COM15, 0xc1 }, /* Output range, RGB 555/565 */ + { REG_GRCOM, 0x2f }, /* Analog BLC & regulator */ + { REG_COM6, 0x43 }, /* HREF & ADBLC options */ + { REG_COM8, 0xe5 }, /* AGC/AEC options */ + { REG_COM13, 0x90 }, /* Gamma selection, colour matrix, UV delay */ + { REG_HV, 0x80 }, /* Manual banding filter MSB */ + { 0x5c, 0x96 }, /* Reserved up to 0xa5 */ + { 0x5d, 0x96 }, + { 0x5e, 0x10 }, + { 0x59, 0xeb }, + { 0x5a, 0x9c }, + { 0x5b, 0x55 }, + { 0x43, 0xf0 }, + { 0x44, 0x10 }, + { 0x45, 0x55 }, + { 0x46, 0x86 }, + { 0x47, 0x64 }, + { 0x48, 0x86 }, + { 0x5f, 0xe0 }, + { 0x60, 0x8c }, + { 0x61, 0x20 }, + { 0xa5, 0xd9 }, + { 0xa4, 0x74 }, /* reserved */ + { REG_COM23, 0x02 }, /* Color gain analog/_digital_ */ + { REG_COM8, 0xe7 }, /* Enable AEC, AWB, AEC */ + { REG_COM22, 0x23 }, /* Edge enhancement, denoising */ + { 0xa9, 0xb8 }, + { 0xaa, 0x92 }, + { 0xab, 0x0a }, + { REG_DBLC1, 0xdf }, /* Digital BLC */ + { REG_DBLC_B, 0x00 }, /* Digital BLC B chan offset */ + { REG_DBLC_R, 0x00 }, /* Digital BLC R chan offset */ + { REG_DBLC_GB, 0x00 }, /* Digital BLC GB chan offset */ + { REG_DBLC_GR, 0x00 }, + { REG_COM9, 0x3a }, /* Gain ceiling 16x */ + { REG_NULL, 0 } +}; + +#define NUM_FMT_REGS 14 +/* + * COM7, COM3, COM4, HSTART, HSTOP, HREF, VSTART, VSTOP, VREF, + * EXHCH, EXHCL, ADC, OCOM, OFON + */ +static const u8 frame_size_reg_addr[NUM_FMT_REGS] = { + 0x12, 0x0c, 0x0d, 0x17, 0x18, 0x32, 0x19, 0x1a, 0x03, + 0x2a, 0x2b, 0x37, 0x38, 0x39, +}; + +static const u8 ov965x_sxga_regs[NUM_FMT_REGS] = { + 0x00, 0x00, 0x00, 0x1e, 0xbe, 0xbf, 0x01, 0x81, 0x12, + 0x10, 0x34, 0x81, 0x93, 0x51, +}; + +static const u8 ov965x_vga_regs[NUM_FMT_REGS] = { + 0x40, 0x04, 0x80, 0x26, 0xc6, 0xed, 0x01, 0x3d, 0x00, + 0x10, 0x40, 0x91, 0x12, 0x43, +}; + +/* Determined empirically. */ +static const u8 ov965x_qvga_regs[NUM_FMT_REGS] = { + 0x10, 0x04, 0x80, 0x25, 0xc5, 0xbf, 0x00, 0x80, 0x12, + 0x10, 0x40, 0x91, 0x12, 0x43, +}; + +static const struct ov965x_framesize ov965x_framesizes[] = { + { + .width = SXGA_WIDTH, + .height = SXGA_HEIGHT, + .regs = ov965x_sxga_regs, + .max_exp_lines = 1048, + }, { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .regs = ov965x_vga_regs, + .max_exp_lines = 498, + }, { + .width = QVGA_WIDTH, + .height = QVGA_HEIGHT, + .regs = ov965x_qvga_regs, + .max_exp_lines = 248, + }, +}; + +struct ov965x_pixfmt { + u32 code; + u32 colorspace; + /* REG_TSLB value, only bits [3:2] may be set. */ + u8 tslb_reg; +}; + +static const struct ov965x_pixfmt ov965x_formats[] = { + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG, 0x00}, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_COLORSPACE_JPEG, 0x04}, + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_COLORSPACE_JPEG, 0x0c}, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_COLORSPACE_JPEG, 0x08}, +}; + +/* + * This table specifies possible frame resolution and interval + * combinations. Default CLKRC[5:0] divider values are valid + * only for 24 MHz external clock frequency. + */ +static struct ov965x_interval ov965x_intervals[] = { + {{ 100, 625 }, { SXGA_WIDTH, SXGA_HEIGHT }, 0 }, /* 6.25 fps */ + {{ 10, 125 }, { VGA_WIDTH, VGA_HEIGHT }, 1 }, /* 12.5 fps */ + {{ 10, 125 }, { QVGA_WIDTH, QVGA_HEIGHT }, 3 }, /* 12.5 fps */ + {{ 1, 25 }, { VGA_WIDTH, VGA_HEIGHT }, 0 }, /* 25 fps */ + {{ 1, 25 }, { QVGA_WIDTH, QVGA_HEIGHT }, 1 }, /* 25 fps */ +}; + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ov965x, ctrls.handler)->sd; +} + +static inline struct ov965x *to_ov965x(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov965x, sd); +} + +static int ov965x_read(struct ov965x *ov965x, u8 addr, u8 *val) +{ + int ret; + unsigned int buf; + + ret = regmap_read(ov965x->regmap, addr, &buf); + if (!ret) + *val = buf; + else + *val = -1; + + v4l2_dbg(2, debug, &ov965x->sd, "%s: 0x%02x @ 0x%02x. (%d)\n", + __func__, *val, addr, ret); + + return ret; +} + +static int ov965x_write(struct ov965x *ov965x, u8 addr, u8 val) +{ + int ret; + + ret = regmap_write(ov965x->regmap, addr, val); + + v4l2_dbg(2, debug, &ov965x->sd, "%s: 0x%02x @ 0x%02X (%d)\n", + __func__, val, addr, ret); + + return ret; +} + +static int ov965x_write_array(struct ov965x *ov965x, + const struct i2c_rv *regs) +{ + int i, ret = 0; + + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) + ret = ov965x_write(ov965x, regs[i].addr, regs[i].value); + + return ret; +} + +static int ov965x_set_default_gamma_curve(struct ov965x *ov965x) +{ + static const u8 gamma_curve[] = { + /* Values taken from OV application note. */ + 0x40, 0x30, 0x4b, 0x60, 0x70, 0x70, 0x70, 0x70, + 0x60, 0x60, 0x50, 0x48, 0x3a, 0x2e, 0x28, 0x22, + 0x04, 0x07, 0x10, 0x28, 0x36, 0x44, 0x52, 0x60, + 0x6c, 0x78, 0x8c, 0x9e, 0xbb, 0xd2, 0xe6 + }; + u8 addr = REG_GSP; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(gamma_curve); i++) { + int ret = ov965x_write(ov965x, addr, gamma_curve[i]); + + if (ret < 0) + return ret; + addr++; + } + + return 0; +}; + +static int ov965x_set_color_matrix(struct ov965x *ov965x) +{ + static const u8 mtx[] = { + /* MTX1..MTX9, MTXS */ + 0x3a, 0x3d, 0x03, 0x12, 0x26, 0x38, 0x40, 0x40, 0x40, 0x0d + }; + u8 addr = REG_MTX(1); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mtx); i++) { + int ret = ov965x_write(ov965x, addr, mtx[i]); + + if (ret < 0) + return ret; + addr++; + } + + return 0; +} + +static int __ov965x_set_power(struct ov965x *ov965x, int on) +{ + if (on) { + int ret = clk_prepare_enable(ov965x->clk); + + if (ret) + return ret; + + gpiod_set_value_cansleep(ov965x->gpios[GPIO_PWDN], 0); + gpiod_set_value_cansleep(ov965x->gpios[GPIO_RST], 0); + msleep(25); + } else { + gpiod_set_value_cansleep(ov965x->gpios[GPIO_RST], 1); + gpiod_set_value_cansleep(ov965x->gpios[GPIO_PWDN], 1); + + clk_disable_unprepare(ov965x->clk); + } + + ov965x->streaming = 0; + + return 0; +} + +static int ov965x_s_power(struct v4l2_subdev *sd, int on) +{ + struct ov965x *ov965x = to_ov965x(sd); + int ret = 0; + + v4l2_dbg(1, debug, sd, "%s: on: %d\n", __func__, on); + + mutex_lock(&ov965x->lock); + if (ov965x->power == !on) { + ret = __ov965x_set_power(ov965x, on); + if (!ret && on) { + ret = ov965x_write_array(ov965x, + ov965x_init_regs); + ov965x->apply_frame_fmt = 1; + ov965x->ctrls.update = 1; + } + } + if (!ret) + ov965x->power += on ? 1 : -1; + + WARN_ON(ov965x->power < 0); + mutex_unlock(&ov965x->lock); + return ret; +} + +/* + * V4L2 controls + */ + +static void ov965x_update_exposure_ctrl(struct ov965x *ov965x) +{ + struct v4l2_ctrl *ctrl = ov965x->ctrls.exposure; + unsigned long fint, trow; + int min, max, def; + u8 clkrc; + + mutex_lock(&ov965x->lock); + if (WARN_ON(!ctrl || !ov965x->frame_size)) { + mutex_unlock(&ov965x->lock); + return; + } + clkrc = DEF_CLKRC + ov965x->fiv->clkrc_div; + /* Calculate internal clock frequency */ + fint = ov965x->mclk_frequency * ((clkrc >> 7) + 1) / + ((2 * ((clkrc & 0x3f) + 1))); + /* and the row interval (in us). */ + trow = (2 * 1520 * 1000000UL) / fint; + max = ov965x->frame_size->max_exp_lines * trow; + ov965x->exp_row_interval = trow; + mutex_unlock(&ov965x->lock); + + v4l2_dbg(1, debug, &ov965x->sd, "clkrc: %#x, fi: %lu, tr: %lu, %d\n", + clkrc, fint, trow, max); + + /* Update exposure time range to match current frame format. */ + min = (trow + 100) / 100; + max = (max - 100) / 100; + def = min + (max - min) / 2; + + if (v4l2_ctrl_modify_range(ctrl, min, max, 1, def)) + v4l2_err(&ov965x->sd, "Exposure ctrl range update failed\n"); +} + +static int ov965x_set_banding_filter(struct ov965x *ov965x, int value) +{ + unsigned long mbd, light_freq; + int ret; + u8 reg; + + ret = ov965x_read(ov965x, REG_COM8, ®); + if (!ret) { + if (value == V4L2_CID_POWER_LINE_FREQUENCY_DISABLED) + reg &= ~COM8_BFILT; + else + reg |= COM8_BFILT; + ret = ov965x_write(ov965x, REG_COM8, reg); + } + if (value == V4L2_CID_POWER_LINE_FREQUENCY_DISABLED) + return 0; + if (WARN_ON(!ov965x->fiv)) + return -EINVAL; + /* Set minimal exposure time for 50/60 HZ lighting */ + if (value == V4L2_CID_POWER_LINE_FREQUENCY_50HZ) + light_freq = 50; + else + light_freq = 60; + mbd = (1000UL * ov965x->fiv->interval.denominator * + ov965x->frame_size->max_exp_lines) / + ov965x->fiv->interval.numerator; + mbd = ((mbd / (light_freq * 2)) + 500) / 1000UL; + + return ov965x_write(ov965x, REG_MBD, mbd); +} + +static int ov965x_set_white_balance(struct ov965x *ov965x, int awb) +{ + int ret; + u8 reg; + + ret = ov965x_read(ov965x, REG_COM8, ®); + if (!ret) { + reg = awb ? reg | REG_COM8 : reg & ~REG_COM8; + ret = ov965x_write(ov965x, REG_COM8, reg); + } + if (!ret && !awb) { + ret = ov965x_write(ov965x, REG_BLUE, + ov965x->ctrls.blue_balance->val); + if (ret < 0) + return ret; + ret = ov965x_write(ov965x, REG_RED, + ov965x->ctrls.red_balance->val); + } + return ret; +} + +#define NUM_BR_LEVELS 7 +#define NUM_BR_REGS 3 + +static int ov965x_set_brightness(struct ov965x *ov965x, int val) +{ + static const u8 regs[NUM_BR_LEVELS + 1][NUM_BR_REGS] = { + { REG_AEW, REG_AEB, REG_VPT }, + { 0x1c, 0x12, 0x50 }, /* -3 */ + { 0x3d, 0x30, 0x71 }, /* -2 */ + { 0x50, 0x44, 0x92 }, /* -1 */ + { 0x70, 0x64, 0xc3 }, /* 0 */ + { 0x90, 0x84, 0xd4 }, /* +1 */ + { 0xc4, 0xbf, 0xf9 }, /* +2 */ + { 0xd8, 0xd0, 0xfa }, /* +3 */ + }; + int i, ret = 0; + + val += (NUM_BR_LEVELS / 2 + 1); + if (val > NUM_BR_LEVELS) + return -EINVAL; + + for (i = 0; i < NUM_BR_REGS && !ret; i++) + ret = ov965x_write(ov965x, regs[0][i], + regs[val][i]); + return ret; +} + +static int ov965x_set_gain(struct ov965x *ov965x, int auto_gain) +{ + struct ov965x_ctrls *ctrls = &ov965x->ctrls; + int ret = 0; + u8 reg; + /* + * For manual mode we need to disable AGC first, so + * gain value in REG_VREF, REG_GAIN is not overwritten. + */ + if (ctrls->auto_gain->is_new) { + ret = ov965x_read(ov965x, REG_COM8, ®); + if (ret < 0) + return ret; + if (ctrls->auto_gain->val) + reg |= COM8_AGC; + else + reg &= ~COM8_AGC; + ret = ov965x_write(ov965x, REG_COM8, reg); + if (ret < 0) + return ret; + } + + if (ctrls->gain->is_new && !auto_gain) { + unsigned int gain = ctrls->gain->val; + unsigned int rgain; + int m; + /* + * Convert gain control value to the sensor's gain + * registers (VREF[7:6], GAIN[7:0]) format. + */ + for (m = 6; m >= 0; m--) + if (gain >= (1 << m) * 16) + break; + + /* Sanity check: don't adjust the gain with a negative value */ + if (m < 0) + return -EINVAL; + + rgain = (gain - ((1 << m) * 16)) / (1 << m); + rgain |= (((1 << m) - 1) << 4); + + ret = ov965x_write(ov965x, REG_GAIN, rgain & 0xff); + if (ret < 0) + return ret; + ret = ov965x_read(ov965x, REG_VREF, ®); + if (ret < 0) + return ret; + reg &= ~VREF_GAIN_MASK; + reg |= (((rgain >> 8) & 0x3) << 6); + ret = ov965x_write(ov965x, REG_VREF, reg); + if (ret < 0) + return ret; + /* Return updated control's value to userspace */ + ctrls->gain->val = (1 << m) * (16 + (rgain & 0xf)); + } + + return ret; +} + +static int ov965x_set_sharpness(struct ov965x *ov965x, unsigned int value) +{ + u8 com14, edge; + int ret; + + ret = ov965x_read(ov965x, REG_COM14, &com14); + if (ret < 0) + return ret; + ret = ov965x_read(ov965x, REG_EDGE, &edge); + if (ret < 0) + return ret; + com14 = value ? com14 | COM14_EDGE_EN : com14 & ~COM14_EDGE_EN; + value--; + if (value > 0x0f) { + com14 |= COM14_EEF_X2; + value >>= 1; + } else { + com14 &= ~COM14_EEF_X2; + } + ret = ov965x_write(ov965x, REG_COM14, com14); + if (ret < 0) + return ret; + + edge &= ~EDGE_FACTOR_MASK; + edge |= ((u8)value & 0x0f); + + return ov965x_write(ov965x, REG_EDGE, edge); +} + +static int ov965x_set_exposure(struct ov965x *ov965x, int exp) +{ + struct ov965x_ctrls *ctrls = &ov965x->ctrls; + bool auto_exposure = (exp == V4L2_EXPOSURE_AUTO); + int ret; + u8 reg; + + if (ctrls->auto_exp->is_new) { + ret = ov965x_read(ov965x, REG_COM8, ®); + if (ret < 0) + return ret; + if (auto_exposure) + reg |= (COM8_AEC | COM8_AGC); + else + reg &= ~(COM8_AEC | COM8_AGC); + ret = ov965x_write(ov965x, REG_COM8, reg); + if (ret < 0) + return ret; + } + + if (!auto_exposure && ctrls->exposure->is_new) { + unsigned int exposure = (ctrls->exposure->val * 100) + / ov965x->exp_row_interval; + /* + * Manual exposure value + * [b15:b0] - AECHM (b15:b10), AECH (b9:b2), COM1 (b1:b0) + */ + ret = ov965x_write(ov965x, REG_COM1, exposure & 0x3); + if (!ret) + ret = ov965x_write(ov965x, REG_AECH, + (exposure >> 2) & 0xff); + if (!ret) + ret = ov965x_write(ov965x, REG_AECHM, + (exposure >> 10) & 0x3f); + /* Update the value to minimize rounding errors */ + ctrls->exposure->val = ((exposure * ov965x->exp_row_interval) + + 50) / 100; + if (ret < 0) + return ret; + } + + v4l2_ctrl_activate(ov965x->ctrls.brightness, !exp); + return 0; +} + +static int ov965x_set_flip(struct ov965x *ov965x) +{ + u8 mvfp = 0; + + if (ov965x->ctrls.hflip->val) + mvfp |= MVFP_MIRROR; + + if (ov965x->ctrls.vflip->val) + mvfp |= MVFP_FLIP; + + return ov965x_write(ov965x, REG_MVFP, mvfp); +} + +#define NUM_SAT_LEVELS 5 +#define NUM_SAT_REGS 6 + +static int ov965x_set_saturation(struct ov965x *ov965x, int val) +{ + static const u8 regs[NUM_SAT_LEVELS][NUM_SAT_REGS] = { + /* MTX(1)...MTX(6) */ + { 0x1d, 0x1f, 0x02, 0x09, 0x13, 0x1c }, /* -2 */ + { 0x2e, 0x31, 0x02, 0x0e, 0x1e, 0x2d }, /* -1 */ + { 0x3a, 0x3d, 0x03, 0x12, 0x26, 0x38 }, /* 0 */ + { 0x46, 0x49, 0x04, 0x16, 0x2e, 0x43 }, /* +1 */ + { 0x57, 0x5c, 0x05, 0x1b, 0x39, 0x54 }, /* +2 */ + }; + u8 addr = REG_MTX(1); + int i, ret = 0; + + val += (NUM_SAT_LEVELS / 2); + if (val >= NUM_SAT_LEVELS) + return -EINVAL; + + for (i = 0; i < NUM_SAT_REGS && !ret; i++) + ret = ov965x_write(ov965x, addr + i, regs[val][i]); + + return ret; +} + +static int ov965x_set_test_pattern(struct ov965x *ov965x, int value) +{ + int ret; + u8 reg; + + ret = ov965x_read(ov965x, REG_COM23, ®); + if (ret < 0) + return ret; + reg = value ? reg | COM23_TEST_MODE : reg & ~COM23_TEST_MODE; + return ov965x_write(ov965x, REG_COM23, reg); +} + +static int __g_volatile_ctrl(struct ov965x *ov965x, struct v4l2_ctrl *ctrl) +{ + unsigned int exposure, gain, m; + u8 reg0, reg1, reg2; + int ret; + + if (!ov965x->power) + return 0; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + if (!ctrl->val) + return 0; + ret = ov965x_read(ov965x, REG_GAIN, ®0); + if (ret < 0) + return ret; + ret = ov965x_read(ov965x, REG_VREF, ®1); + if (ret < 0) + return ret; + gain = ((reg1 >> 6) << 8) | reg0; + m = 0x01 << fls(gain >> 4); + ov965x->ctrls.gain->val = m * (16 + (gain & 0xf)); + break; + + case V4L2_CID_EXPOSURE_AUTO: + if (ctrl->val == V4L2_EXPOSURE_MANUAL) + return 0; + ret = ov965x_read(ov965x, REG_COM1, ®0); + if (ret < 0) + return ret; + ret = ov965x_read(ov965x, REG_AECH, ®1); + if (ret < 0) + return ret; + ret = ov965x_read(ov965x, REG_AECHM, ®2); + if (ret < 0) + return ret; + exposure = ((reg2 & 0x3f) << 10) | (reg1 << 2) | + (reg0 & 0x3); + ov965x->ctrls.exposure->val = ((exposure * + ov965x->exp_row_interval) + 50) / 100; + break; + } + + return 0; +} + +static int ov965x_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct ov965x *ov965x = to_ov965x(sd); + int ret; + + v4l2_dbg(1, debug, sd, "g_ctrl: %s\n", ctrl->name); + + mutex_lock(&ov965x->lock); + ret = __g_volatile_ctrl(ov965x, ctrl); + mutex_unlock(&ov965x->lock); + return ret; +} + +static int ov965x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct ov965x *ov965x = to_ov965x(sd); + int ret = -EINVAL; + + v4l2_dbg(1, debug, sd, "s_ctrl: %s, value: %d. power: %d\n", + ctrl->name, ctrl->val, ov965x->power); + + mutex_lock(&ov965x->lock); + /* + * If the device is not powered up now postpone applying control's + * value to the hardware, until it is ready to accept commands. + */ + if (ov965x->power == 0) { + mutex_unlock(&ov965x->lock); + return 0; + } + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov965x_set_white_balance(ov965x, ctrl->val); + break; + + case V4L2_CID_BRIGHTNESS: + ret = ov965x_set_brightness(ov965x, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + ret = ov965x_set_exposure(ov965x, ctrl->val); + break; + + case V4L2_CID_AUTOGAIN: + ret = ov965x_set_gain(ov965x, ctrl->val); + break; + + case V4L2_CID_HFLIP: + ret = ov965x_set_flip(ov965x); + break; + + case V4L2_CID_POWER_LINE_FREQUENCY: + ret = ov965x_set_banding_filter(ov965x, ctrl->val); + break; + + case V4L2_CID_SATURATION: + ret = ov965x_set_saturation(ov965x, ctrl->val); + break; + + case V4L2_CID_SHARPNESS: + ret = ov965x_set_sharpness(ov965x, ctrl->val); + break; + + case V4L2_CID_TEST_PATTERN: + ret = ov965x_set_test_pattern(ov965x, ctrl->val); + break; + } + + mutex_unlock(&ov965x->lock); + return ret; +} + +static const struct v4l2_ctrl_ops ov965x_ctrl_ops = { + .g_volatile_ctrl = ov965x_g_volatile_ctrl, + .s_ctrl = ov965x_s_ctrl, +}; + +static const char * const test_pattern_menu[] = { + "Disabled", + "Color bars", +}; + +static int ov965x_initialize_controls(struct ov965x *ov965x) +{ + const struct v4l2_ctrl_ops *ops = &ov965x_ctrl_ops; + struct ov965x_ctrls *ctrls = &ov965x->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int ret; + + ret = v4l2_ctrl_handler_init(hdl, 16); + if (ret < 0) + return ret; + + /* Auto/manual white balance */ + ctrls->auto_wb = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, + 0, 0xff, 1, 0x80); + ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, + 0, 0xff, 1, 0x80); + /* Auto/manual exposure */ + ctrls->auto_exp = + v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, + V4L2_EXPOSURE_AUTO); + /* Exposure time, in 100 us units. min/max is updated dynamically. */ + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_EXPOSURE_ABSOLUTE, + 2, 1500, 1, 500); + /* Auto/manual gain */ + ctrls->auto_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTOGAIN, + 0, 1, 1, 1); + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, + 16, 64 * (16 + 15), 1, 64 * 16); + + ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, + -2, 2, 1, 0); + ctrls->brightness = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, + -3, 3, 1, 0); + ctrls->sharpness = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SHARPNESS, + 0, 32, 1, 6); + + ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + + ctrls->light_freq = + v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_60HZ, ~0x7, + V4L2_CID_POWER_LINE_FREQUENCY_50HZ); + + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(test_pattern_menu) - 1, 0, 0, + test_pattern_menu); + if (hdl->error) { + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + ctrls->gain->flags |= V4L2_CTRL_FLAG_VOLATILE; + ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; + + v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false); + v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true); + v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, 1, true); + v4l2_ctrl_cluster(2, &ctrls->hflip); + + ov965x->sd.ctrl_handler = hdl; + return 0; +} + +/* + * V4L2 subdev video and pad level operations + */ +static void ov965x_get_default_format(struct v4l2_mbus_framefmt *mf) +{ + mf->width = ov965x_framesizes[0].width; + mf->height = ov965x_framesizes[0].height; + mf->colorspace = ov965x_formats[0].colorspace; + mf->code = ov965x_formats[0].code; + mf->field = V4L2_FIELD_NONE; +} + +static int ov965x_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(ov965x_formats)) + return -EINVAL; + + code->code = ov965x_formats[code->index].code; + return 0; +} + +static int ov965x_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + int i = ARRAY_SIZE(ov965x_formats); + + if (fse->index >= ARRAY_SIZE(ov965x_framesizes)) + return -EINVAL; + + while (--i) + if (fse->code == ov965x_formats[i].code) + break; + + fse->code = ov965x_formats[i].code; + + fse->min_width = ov965x_framesizes[fse->index].width; + fse->max_width = fse->min_width; + fse->max_height = ov965x_framesizes[fse->index].height; + fse->min_height = fse->max_height; + + return 0; +} + +static int ov965x_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ov965x *ov965x = to_ov965x(sd); + + mutex_lock(&ov965x->lock); + fi->interval = ov965x->fiv->interval; + mutex_unlock(&ov965x->lock); + + return 0; +} + +static int __ov965x_set_frame_interval(struct ov965x *ov965x, + struct v4l2_subdev_frame_interval *fi) +{ + struct v4l2_mbus_framefmt *mbus_fmt = &ov965x->format; + const struct ov965x_interval *fiv = &ov965x_intervals[0]; + u64 req_int, err, min_err = ~0ULL; + unsigned int i; + + if (fi->interval.denominator == 0) + return -EINVAL; + + req_int = (u64)fi->interval.numerator * 10000; + do_div(req_int, fi->interval.denominator); + + for (i = 0; i < ARRAY_SIZE(ov965x_intervals); i++) { + const struct ov965x_interval *iv = &ov965x_intervals[i]; + + if (mbus_fmt->width != iv->size.width || + mbus_fmt->height != iv->size.height) + continue; + err = abs((u64)(iv->interval.numerator * 10000) / + iv->interval.denominator - req_int); + if (err < min_err) { + fiv = iv; + min_err = err; + } + } + ov965x->fiv = fiv; + + v4l2_dbg(1, debug, &ov965x->sd, "Changed frame interval to %u us\n", + fiv->interval.numerator * 1000000 / fiv->interval.denominator); + + return 0; +} + +static int ov965x_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ov965x *ov965x = to_ov965x(sd); + int ret; + + v4l2_dbg(1, debug, sd, "Setting %d/%d frame interval\n", + fi->interval.numerator, fi->interval.denominator); + + mutex_lock(&ov965x->lock); + ret = __ov965x_set_frame_interval(ov965x, fi); + ov965x->apply_frame_fmt = 1; + mutex_unlock(&ov965x->lock); + return ret; +} + +static int ov965x_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov965x *ov965x = to_ov965x(sd); + struct v4l2_mbus_framefmt *mf; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + fmt->format = *mf; + return 0; + } + + mutex_lock(&ov965x->lock); + fmt->format = ov965x->format; + mutex_unlock(&ov965x->lock); + + return 0; +} + +static void __ov965x_try_frame_size(struct v4l2_mbus_framefmt *mf, + const struct ov965x_framesize **size) +{ + const struct ov965x_framesize *fsize = &ov965x_framesizes[0], + *match = NULL; + int i = ARRAY_SIZE(ov965x_framesizes); + unsigned int min_err = UINT_MAX; + + while (i--) { + int err = abs(fsize->width - mf->width) + + abs(fsize->height - mf->height); + if (err < min_err) { + min_err = err; + match = fsize; + } + fsize++; + } + if (!match) + match = &ov965x_framesizes[0]; + mf->width = match->width; + mf->height = match->height; + if (size) + *size = match; +} + +static int ov965x_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + unsigned int index = ARRAY_SIZE(ov965x_formats); + struct v4l2_mbus_framefmt *mf = &fmt->format; + struct ov965x *ov965x = to_ov965x(sd); + const struct ov965x_framesize *size = NULL; + int ret = 0; + + __ov965x_try_frame_size(mf, &size); + + while (--index) + if (ov965x_formats[index].code == mf->code) + break; + + mf->colorspace = V4L2_COLORSPACE_JPEG; + mf->code = ov965x_formats[index].code; + mf->field = V4L2_FIELD_NONE; + + mutex_lock(&ov965x->lock); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + if (cfg) { + mf = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *mf = fmt->format; + } + } else { + if (ov965x->streaming) { + ret = -EBUSY; + } else { + ov965x->frame_size = size; + ov965x->format = fmt->format; + ov965x->tslb_reg = ov965x_formats[index].tslb_reg; + ov965x->apply_frame_fmt = 1; + } + } + + if (!ret && fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + struct v4l2_subdev_frame_interval fiv = { + .interval = { 0, 1 } + }; + /* Reset to minimum possible frame interval */ + __ov965x_set_frame_interval(ov965x, &fiv); + } + mutex_unlock(&ov965x->lock); + + if (!ret) + ov965x_update_exposure_ctrl(ov965x); + + return ret; +} + +static int ov965x_set_frame_size(struct ov965x *ov965x) +{ + int i, ret = 0; + + for (i = 0; ret == 0 && i < NUM_FMT_REGS; i++) + ret = ov965x_write(ov965x, frame_size_reg_addr[i], + ov965x->frame_size->regs[i]); + return ret; +} + +static int __ov965x_set_params(struct ov965x *ov965x) +{ + struct ov965x_ctrls *ctrls = &ov965x->ctrls; + int ret = 0; + u8 reg; + + if (ov965x->apply_frame_fmt) { + reg = DEF_CLKRC + ov965x->fiv->clkrc_div; + ret = ov965x_write(ov965x, REG_CLKRC, reg); + if (ret < 0) + return ret; + ret = ov965x_set_frame_size(ov965x); + if (ret < 0) + return ret; + ret = ov965x_read(ov965x, REG_TSLB, ®); + if (ret < 0) + return ret; + reg &= ~TSLB_YUYV_MASK; + reg |= ov965x->tslb_reg; + ret = ov965x_write(ov965x, REG_TSLB, reg); + if (ret < 0) + return ret; + } + ret = ov965x_set_default_gamma_curve(ov965x); + if (ret < 0) + return ret; + ret = ov965x_set_color_matrix(ov965x); + if (ret < 0) + return ret; + /* + * Select manual banding filter, the filter will + * be enabled further if required. + */ + ret = ov965x_read(ov965x, REG_COM11, ®); + if (!ret) + reg |= COM11_BANDING; + ret = ov965x_write(ov965x, REG_COM11, reg); + if (ret < 0) + return ret; + /* + * Banding filter (REG_MBD value) needs to match selected + * resolution and frame rate, so it's always updated here. + */ + return ov965x_set_banding_filter(ov965x, ctrls->light_freq->val); +} + +static int ov965x_s_stream(struct v4l2_subdev *sd, int on) +{ + struct ov965x *ov965x = to_ov965x(sd); + struct ov965x_ctrls *ctrls = &ov965x->ctrls; + int ret = 0; + + v4l2_dbg(1, debug, sd, "%s: on: %d\n", __func__, on); + + mutex_lock(&ov965x->lock); + if (ov965x->streaming == !on) { + if (on) + ret = __ov965x_set_params(ov965x); + + if (!ret && ctrls->update) { + /* + * ov965x_s_ctrl callback takes the mutex + * so it needs to be released here. + */ + mutex_unlock(&ov965x->lock); + ret = v4l2_ctrl_handler_setup(&ctrls->handler); + + mutex_lock(&ov965x->lock); + if (!ret) + ctrls->update = 0; + } + if (!ret) + ret = ov965x_write(ov965x, REG_COM2, + on ? 0x01 : 0x11); + } + if (!ret) + ov965x->streaming += on ? 1 : -1; + + WARN_ON(ov965x->streaming < 0); + mutex_unlock(&ov965x->lock); + + return ret; +} + +/* + * V4L2 subdev internal operations + */ +static int ov965x_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *mf = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + + ov965x_get_default_format(mf); + return 0; +} + +static const struct v4l2_subdev_pad_ops ov965x_pad_ops = { + .enum_mbus_code = ov965x_enum_mbus_code, + .enum_frame_size = ov965x_enum_frame_sizes, + .get_fmt = ov965x_get_fmt, + .set_fmt = ov965x_set_fmt, +}; + +static const struct v4l2_subdev_video_ops ov965x_video_ops = { + .s_stream = ov965x_s_stream, + .g_frame_interval = ov965x_g_frame_interval, + .s_frame_interval = ov965x_s_frame_interval, + +}; + +static const struct v4l2_subdev_internal_ops ov965x_sd_internal_ops = { + .open = ov965x_open, +}; + +static const struct v4l2_subdev_core_ops ov965x_core_ops = { + .s_power = ov965x_s_power, + .log_status = v4l2_ctrl_subdev_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops ov965x_subdev_ops = { + .core = &ov965x_core_ops, + .pad = &ov965x_pad_ops, + .video = &ov965x_video_ops, +}; + +/* + * Reset and power down GPIOs configuration + */ +static int ov965x_configure_gpios_pdata(struct ov965x *ov965x, + const struct ov9650_platform_data *pdata) +{ + int ret, i; + int gpios[NUM_GPIOS]; + struct device *dev = regmap_get_device(ov965x->regmap); + + gpios[GPIO_PWDN] = pdata->gpio_pwdn; + gpios[GPIO_RST] = pdata->gpio_reset; + + for (i = 0; i < ARRAY_SIZE(ov965x->gpios); i++) { + int gpio = gpios[i]; + + if (!gpio_is_valid(gpio)) + continue; + ret = devm_gpio_request_one(dev, gpio, + GPIOF_OUT_INIT_HIGH, "OV965X"); + if (ret < 0) + return ret; + v4l2_dbg(1, debug, &ov965x->sd, "set gpio %d to 1\n", gpio); + + gpio_set_value_cansleep(gpio, 1); + gpio_export(gpio, 0); + ov965x->gpios[i] = gpio_to_desc(gpio); + } + + return 0; +} + +static int ov965x_configure_gpios(struct ov965x *ov965x) +{ + struct device *dev = regmap_get_device(ov965x->regmap); + + ov965x->gpios[GPIO_PWDN] = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(ov965x->gpios[GPIO_PWDN])) { + dev_info(dev, "can't get %s GPIO\n", "powerdown"); + return PTR_ERR(ov965x->gpios[GPIO_PWDN]); + } + + ov965x->gpios[GPIO_RST] = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ov965x->gpios[GPIO_RST])) { + dev_info(dev, "can't get %s GPIO\n", "reset"); + return PTR_ERR(ov965x->gpios[GPIO_RST]); + } + + return 0; +} + +static int ov965x_detect_sensor(struct v4l2_subdev *sd) +{ + struct ov965x *ov965x = to_ov965x(sd); + u8 pid, ver; + int ret; + + mutex_lock(&ov965x->lock); + ret = __ov965x_set_power(ov965x, 1); + if (ret) + goto out; + + msleep(25); + + /* Check sensor revision */ + ret = ov965x_read(ov965x, REG_PID, &pid); + if (!ret) + ret = ov965x_read(ov965x, REG_VER, &ver); + + __ov965x_set_power(ov965x, 0); + + if (!ret) { + ov965x->id = OV965X_ID(pid, ver); + if (ov965x->id == OV9650_ID || ov965x->id == OV9652_ID) { + v4l2_info(sd, "Found OV%04X sensor\n", ov965x->id); + } else { + v4l2_err(sd, "Sensor detection failed (%04X, %d)\n", + ov965x->id, ret); + ret = -ENODEV; + } + } +out: + mutex_unlock(&ov965x->lock); + + return ret; +} + +static int ov965x_probe(struct i2c_client *client) +{ + const struct ov9650_platform_data *pdata = client->dev.platform_data; + struct v4l2_subdev *sd; + struct ov965x *ov965x; + int ret; + static const struct regmap_config ov965x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xab, + }; + + ov965x = devm_kzalloc(&client->dev, sizeof(*ov965x), GFP_KERNEL); + if (!ov965x) + return -ENOMEM; + + ov965x->regmap = devm_regmap_init_sccb(client, &ov965x_regmap_config); + if (IS_ERR(ov965x->regmap)) { + dev_err(&client->dev, "Failed to allocate register map\n"); + return PTR_ERR(ov965x->regmap); + } + + if (pdata) { + if (pdata->mclk_frequency == 0) { + dev_err(&client->dev, "MCLK frequency not specified\n"); + return -EINVAL; + } + ov965x->mclk_frequency = pdata->mclk_frequency; + + ret = ov965x_configure_gpios_pdata(ov965x, pdata); + if (ret < 0) + return ret; + } else if (dev_fwnode(&client->dev)) { + ov965x->clk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(ov965x->clk)) + return PTR_ERR(ov965x->clk); + ov965x->mclk_frequency = clk_get_rate(ov965x->clk); + + ret = ov965x_configure_gpios(ov965x); + if (ret < 0) + return ret; + } else { + dev_err(&client->dev, + "Neither platform data nor device property specified\n"); + + return -EINVAL; + } + + mutex_init(&ov965x->lock); + + sd = &ov965x->sd; + v4l2_i2c_subdev_init(sd, client, &ov965x_subdev_ops); + strscpy(sd->name, DRIVER_NAME, sizeof(sd->name)); + + sd->internal_ops = &ov965x_sd_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + + ov965x->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &ov965x->pad); + if (ret < 0) + goto err_mutex; + + ret = ov965x_initialize_controls(ov965x); + if (ret < 0) + goto err_me; + + ov965x_get_default_format(&ov965x->format); + ov965x->frame_size = &ov965x_framesizes[0]; + ov965x->fiv = &ov965x_intervals[0]; + + ret = ov965x_detect_sensor(sd); + if (ret < 0) + goto err_ctrls; + + /* Update exposure time min/max to match frame format */ + ov965x_update_exposure_ctrl(ov965x); + + ret = v4l2_async_register_subdev(sd); + if (ret < 0) + goto err_ctrls; + + return 0; +err_ctrls: + v4l2_ctrl_handler_free(sd->ctrl_handler); +err_me: + media_entity_cleanup(&sd->entity); +err_mutex: + mutex_destroy(&ov965x->lock); + return ret; +} + +static int ov965x_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov965x *ov965x = to_ov965x(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + media_entity_cleanup(&sd->entity); + mutex_destroy(&ov965x->lock); + + return 0; +} + +static const struct i2c_device_id ov965x_id[] = { + { "OV9650", 0 }, + { "OV9652", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, ov965x_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id ov965x_of_match[] = { + { .compatible = "ovti,ov9650", }, + { .compatible = "ovti,ov9652", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ov965x_of_match); +#endif + +static struct i2c_driver ov965x_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(ov965x_of_match), + }, + .probe_new = ov965x_probe, + .remove = ov965x_remove, + .id_table = ov965x_id, +}; + +module_i2c_driver(ov965x_i2c_driver); + +MODULE_AUTHOR("Sylwester Nawrocki <sylvester.nawrocki@gmail.com>"); +MODULE_DESCRIPTION("OV9650/OV9652 CMOS Image Sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/rdacm20.c b/drivers/media/i2c/rdacm20.c new file mode 100644 index 000000000..16bcb764b --- /dev/null +++ b/drivers/media/i2c/rdacm20.c @@ -0,0 +1,676 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IMI RDACM20 GMSL Camera Driver + * + * Copyright (C) 2017-2020 Jacopo Mondi + * Copyright (C) 2017-2020 Kieran Bingham + * Copyright (C) 2017-2019 Laurent Pinchart + * Copyright (C) 2017-2019 Niklas Söderlund + * Copyright (C) 2016 Renesas Electronics Corporation + * Copyright (C) 2015 Cogent Embedded, Inc. + */ + +/* + * The camera is made of an Omnivision OV10635 sensor connected to a Maxim + * MAX9271 GMSL serializer. + */ + +#include <linux/delay.h> +#include <linux/fwnode.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#include "max9271.h" + +#define OV10635_I2C_ADDRESS 0x30 + +#define OV10635_SOFTWARE_RESET 0x0103 +#define OV10635_PID 0x300a +#define OV10635_VER 0x300b +#define OV10635_SC_CMMN_SCCB_ID 0x300c +#define OV10635_SC_CMMN_SCCB_ID_SELECT BIT(0) +#define OV10635_VERSION 0xa635 + +#define OV10635_WIDTH 1280 +#define OV10635_HEIGHT 800 + +/* VTS = PCLK / FPS / HTS / 2 (= 88MHz / 1572 / 30 / 2) */ +#define OV10635_HTS 1572 +/* FPS = 29,9998 */ +#define OV10635_VTS 933 + +/* + * As the drivers supports a single MEDIA_BUS_FMT_UYVY8_2X8 format we + * can harcode the pixel rate. + * + * PCLK is fed through the system clock, programmed @88MHz. + * MEDIA_BUS_FMT_UYVY8_2X8 format = 2 samples per pixel. + * + * Pixelrate = PCLK / 2 + * FPS = (OV10635_VTS * OV10635_HTS) / PixelRate + * = 29,9998 + */ +#define OV10635_PIXEL_RATE (44000000) + +static const struct ov10635_reg { + u16 reg; + u8 val; +} ov10635_regs_wizard[] = { + { 0x301b, 0xff }, { 0x301c, 0xff }, { 0x301a, 0xff }, { 0x3011, 0x42 }, + { 0x6900, 0x0c }, { 0x6901, 0x19 }, { 0x3503, 0x10 }, { 0x3025, 0x03 }, + { 0x3003, 0x16 }, { 0x3004, 0x30 }, { 0x3005, 0x40 }, { 0x3006, 0x91 }, + { 0x3600, 0x74 }, { 0x3601, 0x2b }, { 0x3612, 0x00 }, { 0x3611, 0x67 }, + { 0x3633, 0xca }, { 0x3602, 0xaf }, { 0x3603, 0x04 }, { 0x3630, 0x28 }, + { 0x3631, 0x16 }, { 0x3714, 0x10 }, { 0x371d, 0x01 }, { 0x4300, 0x3a }, + { 0x3007, 0x01 }, { 0x3024, 0x03 }, { 0x3020, 0x0a }, { 0x3702, 0x0d }, + { 0x3703, 0x20 }, { 0x3704, 0x15 }, { 0x3709, 0xa8 }, { 0x370c, 0xc7 }, + { 0x370d, 0x80 }, { 0x3712, 0x00 }, { 0x3713, 0x20 }, { 0x3715, 0x04 }, + { 0x381d, 0x40 }, { 0x381c, 0x00 }, { 0x3822, 0x50 }, { 0x3824, 0x10 }, + { 0x3815, 0x8c }, { 0x3804, 0x05 }, { 0x3805, 0x1f }, { 0x3800, 0x00 }, + { 0x3801, 0x00 }, { 0x3806, 0x03 }, { 0x3807, 0x28 }, { 0x3802, 0x00 }, + { 0x3803, 0x07 }, { 0x3808, 0x05 }, { 0x3809, 0x00 }, { 0x380a, 0x03 }, + { 0x380b, 0x20 }, { 0x380c, OV10635_HTS >> 8 }, + { 0x380d, OV10635_HTS & 0xff }, { 0x380e, OV10635_VTS >> 8 }, + { 0x380f, OV10635_VTS & 0xff }, { 0x3813, 0x02 }, { 0x3811, 0x08 }, + { 0x381f, 0x0c }, { 0x3819, 0x04 }, { 0x3804, 0x01 }, { 0x3805, 0x00 }, + { 0x3828, 0x03 }, { 0x3829, 0x10 }, { 0x382a, 0x10 }, { 0x3621, 0x63 }, + { 0x5005, 0x08 }, { 0x56d5, 0x00 }, { 0x56d6, 0x80 }, { 0x56d7, 0x00 }, + { 0x56d8, 0x00 }, { 0x56d9, 0x00 }, { 0x56da, 0x80 }, { 0x56db, 0x00 }, + { 0x56dc, 0x00 }, { 0x56e8, 0x00 }, { 0x56e9, 0x7f }, { 0x56ea, 0x00 }, + { 0x56eb, 0x7f }, { 0x5100, 0x00 }, { 0x5101, 0x80 }, { 0x5102, 0x00 }, + { 0x5103, 0x80 }, { 0x5104, 0x00 }, { 0x5105, 0x80 }, { 0x5106, 0x00 }, + { 0x5107, 0x80 }, { 0x5108, 0x00 }, { 0x5109, 0x00 }, { 0x510a, 0x00 }, + { 0x510b, 0x00 }, { 0x510c, 0x00 }, { 0x510d, 0x00 }, { 0x510e, 0x00 }, + { 0x510f, 0x00 }, { 0x5110, 0x00 }, { 0x5111, 0x80 }, { 0x5112, 0x00 }, + { 0x5113, 0x80 }, { 0x5114, 0x00 }, { 0x5115, 0x80 }, { 0x5116, 0x00 }, + { 0x5117, 0x80 }, { 0x5118, 0x00 }, { 0x5119, 0x00 }, { 0x511a, 0x00 }, + { 0x511b, 0x00 }, { 0x511c, 0x00 }, { 0x511d, 0x00 }, { 0x511e, 0x00 }, + { 0x511f, 0x00 }, { 0x56d0, 0x00 }, { 0x5006, 0x04 }, { 0x5608, 0x05 }, + { 0x52d7, 0x06 }, { 0x528d, 0x08 }, { 0x5293, 0x12 }, { 0x52d3, 0x12 }, + { 0x5288, 0x06 }, { 0x5289, 0x20 }, { 0x52c8, 0x06 }, { 0x52c9, 0x20 }, + { 0x52cd, 0x04 }, { 0x5381, 0x00 }, { 0x5382, 0xff }, { 0x5589, 0x76 }, + { 0x558a, 0x47 }, { 0x558b, 0xef }, { 0x558c, 0xc9 }, { 0x558d, 0x49 }, + { 0x558e, 0x30 }, { 0x558f, 0x67 }, { 0x5590, 0x3f }, { 0x5591, 0xf0 }, + { 0x5592, 0x10 }, { 0x55a2, 0x6d }, { 0x55a3, 0x55 }, { 0x55a4, 0xc3 }, + { 0x55a5, 0xb5 }, { 0x55a6, 0x43 }, { 0x55a7, 0x38 }, { 0x55a8, 0x5f }, + { 0x55a9, 0x4b }, { 0x55aa, 0xf0 }, { 0x55ab, 0x10 }, { 0x5581, 0x52 }, + { 0x5300, 0x01 }, { 0x5301, 0x00 }, { 0x5302, 0x00 }, { 0x5303, 0x0e }, + { 0x5304, 0x00 }, { 0x5305, 0x0e }, { 0x5306, 0x00 }, { 0x5307, 0x36 }, + { 0x5308, 0x00 }, { 0x5309, 0xd9 }, { 0x530a, 0x00 }, { 0x530b, 0x0f }, + { 0x530c, 0x00 }, { 0x530d, 0x2c }, { 0x530e, 0x00 }, { 0x530f, 0x59 }, + { 0x5310, 0x00 }, { 0x5311, 0x7b }, { 0x5312, 0x00 }, { 0x5313, 0x22 }, + { 0x5314, 0x00 }, { 0x5315, 0xd5 }, { 0x5316, 0x00 }, { 0x5317, 0x13 }, + { 0x5318, 0x00 }, { 0x5319, 0x18 }, { 0x531a, 0x00 }, { 0x531b, 0x26 }, + { 0x531c, 0x00 }, { 0x531d, 0xdc }, { 0x531e, 0x00 }, { 0x531f, 0x02 }, + { 0x5320, 0x00 }, { 0x5321, 0x24 }, { 0x5322, 0x00 }, { 0x5323, 0x56 }, + { 0x5324, 0x00 }, { 0x5325, 0x85 }, { 0x5326, 0x00 }, { 0x5327, 0x20 }, + { 0x5609, 0x01 }, { 0x560a, 0x40 }, { 0x560b, 0x01 }, { 0x560c, 0x40 }, + { 0x560d, 0x00 }, { 0x560e, 0xfa }, { 0x560f, 0x00 }, { 0x5610, 0xfa }, + { 0x5611, 0x02 }, { 0x5612, 0x80 }, { 0x5613, 0x02 }, { 0x5614, 0x80 }, + { 0x5615, 0x01 }, { 0x5616, 0x2c }, { 0x5617, 0x01 }, { 0x5618, 0x2c }, + { 0x563b, 0x01 }, { 0x563c, 0x01 }, { 0x563d, 0x01 }, { 0x563e, 0x01 }, + { 0x563f, 0x03 }, { 0x5640, 0x03 }, { 0x5641, 0x03 }, { 0x5642, 0x05 }, + { 0x5643, 0x09 }, { 0x5644, 0x05 }, { 0x5645, 0x05 }, { 0x5646, 0x05 }, + { 0x5647, 0x05 }, { 0x5651, 0x00 }, { 0x5652, 0x80 }, { 0x521a, 0x01 }, + { 0x521b, 0x03 }, { 0x521c, 0x06 }, { 0x521d, 0x0a }, { 0x521e, 0x0e }, + { 0x521f, 0x12 }, { 0x5220, 0x16 }, { 0x5223, 0x02 }, { 0x5225, 0x04 }, + { 0x5227, 0x08 }, { 0x5229, 0x0c }, { 0x522b, 0x12 }, { 0x522d, 0x18 }, + { 0x522f, 0x1e }, { 0x5241, 0x04 }, { 0x5242, 0x01 }, { 0x5243, 0x03 }, + { 0x5244, 0x06 }, { 0x5245, 0x0a }, { 0x5246, 0x0e }, { 0x5247, 0x12 }, + { 0x5248, 0x16 }, { 0x524a, 0x03 }, { 0x524c, 0x04 }, { 0x524e, 0x08 }, + { 0x5250, 0x0c }, { 0x5252, 0x12 }, { 0x5254, 0x18 }, { 0x5256, 0x1e }, + /* fifo_line_length = 2*hts */ + { 0x4606, (2 * OV10635_HTS) >> 8 }, { 0x4607, (2 * OV10635_HTS) & 0xff }, + /* fifo_hsync_start = 2*(hts - xres) */ + { 0x460a, (2 * (OV10635_HTS - OV10635_WIDTH)) >> 8 }, + { 0x460b, (2 * (OV10635_HTS - OV10635_WIDTH)) & 0xff }, + { 0x460c, 0x00 }, { 0x4620, 0x0e }, + /* BT601: 0x08 is also acceptable as HS/VS mode */ + { 0x4700, 0x04 }, { 0x4701, 0x00 }, { 0x4702, 0x01 }, { 0x4004, 0x04 }, + { 0x4005, 0x18 }, { 0x4001, 0x06 }, { 0x4050, 0x22 }, { 0x4051, 0x24 }, + { 0x4052, 0x02 }, { 0x4057, 0x9c }, { 0x405a, 0x00 }, { 0x4202, 0x02 }, + { 0x3023, 0x10 }, { 0x0100, 0x01 }, { 0x0100, 0x01 }, { 0x6f10, 0x07 }, + { 0x6f11, 0x82 }, { 0x6f12, 0x04 }, { 0x6f13, 0x00 }, { 0xd000, 0x19 }, + { 0xd001, 0xa0 }, { 0xd002, 0x00 }, { 0xd003, 0x01 }, { 0xd004, 0xa9 }, + { 0xd005, 0xad }, { 0xd006, 0x10 }, { 0xd007, 0x40 }, { 0xd008, 0x44 }, + { 0xd009, 0x00 }, { 0xd00a, 0x68 }, { 0xd00b, 0x00 }, { 0xd00c, 0x15 }, + { 0xd00d, 0x00 }, { 0xd00e, 0x00 }, { 0xd00f, 0x00 }, { 0xd040, 0x9c }, + { 0xd041, 0x21 }, { 0xd042, 0xff }, { 0xd043, 0xf8 }, { 0xd044, 0xd4 }, + { 0xd045, 0x01 }, { 0xd046, 0x48 }, { 0xd047, 0x00 }, { 0xd048, 0xd4 }, + { 0xd049, 0x01 }, { 0xd04a, 0x50 }, { 0xd04b, 0x04 }, { 0xd04c, 0x18 }, + { 0xd04d, 0x60 }, { 0xd04e, 0x00 }, { 0xd04f, 0x01 }, { 0xd050, 0xa8 }, + { 0xd051, 0x63 }, { 0xd052, 0x02 }, { 0xd053, 0xa4 }, { 0xd054, 0x85 }, + { 0xd055, 0x43 }, { 0xd056, 0x00 }, { 0xd057, 0x00 }, { 0xd058, 0x18 }, + { 0xd059, 0x60 }, { 0xd05a, 0x00 }, { 0xd05b, 0x01 }, { 0xd05c, 0xa8 }, + { 0xd05d, 0x63 }, { 0xd05e, 0x03 }, { 0xd05f, 0xf0 }, { 0xd060, 0x98 }, + { 0xd061, 0xa3 }, { 0xd062, 0x00 }, { 0xd063, 0x00 }, { 0xd064, 0x8c }, + { 0xd065, 0x6a }, { 0xd066, 0x00 }, { 0xd067, 0x6e }, { 0xd068, 0xe5 }, + { 0xd069, 0x85 }, { 0xd06a, 0x18 }, { 0xd06b, 0x00 }, { 0xd06c, 0x10 }, + { 0xd06d, 0x00 }, { 0xd06e, 0x00 }, { 0xd06f, 0x10 }, { 0xd070, 0x9c }, + { 0xd071, 0x80 }, { 0xd072, 0x00 }, { 0xd073, 0x03 }, { 0xd074, 0x18 }, + { 0xd075, 0x60 }, { 0xd076, 0x00 }, { 0xd077, 0x01 }, { 0xd078, 0xa8 }, + { 0xd079, 0x63 }, { 0xd07a, 0x07 }, { 0xd07b, 0x80 }, { 0xd07c, 0x07 }, + { 0xd07d, 0xff }, { 0xd07e, 0xf9 }, { 0xd07f, 0x03 }, { 0xd080, 0x8c }, + { 0xd081, 0x63 }, { 0xd082, 0x00 }, { 0xd083, 0x00 }, { 0xd084, 0xa5 }, + { 0xd085, 0x6b }, { 0xd086, 0x00 }, { 0xd087, 0xff }, { 0xd088, 0x18 }, + { 0xd089, 0x80 }, { 0xd08a, 0x00 }, { 0xd08b, 0x01 }, { 0xd08c, 0xa8 }, + { 0xd08d, 0x84 }, { 0xd08e, 0x01 }, { 0xd08f, 0x04 }, { 0xd090, 0xe1 }, + { 0xd091, 0x6b }, { 0xd092, 0x58 }, { 0xd093, 0x00 }, { 0xd094, 0x94 }, + { 0xd095, 0x6a }, { 0xd096, 0x00 }, { 0xd097, 0x70 }, { 0xd098, 0xe1 }, + { 0xd099, 0x6b }, { 0xd09a, 0x20 }, { 0xd09b, 0x00 }, { 0xd09c, 0x95 }, + { 0xd09d, 0x6b }, { 0xd09e, 0x00 }, { 0xd09f, 0x00 }, { 0xd0a0, 0xe4 }, + { 0xd0a1, 0x8b }, { 0xd0a2, 0x18 }, { 0xd0a3, 0x00 }, { 0xd0a4, 0x0c }, + { 0xd0a5, 0x00 }, { 0xd0a6, 0x00 }, { 0xd0a7, 0x23 }, { 0xd0a8, 0x15 }, + { 0xd0a9, 0x00 }, { 0xd0aa, 0x00 }, { 0xd0ab, 0x00 }, { 0xd0ac, 0x18 }, + { 0xd0ad, 0x60 }, { 0xd0ae, 0x80 }, { 0xd0af, 0x06 }, { 0xd0b0, 0xa8 }, + { 0xd0b1, 0x83 }, { 0xd0b2, 0x40 }, { 0xd0b3, 0x08 }, { 0xd0b4, 0xa8 }, + { 0xd0b5, 0xe3 }, { 0xd0b6, 0x38 }, { 0xd0b7, 0x2a }, { 0xd0b8, 0xa8 }, + { 0xd0b9, 0xc3 }, { 0xd0ba, 0x40 }, { 0xd0bb, 0x09 }, { 0xd0bc, 0xa8 }, + { 0xd0bd, 0xa3 }, { 0xd0be, 0x38 }, { 0xd0bf, 0x29 }, { 0xd0c0, 0x8c }, + { 0xd0c1, 0x65 }, { 0xd0c2, 0x00 }, { 0xd0c3, 0x00 }, { 0xd0c4, 0xd8 }, + { 0xd0c5, 0x04 }, { 0xd0c6, 0x18 }, { 0xd0c7, 0x00 }, { 0xd0c8, 0x8c }, + { 0xd0c9, 0x67 }, { 0xd0ca, 0x00 }, { 0xd0cb, 0x00 }, { 0xd0cc, 0xd8 }, + { 0xd0cd, 0x06 }, { 0xd0ce, 0x18 }, { 0xd0cf, 0x00 }, { 0xd0d0, 0x18 }, + { 0xd0d1, 0x60 }, { 0xd0d2, 0x80 }, { 0xd0d3, 0x06 }, { 0xd0d4, 0xa8 }, + { 0xd0d5, 0xe3 }, { 0xd0d6, 0x67 }, { 0xd0d7, 0x02 }, { 0xd0d8, 0xa9 }, + { 0xd0d9, 0x03 }, { 0xd0da, 0x67 }, { 0xd0db, 0x03 }, { 0xd0dc, 0xa8 }, + { 0xd0dd, 0xc3 }, { 0xd0de, 0x3d }, { 0xd0df, 0x05 }, { 0xd0e0, 0x8c }, + { 0xd0e1, 0x66 }, { 0xd0e2, 0x00 }, { 0xd0e3, 0x00 }, { 0xd0e4, 0xb8 }, + { 0xd0e5, 0x63 }, { 0xd0e6, 0x00 }, { 0xd0e7, 0x18 }, { 0xd0e8, 0xb8 }, + { 0xd0e9, 0x63 }, { 0xd0ea, 0x00 }, { 0xd0eb, 0x98 }, { 0xd0ec, 0xbc }, + { 0xd0ed, 0x03 }, { 0xd0ee, 0x00 }, { 0xd0ef, 0x00 }, { 0xd0f0, 0x10 }, + { 0xd0f1, 0x00 }, { 0xd0f2, 0x00 }, { 0xd0f3, 0x16 }, { 0xd0f4, 0xb8 }, + { 0xd0f5, 0x83 }, { 0xd0f6, 0x00 }, { 0xd0f7, 0x19 }, { 0xd0f8, 0x8c }, + { 0xd0f9, 0x67 }, { 0xd0fa, 0x00 }, { 0xd0fb, 0x00 }, { 0xd0fc, 0xb8 }, + { 0xd0fd, 0xa4 }, { 0xd0fe, 0x00 }, { 0xd0ff, 0x98 }, { 0xd100, 0xb8 }, + { 0xd101, 0x83 }, { 0xd102, 0x00 }, { 0xd103, 0x08 }, { 0xd104, 0x8c }, + { 0xd105, 0x68 }, { 0xd106, 0x00 }, { 0xd107, 0x00 }, { 0xd108, 0xe0 }, + { 0xd109, 0x63 }, { 0xd10a, 0x20 }, { 0xd10b, 0x04 }, { 0xd10c, 0xe0 }, + { 0xd10d, 0x65 }, { 0xd10e, 0x18 }, { 0xd10f, 0x00 }, { 0xd110, 0xa4 }, + { 0xd111, 0x83 }, { 0xd112, 0xff }, { 0xd113, 0xff }, { 0xd114, 0xb8 }, + { 0xd115, 0x64 }, { 0xd116, 0x00 }, { 0xd117, 0x48 }, { 0xd118, 0xd8 }, + { 0xd119, 0x07 }, { 0xd11a, 0x18 }, { 0xd11b, 0x00 }, { 0xd11c, 0xd8 }, + { 0xd11d, 0x08 }, { 0xd11e, 0x20 }, { 0xd11f, 0x00 }, { 0xd120, 0x9c }, + { 0xd121, 0x60 }, { 0xd122, 0x00 }, { 0xd123, 0x00 }, { 0xd124, 0xd8 }, + { 0xd125, 0x06 }, { 0xd126, 0x18 }, { 0xd127, 0x00 }, { 0xd128, 0x00 }, + { 0xd129, 0x00 }, { 0xd12a, 0x00 }, { 0xd12b, 0x08 }, { 0xd12c, 0x15 }, + { 0xd12d, 0x00 }, { 0xd12e, 0x00 }, { 0xd12f, 0x00 }, { 0xd130, 0x8c }, + { 0xd131, 0x6a }, { 0xd132, 0x00 }, { 0xd133, 0x76 }, { 0xd134, 0xbc }, + { 0xd135, 0x23 }, { 0xd136, 0x00 }, { 0xd137, 0x00 }, { 0xd138, 0x13 }, + { 0xd139, 0xff }, { 0xd13a, 0xff }, { 0xd13b, 0xe6 }, { 0xd13c, 0x18 }, + { 0xd13d, 0x60 }, { 0xd13e, 0x80 }, { 0xd13f, 0x06 }, { 0xd140, 0x03 }, + { 0xd141, 0xff }, { 0xd142, 0xff }, { 0xd143, 0xdd }, { 0xd144, 0xa8 }, + { 0xd145, 0x83 }, { 0xd146, 0x40 }, { 0xd147, 0x08 }, { 0xd148, 0x85 }, + { 0xd149, 0x21 }, { 0xd14a, 0x00 }, { 0xd14b, 0x00 }, { 0xd14c, 0x85 }, + { 0xd14d, 0x41 }, { 0xd14e, 0x00 }, { 0xd14f, 0x04 }, { 0xd150, 0x44 }, + { 0xd151, 0x00 }, { 0xd152, 0x48 }, { 0xd153, 0x00 }, { 0xd154, 0x9c }, + { 0xd155, 0x21 }, { 0xd156, 0x00 }, { 0xd157, 0x08 }, { 0x6f0e, 0x03 }, + { 0x6f0f, 0x00 }, { 0x460e, 0x08 }, { 0x460f, 0x01 }, { 0x4610, 0x00 }, + { 0x4611, 0x01 }, { 0x4612, 0x00 }, { 0x4613, 0x01 }, + /* 8 bits */ + { 0x4605, 0x08 }, + /* Swap data bits order [9:0] -> [0:9] */ + { 0x4709, 0x10 }, { 0x4608, 0x00 }, { 0x4609, 0x08 }, { 0x6804, 0x00 }, + { 0x6805, 0x06 }, { 0x6806, 0x00 }, { 0x5120, 0x00 }, { 0x3510, 0x00 }, + { 0x3504, 0x00 }, { 0x6800, 0x00 }, { 0x6f0d, 0x01 }, + /* PCLK falling edge */ + { 0x4708, 0x01 }, { 0x5000, 0xff }, { 0x5001, 0xbf }, { 0x5002, 0x7e }, + { 0x503d, 0x00 }, { 0xc450, 0x01 }, { 0xc452, 0x04 }, { 0xc453, 0x00 }, + { 0xc454, 0x00 }, { 0xc455, 0x01 }, { 0xc456, 0x01 }, { 0xc457, 0x00 }, + { 0xc458, 0x00 }, { 0xc459, 0x00 }, { 0xc45b, 0x00 }, { 0xc45c, 0x01 }, + { 0xc45d, 0x00 }, { 0xc45e, 0x00 }, { 0xc45f, 0x00 }, { 0xc460, 0x00 }, + { 0xc461, 0x01 }, { 0xc462, 0x01 }, { 0xc464, 0x03 }, { 0xc465, 0x00 }, + { 0xc466, 0x8a }, { 0xc467, 0x00 }, { 0xc468, 0x86 }, { 0xc469, 0x00 }, + { 0xc46a, 0x40 }, { 0xc46b, 0x50 }, { 0xc46c, 0x30 }, { 0xc46d, 0x28 }, + { 0xc46e, 0x60 }, { 0xc46f, 0x40 }, { 0xc47c, 0x01 }, { 0xc47d, 0x38 }, + { 0xc47e, 0x00 }, { 0xc47f, 0x00 }, { 0xc480, 0x00 }, { 0xc481, 0xff }, + { 0xc482, 0x00 }, { 0xc483, 0x40 }, { 0xc484, 0x00 }, { 0xc485, 0x18 }, + { 0xc486, 0x00 }, { 0xc487, 0x18 }, + { 0xc488, (OV10635_VTS - 8) * 16 >> 8}, + { 0xc489, (OV10635_VTS - 8) * 16 & 0xff}, + { 0xc48a, (OV10635_VTS - 8) * 16 >> 8}, + { 0xc48b, (OV10635_VTS - 8) * 16 & 0xff}, { 0xc48c, 0x00 }, + { 0xc48d, 0x04 }, { 0xc48e, 0x00 }, { 0xc48f, 0x04 }, { 0xc490, 0x03 }, + { 0xc492, 0x20 }, { 0xc493, 0x08 }, { 0xc498, 0x02 }, { 0xc499, 0x00 }, + { 0xc49a, 0x02 }, { 0xc49b, 0x00 }, { 0xc49c, 0x02 }, { 0xc49d, 0x00 }, + { 0xc49e, 0x02 }, { 0xc49f, 0x60 }, { 0xc4a0, 0x03 }, { 0xc4a1, 0x00 }, + { 0xc4a2, 0x04 }, { 0xc4a3, 0x00 }, { 0xc4a4, 0x00 }, { 0xc4a5, 0x10 }, + { 0xc4a6, 0x00 }, { 0xc4a7, 0x40 }, { 0xc4a8, 0x00 }, { 0xc4a9, 0x80 }, + { 0xc4aa, 0x0d }, { 0xc4ab, 0x00 }, { 0xc4ac, 0x0f }, { 0xc4ad, 0xc0 }, + { 0xc4b4, 0x01 }, { 0xc4b5, 0x01 }, { 0xc4b6, 0x00 }, { 0xc4b7, 0x01 }, + { 0xc4b8, 0x00 }, { 0xc4b9, 0x01 }, { 0xc4ba, 0x01 }, { 0xc4bb, 0x00 }, + { 0xc4bc, 0x01 }, { 0xc4bd, 0x60 }, { 0xc4be, 0x02 }, { 0xc4bf, 0x33 }, + { 0xc4c8, 0x03 }, { 0xc4c9, 0xd0 }, { 0xc4ca, 0x0e }, { 0xc4cb, 0x00 }, + { 0xc4cc, 0x0e }, { 0xc4cd, 0x51 }, { 0xc4ce, 0x0e }, { 0xc4cf, 0x51 }, + { 0xc4d0, 0x04 }, { 0xc4d1, 0x80 }, { 0xc4e0, 0x04 }, { 0xc4e1, 0x02 }, + { 0xc4e2, 0x01 }, { 0xc4e4, 0x10 }, { 0xc4e5, 0x20 }, { 0xc4e6, 0x30 }, + { 0xc4e7, 0x40 }, { 0xc4e8, 0x50 }, { 0xc4e9, 0x60 }, { 0xc4ea, 0x70 }, + { 0xc4eb, 0x80 }, { 0xc4ec, 0x90 }, { 0xc4ed, 0xa0 }, { 0xc4ee, 0xb0 }, + { 0xc4ef, 0xc0 }, { 0xc4f0, 0xd0 }, { 0xc4f1, 0xe0 }, { 0xc4f2, 0xf0 }, + { 0xc4f3, 0x80 }, { 0xc4f4, 0x00 }, { 0xc4f5, 0x20 }, { 0xc4f6, 0x02 }, + { 0xc4f7, 0x00 }, { 0xc4f8, 0x00 }, { 0xc4f9, 0x00 }, { 0xc4fa, 0x00 }, + { 0xc4fb, 0x01 }, { 0xc4fc, 0x01 }, { 0xc4fd, 0x00 }, { 0xc4fe, 0x04 }, + { 0xc4ff, 0x02 }, { 0xc500, 0x48 }, { 0xc501, 0x74 }, { 0xc502, 0x58 }, + { 0xc503, 0x80 }, { 0xc504, 0x05 }, { 0xc505, 0x80 }, { 0xc506, 0x03 }, + { 0xc507, 0x80 }, { 0xc508, 0x01 }, { 0xc509, 0xc0 }, { 0xc50a, 0x01 }, + { 0xc50b, 0xa0 }, { 0xc50c, 0x01 }, { 0xc50d, 0x2c }, { 0xc50e, 0x01 }, + { 0xc50f, 0x0a }, { 0xc510, 0x00 }, { 0xc511, 0x00 }, { 0xc512, 0xe5 }, + { 0xc513, 0x14 }, { 0xc514, 0x04 }, { 0xc515, 0x00 }, { 0xc518, OV10635_VTS >> 8}, + { 0xc519, OV10635_VTS & 0xff}, { 0xc51a, OV10635_HTS >> 8}, + { 0xc51b, OV10635_HTS & 0xff}, { 0xc2e0, 0x00 }, { 0xc2e1, 0x51 }, + { 0xc2e2, 0x00 }, { 0xc2e3, 0xd6 }, { 0xc2e4, 0x01 }, { 0xc2e5, 0x5e }, + { 0xc2e9, 0x01 }, { 0xc2ea, 0x7a }, { 0xc2eb, 0x90 }, { 0xc2ed, 0x00 }, + { 0xc2ee, 0x7a }, { 0xc2ef, 0x64 }, { 0xc308, 0x00 }, { 0xc309, 0x00 }, + { 0xc30a, 0x00 }, { 0xc30c, 0x00 }, { 0xc30d, 0x01 }, { 0xc30e, 0x00 }, + { 0xc30f, 0x00 }, { 0xc310, 0x01 }, { 0xc311, 0x60 }, { 0xc312, 0xff }, + { 0xc313, 0x08 }, { 0xc314, 0x01 }, { 0xc315, 0x00 }, { 0xc316, 0xff }, + { 0xc317, 0x0b }, { 0xc318, 0x00 }, { 0xc319, 0x0c }, { 0xc31a, 0x00 }, + { 0xc31b, 0xe0 }, { 0xc31c, 0x00 }, { 0xc31d, 0x14 }, { 0xc31e, 0x00 }, + { 0xc31f, 0xc5 }, { 0xc320, 0xff }, { 0xc321, 0x4b }, { 0xc322, 0xff }, + { 0xc323, 0xf0 }, { 0xc324, 0xff }, { 0xc325, 0xe8 }, { 0xc326, 0x00 }, + { 0xc327, 0x46 }, { 0xc328, 0xff }, { 0xc329, 0xd2 }, { 0xc32a, 0xff }, + { 0xc32b, 0xe4 }, { 0xc32c, 0xff }, { 0xc32d, 0xbb }, { 0xc32e, 0x00 }, + { 0xc32f, 0x61 }, { 0xc330, 0xff }, { 0xc331, 0xf9 }, { 0xc332, 0x00 }, + { 0xc333, 0xd9 }, { 0xc334, 0x00 }, { 0xc335, 0x2e }, { 0xc336, 0x00 }, + { 0xc337, 0xb1 }, { 0xc338, 0xff }, { 0xc339, 0x64 }, { 0xc33a, 0xff }, + { 0xc33b, 0xeb }, { 0xc33c, 0xff }, { 0xc33d, 0xe8 }, { 0xc33e, 0x00 }, + { 0xc33f, 0x48 }, { 0xc340, 0xff }, { 0xc341, 0xd0 }, { 0xc342, 0xff }, + { 0xc343, 0xed }, { 0xc344, 0xff }, { 0xc345, 0xad }, { 0xc346, 0x00 }, + { 0xc347, 0x66 }, { 0xc348, 0x01 }, { 0xc349, 0x00 }, { 0x6700, 0x04 }, + { 0x6701, 0x7b }, { 0x6702, 0xfd }, { 0x6703, 0xf9 }, { 0x6704, 0x3d }, + { 0x6705, 0x71 }, { 0x6706, 0x78 }, { 0x6708, 0x05 }, { 0x6f06, 0x6f }, + { 0x6f07, 0x00 }, { 0x6f0a, 0x6f }, { 0x6f0b, 0x00 }, { 0x6f00, 0x03 }, + { 0xc34c, 0x01 }, { 0xc34d, 0x00 }, { 0xc34e, 0x46 }, { 0xc34f, 0x55 }, + { 0xc350, 0x00 }, { 0xc351, 0x40 }, { 0xc352, 0x00 }, { 0xc353, 0xff }, + { 0xc354, 0x04 }, { 0xc355, 0x08 }, { 0xc356, 0x01 }, { 0xc357, 0xef }, + { 0xc358, 0x30 }, { 0xc359, 0x01 }, { 0xc35a, 0x64 }, { 0xc35b, 0x46 }, + { 0xc35c, 0x00 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0xc261, 0x01 }, + { 0x301b, 0xf0 }, { 0x301c, 0xf0 }, { 0x301a, 0xf0 }, { 0x6f00, 0xc3 }, + { 0xc46a, 0x30 }, { 0xc46d, 0x20 }, { 0xc464, 0x84 }, { 0xc465, 0x00 }, + { 0x6f00, 0x03 }, { 0x6f00, 0x43 }, { 0x381c, 0x00 }, { 0x381d, 0x40 }, + { 0xc454, 0x01 }, { 0x6f00, 0xc3 }, { 0xc454, 0x00 }, { 0xc4b1, 0x02 }, + { 0xc4b2, 0x01 }, { 0xc4b3, 0x03 }, { 0x6f00, 0x03 }, { 0x6f00, 0x43 }, + /* enable FSIN (FRAMESYNC input) functionality */ + { 0x3832, (0x0d + 2 * 0x20 + 0x15 + 38) >> 8 }, + { 0x3833, (0x0d + 2 * 0x20 + 0x15 + 38) & 0xff }, + { 0x3834, OV10635_VTS >> 8 }, { 0x3835, OV10635_VTS & 0xff }, + { 0x302e, 0x01 }, +}; + +struct rdacm20_device { + struct device *dev; + struct max9271_device *serializer; + struct i2c_client *sensor; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrls; + u32 addrs[2]; +}; + +static inline struct rdacm20_device *sd_to_rdacm20(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rdacm20_device, sd); +} + +static inline struct rdacm20_device *i2c_to_rdacm20(struct i2c_client *client) +{ + return sd_to_rdacm20(i2c_get_clientdata(client)); +} + +static int ov10635_read16(struct rdacm20_device *dev, u16 reg) +{ + u8 buf[2] = { reg >> 8, reg & 0xff }; + int ret; + + ret = i2c_master_send(dev->sensor, buf, 2); + if (ret != 2) { + dev_dbg(dev->dev, "%s: register 0x%04x write failed (%d)\n", + __func__, reg, ret); + return ret; + } + + ret = i2c_master_recv(dev->sensor, buf, 2); + if (ret < 0) { + dev_dbg(dev->dev, "%s: register 0x%04x read failed (%d)\n", + __func__, reg, ret); + return ret; + } + + return (buf[0] << 8) | buf[1]; +} + +static int __ov10635_write(struct rdacm20_device *dev, u16 reg, u8 val) +{ + u8 buf[3] = { reg >> 8, reg & 0xff, val }; + int ret; + + dev_dbg(dev->dev, "%s(0x%04x, 0x%02x)\n", __func__, reg, val); + + ret = i2c_master_send(dev->sensor, buf, 3); + return ret < 0 ? ret : 0; +} + +static int ov10635_write(struct rdacm20_device *dev, u16 reg, u8 val) +{ + int ret; + + ret = __ov10635_write(dev, reg, val); + if (ret < 0) + dev_err(dev->dev, "%s: register 0x%04x write failed (%d)\n", + __func__, reg, ret); + + return ret; +} + +static int ov10635_set_regs(struct rdacm20_device *dev, + const struct ov10635_reg *regs, + unsigned int nr_regs) +{ + unsigned int i; + int ret; + + for (i = 0; i < nr_regs; i++) { + ret = __ov10635_write(dev, regs[i].reg, regs[i].val); + if (ret) { + dev_err(dev->dev, + "%s: register %u (0x%04x) write failed (%d)\n", + __func__, i, regs[i].reg, ret); + return ret; + } + } + + return 0; +} + +static int rdacm20_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rdacm20_device *dev = sd_to_rdacm20(sd); + + return max9271_set_serial_link(dev->serializer, enable); +} + +static int rdacm20_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_2X8; + + return 0; +} + +static int rdacm20_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + + if (format->pad) + return -EINVAL; + + mf->width = OV10635_WIDTH; + mf->height = OV10635_HEIGHT; + mf->code = MEDIA_BUS_FMT_UYVY8_2X8; + mf->colorspace = V4L2_COLORSPACE_RAW; + mf->field = V4L2_FIELD_NONE; + mf->ycbcr_enc = V4L2_YCBCR_ENC_601; + mf->quantization = V4L2_QUANTIZATION_FULL_RANGE; + mf->xfer_func = V4L2_XFER_FUNC_NONE; + + return 0; +} + +static struct v4l2_subdev_video_ops rdacm20_video_ops = { + .s_stream = rdacm20_s_stream, +}; + +static const struct v4l2_subdev_pad_ops rdacm20_subdev_pad_ops = { + .enum_mbus_code = rdacm20_enum_mbus_code, + .get_fmt = rdacm20_get_fmt, + .set_fmt = rdacm20_get_fmt, +}; + +static struct v4l2_subdev_ops rdacm20_subdev_ops = { + .video = &rdacm20_video_ops, + .pad = &rdacm20_subdev_pad_ops, +}; + +static int rdacm20_initialize(struct rdacm20_device *dev) +{ + unsigned int retry = 3; + int ret; + + /* Verify communication with the MAX9271: ping to wakeup. */ + dev->serializer->client->addr = MAX9271_DEFAULT_ADDR; + i2c_smbus_read_byte(dev->serializer->client); + + /* Serial link disabled during config as it needs a valid pixel clock. */ + ret = max9271_set_serial_link(dev->serializer, false); + if (ret) + return ret; + + /* + * Ensure that we have a good link configuration before attempting to + * identify the device. + */ + max9271_configure_i2c(dev->serializer, MAX9271_I2CSLVSH_469NS_234NS | + MAX9271_I2CSLVTO_1024US | + MAX9271_I2CMSTBT_105KBPS); + + max9271_configure_gmsl_link(dev->serializer); + + ret = max9271_verify_id(dev->serializer); + if (ret < 0) + return ret; + + ret = max9271_set_address(dev->serializer, dev->addrs[0]); + if (ret < 0) + return ret; + dev->serializer->client->addr = dev->addrs[0]; + + /* + * Reset the sensor by cycling the OV10635 reset signal connected to the + * MAX9271 GPIO1 and verify communication with the OV10635. + */ + ret = max9271_enable_gpios(dev->serializer, MAX9271_GPIO1OUT); + if (ret) + return ret; + + ret = max9271_clear_gpios(dev->serializer, MAX9271_GPIO1OUT); + if (ret) + return ret; + usleep_range(10000, 15000); + + ret = max9271_set_gpios(dev->serializer, MAX9271_GPIO1OUT); + if (ret) + return ret; + usleep_range(10000, 15000); + +again: + ret = ov10635_read16(dev, OV10635_PID); + if (ret < 0) { + if (retry--) + goto again; + + dev_err(dev->dev, "OV10635 ID read failed (%d)\n", + ret); + return -ENXIO; + } + + if (ret != OV10635_VERSION) { + if (retry--) + goto again; + + dev_err(dev->dev, "OV10635 ID mismatch (0x%04x)\n", + ret); + return -ENXIO; + } + + /* Change the sensor I2C address. */ + ret = ov10635_write(dev, OV10635_SC_CMMN_SCCB_ID, + (dev->addrs[1] << 1) | + OV10635_SC_CMMN_SCCB_ID_SELECT); + if (ret < 0) { + dev_err(dev->dev, + "OV10635 I2C address change failed (%d)\n", ret); + return ret; + } + dev->sensor->addr = dev->addrs[1]; + usleep_range(3500, 5000); + + /* Program the 0V10635 initial configuration. */ + ret = ov10635_set_regs(dev, ov10635_regs_wizard, + ARRAY_SIZE(ov10635_regs_wizard)); + if (ret) + return ret; + + dev_info(dev->dev, "Identified MAX9271 + OV10635 device\n"); + + return 0; +} + +static int rdacm20_probe(struct i2c_client *client) +{ + struct rdacm20_device *dev; + struct fwnode_handle *ep; + int ret; + + dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->dev = &client->dev; + + dev->serializer = devm_kzalloc(&client->dev, sizeof(*dev->serializer), + GFP_KERNEL); + if (!dev->serializer) + return -ENOMEM; + + dev->serializer->client = client; + + ret = of_property_read_u32_array(client->dev.of_node, "reg", + dev->addrs, 2); + if (ret < 0) { + dev_err(dev->dev, "Invalid DT reg property: %d\n", ret); + return -EINVAL; + } + + /* Create the dummy I2C client for the sensor. */ + dev->sensor = i2c_new_dummy_device(client->adapter, + OV10635_I2C_ADDRESS); + if (IS_ERR(dev->sensor)) { + ret = PTR_ERR(dev->sensor); + goto error; + } + + /* Initialize the hardware. */ + ret = rdacm20_initialize(dev); + if (ret < 0) + goto error; + + /* Initialize and register the subdevice. */ + v4l2_i2c_subdev_init(&dev->sd, client, &rdacm20_subdev_ops); + dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + v4l2_ctrl_handler_init(&dev->ctrls, 1); + v4l2_ctrl_new_std(&dev->ctrls, NULL, V4L2_CID_PIXEL_RATE, + OV10635_PIXEL_RATE, OV10635_PIXEL_RATE, 1, + OV10635_PIXEL_RATE); + dev->sd.ctrl_handler = &dev->ctrls; + + ret = dev->ctrls.error; + if (ret) + goto error_free_ctrls; + + dev->pad.flags = MEDIA_PAD_FL_SOURCE; + dev->sd.entity.flags |= MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&dev->sd.entity, 1, &dev->pad); + if (ret < 0) + goto error_free_ctrls; + + ep = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL); + if (!ep) { + dev_err(&client->dev, + "Unable to get endpoint in node %pOF\n", + client->dev.of_node); + ret = -ENOENT; + goto error_free_ctrls; + } + dev->sd.fwnode = ep; + + ret = v4l2_async_register_subdev(&dev->sd); + if (ret) + goto error_put_node; + + return 0; + +error_put_node: + fwnode_handle_put(ep); +error_free_ctrls: + v4l2_ctrl_handler_free(&dev->ctrls); +error: + media_entity_cleanup(&dev->sd.entity); + if (dev->sensor) + i2c_unregister_device(dev->sensor); + + dev_err(&client->dev, "probe failed\n"); + + return ret; +} + +static int rdacm20_remove(struct i2c_client *client) +{ + struct rdacm20_device *dev = i2c_to_rdacm20(client); + + fwnode_handle_put(dev->sd.fwnode); + v4l2_async_unregister_subdev(&dev->sd); + v4l2_ctrl_handler_free(&dev->ctrls); + media_entity_cleanup(&dev->sd.entity); + i2c_unregister_device(dev->sensor); + + return 0; +} + +static void rdacm20_shutdown(struct i2c_client *client) +{ + struct rdacm20_device *dev = i2c_to_rdacm20(client); + + /* make sure stream off during shutdown (reset/reboot) */ + rdacm20_s_stream(&dev->sd, 0); +} + +static const struct of_device_id rdacm20_of_ids[] = { + { .compatible = "imi,rdacm20", }, + { } +}; +MODULE_DEVICE_TABLE(of, rdacm20_of_ids); + +static struct i2c_driver rdacm20_i2c_driver = { + .driver = { + .name = "rdacm20", + .of_match_table = rdacm20_of_ids, + }, + .probe_new = rdacm20_probe, + .remove = rdacm20_remove, + .shutdown = rdacm20_shutdown, +}; + +module_i2c_driver(rdacm20_i2c_driver); + +MODULE_DESCRIPTION("GMSL Camera driver for RDACM20"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/rj54n1cb0c.c b/drivers/media/i2c/rj54n1cb0c.c new file mode 100644 index 000000000..4cc51e001 --- /dev/null +++ b/drivers/media/i2c/rj54n1cb0c.c @@ -0,0 +1,1436 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for RJ54N1CB0C CMOS Image Sensor from Sharp + * + * Copyright (C) 2018, Jacopo Mondi <jacopo@jmondi.org> + * + * Copyright (C) 2009, Guennadi Liakhovetski <g.liakhovetski@gmx.de> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/v4l2-mediabus.h> +#include <linux/videodev2.h> + +#include <media/i2c/rj54n1cb0c.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#define RJ54N1_DEV_CODE 0x0400 +#define RJ54N1_DEV_CODE2 0x0401 +#define RJ54N1_OUT_SEL 0x0403 +#define RJ54N1_XY_OUTPUT_SIZE_S_H 0x0404 +#define RJ54N1_X_OUTPUT_SIZE_S_L 0x0405 +#define RJ54N1_Y_OUTPUT_SIZE_S_L 0x0406 +#define RJ54N1_XY_OUTPUT_SIZE_P_H 0x0407 +#define RJ54N1_X_OUTPUT_SIZE_P_L 0x0408 +#define RJ54N1_Y_OUTPUT_SIZE_P_L 0x0409 +#define RJ54N1_LINE_LENGTH_PCK_S_H 0x040a +#define RJ54N1_LINE_LENGTH_PCK_S_L 0x040b +#define RJ54N1_LINE_LENGTH_PCK_P_H 0x040c +#define RJ54N1_LINE_LENGTH_PCK_P_L 0x040d +#define RJ54N1_RESIZE_N 0x040e +#define RJ54N1_RESIZE_N_STEP 0x040f +#define RJ54N1_RESIZE_STEP 0x0410 +#define RJ54N1_RESIZE_HOLD_H 0x0411 +#define RJ54N1_RESIZE_HOLD_L 0x0412 +#define RJ54N1_H_OBEN_OFS 0x0413 +#define RJ54N1_V_OBEN_OFS 0x0414 +#define RJ54N1_RESIZE_CONTROL 0x0415 +#define RJ54N1_STILL_CONTROL 0x0417 +#define RJ54N1_INC_USE_SEL_H 0x0425 +#define RJ54N1_INC_USE_SEL_L 0x0426 +#define RJ54N1_MIRROR_STILL_MODE 0x0427 +#define RJ54N1_INIT_START 0x0428 +#define RJ54N1_SCALE_1_2_LEV 0x0429 +#define RJ54N1_SCALE_4_LEV 0x042a +#define RJ54N1_Y_GAIN 0x04d8 +#define RJ54N1_APT_GAIN_UP 0x04fa +#define RJ54N1_RA_SEL_UL 0x0530 +#define RJ54N1_BYTE_SWAP 0x0531 +#define RJ54N1_OUT_SIGPO 0x053b +#define RJ54N1_WB_SEL_WEIGHT_I 0x054e +#define RJ54N1_BIT8_WB 0x0569 +#define RJ54N1_HCAPS_WB 0x056a +#define RJ54N1_VCAPS_WB 0x056b +#define RJ54N1_HCAPE_WB 0x056c +#define RJ54N1_VCAPE_WB 0x056d +#define RJ54N1_EXPOSURE_CONTROL 0x058c +#define RJ54N1_FRAME_LENGTH_S_H 0x0595 +#define RJ54N1_FRAME_LENGTH_S_L 0x0596 +#define RJ54N1_FRAME_LENGTH_P_H 0x0597 +#define RJ54N1_FRAME_LENGTH_P_L 0x0598 +#define RJ54N1_PEAK_H 0x05b7 +#define RJ54N1_PEAK_50 0x05b8 +#define RJ54N1_PEAK_60 0x05b9 +#define RJ54N1_PEAK_DIFF 0x05ba +#define RJ54N1_IOC 0x05ef +#define RJ54N1_TG_BYPASS 0x0700 +#define RJ54N1_PLL_L 0x0701 +#define RJ54N1_PLL_N 0x0702 +#define RJ54N1_PLL_EN 0x0704 +#define RJ54N1_RATIO_TG 0x0706 +#define RJ54N1_RATIO_T 0x0707 +#define RJ54N1_RATIO_R 0x0708 +#define RJ54N1_RAMP_TGCLK_EN 0x0709 +#define RJ54N1_OCLK_DSP 0x0710 +#define RJ54N1_RATIO_OP 0x0711 +#define RJ54N1_RATIO_O 0x0712 +#define RJ54N1_OCLK_SEL_EN 0x0713 +#define RJ54N1_CLK_RST 0x0717 +#define RJ54N1_RESET_STANDBY 0x0718 +#define RJ54N1_FWFLG 0x07fe + +#define E_EXCLK (1 << 7) +#define SOFT_STDBY (1 << 4) +#define SEN_RSTX (1 << 2) +#define TG_RSTX (1 << 1) +#define DSP_RSTX (1 << 0) + +#define RESIZE_HOLD_SEL (1 << 2) +#define RESIZE_GO (1 << 1) + +/* + * When cropping, the camera automatically centers the cropped region, there + * doesn't seem to be a way to specify an explicit location of the rectangle. + */ +#define RJ54N1_COLUMN_SKIP 0 +#define RJ54N1_ROW_SKIP 0 +#define RJ54N1_MAX_WIDTH 1600 +#define RJ54N1_MAX_HEIGHT 1200 + +#define PLL_L 2 +#define PLL_N 0x31 + +/* I2C addresses: 0x50, 0x51, 0x60, 0x61 */ + +/* RJ54N1CB0C has only one fixed colorspace per pixelcode */ +struct rj54n1_datafmt { + u32 code; + enum v4l2_colorspace colorspace; +}; + +/* Find a data format by a pixel code in an array */ +static const struct rj54n1_datafmt *rj54n1_find_datafmt( + u32 code, const struct rj54n1_datafmt *fmt, + int n) +{ + int i; + for (i = 0; i < n; i++) + if (fmt[i].code == code) + return fmt + i; + + return NULL; +} + +static const struct rj54n1_datafmt rj54n1_colour_fmts[] = { + {MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG}, + {MEDIA_BUS_FMT_YVYU8_2X8, V4L2_COLORSPACE_JPEG}, + {MEDIA_BUS_FMT_RGB565_2X8_LE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE, V4L2_COLORSPACE_SRGB}, + {MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_SRGB}, +}; + +struct rj54n1_clock_div { + u8 ratio_tg; /* can be 0 or an odd number */ + u8 ratio_t; + u8 ratio_r; + u8 ratio_op; + u8 ratio_o; +}; + +struct rj54n1 { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct clk *clk; + struct gpio_desc *pwup_gpio; + struct gpio_desc *enable_gpio; + struct rj54n1_clock_div clk_div; + const struct rj54n1_datafmt *fmt; + struct v4l2_rect rect; /* Sensor window */ + unsigned int tgclk_mhz; + bool auto_wb; + unsigned short width; /* Output window */ + unsigned short height; + unsigned short resize; /* Sensor * 1024 / resize = Output */ + unsigned short scale; + u8 bank; +}; + +struct rj54n1_reg_val { + u16 reg; + u8 val; +}; + +static const struct rj54n1_reg_val bank_4[] = { + {0x417, 0}, + {0x42c, 0}, + {0x42d, 0xf0}, + {0x42e, 0}, + {0x42f, 0x50}, + {0x430, 0xf5}, + {0x431, 0x16}, + {0x432, 0x20}, + {0x433, 0}, + {0x434, 0xc8}, + {0x43c, 8}, + {0x43e, 0x90}, + {0x445, 0x83}, + {0x4ba, 0x58}, + {0x4bb, 4}, + {0x4bc, 0x20}, + {0x4db, 4}, + {0x4fe, 2}, +}; + +static const struct rj54n1_reg_val bank_5[] = { + {0x514, 0}, + {0x516, 0}, + {0x518, 0}, + {0x51a, 0}, + {0x51d, 0xff}, + {0x56f, 0x28}, + {0x575, 0x40}, + {0x5bc, 0x48}, + {0x5c1, 6}, + {0x5e5, 0x11}, + {0x5e6, 0x43}, + {0x5e7, 0x33}, + {0x5e8, 0x21}, + {0x5e9, 0x30}, + {0x5ea, 0x0}, + {0x5eb, 0xa5}, + {0x5ec, 0xff}, + {0x5fe, 2}, +}; + +static const struct rj54n1_reg_val bank_7[] = { + {0x70a, 0}, + {0x714, 0xff}, + {0x715, 0xff}, + {0x716, 0x1f}, + {0x7FE, 2}, +}; + +static const struct rj54n1_reg_val bank_8[] = { + {0x800, 0x00}, + {0x801, 0x01}, + {0x802, 0x61}, + {0x805, 0x00}, + {0x806, 0x00}, + {0x807, 0x00}, + {0x808, 0x00}, + {0x809, 0x01}, + {0x80A, 0x61}, + {0x80B, 0x00}, + {0x80C, 0x01}, + {0x80D, 0x00}, + {0x80E, 0x00}, + {0x80F, 0x00}, + {0x810, 0x00}, + {0x811, 0x01}, + {0x812, 0x61}, + {0x813, 0x00}, + {0x814, 0x11}, + {0x815, 0x00}, + {0x816, 0x41}, + {0x817, 0x00}, + {0x818, 0x51}, + {0x819, 0x01}, + {0x81A, 0x1F}, + {0x81B, 0x00}, + {0x81C, 0x01}, + {0x81D, 0x00}, + {0x81E, 0x11}, + {0x81F, 0x00}, + {0x820, 0x41}, + {0x821, 0x00}, + {0x822, 0x51}, + {0x823, 0x00}, + {0x824, 0x00}, + {0x825, 0x00}, + {0x826, 0x47}, + {0x827, 0x01}, + {0x828, 0x4F}, + {0x829, 0x00}, + {0x82A, 0x00}, + {0x82B, 0x00}, + {0x82C, 0x30}, + {0x82D, 0x00}, + {0x82E, 0x40}, + {0x82F, 0x00}, + {0x830, 0xB3}, + {0x831, 0x00}, + {0x832, 0xE3}, + {0x833, 0x00}, + {0x834, 0x00}, + {0x835, 0x00}, + {0x836, 0x00}, + {0x837, 0x00}, + {0x838, 0x00}, + {0x839, 0x01}, + {0x83A, 0x61}, + {0x83B, 0x00}, + {0x83C, 0x01}, + {0x83D, 0x00}, + {0x83E, 0x00}, + {0x83F, 0x00}, + {0x840, 0x00}, + {0x841, 0x01}, + {0x842, 0x61}, + {0x843, 0x00}, + {0x844, 0x1D}, + {0x845, 0x00}, + {0x846, 0x00}, + {0x847, 0x00}, + {0x848, 0x00}, + {0x849, 0x01}, + {0x84A, 0x1F}, + {0x84B, 0x00}, + {0x84C, 0x05}, + {0x84D, 0x00}, + {0x84E, 0x19}, + {0x84F, 0x01}, + {0x850, 0x21}, + {0x851, 0x01}, + {0x852, 0x5D}, + {0x853, 0x00}, + {0x854, 0x00}, + {0x855, 0x00}, + {0x856, 0x19}, + {0x857, 0x01}, + {0x858, 0x21}, + {0x859, 0x00}, + {0x85A, 0x00}, + {0x85B, 0x00}, + {0x85C, 0x00}, + {0x85D, 0x00}, + {0x85E, 0x00}, + {0x85F, 0x00}, + {0x860, 0xB3}, + {0x861, 0x00}, + {0x862, 0xE3}, + {0x863, 0x00}, + {0x864, 0x00}, + {0x865, 0x00}, + {0x866, 0x00}, + {0x867, 0x00}, + {0x868, 0x00}, + {0x869, 0xE2}, + {0x86A, 0x00}, + {0x86B, 0x01}, + {0x86C, 0x06}, + {0x86D, 0x00}, + {0x86E, 0x00}, + {0x86F, 0x00}, + {0x870, 0x60}, + {0x871, 0x8C}, + {0x872, 0x10}, + {0x873, 0x00}, + {0x874, 0xE0}, + {0x875, 0x00}, + {0x876, 0x27}, + {0x877, 0x01}, + {0x878, 0x00}, + {0x879, 0x00}, + {0x87A, 0x00}, + {0x87B, 0x03}, + {0x87C, 0x00}, + {0x87D, 0x00}, + {0x87E, 0x00}, + {0x87F, 0x00}, + {0x880, 0x00}, + {0x881, 0x00}, + {0x882, 0x00}, + {0x883, 0x00}, + {0x884, 0x00}, + {0x885, 0x00}, + {0x886, 0xF8}, + {0x887, 0x00}, + {0x888, 0x03}, + {0x889, 0x00}, + {0x88A, 0x64}, + {0x88B, 0x00}, + {0x88C, 0x03}, + {0x88D, 0x00}, + {0x88E, 0xB1}, + {0x88F, 0x00}, + {0x890, 0x03}, + {0x891, 0x01}, + {0x892, 0x1D}, + {0x893, 0x00}, + {0x894, 0x03}, + {0x895, 0x01}, + {0x896, 0x4B}, + {0x897, 0x00}, + {0x898, 0xE5}, + {0x899, 0x00}, + {0x89A, 0x01}, + {0x89B, 0x00}, + {0x89C, 0x01}, + {0x89D, 0x04}, + {0x89E, 0xC8}, + {0x89F, 0x00}, + {0x8A0, 0x01}, + {0x8A1, 0x01}, + {0x8A2, 0x61}, + {0x8A3, 0x00}, + {0x8A4, 0x01}, + {0x8A5, 0x00}, + {0x8A6, 0x00}, + {0x8A7, 0x00}, + {0x8A8, 0x00}, + {0x8A9, 0x00}, + {0x8AA, 0x7F}, + {0x8AB, 0x03}, + {0x8AC, 0x00}, + {0x8AD, 0x00}, + {0x8AE, 0x00}, + {0x8AF, 0x00}, + {0x8B0, 0x00}, + {0x8B1, 0x00}, + {0x8B6, 0x00}, + {0x8B7, 0x01}, + {0x8B8, 0x00}, + {0x8B9, 0x00}, + {0x8BA, 0x02}, + {0x8BB, 0x00}, + {0x8BC, 0xFF}, + {0x8BD, 0x00}, + {0x8FE, 2}, +}; + +static const struct rj54n1_reg_val bank_10[] = { + {0x10bf, 0x69} +}; + +/* Clock dividers - these are default register values, divider = register + 1 */ +static const struct rj54n1_clock_div clk_div = { + .ratio_tg = 3 /* default: 5 */, + .ratio_t = 4 /* default: 1 */, + .ratio_r = 4 /* default: 0 */, + .ratio_op = 1 /* default: 5 */, + .ratio_o = 9 /* default: 0 */, +}; + +static struct rj54n1 *to_rj54n1(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct rj54n1, subdev); +} + +static int reg_read(struct i2c_client *client, const u16 reg) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + int ret; + + /* set bank */ + if (rj54n1->bank != reg >> 8) { + dev_dbg(&client->dev, "[0x%x] = 0x%x\n", 0xff, reg >> 8); + ret = i2c_smbus_write_byte_data(client, 0xff, reg >> 8); + if (ret < 0) + return ret; + rj54n1->bank = reg >> 8; + } + return i2c_smbus_read_byte_data(client, reg & 0xff); +} + +static int reg_write(struct i2c_client *client, const u16 reg, + const u8 data) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + int ret; + + /* set bank */ + if (rj54n1->bank != reg >> 8) { + dev_dbg(&client->dev, "[0x%x] = 0x%x\n", 0xff, reg >> 8); + ret = i2c_smbus_write_byte_data(client, 0xff, reg >> 8); + if (ret < 0) + return ret; + rj54n1->bank = reg >> 8; + } + dev_dbg(&client->dev, "[0x%x] = 0x%x\n", reg & 0xff, data); + return i2c_smbus_write_byte_data(client, reg & 0xff, data); +} + +static int reg_set(struct i2c_client *client, const u16 reg, + const u8 data, const u8 mask) +{ + int ret; + + ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, (ret & ~mask) | (data & mask)); +} + +static int reg_write_multiple(struct i2c_client *client, + const struct rj54n1_reg_val *rv, const int n) +{ + int i, ret; + + for (i = 0; i < n; i++) { + ret = reg_write(client, rv->reg, rv->val); + if (ret < 0) + return ret; + rv++; + } + + return 0; +} + +static int rj54n1_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(rj54n1_colour_fmts)) + return -EINVAL; + + code->code = rj54n1_colour_fmts[code->index].code; + return 0; +} + +static int rj54n1_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* Switch between preview and still shot modes */ + return reg_set(client, RJ54N1_STILL_CONTROL, (!enable) << 7, 0x80); +} + +static int rj54n1_set_rect(struct i2c_client *client, + u16 reg_x, u16 reg_y, u16 reg_xy, + u32 width, u32 height) +{ + int ret; + + ret = reg_write(client, reg_xy, + ((width >> 4) & 0x70) | + ((height >> 8) & 7)); + + if (!ret) + ret = reg_write(client, reg_x, width & 0xff); + if (!ret) + ret = reg_write(client, reg_y, height & 0xff); + + return ret; +} + +/* + * Some commands, specifically certain initialisation sequences, require + * a commit operation. + */ +static int rj54n1_commit(struct i2c_client *client) +{ + int ret = reg_write(client, RJ54N1_INIT_START, 1); + msleep(10); + if (!ret) + ret = reg_write(client, RJ54N1_INIT_START, 0); + return ret; +} + +static int rj54n1_sensor_scale(struct v4l2_subdev *sd, s32 *in_w, s32 *in_h, + s32 *out_w, s32 *out_h); + +static int rj54n1_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + const struct v4l2_rect *rect = &sel->r; + int output_w, output_h, input_w = rect->width, input_h = rect->height; + int ret; + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + /* arbitrary minimum width and height, edges unimportant */ + v4l_bound_align_image(&input_w, 8, RJ54N1_MAX_WIDTH, 0, + &input_h, 8, RJ54N1_MAX_HEIGHT, 0, 0); + + output_w = (input_w * 1024 + rj54n1->resize / 2) / rj54n1->resize; + output_h = (input_h * 1024 + rj54n1->resize / 2) / rj54n1->resize; + + dev_dbg(&client->dev, "Scaling for %dx%d : %u = %dx%d\n", + input_w, input_h, rj54n1->resize, output_w, output_h); + + ret = rj54n1_sensor_scale(sd, &input_w, &input_h, &output_w, &output_h); + if (ret < 0) + return ret; + + rj54n1->width = output_w; + rj54n1->height = output_h; + rj54n1->resize = ret; + rj54n1->rect.width = input_w; + rj54n1->rect.height = input_h; + + return 0; +} + +static int rj54n1_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = RJ54N1_COLUMN_SKIP; + sel->r.top = RJ54N1_ROW_SKIP; + sel->r.width = RJ54N1_MAX_WIDTH; + sel->r.height = RJ54N1_MAX_HEIGHT; + return 0; + case V4L2_SEL_TGT_CROP: + sel->r = rj54n1->rect; + return 0; + default: + return -EINVAL; + } +} + +static int rj54n1_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + + if (format->pad) + return -EINVAL; + + mf->code = rj54n1->fmt->code; + mf->colorspace = rj54n1->fmt->colorspace; + mf->ycbcr_enc = V4L2_YCBCR_ENC_601; + mf->xfer_func = V4L2_XFER_FUNC_SRGB; + mf->quantization = V4L2_QUANTIZATION_DEFAULT; + mf->field = V4L2_FIELD_NONE; + mf->width = rj54n1->width; + mf->height = rj54n1->height; + + return 0; +} + +/* + * The actual geometry configuration routine. It scales the input window into + * the output one, updates the window sizes and returns an error or the resize + * coefficient on success. Note: we only use the "Fixed Scaling" on this camera. + */ +static int rj54n1_sensor_scale(struct v4l2_subdev *sd, s32 *in_w, s32 *in_h, + s32 *out_w, s32 *out_h) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + unsigned int skip, resize, input_w = *in_w, input_h = *in_h, + output_w = *out_w, output_h = *out_h; + u16 inc_sel, wb_bit8, wb_left, wb_right, wb_top, wb_bottom; + unsigned int peak, peak_50, peak_60; + int ret; + + /* + * We have a problem with crops, where the window is larger than 512x384 + * and output window is larger than a half of the input one. In this + * case we have to either reduce the input window to equal or below + * 512x384 or the output window to equal or below 1/2 of the input. + */ + if (output_w > max(512U, input_w / 2)) { + if (2 * output_w > RJ54N1_MAX_WIDTH) { + input_w = RJ54N1_MAX_WIDTH; + output_w = RJ54N1_MAX_WIDTH / 2; + } else { + input_w = output_w * 2; + } + + dev_dbg(&client->dev, "Adjusted output width: in %u, out %u\n", + input_w, output_w); + } + + if (output_h > max(384U, input_h / 2)) { + if (2 * output_h > RJ54N1_MAX_HEIGHT) { + input_h = RJ54N1_MAX_HEIGHT; + output_h = RJ54N1_MAX_HEIGHT / 2; + } else { + input_h = output_h * 2; + } + + dev_dbg(&client->dev, "Adjusted output height: in %u, out %u\n", + input_h, output_h); + } + + /* Idea: use the read mode for snapshots, handle separate geometries */ + ret = rj54n1_set_rect(client, RJ54N1_X_OUTPUT_SIZE_S_L, + RJ54N1_Y_OUTPUT_SIZE_S_L, + RJ54N1_XY_OUTPUT_SIZE_S_H, output_w, output_h); + if (!ret) + ret = rj54n1_set_rect(client, RJ54N1_X_OUTPUT_SIZE_P_L, + RJ54N1_Y_OUTPUT_SIZE_P_L, + RJ54N1_XY_OUTPUT_SIZE_P_H, output_w, output_h); + + if (ret < 0) + return ret; + + if (output_w > input_w && output_h > input_h) { + input_w = output_w; + input_h = output_h; + + resize = 1024; + } else { + unsigned int resize_x, resize_y; + resize_x = (input_w * 1024 + output_w / 2) / output_w; + resize_y = (input_h * 1024 + output_h / 2) / output_h; + + /* We want max(resize_x, resize_y), check if it still fits */ + if (resize_x > resize_y && + (output_h * resize_x + 512) / 1024 > RJ54N1_MAX_HEIGHT) + resize = (RJ54N1_MAX_HEIGHT * 1024 + output_h / 2) / + output_h; + else if (resize_y > resize_x && + (output_w * resize_y + 512) / 1024 > RJ54N1_MAX_WIDTH) + resize = (RJ54N1_MAX_WIDTH * 1024 + output_w / 2) / + output_w; + else + resize = max(resize_x, resize_y); + + /* Prohibited value ranges */ + switch (resize) { + case 2040 ... 2047: + resize = 2039; + break; + case 4080 ... 4095: + resize = 4079; + break; + case 8160 ... 8191: + resize = 8159; + break; + case 16320 ... 16384: + resize = 16319; + } + } + + /* Set scaling */ + ret = reg_write(client, RJ54N1_RESIZE_HOLD_L, resize & 0xff); + if (!ret) + ret = reg_write(client, RJ54N1_RESIZE_HOLD_H, resize >> 8); + + if (ret < 0) + return ret; + + /* + * Configure a skipping bitmask. The sensor will select a skipping value + * among set bits automatically. This is very unclear in the datasheet + * too. I was told, in this register one enables all skipping values, + * that are required for a specific resize, and the camera selects + * automatically, which ones to use. But it is unclear how to identify, + * which cropping values are needed. Secondly, why don't we just set all + * bits and let the camera choose? Would it increase processing time and + * reduce the framerate? Using 0xfffc for INC_USE_SEL doesn't seem to + * improve the image quality or stability for larger frames (see comment + * above), but I didn't check the framerate. + */ + skip = min(resize / 1024, 15U); + + inc_sel = 1 << skip; + + if (inc_sel <= 2) + inc_sel = 0xc; + else if (resize & 1023 && skip < 15) + inc_sel |= 1 << (skip + 1); + + ret = reg_write(client, RJ54N1_INC_USE_SEL_L, inc_sel & 0xfc); + if (!ret) + ret = reg_write(client, RJ54N1_INC_USE_SEL_H, inc_sel >> 8); + + if (!rj54n1->auto_wb) { + /* Auto white balance window */ + wb_left = output_w / 16; + wb_right = (3 * output_w / 4 - 3) / 4; + wb_top = output_h / 16; + wb_bottom = (3 * output_h / 4 - 3) / 4; + wb_bit8 = ((wb_left >> 2) & 0x40) | ((wb_top >> 4) & 0x10) | + ((wb_right >> 6) & 4) | ((wb_bottom >> 8) & 1); + + if (!ret) + ret = reg_write(client, RJ54N1_BIT8_WB, wb_bit8); + if (!ret) + ret = reg_write(client, RJ54N1_HCAPS_WB, wb_left); + if (!ret) + ret = reg_write(client, RJ54N1_VCAPS_WB, wb_top); + if (!ret) + ret = reg_write(client, RJ54N1_HCAPE_WB, wb_right); + if (!ret) + ret = reg_write(client, RJ54N1_VCAPE_WB, wb_bottom); + } + + /* Antiflicker */ + peak = 12 * RJ54N1_MAX_WIDTH * (1 << 14) * resize / rj54n1->tgclk_mhz / + 10000; + peak_50 = peak / 6; + peak_60 = peak / 5; + + if (!ret) + ret = reg_write(client, RJ54N1_PEAK_H, + ((peak_50 >> 4) & 0xf0) | (peak_60 >> 8)); + if (!ret) + ret = reg_write(client, RJ54N1_PEAK_50, peak_50); + if (!ret) + ret = reg_write(client, RJ54N1_PEAK_60, peak_60); + if (!ret) + ret = reg_write(client, RJ54N1_PEAK_DIFF, peak / 150); + + /* Start resizing */ + if (!ret) + ret = reg_write(client, RJ54N1_RESIZE_CONTROL, + RESIZE_HOLD_SEL | RESIZE_GO | 1); + + if (ret < 0) + return ret; + + /* Constant taken from manufacturer's example */ + msleep(230); + + ret = reg_write(client, RJ54N1_RESIZE_CONTROL, RESIZE_HOLD_SEL | 1); + if (ret < 0) + return ret; + + *in_w = (output_w * resize + 512) / 1024; + *in_h = (output_h * resize + 512) / 1024; + *out_w = output_w; + *out_h = output_h; + + dev_dbg(&client->dev, "Scaled for %dx%d : %u = %ux%u, skip %u\n", + *in_w, *in_h, resize, output_w, output_h, skip); + + return resize; +} + +static int rj54n1_set_clock(struct i2c_client *client) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + int ret; + + /* Enable external clock */ + ret = reg_write(client, RJ54N1_RESET_STANDBY, E_EXCLK | SOFT_STDBY); + /* Leave stand-by. Note: use this when implementing suspend / resume */ + if (!ret) + ret = reg_write(client, RJ54N1_RESET_STANDBY, E_EXCLK); + + if (!ret) + ret = reg_write(client, RJ54N1_PLL_L, PLL_L); + if (!ret) + ret = reg_write(client, RJ54N1_PLL_N, PLL_N); + + /* TGCLK dividers */ + if (!ret) + ret = reg_write(client, RJ54N1_RATIO_TG, + rj54n1->clk_div.ratio_tg); + if (!ret) + ret = reg_write(client, RJ54N1_RATIO_T, + rj54n1->clk_div.ratio_t); + if (!ret) + ret = reg_write(client, RJ54N1_RATIO_R, + rj54n1->clk_div.ratio_r); + + /* Enable TGCLK & RAMP */ + if (!ret) + ret = reg_write(client, RJ54N1_RAMP_TGCLK_EN, 3); + + /* Disable clock output */ + if (!ret) + ret = reg_write(client, RJ54N1_OCLK_DSP, 0); + + /* Set divisors */ + if (!ret) + ret = reg_write(client, RJ54N1_RATIO_OP, + rj54n1->clk_div.ratio_op); + if (!ret) + ret = reg_write(client, RJ54N1_RATIO_O, + rj54n1->clk_div.ratio_o); + + /* Enable OCLK */ + if (!ret) + ret = reg_write(client, RJ54N1_OCLK_SEL_EN, 1); + + /* Use PLL for Timing Generator, write 2 to reserved bits */ + if (!ret) + ret = reg_write(client, RJ54N1_TG_BYPASS, 2); + + /* Take sensor out of reset */ + if (!ret) + ret = reg_write(client, RJ54N1_RESET_STANDBY, + E_EXCLK | SEN_RSTX); + /* Enable PLL */ + if (!ret) + ret = reg_write(client, RJ54N1_PLL_EN, 1); + + /* Wait for PLL to stabilise */ + msleep(10); + + /* Enable clock to frequency divider */ + if (!ret) + ret = reg_write(client, RJ54N1_CLK_RST, 1); + + if (!ret) + ret = reg_read(client, RJ54N1_CLK_RST); + if (ret != 1) { + dev_err(&client->dev, + "Resetting RJ54N1CB0C clock failed: %d!\n", ret); + return -EIO; + } + + /* Start the PLL */ + ret = reg_set(client, RJ54N1_OCLK_DSP, 1, 1); + + /* Enable OCLK */ + if (!ret) + ret = reg_write(client, RJ54N1_OCLK_SEL_EN, 1); + + return ret; +} + +static int rj54n1_reg_init(struct i2c_client *client) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + int ret = rj54n1_set_clock(client); + + if (!ret) + ret = reg_write_multiple(client, bank_7, ARRAY_SIZE(bank_7)); + if (!ret) + ret = reg_write_multiple(client, bank_10, ARRAY_SIZE(bank_10)); + + /* Set binning divisors */ + if (!ret) + ret = reg_write(client, RJ54N1_SCALE_1_2_LEV, 3 | (7 << 4)); + if (!ret) + ret = reg_write(client, RJ54N1_SCALE_4_LEV, 0xf); + + /* Switch to fixed resize mode */ + if (!ret) + ret = reg_write(client, RJ54N1_RESIZE_CONTROL, + RESIZE_HOLD_SEL | 1); + + /* Set gain */ + if (!ret) + ret = reg_write(client, RJ54N1_Y_GAIN, 0x84); + + /* + * Mirror the image back: default is upside down and left-to-right... + * Set manual preview / still shot switching + */ + if (!ret) + ret = reg_write(client, RJ54N1_MIRROR_STILL_MODE, 0x27); + + if (!ret) + ret = reg_write_multiple(client, bank_4, ARRAY_SIZE(bank_4)); + + /* Auto exposure area */ + if (!ret) + ret = reg_write(client, RJ54N1_EXPOSURE_CONTROL, 0x80); + /* Check current auto WB config */ + if (!ret) + ret = reg_read(client, RJ54N1_WB_SEL_WEIGHT_I); + if (ret >= 0) { + rj54n1->auto_wb = ret & 0x80; + ret = reg_write_multiple(client, bank_5, ARRAY_SIZE(bank_5)); + } + if (!ret) + ret = reg_write_multiple(client, bank_8, ARRAY_SIZE(bank_8)); + + if (!ret) + ret = reg_write(client, RJ54N1_RESET_STANDBY, + E_EXCLK | DSP_RSTX | SEN_RSTX); + + /* Commit init */ + if (!ret) + ret = rj54n1_commit(client); + + /* Take DSP, TG, sensor out of reset */ + if (!ret) + ret = reg_write(client, RJ54N1_RESET_STANDBY, + E_EXCLK | DSP_RSTX | TG_RSTX | SEN_RSTX); + + /* Start register update? Same register as 0x?FE in many bank_* sets */ + if (!ret) + ret = reg_write(client, RJ54N1_FWFLG, 2); + + /* Constant taken from manufacturer's example */ + msleep(700); + + return ret; +} + +static int rj54n1_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + const struct rj54n1_datafmt *fmt; + int output_w, output_h, max_w, max_h, + input_w = rj54n1->rect.width, input_h = rj54n1->rect.height; + int align = mf->code == MEDIA_BUS_FMT_SBGGR10_1X10 || + mf->code == MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE || + mf->code == MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE || + mf->code == MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE || + mf->code == MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE; + int ret; + + if (format->pad) + return -EINVAL; + + dev_dbg(&client->dev, "%s: code = %d, width = %u, height = %u\n", + __func__, mf->code, mf->width, mf->height); + + fmt = rj54n1_find_datafmt(mf->code, rj54n1_colour_fmts, + ARRAY_SIZE(rj54n1_colour_fmts)); + if (!fmt) { + fmt = rj54n1->fmt; + mf->code = fmt->code; + } + + mf->field = V4L2_FIELD_NONE; + mf->colorspace = fmt->colorspace; + + v4l_bound_align_image(&mf->width, 112, RJ54N1_MAX_WIDTH, align, + &mf->height, 84, RJ54N1_MAX_HEIGHT, align, 0); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + cfg->try_fmt = *mf; + return 0; + } + + /* + * Verify if the sensor has just been powered on. TODO: replace this + * with proper PM, when a suitable API is available. + */ + ret = reg_read(client, RJ54N1_RESET_STANDBY); + if (ret < 0) + return ret; + + if (!(ret & E_EXCLK)) { + ret = rj54n1_reg_init(client); + if (ret < 0) + return ret; + } + + /* RA_SEL_UL is only relevant for raw modes, ignored otherwise. */ + switch (mf->code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + ret = reg_write(client, RJ54N1_OUT_SEL, 0); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 8, 8); + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + ret = reg_write(client, RJ54N1_OUT_SEL, 0); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 0, 8); + break; + case MEDIA_BUS_FMT_RGB565_2X8_LE: + ret = reg_write(client, RJ54N1_OUT_SEL, 0x11); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 8, 8); + break; + case MEDIA_BUS_FMT_RGB565_2X8_BE: + ret = reg_write(client, RJ54N1_OUT_SEL, 0x11); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 0, 8); + break; + case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE: + ret = reg_write(client, RJ54N1_OUT_SEL, 4); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 8, 8); + if (!ret) + ret = reg_write(client, RJ54N1_RA_SEL_UL, 0); + break; + case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE: + ret = reg_write(client, RJ54N1_OUT_SEL, 4); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 8, 8); + if (!ret) + ret = reg_write(client, RJ54N1_RA_SEL_UL, 8); + break; + case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE: + ret = reg_write(client, RJ54N1_OUT_SEL, 4); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 0, 8); + if (!ret) + ret = reg_write(client, RJ54N1_RA_SEL_UL, 0); + break; + case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE: + ret = reg_write(client, RJ54N1_OUT_SEL, 4); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 0, 8); + if (!ret) + ret = reg_write(client, RJ54N1_RA_SEL_UL, 8); + break; + case MEDIA_BUS_FMT_SBGGR10_1X10: + ret = reg_write(client, RJ54N1_OUT_SEL, 5); + break; + default: + ret = -EINVAL; + } + + /* Special case: a raw mode with 10 bits of data per clock tick */ + if (!ret) + ret = reg_set(client, RJ54N1_OCLK_SEL_EN, + (mf->code == MEDIA_BUS_FMT_SBGGR10_1X10) << 1, 2); + + if (ret < 0) + return ret; + + /* Supported scales 1:1 >= scale > 1:16 */ + max_w = mf->width * (16 * 1024 - 1) / 1024; + if (input_w > max_w) + input_w = max_w; + max_h = mf->height * (16 * 1024 - 1) / 1024; + if (input_h > max_h) + input_h = max_h; + + output_w = mf->width; + output_h = mf->height; + + ret = rj54n1_sensor_scale(sd, &input_w, &input_h, &output_w, &output_h); + if (ret < 0) + return ret; + + fmt = rj54n1_find_datafmt(mf->code, rj54n1_colour_fmts, + ARRAY_SIZE(rj54n1_colour_fmts)); + + rj54n1->fmt = fmt; + rj54n1->resize = ret; + rj54n1->rect.width = input_w; + rj54n1->rect.height = input_h; + rj54n1->width = output_w; + rj54n1->height = output_h; + + mf->width = output_w; + mf->height = output_h; + mf->field = V4L2_FIELD_NONE; + mf->colorspace = fmt->colorspace; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int rj54n1_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg < 0x400 || reg->reg > 0x1fff) + /* Registers > 0x0800 are only available from Sharp support */ + return -EINVAL; + + reg->size = 1; + reg->val = reg_read(client, reg->reg); + + if (reg->val > 0xff) + return -EIO; + + return 0; +} + +static int rj54n1_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg < 0x400 || reg->reg > 0x1fff) + /* Registers >= 0x0800 are only available from Sharp support */ + return -EINVAL; + + if (reg_write(client, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static int rj54n1_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + + if (on) { + if (rj54n1->pwup_gpio) + gpiod_set_value(rj54n1->pwup_gpio, 1); + if (rj54n1->enable_gpio) + gpiod_set_value(rj54n1->enable_gpio, 1); + + msleep(1); + + return clk_prepare_enable(rj54n1->clk); + } + + clk_disable_unprepare(rj54n1->clk); + + if (rj54n1->enable_gpio) + gpiod_set_value(rj54n1->enable_gpio, 0); + if (rj54n1->pwup_gpio) + gpiod_set_value(rj54n1->pwup_gpio, 0); + + return 0; +} + +static int rj54n1_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct rj54n1 *rj54n1 = container_of(ctrl->handler, struct rj54n1, hdl); + struct v4l2_subdev *sd = &rj54n1->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + int data; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (ctrl->val) + data = reg_set(client, RJ54N1_MIRROR_STILL_MODE, 0, 1); + else + data = reg_set(client, RJ54N1_MIRROR_STILL_MODE, 1, 1); + if (data < 0) + return -EIO; + return 0; + case V4L2_CID_HFLIP: + if (ctrl->val) + data = reg_set(client, RJ54N1_MIRROR_STILL_MODE, 0, 2); + else + data = reg_set(client, RJ54N1_MIRROR_STILL_MODE, 2, 2); + if (data < 0) + return -EIO; + return 0; + case V4L2_CID_GAIN: + if (reg_write(client, RJ54N1_Y_GAIN, ctrl->val * 2) < 0) + return -EIO; + return 0; + case V4L2_CID_AUTO_WHITE_BALANCE: + /* Auto WB area - whole image */ + if (reg_set(client, RJ54N1_WB_SEL_WEIGHT_I, ctrl->val << 7, + 0x80) < 0) + return -EIO; + rj54n1->auto_wb = ctrl->val; + return 0; + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops rj54n1_ctrl_ops = { + .s_ctrl = rj54n1_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops rj54n1_subdev_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = rj54n1_g_register, + .s_register = rj54n1_s_register, +#endif + .s_power = rj54n1_s_power, +}; + +static const struct v4l2_subdev_video_ops rj54n1_subdev_video_ops = { + .s_stream = rj54n1_s_stream, +}; + +static const struct v4l2_subdev_pad_ops rj54n1_subdev_pad_ops = { + .enum_mbus_code = rj54n1_enum_mbus_code, + .get_selection = rj54n1_get_selection, + .set_selection = rj54n1_set_selection, + .get_fmt = rj54n1_get_fmt, + .set_fmt = rj54n1_set_fmt, +}; + +static const struct v4l2_subdev_ops rj54n1_subdev_ops = { + .core = &rj54n1_subdev_core_ops, + .video = &rj54n1_subdev_video_ops, + .pad = &rj54n1_subdev_pad_ops, +}; + +/* + * Interface active, can use i2c. If it fails, it can indeed mean, that + * this wasn't our capture interface, so, we wait for the right one + */ +static int rj54n1_video_probe(struct i2c_client *client, + struct rj54n1_pdata *priv) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + int data1, data2; + int ret; + + ret = rj54n1_s_power(&rj54n1->subdev, 1); + if (ret < 0) + return ret; + + /* Read out the chip version register */ + data1 = reg_read(client, RJ54N1_DEV_CODE); + data2 = reg_read(client, RJ54N1_DEV_CODE2); + + if (data1 != 0x51 || data2 != 0x10) { + ret = -ENODEV; + dev_info(&client->dev, "No RJ54N1CB0C found, read 0x%x:0x%x\n", + data1, data2); + goto done; + } + + /* Configure IOCTL polarity from the platform data: 0 or 1 << 7. */ + ret = reg_write(client, RJ54N1_IOC, priv->ioctl_high << 7); + if (ret < 0) + goto done; + + dev_info(&client->dev, "Detected a RJ54N1CB0C chip ID 0x%x:0x%x\n", + data1, data2); + + ret = v4l2_ctrl_handler_setup(&rj54n1->hdl); + +done: + rj54n1_s_power(&rj54n1->subdev, 0); + return ret; +} + +static int rj54n1_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct rj54n1 *rj54n1; + struct i2c_adapter *adapter = client->adapter; + struct rj54n1_pdata *rj54n1_priv; + int ret; + + if (!client->dev.platform_data) { + dev_err(&client->dev, "RJ54N1CB0C: missing platform data!\n"); + return -EINVAL; + } + + rj54n1_priv = client->dev.platform_data; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_BYTE\n"); + return -EIO; + } + + rj54n1 = devm_kzalloc(&client->dev, sizeof(struct rj54n1), GFP_KERNEL); + if (!rj54n1) + return -ENOMEM; + + v4l2_i2c_subdev_init(&rj54n1->subdev, client, &rj54n1_subdev_ops); + v4l2_ctrl_handler_init(&rj54n1->hdl, 4); + v4l2_ctrl_new_std(&rj54n1->hdl, &rj54n1_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&rj54n1->hdl, &rj54n1_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&rj54n1->hdl, &rj54n1_ctrl_ops, + V4L2_CID_GAIN, 0, 127, 1, 66); + v4l2_ctrl_new_std(&rj54n1->hdl, &rj54n1_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + rj54n1->subdev.ctrl_handler = &rj54n1->hdl; + if (rj54n1->hdl.error) + return rj54n1->hdl.error; + + rj54n1->clk_div = clk_div; + rj54n1->rect.left = RJ54N1_COLUMN_SKIP; + rj54n1->rect.top = RJ54N1_ROW_SKIP; + rj54n1->rect.width = RJ54N1_MAX_WIDTH; + rj54n1->rect.height = RJ54N1_MAX_HEIGHT; + rj54n1->width = RJ54N1_MAX_WIDTH; + rj54n1->height = RJ54N1_MAX_HEIGHT; + rj54n1->fmt = &rj54n1_colour_fmts[0]; + rj54n1->resize = 1024; + rj54n1->tgclk_mhz = (rj54n1_priv->mclk_freq / PLL_L * PLL_N) / + (clk_div.ratio_tg + 1) / (clk_div.ratio_t + 1); + + rj54n1->clk = clk_get(&client->dev, NULL); + if (IS_ERR(rj54n1->clk)) { + ret = PTR_ERR(rj54n1->clk); + goto err_free_ctrl; + } + + rj54n1->pwup_gpio = gpiod_get_optional(&client->dev, "powerup", + GPIOD_OUT_LOW); + if (IS_ERR(rj54n1->pwup_gpio)) { + dev_info(&client->dev, "Unable to get GPIO \"powerup\": %ld\n", + PTR_ERR(rj54n1->pwup_gpio)); + ret = PTR_ERR(rj54n1->pwup_gpio); + goto err_clk_put; + } + + rj54n1->enable_gpio = gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(rj54n1->enable_gpio)) { + dev_info(&client->dev, "Unable to get GPIO \"enable\": %ld\n", + PTR_ERR(rj54n1->enable_gpio)); + ret = PTR_ERR(rj54n1->enable_gpio); + goto err_gpio_put; + } + + ret = rj54n1_video_probe(client, rj54n1_priv); + if (ret < 0) + goto err_gpio_put; + + ret = v4l2_async_register_subdev(&rj54n1->subdev); + if (ret) + goto err_gpio_put; + + return 0; + +err_gpio_put: + if (rj54n1->enable_gpio) + gpiod_put(rj54n1->enable_gpio); + + if (rj54n1->pwup_gpio) + gpiod_put(rj54n1->pwup_gpio); + +err_clk_put: + clk_put(rj54n1->clk); + +err_free_ctrl: + v4l2_ctrl_handler_free(&rj54n1->hdl); + + return ret; +} + +static int rj54n1_remove(struct i2c_client *client) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + + if (rj54n1->enable_gpio) + gpiod_put(rj54n1->enable_gpio); + if (rj54n1->pwup_gpio) + gpiod_put(rj54n1->pwup_gpio); + + clk_put(rj54n1->clk); + v4l2_ctrl_handler_free(&rj54n1->hdl); + v4l2_async_unregister_subdev(&rj54n1->subdev); + + return 0; +} + +static const struct i2c_device_id rj54n1_id[] = { + { "rj54n1cb0c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rj54n1_id); + +static struct i2c_driver rj54n1_i2c_driver = { + .driver = { + .name = "rj54n1cb0c", + }, + .probe = rj54n1_probe, + .remove = rj54n1_remove, + .id_table = rj54n1_id, +}; + +module_i2c_driver(rj54n1_i2c_driver); + +MODULE_DESCRIPTION("Sharp RJ54N1CB0C Camera driver"); +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/s5c73m3/Makefile b/drivers/media/i2c/s5c73m3/Makefile new file mode 100644 index 000000000..ddb9dc62d --- /dev/null +++ b/drivers/media/i2c/s5c73m3/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +s5c73m3-objs := s5c73m3-core.o s5c73m3-spi.o s5c73m3-ctrls.o +obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3.o diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-core.c b/drivers/media/i2c/s5c73m3/s5c73m3-core.c new file mode 100644 index 000000000..71804a70b --- /dev/null +++ b/drivers/media/i2c/s5c73m3/s5c73m3-core.c @@ -0,0 +1,1817 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung LSI S5C73M3 8M pixel camera driver + * + * Copyright (C) 2012, Samsung Electronics, Co., Ltd. + * Sylwester Nawrocki <s.nawrocki@samsung.com> + * Andrzej Hajda <a.hajda@samsung.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/media.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/videodev2.h> +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-mediabus.h> +#include <media/i2c/s5c73m3.h> +#include <media/v4l2-fwnode.h> + +#include "s5c73m3.h" + +int s5c73m3_dbg; +module_param_named(debug, s5c73m3_dbg, int, 0644); + +static int boot_from_rom = 1; +module_param(boot_from_rom, int, 0644); + +static int update_fw; +module_param(update_fw, int, 0644); + +#define S5C73M3_EMBEDDED_DATA_MAXLEN SZ_4K +#define S5C73M3_MIPI_DATA_LANES 4 +#define S5C73M3_CLK_NAME "cis_extclk" + +static const char * const s5c73m3_supply_names[S5C73M3_MAX_SUPPLIES] = { + "vdd-int", /* Digital Core supply (1.2V), CAM_ISP_CORE_1.2V */ + "vdda", /* Analog Core supply (1.2V), CAM_SENSOR_CORE_1.2V */ + "vdd-reg", /* Regulator input supply (2.8V), CAM_SENSOR_A2.8V */ + "vddio-host", /* Digital Host I/O power supply (1.8V...2.8V), + CAM_ISP_SENSOR_1.8V */ + "vddio-cis", /* Digital CIS I/O power (1.2V...1.8V), + CAM_ISP_MIPI_1.2V */ + "vdd-af", /* Lens, CAM_AF_2.8V */ +}; + +static const struct s5c73m3_frame_size s5c73m3_isp_resolutions[] = { + { 320, 240, COMM_CHG_MODE_YUV_320_240 }, + { 352, 288, COMM_CHG_MODE_YUV_352_288 }, + { 640, 480, COMM_CHG_MODE_YUV_640_480 }, + { 880, 720, COMM_CHG_MODE_YUV_880_720 }, + { 960, 720, COMM_CHG_MODE_YUV_960_720 }, + { 1008, 672, COMM_CHG_MODE_YUV_1008_672 }, + { 1184, 666, COMM_CHG_MODE_YUV_1184_666 }, + { 1280, 720, COMM_CHG_MODE_YUV_1280_720 }, + { 1536, 864, COMM_CHG_MODE_YUV_1536_864 }, + { 1600, 1200, COMM_CHG_MODE_YUV_1600_1200 }, + { 1632, 1224, COMM_CHG_MODE_YUV_1632_1224 }, + { 1920, 1080, COMM_CHG_MODE_YUV_1920_1080 }, + { 1920, 1440, COMM_CHG_MODE_YUV_1920_1440 }, + { 2304, 1296, COMM_CHG_MODE_YUV_2304_1296 }, + { 3264, 2448, COMM_CHG_MODE_YUV_3264_2448 }, +}; + +static const struct s5c73m3_frame_size s5c73m3_jpeg_resolutions[] = { + { 640, 480, COMM_CHG_MODE_JPEG_640_480 }, + { 800, 450, COMM_CHG_MODE_JPEG_800_450 }, + { 800, 600, COMM_CHG_MODE_JPEG_800_600 }, + { 1024, 768, COMM_CHG_MODE_JPEG_1024_768 }, + { 1280, 720, COMM_CHG_MODE_JPEG_1280_720 }, + { 1280, 960, COMM_CHG_MODE_JPEG_1280_960 }, + { 1600, 900, COMM_CHG_MODE_JPEG_1600_900 }, + { 1600, 1200, COMM_CHG_MODE_JPEG_1600_1200 }, + { 2048, 1152, COMM_CHG_MODE_JPEG_2048_1152 }, + { 2048, 1536, COMM_CHG_MODE_JPEG_2048_1536 }, + { 2560, 1440, COMM_CHG_MODE_JPEG_2560_1440 }, + { 2560, 1920, COMM_CHG_MODE_JPEG_2560_1920 }, + { 3264, 1836, COMM_CHG_MODE_JPEG_3264_1836 }, + { 3264, 2176, COMM_CHG_MODE_JPEG_3264_2176 }, + { 3264, 2448, COMM_CHG_MODE_JPEG_3264_2448 }, +}; + +static const struct s5c73m3_frame_size * const s5c73m3_resolutions[] = { + [RES_ISP] = s5c73m3_isp_resolutions, + [RES_JPEG] = s5c73m3_jpeg_resolutions +}; + +static const int s5c73m3_resolutions_len[] = { + [RES_ISP] = ARRAY_SIZE(s5c73m3_isp_resolutions), + [RES_JPEG] = ARRAY_SIZE(s5c73m3_jpeg_resolutions) +}; + +static const struct s5c73m3_interval s5c73m3_intervals[] = { + { COMM_FRAME_RATE_FIXED_7FPS, {142857, 1000000}, {3264, 2448} }, + { COMM_FRAME_RATE_FIXED_15FPS, {66667, 1000000}, {3264, 2448} }, + { COMM_FRAME_RATE_FIXED_20FPS, {50000, 1000000}, {2304, 1296} }, + { COMM_FRAME_RATE_FIXED_30FPS, {33333, 1000000}, {2304, 1296} }, +}; + +#define S5C73M3_DEFAULT_FRAME_INTERVAL 3 /* 30 fps */ + +static void s5c73m3_fill_mbus_fmt(struct v4l2_mbus_framefmt *mf, + const struct s5c73m3_frame_size *fs, + u32 code) +{ + mf->width = fs->width; + mf->height = fs->height; + mf->code = code; + mf->colorspace = V4L2_COLORSPACE_JPEG; + mf->field = V4L2_FIELD_NONE; +} + +static int s5c73m3_i2c_write(struct i2c_client *client, u16 addr, u16 data) +{ + u8 buf[4] = { addr >> 8, addr & 0xff, data >> 8, data & 0xff }; + + int ret = i2c_master_send(client, buf, sizeof(buf)); + + v4l_dbg(4, s5c73m3_dbg, client, "%s: addr 0x%04x, data 0x%04x\n", + __func__, addr, data); + + if (ret == 4) + return 0; + + return ret < 0 ? ret : -EREMOTEIO; +} + +static int s5c73m3_i2c_read(struct i2c_client *client, u16 addr, u16 *data) +{ + int ret; + u8 rbuf[2], wbuf[2] = { addr >> 8, addr & 0xff }; + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(wbuf), + .buf = wbuf + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(rbuf), + .buf = rbuf + } + }; + /* + * Issue repeated START after writing 2 address bytes and + * just one STOP only after reading the data bytes. + */ + ret = i2c_transfer(client->adapter, msg, 2); + if (ret == 2) { + *data = be16_to_cpup((__be16 *)rbuf); + v4l2_dbg(4, s5c73m3_dbg, client, + "%s: addr: 0x%04x, data: 0x%04x\n", + __func__, addr, *data); + return 0; + } + + v4l2_err(client, "I2C read failed: addr: %04x, (%d)\n", addr, ret); + + return ret >= 0 ? -EREMOTEIO : ret; +} + +int s5c73m3_write(struct s5c73m3 *state, u32 addr, u16 data) +{ + struct i2c_client *client = state->i2c_client; + int ret; + + if ((addr ^ state->i2c_write_address) & 0xffff0000) { + ret = s5c73m3_i2c_write(client, REG_CMDWR_ADDRH, addr >> 16); + if (ret < 0) { + state->i2c_write_address = 0; + return ret; + } + } + + if ((addr ^ state->i2c_write_address) & 0xffff) { + ret = s5c73m3_i2c_write(client, REG_CMDWR_ADDRL, addr & 0xffff); + if (ret < 0) { + state->i2c_write_address = 0; + return ret; + } + } + + state->i2c_write_address = addr; + + ret = s5c73m3_i2c_write(client, REG_CMDBUF_ADDR, data); + if (ret < 0) + return ret; + + state->i2c_write_address += 2; + + return ret; +} + +int s5c73m3_read(struct s5c73m3 *state, u32 addr, u16 *data) +{ + struct i2c_client *client = state->i2c_client; + int ret; + + if ((addr ^ state->i2c_read_address) & 0xffff0000) { + ret = s5c73m3_i2c_write(client, REG_CMDRD_ADDRH, addr >> 16); + if (ret < 0) { + state->i2c_read_address = 0; + return ret; + } + } + + if ((addr ^ state->i2c_read_address) & 0xffff) { + ret = s5c73m3_i2c_write(client, REG_CMDRD_ADDRL, addr & 0xffff); + if (ret < 0) { + state->i2c_read_address = 0; + return ret; + } + } + + state->i2c_read_address = addr; + + ret = s5c73m3_i2c_read(client, REG_CMDBUF_ADDR, data); + if (ret < 0) + return ret; + + state->i2c_read_address += 2; + + return ret; +} + +static int s5c73m3_check_status(struct s5c73m3 *state, unsigned int value) +{ + unsigned long start = jiffies; + unsigned long end = start + msecs_to_jiffies(2000); + int ret; + u16 status; + int count = 0; + + do { + ret = s5c73m3_read(state, REG_STATUS, &status); + if (ret < 0 || status == value) + break; + usleep_range(500, 1000); + ++count; + } while (time_is_after_jiffies(end)); + + if (count > 0) + v4l2_dbg(1, s5c73m3_dbg, &state->sensor_sd, + "status check took %dms\n", + jiffies_to_msecs(jiffies - start)); + + if (ret == 0 && status != value) { + u16 i2c_status = 0; + u16 i2c_seq_status = 0; + + s5c73m3_read(state, REG_I2C_STATUS, &i2c_status); + s5c73m3_read(state, REG_I2C_SEQ_STATUS, &i2c_seq_status); + + v4l2_err(&state->sensor_sd, + "wrong status %#x, expected: %#x, i2c_status: %#x/%#x\n", + status, value, i2c_status, i2c_seq_status); + + return -ETIMEDOUT; + } + + return ret; +} + +int s5c73m3_isp_command(struct s5c73m3 *state, u16 command, u16 data) +{ + int ret; + + ret = s5c73m3_check_status(state, REG_STATUS_ISP_COMMAND_COMPLETED); + if (ret < 0) + return ret; + + ret = s5c73m3_write(state, 0x00095000, command); + if (ret < 0) + return ret; + + ret = s5c73m3_write(state, 0x00095002, data); + if (ret < 0) + return ret; + + return s5c73m3_write(state, REG_STATUS, 0x0001); +} + +static int s5c73m3_isp_comm_result(struct s5c73m3 *state, u16 command, + u16 *data) +{ + return s5c73m3_read(state, COMM_RESULT_OFFSET + command, data); +} + +static int s5c73m3_set_af_softlanding(struct s5c73m3 *state) +{ + unsigned long start = jiffies; + u16 af_softlanding; + int count = 0; + int ret; + const char *msg; + + ret = s5c73m3_isp_command(state, COMM_AF_SOFTLANDING, + COMM_AF_SOFTLANDING_ON); + if (ret < 0) { + v4l2_info(&state->sensor_sd, "AF soft-landing failed\n"); + return ret; + } + + for (;;) { + ret = s5c73m3_isp_comm_result(state, COMM_AF_SOFTLANDING, + &af_softlanding); + if (ret < 0) { + msg = "failed"; + break; + } + if (af_softlanding == COMM_AF_SOFTLANDING_RES_COMPLETE) { + msg = "succeeded"; + break; + } + if (++count > 100) { + ret = -ETIME; + msg = "timed out"; + break; + } + msleep(25); + } + + v4l2_info(&state->sensor_sd, "AF soft-landing %s after %dms\n", + msg, jiffies_to_msecs(jiffies - start)); + + return ret; +} + +static int s5c73m3_load_fw(struct v4l2_subdev *sd) +{ + struct s5c73m3 *state = sensor_sd_to_s5c73m3(sd); + struct i2c_client *client = state->i2c_client; + const struct firmware *fw; + int ret; + char fw_name[20]; + + snprintf(fw_name, sizeof(fw_name), "SlimISP_%.2s.bin", + state->fw_file_version); + ret = request_firmware(&fw, fw_name, &client->dev); + if (ret < 0) { + v4l2_err(sd, "Firmware request failed (%s)\n", fw_name); + return -EINVAL; + } + + v4l2_info(sd, "Loading firmware (%s, %zu B)\n", fw_name, fw->size); + + ret = s5c73m3_spi_write(state, fw->data, fw->size, 64); + + if (ret >= 0) + state->isp_ready = 1; + else + v4l2_err(sd, "SPI write failed\n"); + + release_firmware(fw); + + return ret; +} + +static int s5c73m3_set_frame_size(struct s5c73m3 *state) +{ + const struct s5c73m3_frame_size *prev_size = + state->sensor_pix_size[RES_ISP]; + const struct s5c73m3_frame_size *cap_size = + state->sensor_pix_size[RES_JPEG]; + unsigned int chg_mode; + + v4l2_dbg(1, s5c73m3_dbg, &state->sensor_sd, + "Preview size: %dx%d, reg_val: 0x%x\n", + prev_size->width, prev_size->height, prev_size->reg_val); + + chg_mode = prev_size->reg_val | COMM_CHG_MODE_NEW; + + if (state->mbus_code == S5C73M3_JPEG_FMT) { + v4l2_dbg(1, s5c73m3_dbg, &state->sensor_sd, + "Capture size: %dx%d, reg_val: 0x%x\n", + cap_size->width, cap_size->height, cap_size->reg_val); + chg_mode |= cap_size->reg_val; + } + + return s5c73m3_isp_command(state, COMM_CHG_MODE, chg_mode); +} + +static int s5c73m3_set_frame_rate(struct s5c73m3 *state) +{ + int ret; + + if (state->ctrls.stabilization->val) + return 0; + + if (WARN_ON(state->fiv == NULL)) + return -EINVAL; + + ret = s5c73m3_isp_command(state, COMM_FRAME_RATE, state->fiv->fps_reg); + if (!ret) + state->apply_fiv = 0; + + return ret; +} + +static int __s5c73m3_s_stream(struct s5c73m3 *state, struct v4l2_subdev *sd, + int on) +{ + u16 mode; + int ret; + + if (on && state->apply_fmt) { + if (state->mbus_code == S5C73M3_JPEG_FMT) + mode = COMM_IMG_OUTPUT_INTERLEAVED; + else + mode = COMM_IMG_OUTPUT_YUV; + + ret = s5c73m3_isp_command(state, COMM_IMG_OUTPUT, mode); + if (!ret) + ret = s5c73m3_set_frame_size(state); + if (ret) + return ret; + state->apply_fmt = 0; + } + + ret = s5c73m3_isp_command(state, COMM_SENSOR_STREAMING, !!on); + if (ret) + return ret; + + state->streaming = !!on; + + if (!on) + return ret; + + if (state->apply_fiv) { + ret = s5c73m3_set_frame_rate(state); + if (ret < 0) + v4l2_err(sd, "Error setting frame rate(%d)\n", ret); + } + + return s5c73m3_check_status(state, REG_STATUS_ISP_COMMAND_COMPLETED); +} + +static int s5c73m3_oif_s_stream(struct v4l2_subdev *sd, int on) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + int ret; + + mutex_lock(&state->lock); + ret = __s5c73m3_s_stream(state, sd, on); + mutex_unlock(&state->lock); + + return ret; +} + +static int s5c73m3_system_status_wait(struct s5c73m3 *state, u32 value, + unsigned int delay, unsigned int steps) +{ + u16 reg = 0; + + while (steps-- > 0) { + int ret = s5c73m3_read(state, 0x30100010, ®); + if (ret < 0) + return ret; + if (reg == value) + return 0; + usleep_range(delay, delay + 25); + } + return -ETIMEDOUT; +} + +static int s5c73m3_read_fw_version(struct s5c73m3 *state) +{ + struct v4l2_subdev *sd = &state->sensor_sd; + int i, ret; + u16 data[2]; + int offset; + + offset = state->isp_ready ? 0x60 : 0; + + for (i = 0; i < S5C73M3_SENSOR_FW_LEN / 2; i++) { + ret = s5c73m3_read(state, offset + i * 2, data); + if (ret < 0) + return ret; + state->sensor_fw[i * 2] = (char)(*data & 0xff); + state->sensor_fw[i * 2 + 1] = (char)(*data >> 8); + } + state->sensor_fw[S5C73M3_SENSOR_FW_LEN] = '\0'; + + + for (i = 0; i < S5C73M3_SENSOR_TYPE_LEN / 2; i++) { + ret = s5c73m3_read(state, offset + 6 + i * 2, data); + if (ret < 0) + return ret; + state->sensor_type[i * 2] = (char)(*data & 0xff); + state->sensor_type[i * 2 + 1] = (char)(*data >> 8); + } + state->sensor_type[S5C73M3_SENSOR_TYPE_LEN] = '\0'; + + ret = s5c73m3_read(state, offset + 0x14, data); + if (ret >= 0) { + ret = s5c73m3_read(state, offset + 0x16, data + 1); + if (ret >= 0) + state->fw_size = data[0] + (data[1] << 16); + } + + v4l2_info(sd, "Sensor type: %s, FW version: %s\n", + state->sensor_type, state->sensor_fw); + return ret; +} + +static int s5c73m3_fw_update_from(struct s5c73m3 *state) +{ + struct v4l2_subdev *sd = &state->sensor_sd; + u16 status = COMM_FW_UPDATE_NOT_READY; + int ret; + int count = 0; + + v4l2_warn(sd, "Updating F-ROM firmware.\n"); + do { + if (status == COMM_FW_UPDATE_NOT_READY) { + ret = s5c73m3_isp_command(state, COMM_FW_UPDATE, 0); + if (ret < 0) + return ret; + } + + ret = s5c73m3_read(state, 0x00095906, &status); + if (ret < 0) + return ret; + switch (status) { + case COMM_FW_UPDATE_FAIL: + v4l2_warn(sd, "Updating F-ROM firmware failed.\n"); + return -EIO; + case COMM_FW_UPDATE_SUCCESS: + v4l2_warn(sd, "Updating F-ROM firmware finished.\n"); + return 0; + } + ++count; + msleep(20); + } while (count < 500); + + v4l2_warn(sd, "Updating F-ROM firmware timed-out.\n"); + return -ETIMEDOUT; +} + +static int s5c73m3_spi_boot(struct s5c73m3 *state, bool load_fw) +{ + struct v4l2_subdev *sd = &state->sensor_sd; + int ret; + + /* Run ARM MCU */ + ret = s5c73m3_write(state, 0x30000004, 0xffff); + if (ret < 0) + return ret; + + usleep_range(400, 500); + + /* Check booting status */ + ret = s5c73m3_system_status_wait(state, 0x0c, 100, 3); + if (ret < 0) { + v4l2_err(sd, "booting failed: %d\n", ret); + return ret; + } + + /* P,M,S and Boot Mode */ + ret = s5c73m3_write(state, 0x30100014, 0x2146); + if (ret < 0) + return ret; + + ret = s5c73m3_write(state, 0x30100010, 0x210c); + if (ret < 0) + return ret; + + usleep_range(200, 250); + + /* Check SPI status */ + ret = s5c73m3_system_status_wait(state, 0x210d, 100, 300); + if (ret < 0) + v4l2_err(sd, "SPI not ready: %d\n", ret); + + /* Firmware download over SPI */ + if (load_fw) + s5c73m3_load_fw(sd); + + /* MCU reset */ + ret = s5c73m3_write(state, 0x30000004, 0xfffd); + if (ret < 0) + return ret; + + /* Remap */ + ret = s5c73m3_write(state, 0x301000a4, 0x0183); + if (ret < 0) + return ret; + + /* MCU restart */ + ret = s5c73m3_write(state, 0x30000004, 0xffff); + if (ret < 0 || !load_fw) + return ret; + + ret = s5c73m3_read_fw_version(state); + if (ret < 0) + return ret; + + if (load_fw && update_fw) { + ret = s5c73m3_fw_update_from(state); + update_fw = 0; + } + + return ret; +} + +static int s5c73m3_set_timing_register_for_vdd(struct s5c73m3 *state) +{ + static const u32 regs[][2] = { + { 0x30100018, 0x0618 }, + { 0x3010001c, 0x10c1 }, + { 0x30100020, 0x249e } + }; + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + ret = s5c73m3_write(state, regs[i][0], regs[i][1]); + if (ret < 0) + return ret; + } + + return 0; +} + +static void s5c73m3_set_fw_file_version(struct s5c73m3 *state) +{ + switch (state->sensor_fw[0]) { + case 'G': + case 'O': + state->fw_file_version[0] = 'G'; + break; + case 'S': + case 'Z': + state->fw_file_version[0] = 'Z'; + break; + } + + switch (state->sensor_fw[1]) { + case 'C'...'F': + state->fw_file_version[1] = state->sensor_fw[1]; + break; + } +} + +static int s5c73m3_get_fw_version(struct s5c73m3 *state) +{ + struct v4l2_subdev *sd = &state->sensor_sd; + int ret; + + /* Run ARM MCU */ + ret = s5c73m3_write(state, 0x30000004, 0xffff); + if (ret < 0) + return ret; + usleep_range(400, 500); + + /* Check booting status */ + ret = s5c73m3_system_status_wait(state, 0x0c, 100, 3); + if (ret < 0) { + + v4l2_err(sd, "%s: booting failed: %d\n", __func__, ret); + return ret; + } + + /* Change I/O Driver Current in order to read from F-ROM */ + ret = s5c73m3_write(state, 0x30100120, 0x0820); + ret = s5c73m3_write(state, 0x30100124, 0x0820); + + /* Offset Setting */ + ret = s5c73m3_write(state, 0x00010418, 0x0008); + + /* P,M,S and Boot Mode */ + ret = s5c73m3_write(state, 0x30100014, 0x2146); + if (ret < 0) + return ret; + ret = s5c73m3_write(state, 0x30100010, 0x230c); + if (ret < 0) + return ret; + + usleep_range(200, 250); + + /* Check SPI status */ + ret = s5c73m3_system_status_wait(state, 0x230e, 100, 300); + if (ret < 0) + v4l2_err(sd, "SPI not ready: %d\n", ret); + + /* ARM reset */ + ret = s5c73m3_write(state, 0x30000004, 0xfffd); + if (ret < 0) + return ret; + + /* Remap */ + ret = s5c73m3_write(state, 0x301000a4, 0x0183); + if (ret < 0) + return ret; + + s5c73m3_set_timing_register_for_vdd(state); + + ret = s5c73m3_read_fw_version(state); + + s5c73m3_set_fw_file_version(state); + + return ret; +} + +static int s5c73m3_rom_boot(struct s5c73m3 *state, bool load_fw) +{ + static const u32 boot_regs[][2] = { + { 0x3100010c, 0x0044 }, + { 0x31000108, 0x000d }, + { 0x31000304, 0x0001 }, + { 0x00010000, 0x5800 }, + { 0x00010002, 0x0002 }, + { 0x31000000, 0x0001 }, + { 0x30100014, 0x1b85 }, + { 0x30100010, 0x230c } + }; + struct v4l2_subdev *sd = &state->sensor_sd; + int i, ret; + + /* Run ARM MCU */ + ret = s5c73m3_write(state, 0x30000004, 0xffff); + if (ret < 0) + return ret; + usleep_range(400, 450); + + /* Check booting status */ + ret = s5c73m3_system_status_wait(state, 0x0c, 100, 4); + if (ret < 0) { + v4l2_err(sd, "Booting failed: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(boot_regs); i++) { + ret = s5c73m3_write(state, boot_regs[i][0], boot_regs[i][1]); + if (ret < 0) + return ret; + } + msleep(200); + + /* Check the binary read status */ + ret = s5c73m3_system_status_wait(state, 0x230e, 1000, 150); + if (ret < 0) { + v4l2_err(sd, "Binary read failed: %d\n", ret); + return ret; + } + + /* ARM reset */ + ret = s5c73m3_write(state, 0x30000004, 0xfffd); + if (ret < 0) + return ret; + /* Remap */ + ret = s5c73m3_write(state, 0x301000a4, 0x0183); + if (ret < 0) + return ret; + /* MCU re-start */ + ret = s5c73m3_write(state, 0x30000004, 0xffff); + if (ret < 0) + return ret; + + state->isp_ready = 1; + + return s5c73m3_read_fw_version(state); +} + +static int s5c73m3_isp_init(struct s5c73m3 *state) +{ + int ret; + + state->i2c_read_address = 0; + state->i2c_write_address = 0; + + ret = s5c73m3_i2c_write(state->i2c_client, AHB_MSB_ADDR_PTR, 0x3310); + if (ret < 0) + return ret; + + if (boot_from_rom) + return s5c73m3_rom_boot(state, true); + else + return s5c73m3_spi_boot(state, true); +} + +static const struct s5c73m3_frame_size *s5c73m3_find_frame_size( + struct v4l2_mbus_framefmt *fmt, + enum s5c73m3_resolution_types idx) +{ + const struct s5c73m3_frame_size *fs; + const struct s5c73m3_frame_size *best_fs; + int best_dist = INT_MAX; + int i; + + fs = s5c73m3_resolutions[idx]; + best_fs = NULL; + for (i = 0; i < s5c73m3_resolutions_len[idx]; ++i) { + int dist = abs(fs->width - fmt->width) + + abs(fs->height - fmt->height); + if (dist < best_dist) { + best_dist = dist; + best_fs = fs; + } + ++fs; + } + + return best_fs; +} + +static void s5c73m3_oif_try_format(struct s5c73m3 *state, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt, + const struct s5c73m3_frame_size **fs) +{ + struct v4l2_subdev *sd = &state->sensor_sd; + u32 code; + + switch (fmt->pad) { + case OIF_ISP_PAD: + *fs = s5c73m3_find_frame_size(&fmt->format, RES_ISP); + code = S5C73M3_ISP_FMT; + break; + case OIF_JPEG_PAD: + *fs = s5c73m3_find_frame_size(&fmt->format, RES_JPEG); + code = S5C73M3_JPEG_FMT; + break; + case OIF_SOURCE_PAD: + default: + if (fmt->format.code == S5C73M3_JPEG_FMT) + code = S5C73M3_JPEG_FMT; + else + code = S5C73M3_ISP_FMT; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + *fs = state->oif_pix_size[RES_ISP]; + else + *fs = s5c73m3_find_frame_size( + v4l2_subdev_get_try_format(sd, cfg, + OIF_ISP_PAD), + RES_ISP); + break; + } + + s5c73m3_fill_mbus_fmt(&fmt->format, *fs, code); +} + +static void s5c73m3_try_format(struct s5c73m3 *state, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt, + const struct s5c73m3_frame_size **fs) +{ + u32 code; + + if (fmt->pad == S5C73M3_ISP_PAD) { + *fs = s5c73m3_find_frame_size(&fmt->format, RES_ISP); + code = S5C73M3_ISP_FMT; + } else { + *fs = s5c73m3_find_frame_size(&fmt->format, RES_JPEG); + code = S5C73M3_JPEG_FMT; + } + + s5c73m3_fill_mbus_fmt(&fmt->format, *fs, code); +} + +static int s5c73m3_oif_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + + if (fi->pad != OIF_SOURCE_PAD) + return -EINVAL; + + mutex_lock(&state->lock); + fi->interval = state->fiv->interval; + mutex_unlock(&state->lock); + + return 0; +} + +static int __s5c73m3_set_frame_interval(struct s5c73m3 *state, + struct v4l2_subdev_frame_interval *fi) +{ + const struct s5c73m3_frame_size *prev_size = + state->sensor_pix_size[RES_ISP]; + const struct s5c73m3_interval *fiv = &s5c73m3_intervals[0]; + unsigned int ret, min_err = UINT_MAX; + unsigned int i, fr_time; + + if (fi->interval.denominator == 0) + return -EINVAL; + + fr_time = fi->interval.numerator * 1000 / fi->interval.denominator; + + for (i = 0; i < ARRAY_SIZE(s5c73m3_intervals); i++) { + const struct s5c73m3_interval *iv = &s5c73m3_intervals[i]; + + if (prev_size->width > iv->size.width || + prev_size->height > iv->size.height) + continue; + + ret = abs(iv->interval.numerator / 1000 - fr_time); + if (ret < min_err) { + fiv = iv; + min_err = ret; + } + } + state->fiv = fiv; + + v4l2_dbg(1, s5c73m3_dbg, &state->sensor_sd, + "Changed frame interval to %u us\n", fiv->interval.numerator); + return 0; +} + +static int s5c73m3_oif_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + int ret; + + if (fi->pad != OIF_SOURCE_PAD) + return -EINVAL; + + v4l2_dbg(1, s5c73m3_dbg, sd, "Setting %d/%d frame interval\n", + fi->interval.numerator, fi->interval.denominator); + + mutex_lock(&state->lock); + + ret = __s5c73m3_set_frame_interval(state, fi); + if (!ret) { + if (state->streaming) + ret = s5c73m3_set_frame_rate(state); + else + state->apply_fiv = 1; + } + mutex_unlock(&state->lock); + return ret; +} + +static int s5c73m3_oif_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + const struct s5c73m3_interval *fi; + int ret = 0; + + if (fie->pad != OIF_SOURCE_PAD) + return -EINVAL; + if (fie->index >= ARRAY_SIZE(s5c73m3_intervals)) + return -EINVAL; + + mutex_lock(&state->lock); + fi = &s5c73m3_intervals[fie->index]; + if (fie->width > fi->size.width || fie->height > fi->size.height) + ret = -EINVAL; + else + fie->interval = fi->interval; + mutex_unlock(&state->lock); + + return ret; +} + +static int s5c73m3_oif_get_pad_code(int pad, int index) +{ + if (pad == OIF_SOURCE_PAD) { + if (index > 1) + return -EINVAL; + return (index == 0) ? S5C73M3_ISP_FMT : S5C73M3_JPEG_FMT; + } + + if (index > 0) + return -EINVAL; + + return (pad == OIF_ISP_PAD) ? S5C73M3_ISP_FMT : S5C73M3_JPEG_FMT; +} + +static int s5c73m3_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct s5c73m3 *state = sensor_sd_to_s5c73m3(sd); + const struct s5c73m3_frame_size *fs; + u32 code; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + return 0; + } + + mutex_lock(&state->lock); + + switch (fmt->pad) { + case S5C73M3_ISP_PAD: + code = S5C73M3_ISP_FMT; + fs = state->sensor_pix_size[RES_ISP]; + break; + case S5C73M3_JPEG_PAD: + code = S5C73M3_JPEG_FMT; + fs = state->sensor_pix_size[RES_JPEG]; + break; + default: + mutex_unlock(&state->lock); + return -EINVAL; + } + s5c73m3_fill_mbus_fmt(&fmt->format, fs, code); + + mutex_unlock(&state->lock); + return 0; +} + +static int s5c73m3_oif_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + const struct s5c73m3_frame_size *fs; + u32 code; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + return 0; + } + + mutex_lock(&state->lock); + + switch (fmt->pad) { + case OIF_ISP_PAD: + code = S5C73M3_ISP_FMT; + fs = state->oif_pix_size[RES_ISP]; + break; + case OIF_JPEG_PAD: + code = S5C73M3_JPEG_FMT; + fs = state->oif_pix_size[RES_JPEG]; + break; + case OIF_SOURCE_PAD: + code = state->mbus_code; + fs = state->oif_pix_size[RES_ISP]; + break; + default: + mutex_unlock(&state->lock); + return -EINVAL; + } + s5c73m3_fill_mbus_fmt(&fmt->format, fs, code); + + mutex_unlock(&state->lock); + return 0; +} + +static int s5c73m3_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + const struct s5c73m3_frame_size *frame_size = NULL; + struct s5c73m3 *state = sensor_sd_to_s5c73m3(sd); + struct v4l2_mbus_framefmt *mf; + int ret = 0; + + mutex_lock(&state->lock); + + s5c73m3_try_format(state, cfg, fmt, &frame_size); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *mf = fmt->format; + } else { + switch (fmt->pad) { + case S5C73M3_ISP_PAD: + state->sensor_pix_size[RES_ISP] = frame_size; + break; + case S5C73M3_JPEG_PAD: + state->sensor_pix_size[RES_JPEG] = frame_size; + break; + default: + ret = -EBUSY; + } + + if (state->streaming) + ret = -EBUSY; + else + state->apply_fmt = 1; + } + + mutex_unlock(&state->lock); + + return ret; +} + +static int s5c73m3_oif_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + const struct s5c73m3_frame_size *frame_size = NULL; + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + struct v4l2_mbus_framefmt *mf; + int ret = 0; + + mutex_lock(&state->lock); + + s5c73m3_oif_try_format(state, cfg, fmt, &frame_size); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *mf = fmt->format; + if (fmt->pad == OIF_ISP_PAD) { + mf = v4l2_subdev_get_try_format(sd, cfg, OIF_SOURCE_PAD); + mf->width = fmt->format.width; + mf->height = fmt->format.height; + } + } else { + switch (fmt->pad) { + case OIF_ISP_PAD: + state->oif_pix_size[RES_ISP] = frame_size; + break; + case OIF_JPEG_PAD: + state->oif_pix_size[RES_JPEG] = frame_size; + break; + case OIF_SOURCE_PAD: + state->mbus_code = fmt->format.code; + break; + default: + ret = -EBUSY; + } + + if (state->streaming) + ret = -EBUSY; + else + state->apply_fmt = 1; + } + + mutex_unlock(&state->lock); + + return ret; +} + +static int s5c73m3_oif_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + int i; + + if (pad != OIF_SOURCE_PAD || fd == NULL) + return -EINVAL; + + mutex_lock(&state->lock); + fd->num_entries = 2; + for (i = 0; i < fd->num_entries; i++) + fd->entry[i] = state->frame_desc.entry[i]; + mutex_unlock(&state->lock); + + return 0; +} + +static int s5c73m3_oif_set_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + struct v4l2_mbus_frame_desc *frame_desc = &state->frame_desc; + int i; + + if (pad != OIF_SOURCE_PAD || fd == NULL) + return -EINVAL; + + fd->entry[0].length = 10 * SZ_1M; + fd->entry[1].length = max_t(u32, fd->entry[1].length, + S5C73M3_EMBEDDED_DATA_MAXLEN); + fd->num_entries = 2; + + mutex_lock(&state->lock); + for (i = 0; i < fd->num_entries; i++) + frame_desc->entry[i] = fd->entry[i]; + mutex_unlock(&state->lock); + + return 0; +} + +static int s5c73m3_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const int codes[] = { + [S5C73M3_ISP_PAD] = S5C73M3_ISP_FMT, + [S5C73M3_JPEG_PAD] = S5C73M3_JPEG_FMT}; + + if (code->index > 0 || code->pad >= S5C73M3_NUM_PADS) + return -EINVAL; + + code->code = codes[code->pad]; + + return 0; +} + +static int s5c73m3_oif_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + int ret; + + ret = s5c73m3_oif_get_pad_code(code->pad, code->index); + if (ret < 0) + return ret; + + code->code = ret; + + return 0; +} + +static int s5c73m3_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + int idx; + + if (fse->pad == S5C73M3_ISP_PAD) { + if (fse->code != S5C73M3_ISP_FMT) + return -EINVAL; + idx = RES_ISP; + } else{ + if (fse->code != S5C73M3_JPEG_FMT) + return -EINVAL; + idx = RES_JPEG; + } + + if (fse->index >= s5c73m3_resolutions_len[idx]) + return -EINVAL; + + fse->min_width = s5c73m3_resolutions[idx][fse->index].width; + fse->max_width = fse->min_width; + fse->max_height = s5c73m3_resolutions[idx][fse->index].height; + fse->min_height = fse->max_height; + + return 0; +} + +static int s5c73m3_oif_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + int idx; + + if (fse->pad == OIF_SOURCE_PAD) { + if (fse->index > 0) + return -EINVAL; + + switch (fse->code) { + case S5C73M3_JPEG_FMT: + case S5C73M3_ISP_FMT: { + unsigned w, h; + + if (fse->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_get_try_format(sd, cfg, + OIF_ISP_PAD); + + w = mf->width; + h = mf->height; + } else { + const struct s5c73m3_frame_size *fs; + + fs = state->oif_pix_size[RES_ISP]; + w = fs->width; + h = fs->height; + } + fse->max_width = fse->min_width = w; + fse->max_height = fse->min_height = h; + return 0; + } + default: + return -EINVAL; + } + } + + if (fse->code != s5c73m3_oif_get_pad_code(fse->pad, 0)) + return -EINVAL; + + if (fse->pad == OIF_JPEG_PAD) + idx = RES_JPEG; + else + idx = RES_ISP; + + if (fse->index >= s5c73m3_resolutions_len[idx]) + return -EINVAL; + + fse->min_width = s5c73m3_resolutions[idx][fse->index].width; + fse->max_width = fse->min_width; + fse->max_height = s5c73m3_resolutions[idx][fse->index].height; + fse->min_height = fse->max_height; + + return 0; +} + +static int s5c73m3_oif_log_status(struct v4l2_subdev *sd) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + + v4l2_ctrl_handler_log_status(sd->ctrl_handler, sd->name); + + v4l2_info(sd, "power: %d, apply_fmt: %d\n", state->power, + state->apply_fmt); + + return 0; +} + +static int s5c73m3_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_get_try_format(sd, fh->pad, S5C73M3_ISP_PAD); + s5c73m3_fill_mbus_fmt(mf, &s5c73m3_isp_resolutions[1], + S5C73M3_ISP_FMT); + + mf = v4l2_subdev_get_try_format(sd, fh->pad, S5C73M3_JPEG_PAD); + s5c73m3_fill_mbus_fmt(mf, &s5c73m3_jpeg_resolutions[1], + S5C73M3_JPEG_FMT); + + return 0; +} + +static int s5c73m3_oif_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_get_try_format(sd, fh->pad, OIF_ISP_PAD); + s5c73m3_fill_mbus_fmt(mf, &s5c73m3_isp_resolutions[1], + S5C73M3_ISP_FMT); + + mf = v4l2_subdev_get_try_format(sd, fh->pad, OIF_JPEG_PAD); + s5c73m3_fill_mbus_fmt(mf, &s5c73m3_jpeg_resolutions[1], + S5C73M3_JPEG_FMT); + + mf = v4l2_subdev_get_try_format(sd, fh->pad, OIF_SOURCE_PAD); + s5c73m3_fill_mbus_fmt(mf, &s5c73m3_isp_resolutions[1], + S5C73M3_ISP_FMT); + return 0; +} + +static int s5c73m3_gpio_set_value(struct s5c73m3 *priv, int id, u32 val) +{ + if (!gpio_is_valid(priv->gpio[id].gpio)) + return 0; + gpio_set_value(priv->gpio[id].gpio, !!val); + return 1; +} + +static int s5c73m3_gpio_assert(struct s5c73m3 *priv, int id) +{ + return s5c73m3_gpio_set_value(priv, id, priv->gpio[id].level); +} + +static int s5c73m3_gpio_deassert(struct s5c73m3 *priv, int id) +{ + return s5c73m3_gpio_set_value(priv, id, !priv->gpio[id].level); +} + +static int __s5c73m3_power_on(struct s5c73m3 *state) +{ + int i, ret; + + for (i = 0; i < S5C73M3_MAX_SUPPLIES; i++) { + ret = regulator_enable(state->supplies[i].consumer); + if (ret) + goto err_reg_dis; + } + + ret = clk_set_rate(state->clock, state->mclk_frequency); + if (ret < 0) + goto err_reg_dis; + + ret = clk_prepare_enable(state->clock); + if (ret < 0) + goto err_reg_dis; + + v4l2_dbg(1, s5c73m3_dbg, &state->oif_sd, "clock frequency: %ld\n", + clk_get_rate(state->clock)); + + s5c73m3_gpio_deassert(state, STBY); + usleep_range(100, 200); + + s5c73m3_gpio_deassert(state, RSET); + usleep_range(50, 100); + + return 0; + +err_reg_dis: + for (--i; i >= 0; i--) + regulator_disable(state->supplies[i].consumer); + return ret; +} + +static int __s5c73m3_power_off(struct s5c73m3 *state) +{ + int i, ret; + + if (s5c73m3_gpio_assert(state, RSET)) + usleep_range(10, 50); + + if (s5c73m3_gpio_assert(state, STBY)) + usleep_range(100, 200); + + clk_disable_unprepare(state->clock); + + state->streaming = 0; + state->isp_ready = 0; + + for (i = S5C73M3_MAX_SUPPLIES - 1; i >= 0; i--) { + ret = regulator_disable(state->supplies[i].consumer); + if (ret) + goto err; + } + + return 0; +err: + for (++i; i < S5C73M3_MAX_SUPPLIES; i++) { + int r = regulator_enable(state->supplies[i].consumer); + if (r < 0) + v4l2_err(&state->oif_sd, "Failed to re-enable %s: %d\n", + state->supplies[i].supply, r); + } + + clk_prepare_enable(state->clock); + return ret; +} + +static int s5c73m3_oif_set_power(struct v4l2_subdev *sd, int on) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + int ret = 0; + + mutex_lock(&state->lock); + + if (on && !state->power) { + ret = __s5c73m3_power_on(state); + if (!ret) + ret = s5c73m3_isp_init(state); + if (!ret) { + state->apply_fiv = 1; + state->apply_fmt = 1; + } + } else if (state->power == !on) { + ret = s5c73m3_set_af_softlanding(state); + if (!ret) + ret = __s5c73m3_power_off(state); + else + v4l2_err(sd, "Soft landing lens failed\n"); + } + if (!ret) + state->power += on ? 1 : -1; + + v4l2_dbg(1, s5c73m3_dbg, sd, "%s: power: %d\n", + __func__, state->power); + + mutex_unlock(&state->lock); + return ret; +} + +static int s5c73m3_oif_registered(struct v4l2_subdev *sd) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + int ret; + + ret = v4l2_device_register_subdev(sd->v4l2_dev, &state->sensor_sd); + if (ret) { + v4l2_err(sd->v4l2_dev, "Failed to register %s\n", + state->oif_sd.name); + return ret; + } + + ret = media_create_pad_link(&state->sensor_sd.entity, + S5C73M3_ISP_PAD, &state->oif_sd.entity, OIF_ISP_PAD, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + + ret = media_create_pad_link(&state->sensor_sd.entity, + S5C73M3_JPEG_PAD, &state->oif_sd.entity, OIF_JPEG_PAD, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + + return ret; +} + +static void s5c73m3_oif_unregistered(struct v4l2_subdev *sd) +{ + struct s5c73m3 *state = oif_sd_to_s5c73m3(sd); + v4l2_device_unregister_subdev(&state->sensor_sd); +} + +static const struct v4l2_subdev_internal_ops s5c73m3_internal_ops = { + .open = s5c73m3_open, +}; + +static const struct v4l2_subdev_pad_ops s5c73m3_pad_ops = { + .enum_mbus_code = s5c73m3_enum_mbus_code, + .enum_frame_size = s5c73m3_enum_frame_size, + .get_fmt = s5c73m3_get_fmt, + .set_fmt = s5c73m3_set_fmt, +}; + +static const struct v4l2_subdev_ops s5c73m3_subdev_ops = { + .pad = &s5c73m3_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops oif_internal_ops = { + .registered = s5c73m3_oif_registered, + .unregistered = s5c73m3_oif_unregistered, + .open = s5c73m3_oif_open, +}; + +static const struct v4l2_subdev_pad_ops s5c73m3_oif_pad_ops = { + .enum_mbus_code = s5c73m3_oif_enum_mbus_code, + .enum_frame_size = s5c73m3_oif_enum_frame_size, + .enum_frame_interval = s5c73m3_oif_enum_frame_interval, + .get_fmt = s5c73m3_oif_get_fmt, + .set_fmt = s5c73m3_oif_set_fmt, + .get_frame_desc = s5c73m3_oif_get_frame_desc, + .set_frame_desc = s5c73m3_oif_set_frame_desc, +}; + +static const struct v4l2_subdev_core_ops s5c73m3_oif_core_ops = { + .s_power = s5c73m3_oif_set_power, + .log_status = s5c73m3_oif_log_status, +}; + +static const struct v4l2_subdev_video_ops s5c73m3_oif_video_ops = { + .s_stream = s5c73m3_oif_s_stream, + .g_frame_interval = s5c73m3_oif_g_frame_interval, + .s_frame_interval = s5c73m3_oif_s_frame_interval, +}; + +static const struct v4l2_subdev_ops oif_subdev_ops = { + .core = &s5c73m3_oif_core_ops, + .pad = &s5c73m3_oif_pad_ops, + .video = &s5c73m3_oif_video_ops, +}; + +static int s5c73m3_configure_gpios(struct s5c73m3 *state) +{ + static const char * const gpio_names[] = { + "S5C73M3_STBY", "S5C73M3_RST" + }; + struct i2c_client *c = state->i2c_client; + struct s5c73m3_gpio *g = state->gpio; + int ret, i; + + for (i = 0; i < GPIO_NUM; ++i) { + unsigned int flags = GPIOF_DIR_OUT; + if (g[i].level) + flags |= GPIOF_INIT_HIGH; + ret = devm_gpio_request_one(&c->dev, g[i].gpio, flags, + gpio_names[i]); + if (ret) { + v4l2_err(c, "failed to request gpio %s\n", + gpio_names[i]); + return ret; + } + } + return 0; +} + +static int s5c73m3_parse_gpios(struct s5c73m3 *state) +{ + static const char * const prop_names[] = { + "standby-gpios", "xshutdown-gpios", + }; + struct device *dev = &state->i2c_client->dev; + struct device_node *node = dev->of_node; + int ret, i; + + for (i = 0; i < GPIO_NUM; ++i) { + enum of_gpio_flags of_flags; + + ret = of_get_named_gpio_flags(node, prop_names[i], + 0, &of_flags); + if (ret < 0) { + dev_err(dev, "failed to parse %s DT property\n", + prop_names[i]); + return -EINVAL; + } + state->gpio[i].gpio = ret; + state->gpio[i].level = !(of_flags & OF_GPIO_ACTIVE_LOW); + } + return 0; +} + +static int s5c73m3_get_platform_data(struct s5c73m3 *state) +{ + struct device *dev = &state->i2c_client->dev; + const struct s5c73m3_platform_data *pdata = dev->platform_data; + struct device_node *node = dev->of_node; + struct device_node *node_ep; + struct v4l2_fwnode_endpoint ep = { .bus_type = 0 }; + int ret; + + if (!node) { + if (!pdata) { + dev_err(dev, "Platform data not specified\n"); + return -EINVAL; + } + + state->mclk_frequency = pdata->mclk_frequency; + state->gpio[STBY] = pdata->gpio_stby; + state->gpio[RSET] = pdata->gpio_reset; + return 0; + } + + state->clock = devm_clk_get(dev, S5C73M3_CLK_NAME); + if (IS_ERR(state->clock)) + return PTR_ERR(state->clock); + + if (of_property_read_u32(node, "clock-frequency", + &state->mclk_frequency)) { + state->mclk_frequency = S5C73M3_DEFAULT_MCLK_FREQ; + dev_info(dev, "using default %u Hz clock frequency\n", + state->mclk_frequency); + } + + ret = s5c73m3_parse_gpios(state); + if (ret < 0) + return -EINVAL; + + node_ep = of_graph_get_next_endpoint(node, NULL); + if (!node_ep) { + dev_warn(dev, "no endpoint defined for node: %pOF\n", node); + return 0; + } + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node_ep), &ep); + of_node_put(node_ep); + if (ret) + return ret; + + if (ep.bus_type != V4L2_MBUS_CSI2_DPHY) { + dev_err(dev, "unsupported bus type\n"); + return -EINVAL; + } + /* + * Number of MIPI CSI-2 data lanes is currently not configurable, + * always a default value of 4 lanes is used. + */ + if (ep.bus.mipi_csi2.num_data_lanes != S5C73M3_MIPI_DATA_LANES) + dev_info(dev, "falling back to 4 MIPI CSI-2 data lanes\n"); + + return 0; +} + +static int s5c73m3_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct v4l2_subdev *sd; + struct v4l2_subdev *oif_sd; + struct s5c73m3 *state; + int ret, i; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->i2c_client = client; + ret = s5c73m3_get_platform_data(state); + if (ret < 0) + return ret; + + mutex_init(&state->lock); + sd = &state->sensor_sd; + oif_sd = &state->oif_sd; + + v4l2_subdev_init(sd, &s5c73m3_subdev_ops); + sd->owner = client->dev.driver->owner; + v4l2_set_subdevdata(sd, state); + strscpy(sd->name, "S5C73M3", sizeof(sd->name)); + + sd->internal_ops = &s5c73m3_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + state->sensor_pads[S5C73M3_JPEG_PAD].flags = MEDIA_PAD_FL_SOURCE; + state->sensor_pads[S5C73M3_ISP_PAD].flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&sd->entity, S5C73M3_NUM_PADS, + state->sensor_pads); + if (ret < 0) + return ret; + + v4l2_i2c_subdev_init(oif_sd, client, &oif_subdev_ops); + /* Static name; NEVER use in new drivers! */ + strscpy(oif_sd->name, "S5C73M3-OIF", sizeof(oif_sd->name)); + + oif_sd->internal_ops = &oif_internal_ops; + oif_sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + state->oif_pads[OIF_ISP_PAD].flags = MEDIA_PAD_FL_SINK; + state->oif_pads[OIF_JPEG_PAD].flags = MEDIA_PAD_FL_SINK; + state->oif_pads[OIF_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE; + oif_sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + + ret = media_entity_pads_init(&oif_sd->entity, OIF_NUM_PADS, + state->oif_pads); + if (ret < 0) + return ret; + + ret = s5c73m3_configure_gpios(state); + if (ret) + goto out_err; + + for (i = 0; i < S5C73M3_MAX_SUPPLIES; i++) + state->supplies[i].supply = s5c73m3_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, S5C73M3_MAX_SUPPLIES, + state->supplies); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + goto out_err; + } + + ret = s5c73m3_init_controls(state); + if (ret) + goto out_err; + + state->sensor_pix_size[RES_ISP] = &s5c73m3_isp_resolutions[1]; + state->sensor_pix_size[RES_JPEG] = &s5c73m3_jpeg_resolutions[1]; + state->oif_pix_size[RES_ISP] = state->sensor_pix_size[RES_ISP]; + state->oif_pix_size[RES_JPEG] = state->sensor_pix_size[RES_JPEG]; + + state->mbus_code = S5C73M3_ISP_FMT; + + state->fiv = &s5c73m3_intervals[S5C73M3_DEFAULT_FRAME_INTERVAL]; + + state->fw_file_version[0] = 'G'; + state->fw_file_version[1] = 'C'; + + ret = s5c73m3_register_spi_driver(state); + if (ret < 0) + goto out_err; + + oif_sd->dev = dev; + + ret = __s5c73m3_power_on(state); + if (ret < 0) + goto out_err1; + + ret = s5c73m3_get_fw_version(state); + __s5c73m3_power_off(state); + + if (ret < 0) { + dev_err(dev, "Device detection failed: %d\n", ret); + goto out_err1; + } + + ret = v4l2_async_register_subdev(oif_sd); + if (ret < 0) + goto out_err1; + + v4l2_info(sd, "%s: completed successfully\n", __func__); + return 0; + +out_err1: + s5c73m3_unregister_spi_driver(state); +out_err: + media_entity_cleanup(&sd->entity); + return ret; +} + +static int s5c73m3_remove(struct i2c_client *client) +{ + struct v4l2_subdev *oif_sd = i2c_get_clientdata(client); + struct s5c73m3 *state = oif_sd_to_s5c73m3(oif_sd); + struct v4l2_subdev *sensor_sd = &state->sensor_sd; + + v4l2_async_unregister_subdev(oif_sd); + + v4l2_ctrl_handler_free(oif_sd->ctrl_handler); + media_entity_cleanup(&oif_sd->entity); + + v4l2_device_unregister_subdev(sensor_sd); + media_entity_cleanup(&sensor_sd->entity); + + s5c73m3_unregister_spi_driver(state); + + return 0; +} + +static const struct i2c_device_id s5c73m3_id[] = { + { DRIVER_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, s5c73m3_id); + +#ifdef CONFIG_OF +static const struct of_device_id s5c73m3_of_match[] = { + { .compatible = "samsung,s5c73m3" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5c73m3_of_match); +#endif + +static struct i2c_driver s5c73m3_i2c_driver = { + .driver = { + .of_match_table = of_match_ptr(s5c73m3_of_match), + .name = DRIVER_NAME, + }, + .probe_new = s5c73m3_probe, + .remove = s5c73m3_remove, + .id_table = s5c73m3_id, +}; + +module_i2c_driver(s5c73m3_i2c_driver); + +MODULE_DESCRIPTION("Samsung S5C73M3 camera driver"); +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c b/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c new file mode 100644 index 000000000..141ad0ba7 --- /dev/null +++ b/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung LSI S5C73M3 8M pixel camera driver + * + * Copyright (C) 2012, Samsung Electronics, Co., Ltd. + * Sylwester Nawrocki <s.nawrocki@samsung.com> + * Andrzej Hajda <a.hajda@samsung.com> + */ + +#include <linux/sizes.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/media.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/videodev2.h> +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-mediabus.h> +#include <media/i2c/s5c73m3.h> + +#include "s5c73m3.h" + +static int s5c73m3_get_af_status(struct s5c73m3 *state, struct v4l2_ctrl *ctrl) +{ + u16 reg = REG_AF_STATUS_UNFOCUSED; + + int ret = s5c73m3_read(state, REG_AF_STATUS, ®); + + switch (reg) { + case REG_CAF_STATUS_FIND_SEARCH_DIR: + case REG_AF_STATUS_FOCUSING: + case REG_CAF_STATUS_FOCUSING: + ctrl->val = V4L2_AUTO_FOCUS_STATUS_BUSY; + break; + case REG_CAF_STATUS_FOCUSED: + case REG_AF_STATUS_FOCUSED: + ctrl->val = V4L2_AUTO_FOCUS_STATUS_REACHED; + break; + default: + v4l2_info(&state->sensor_sd, "Unknown AF status %#x\n", reg); + fallthrough; + case REG_CAF_STATUS_UNFOCUSED: + case REG_AF_STATUS_UNFOCUSED: + case REG_AF_STATUS_INVALID: + ctrl->val = V4L2_AUTO_FOCUS_STATUS_FAILED; + break; + } + + return ret; +} + +static int s5c73m3_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sensor_sd(ctrl); + struct s5c73m3 *state = sensor_sd_to_s5c73m3(sd); + int ret; + + if (state->power == 0) + return -EBUSY; + + switch (ctrl->id) { + case V4L2_CID_FOCUS_AUTO: + ret = s5c73m3_get_af_status(state, state->ctrls.af_status); + if (ret) + return ret; + break; + } + + return 0; +} + +static int s5c73m3_set_colorfx(struct s5c73m3 *state, int val) +{ + static const unsigned short colorfx[][2] = { + { V4L2_COLORFX_NONE, COMM_IMAGE_EFFECT_NONE }, + { V4L2_COLORFX_BW, COMM_IMAGE_EFFECT_MONO }, + { V4L2_COLORFX_SEPIA, COMM_IMAGE_EFFECT_SEPIA }, + { V4L2_COLORFX_NEGATIVE, COMM_IMAGE_EFFECT_NEGATIVE }, + { V4L2_COLORFX_AQUA, COMM_IMAGE_EFFECT_AQUA }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(colorfx); i++) { + if (colorfx[i][0] != val) + continue; + + v4l2_dbg(1, s5c73m3_dbg, &state->sensor_sd, + "Setting %s color effect\n", + v4l2_ctrl_get_menu(state->ctrls.colorfx->id)[i]); + + return s5c73m3_isp_command(state, COMM_IMAGE_EFFECT, + colorfx[i][1]); + } + return -EINVAL; +} + +/* Set exposure metering/exposure bias */ +static int s5c73m3_set_exposure(struct s5c73m3 *state, int auto_exp) +{ + struct v4l2_subdev *sd = &state->sensor_sd; + struct s5c73m3_ctrls *ctrls = &state->ctrls; + int ret = 0; + + if (ctrls->exposure_metering->is_new) { + u16 metering; + + switch (ctrls->exposure_metering->val) { + case V4L2_EXPOSURE_METERING_CENTER_WEIGHTED: + metering = COMM_METERING_CENTER; + break; + case V4L2_EXPOSURE_METERING_SPOT: + metering = COMM_METERING_SPOT; + break; + default: + metering = COMM_METERING_AVERAGE; + break; + } + + ret = s5c73m3_isp_command(state, COMM_METERING, metering); + } + + if (!ret && ctrls->exposure_bias->is_new) { + u16 exp_bias = ctrls->exposure_bias->val; + ret = s5c73m3_isp_command(state, COMM_EV, exp_bias); + } + + v4l2_dbg(1, s5c73m3_dbg, sd, + "%s: exposure bias: %#x, metering: %#x (%d)\n", __func__, + ctrls->exposure_bias->val, ctrls->exposure_metering->val, ret); + + return ret; +} + +static int s5c73m3_set_white_balance(struct s5c73m3 *state, int val) +{ + static const unsigned short wb[][2] = { + { V4L2_WHITE_BALANCE_INCANDESCENT, COMM_AWB_MODE_INCANDESCENT}, + { V4L2_WHITE_BALANCE_FLUORESCENT, COMM_AWB_MODE_FLUORESCENT1}, + { V4L2_WHITE_BALANCE_FLUORESCENT_H, COMM_AWB_MODE_FLUORESCENT2}, + { V4L2_WHITE_BALANCE_CLOUDY, COMM_AWB_MODE_CLOUDY}, + { V4L2_WHITE_BALANCE_DAYLIGHT, COMM_AWB_MODE_DAYLIGHT}, + { V4L2_WHITE_BALANCE_AUTO, COMM_AWB_MODE_AUTO}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(wb); i++) { + if (wb[i][0] != val) + continue; + + v4l2_dbg(1, s5c73m3_dbg, &state->sensor_sd, + "Setting white balance to: %s\n", + v4l2_ctrl_get_menu(state->ctrls.auto_wb->id)[i]); + + return s5c73m3_isp_command(state, COMM_AWB_MODE, wb[i][1]); + } + + return -EINVAL; +} + +static int s5c73m3_af_run(struct s5c73m3 *state, bool on) +{ + struct s5c73m3_ctrls *c = &state->ctrls; + + if (!on) + return s5c73m3_isp_command(state, COMM_AF_CON, + COMM_AF_CON_STOP); + + if (c->focus_auto->val) + return s5c73m3_isp_command(state, COMM_AF_MODE, + COMM_AF_MODE_PREVIEW_CAF_START); + + return s5c73m3_isp_command(state, COMM_AF_CON, COMM_AF_CON_START); +} + +static int s5c73m3_3a_lock(struct s5c73m3 *state, struct v4l2_ctrl *ctrl) +{ + bool awb_lock = ctrl->val & V4L2_LOCK_WHITE_BALANCE; + bool ae_lock = ctrl->val & V4L2_LOCK_EXPOSURE; + bool af_lock = ctrl->val & V4L2_LOCK_FOCUS; + int ret = 0; + + if ((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_EXPOSURE) { + ret = s5c73m3_isp_command(state, COMM_AE_CON, + ae_lock ? COMM_AE_STOP : COMM_AE_START); + if (ret) + return ret; + } + + if (((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_WHITE_BALANCE) + && state->ctrls.auto_wb->val) { + ret = s5c73m3_isp_command(state, COMM_AWB_CON, + awb_lock ? COMM_AWB_STOP : COMM_AWB_START); + if (ret) + return ret; + } + + if ((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_FOCUS) + ret = s5c73m3_af_run(state, !af_lock); + + return ret; +} + +static int s5c73m3_set_auto_focus(struct s5c73m3 *state, int caf) +{ + struct s5c73m3_ctrls *c = &state->ctrls; + int ret = 1; + + if (c->af_distance->is_new) { + u16 mode = (c->af_distance->val == V4L2_AUTO_FOCUS_RANGE_MACRO) + ? COMM_AF_MODE_MACRO : COMM_AF_MODE_NORMAL; + ret = s5c73m3_isp_command(state, COMM_AF_MODE, mode); + if (ret != 0) + return ret; + } + + if (!ret || (c->focus_auto->is_new && c->focus_auto->val) || + c->af_start->is_new) + ret = s5c73m3_af_run(state, 1); + else if ((c->focus_auto->is_new && !c->focus_auto->val) || + c->af_stop->is_new) + ret = s5c73m3_af_run(state, 0); + else + ret = 0; + + return ret; +} + +static int s5c73m3_set_contrast(struct s5c73m3 *state, int val) +{ + u16 reg = (val < 0) ? -val + 2 : val; + return s5c73m3_isp_command(state, COMM_CONTRAST, reg); +} + +static int s5c73m3_set_saturation(struct s5c73m3 *state, int val) +{ + u16 reg = (val < 0) ? -val + 2 : val; + return s5c73m3_isp_command(state, COMM_SATURATION, reg); +} + +static int s5c73m3_set_sharpness(struct s5c73m3 *state, int val) +{ + u16 reg = (val < 0) ? -val + 2 : val; + return s5c73m3_isp_command(state, COMM_SHARPNESS, reg); +} + +static int s5c73m3_set_iso(struct s5c73m3 *state, int val) +{ + u32 iso; + + if (val == V4L2_ISO_SENSITIVITY_MANUAL) + iso = state->ctrls.iso->val + 1; + else + iso = 0; + + return s5c73m3_isp_command(state, COMM_ISO, iso); +} + +static int s5c73m3_set_stabilization(struct s5c73m3 *state, int val) +{ + struct v4l2_subdev *sd = &state->sensor_sd; + + v4l2_dbg(1, s5c73m3_dbg, sd, "Image stabilization: %d\n", val); + + return s5c73m3_isp_command(state, COMM_FRAME_RATE, val ? + COMM_FRAME_RATE_ANTI_SHAKE : COMM_FRAME_RATE_AUTO_SET); +} + +static int s5c73m3_set_jpeg_quality(struct s5c73m3 *state, int quality) +{ + int reg; + + if (quality <= 65) + reg = COMM_IMAGE_QUALITY_NORMAL; + else if (quality <= 75) + reg = COMM_IMAGE_QUALITY_FINE; + else + reg = COMM_IMAGE_QUALITY_SUPERFINE; + + return s5c73m3_isp_command(state, COMM_IMAGE_QUALITY, reg); +} + +static int s5c73m3_set_scene_program(struct s5c73m3 *state, int val) +{ + static const unsigned short scene_lookup[] = { + COMM_SCENE_MODE_NONE, /* V4L2_SCENE_MODE_NONE */ + COMM_SCENE_MODE_AGAINST_LIGHT,/* V4L2_SCENE_MODE_BACKLIGHT */ + COMM_SCENE_MODE_BEACH, /* V4L2_SCENE_MODE_BEACH_SNOW */ + COMM_SCENE_MODE_CANDLE, /* V4L2_SCENE_MODE_CANDLE_LIGHT */ + COMM_SCENE_MODE_DAWN, /* V4L2_SCENE_MODE_DAWN_DUSK */ + COMM_SCENE_MODE_FALL, /* V4L2_SCENE_MODE_FALL_COLORS */ + COMM_SCENE_MODE_FIRE, /* V4L2_SCENE_MODE_FIREWORKS */ + COMM_SCENE_MODE_LANDSCAPE, /* V4L2_SCENE_MODE_LANDSCAPE */ + COMM_SCENE_MODE_NIGHT, /* V4L2_SCENE_MODE_NIGHT */ + COMM_SCENE_MODE_INDOOR, /* V4L2_SCENE_MODE_PARTY_INDOOR */ + COMM_SCENE_MODE_PORTRAIT, /* V4L2_SCENE_MODE_PORTRAIT */ + COMM_SCENE_MODE_SPORTS, /* V4L2_SCENE_MODE_SPORTS */ + COMM_SCENE_MODE_SUNSET, /* V4L2_SCENE_MODE_SUNSET */ + COMM_SCENE_MODE_TEXT, /* V4L2_SCENE_MODE_TEXT */ + }; + + v4l2_dbg(1, s5c73m3_dbg, &state->sensor_sd, "Setting %s scene mode\n", + v4l2_ctrl_get_menu(state->ctrls.scene_mode->id)[val]); + + return s5c73m3_isp_command(state, COMM_SCENE_MODE, scene_lookup[val]); +} + +static int s5c73m3_set_power_line_freq(struct s5c73m3 *state, int val) +{ + unsigned int pwr_line_freq = COMM_FLICKER_NONE; + + switch (val) { + case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: + pwr_line_freq = COMM_FLICKER_NONE; + break; + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: + pwr_line_freq = COMM_FLICKER_AUTO_50HZ; + break; + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: + pwr_line_freq = COMM_FLICKER_AUTO_60HZ; + break; + default: + case V4L2_CID_POWER_LINE_FREQUENCY_AUTO: + pwr_line_freq = COMM_FLICKER_NONE; + } + + return s5c73m3_isp_command(state, COMM_FLICKER_MODE, pwr_line_freq); +} + +static int s5c73m3_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sensor_sd(ctrl); + struct s5c73m3 *state = sensor_sd_to_s5c73m3(sd); + int ret = 0; + + v4l2_dbg(1, s5c73m3_dbg, sd, "set_ctrl: %s, value: %d\n", + ctrl->name, ctrl->val); + + mutex_lock(&state->lock); + /* + * If the device is not powered up by the host driver do + * not apply any controls to H/W at this time. Instead + * the controls will be restored right after power-up. + */ + if (state->power == 0) + goto unlock; + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) { + ret = -EINVAL; + goto unlock; + } + + switch (ctrl->id) { + case V4L2_CID_3A_LOCK: + ret = s5c73m3_3a_lock(state, ctrl); + break; + + case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE: + ret = s5c73m3_set_white_balance(state, ctrl->val); + break; + + case V4L2_CID_CONTRAST: + ret = s5c73m3_set_contrast(state, ctrl->val); + break; + + case V4L2_CID_COLORFX: + ret = s5c73m3_set_colorfx(state, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + ret = s5c73m3_set_exposure(state, ctrl->val); + break; + + case V4L2_CID_FOCUS_AUTO: + ret = s5c73m3_set_auto_focus(state, ctrl->val); + break; + + case V4L2_CID_IMAGE_STABILIZATION: + ret = s5c73m3_set_stabilization(state, ctrl->val); + break; + + case V4L2_CID_ISO_SENSITIVITY: + ret = s5c73m3_set_iso(state, ctrl->val); + break; + + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + ret = s5c73m3_set_jpeg_quality(state, ctrl->val); + break; + + case V4L2_CID_POWER_LINE_FREQUENCY: + ret = s5c73m3_set_power_line_freq(state, ctrl->val); + break; + + case V4L2_CID_SATURATION: + ret = s5c73m3_set_saturation(state, ctrl->val); + break; + + case V4L2_CID_SCENE_MODE: + ret = s5c73m3_set_scene_program(state, ctrl->val); + break; + + case V4L2_CID_SHARPNESS: + ret = s5c73m3_set_sharpness(state, ctrl->val); + break; + + case V4L2_CID_WIDE_DYNAMIC_RANGE: + ret = s5c73m3_isp_command(state, COMM_WDR, !!ctrl->val); + break; + + case V4L2_CID_ZOOM_ABSOLUTE: + ret = s5c73m3_isp_command(state, COMM_ZOOM_STEP, ctrl->val); + break; + } +unlock: + mutex_unlock(&state->lock); + return ret; +} + +static const struct v4l2_ctrl_ops s5c73m3_ctrl_ops = { + .g_volatile_ctrl = s5c73m3_g_volatile_ctrl, + .s_ctrl = s5c73m3_s_ctrl, +}; + +/* Supported manual ISO values */ +static const s64 iso_qmenu[] = { + /* COMM_ISO: 0x0001...0x0004 */ + 100, 200, 400, 800, +}; + +/* Supported exposure bias values (-2.0EV...+2.0EV) */ +static const s64 ev_bias_qmenu[] = { + /* COMM_EV: 0x0000...0x0008 */ + -2000, -1500, -1000, -500, 0, 500, 1000, 1500, 2000 +}; + +int s5c73m3_init_controls(struct s5c73m3 *state) +{ + const struct v4l2_ctrl_ops *ops = &s5c73m3_ctrl_ops; + struct s5c73m3_ctrls *ctrls = &state->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + + int ret = v4l2_ctrl_handler_init(hdl, 22); + if (ret) + return ret; + + /* White balance */ + ctrls->auto_wb = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, + 9, ~0x15e, V4L2_WHITE_BALANCE_AUTO); + + /* Exposure (only automatic exposure) */ + ctrls->auto_exposure = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, 0, ~0x01, V4L2_EXPOSURE_AUTO); + + ctrls->exposure_bias = v4l2_ctrl_new_int_menu(hdl, ops, + V4L2_CID_AUTO_EXPOSURE_BIAS, + ARRAY_SIZE(ev_bias_qmenu) - 1, + ARRAY_SIZE(ev_bias_qmenu)/2 - 1, + ev_bias_qmenu); + + ctrls->exposure_metering = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_METERING, + 2, ~0x7, V4L2_EXPOSURE_METERING_AVERAGE); + + /* Auto focus */ + ctrls->focus_auto = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_FOCUS_AUTO, 0, 1, 1, 0); + + ctrls->af_start = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_AUTO_FOCUS_START, 0, 1, 1, 0); + + ctrls->af_stop = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_AUTO_FOCUS_STOP, 0, 1, 1, 0); + + ctrls->af_status = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_AUTO_FOCUS_STATUS, 0, + (V4L2_AUTO_FOCUS_STATUS_BUSY | + V4L2_AUTO_FOCUS_STATUS_REACHED | + V4L2_AUTO_FOCUS_STATUS_FAILED), + 0, V4L2_AUTO_FOCUS_STATUS_IDLE); + + ctrls->af_distance = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_AUTO_FOCUS_RANGE, + V4L2_AUTO_FOCUS_RANGE_MACRO, + ~(1 << V4L2_AUTO_FOCUS_RANGE_NORMAL | + 1 << V4L2_AUTO_FOCUS_RANGE_MACRO), + V4L2_AUTO_FOCUS_RANGE_NORMAL); + /* ISO sensitivity */ + ctrls->auto_iso = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_ISO_SENSITIVITY_AUTO, 1, 0, + V4L2_ISO_SENSITIVITY_AUTO); + + ctrls->iso = v4l2_ctrl_new_int_menu(hdl, ops, + V4L2_CID_ISO_SENSITIVITY, ARRAY_SIZE(iso_qmenu) - 1, + ARRAY_SIZE(iso_qmenu)/2 - 1, iso_qmenu); + + ctrls->contrast = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_CONTRAST, -2, 2, 1, 0); + + ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_SATURATION, -2, 2, 1, 0); + + ctrls->sharpness = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_SHARPNESS, -2, 2, 1, 0); + + ctrls->zoom = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_ZOOM_ABSOLUTE, 0, 30, 1, 0); + + ctrls->colorfx = v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_COLORFX, + V4L2_COLORFX_AQUA, ~0x40f, V4L2_COLORFX_NONE); + + ctrls->wdr = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_WIDE_DYNAMIC_RANGE, 0, 1, 1, 0); + + ctrls->stabilization = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_IMAGE_STABILIZATION, 0, 1, 1, 0); + + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO); + + ctrls->jpeg_quality = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, 1, 100, 1, 80); + + ctrls->scene_mode = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_SCENE_MODE, V4L2_SCENE_MODE_TEXT, ~0x3fff, + V4L2_SCENE_MODE_NONE); + + ctrls->aaa_lock = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_3A_LOCK, 0, 0x7, 0, 0); + + if (hdl->error) { + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + v4l2_ctrl_auto_cluster(3, &ctrls->auto_exposure, 0, false); + ctrls->auto_iso->flags |= V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_UPDATE; + v4l2_ctrl_auto_cluster(2, &ctrls->auto_iso, 0, false); + ctrls->af_status->flags |= V4L2_CTRL_FLAG_VOLATILE; + v4l2_ctrl_cluster(5, &ctrls->focus_auto); + + state->sensor_sd.ctrl_handler = hdl; + + return 0; +} diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-spi.c b/drivers/media/i2c/s5c73m3/s5c73m3-spi.c new file mode 100644 index 000000000..c102c6bbc --- /dev/null +++ b/drivers/media/i2c/s5c73m3/s5c73m3-spi.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung LSI S5C73M3 8M pixel camera driver + * + * Copyright (C) 2012, Samsung Electronics, Co., Ltd. + * Sylwester Nawrocki <s.nawrocki@samsung.com> + * Andrzej Hajda <a.hajda@samsung.com> + */ + +#include <linux/sizes.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/media.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> + +#include "s5c73m3.h" + +#define S5C73M3_SPI_DRV_NAME "S5C73M3-SPI" + +static const struct of_device_id s5c73m3_spi_ids[] = { + { .compatible = "samsung,s5c73m3" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5c73m3_spi_ids); + +enum spi_direction { + SPI_DIR_RX, + SPI_DIR_TX +}; + +static int spi_xmit(struct spi_device *spi_dev, void *addr, const int len, + enum spi_direction dir) +{ + struct spi_message msg; + int r; + struct spi_transfer xfer = { + .len = len, + }; + + if (dir == SPI_DIR_TX) + xfer.tx_buf = addr; + else + xfer.rx_buf = addr; + + if (spi_dev == NULL) { + pr_err("SPI device is uninitialized\n"); + return -ENODEV; + } + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + r = spi_sync(spi_dev, &msg); + if (r < 0) + dev_err(&spi_dev->dev, "%s spi_sync failed %d\n", __func__, r); + + return r; +} + +int s5c73m3_spi_write(struct s5c73m3 *state, const void *addr, + const unsigned int len, const unsigned int tx_size) +{ + struct spi_device *spi_dev = state->spi_dev; + u32 count = len / tx_size; + u32 extra = len % tx_size; + unsigned int i, j = 0; + u8 padding[32]; + int r = 0; + + memset(padding, 0, sizeof(padding)); + + for (i = 0; i < count; i++) { + r = spi_xmit(spi_dev, (void *)addr + j, tx_size, SPI_DIR_TX); + if (r < 0) + return r; + j += tx_size; + } + + if (extra > 0) { + r = spi_xmit(spi_dev, (void *)addr + j, extra, SPI_DIR_TX); + if (r < 0) + return r; + } + + return spi_xmit(spi_dev, padding, sizeof(padding), SPI_DIR_TX); +} + +int s5c73m3_spi_read(struct s5c73m3 *state, void *addr, + const unsigned int len, const unsigned int tx_size) +{ + struct spi_device *spi_dev = state->spi_dev; + u32 count = len / tx_size; + u32 extra = len % tx_size; + unsigned int i, j = 0; + int r = 0; + + for (i = 0; i < count; i++) { + r = spi_xmit(spi_dev, addr + j, tx_size, SPI_DIR_RX); + if (r < 0) + return r; + j += tx_size; + } + + if (extra > 0) + return spi_xmit(spi_dev, addr + j, extra, SPI_DIR_RX); + + return 0; +} + +static int s5c73m3_spi_probe(struct spi_device *spi) +{ + int r; + struct s5c73m3 *state = container_of(spi->dev.driver, struct s5c73m3, + spidrv.driver); + spi->bits_per_word = 32; + + r = spi_setup(spi); + if (r < 0) { + dev_err(&spi->dev, "spi_setup() failed\n"); + return r; + } + + mutex_lock(&state->lock); + state->spi_dev = spi; + mutex_unlock(&state->lock); + + v4l2_info(&state->sensor_sd, "S5C73M3 SPI probed successfully\n"); + return 0; +} + +static int s5c73m3_spi_remove(struct spi_device *spi) +{ + return 0; +} + +int s5c73m3_register_spi_driver(struct s5c73m3 *state) +{ + struct spi_driver *spidrv = &state->spidrv; + + spidrv->remove = s5c73m3_spi_remove; + spidrv->probe = s5c73m3_spi_probe; + spidrv->driver.name = S5C73M3_SPI_DRV_NAME; + spidrv->driver.of_match_table = s5c73m3_spi_ids; + + return spi_register_driver(spidrv); +} + +void s5c73m3_unregister_spi_driver(struct s5c73m3 *state) +{ + spi_unregister_driver(&state->spidrv); +} diff --git a/drivers/media/i2c/s5c73m3/s5c73m3.h b/drivers/media/i2c/s5c73m3/s5c73m3.h new file mode 100644 index 000000000..c3fcfdd3e --- /dev/null +++ b/drivers/media/i2c/s5c73m3/s5c73m3.h @@ -0,0 +1,455 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung LSI S5C73M3 8M pixel camera driver + * + * Copyright (C) 2012, Samsung Electronics, Co., Ltd. + * Sylwester Nawrocki <s.nawrocki@samsung.com> + * Andrzej Hajda <a.hajda@samsung.com> + */ +#ifndef S5C73M3_H_ +#define S5C73M3_H_ + +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> +#include <media/i2c/s5c73m3.h> + +#define DRIVER_NAME "S5C73M3" + +#define S5C73M3_ISP_FMT MEDIA_BUS_FMT_VYUY8_2X8 +#define S5C73M3_JPEG_FMT MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8 + +/* Subdevs pad index definitions */ +enum s5c73m3_pads { + S5C73M3_ISP_PAD, + S5C73M3_JPEG_PAD, + S5C73M3_NUM_PADS +}; + +enum s5c73m3_oif_pads { + OIF_ISP_PAD, + OIF_JPEG_PAD, + OIF_SOURCE_PAD, + OIF_NUM_PADS +}; + +#define S5C73M3_SENSOR_FW_LEN 6 +#define S5C73M3_SENSOR_TYPE_LEN 12 + +#define S5C73M3_REG(_addrh, _addrl) (((_addrh) << 16) | _addrl) + +#define AHB_MSB_ADDR_PTR 0xfcfc +#define REG_CMDWR_ADDRH 0x0050 +#define REG_CMDWR_ADDRL 0x0054 +#define REG_CMDRD_ADDRH 0x0058 +#define REG_CMDRD_ADDRL 0x005c +#define REG_CMDBUF_ADDR 0x0f14 + +#define REG_I2C_SEQ_STATUS S5C73M3_REG(0x0009, 0x59A6) +#define SEQ_END_PLL (1<<0x0) +#define SEQ_END_SENSOR (1<<0x1) +#define SEQ_END_GPIO (1<<0x2) +#define SEQ_END_FROM (1<<0x3) +#define SEQ_END_STABLE_AE_AWB (1<<0x4) +#define SEQ_END_READY_I2C_CMD (1<<0x5) + +#define REG_I2C_STATUS S5C73M3_REG(0x0009, 0x599E) +#define I2C_STATUS_CIS_I2C (1<<0x0) +#define I2C_STATUS_AF_INIT (1<<0x1) +#define I2C_STATUS_CAL_DATA (1<<0x2) +#define I2C_STATUS_FRAME_COUNT (1<<0x3) +#define I2C_STATUS_FROM_INIT (1<<0x4) +#define I2C_STATUS_I2C_CIS_STREAM_OFF (1<<0x5) +#define I2C_STATUS_I2C_N_CMD_OVER (1<<0x6) +#define I2C_STATUS_I2C_N_CMD_MISMATCH (1<<0x7) +#define I2C_STATUS_CHECK_BIN_CRC (1<<0x8) +#define I2C_STATUS_EXCEPTION (1<<0x9) +#define I2C_STATUS_INIF_INIT_STATE (0x8) + +#define REG_STATUS S5C73M3_REG(0x0009, 0x5080) +#define REG_STATUS_BOOT_SUB_MAIN_ENTER 0xff01 +#define REG_STATUS_BOOT_SRAM_TIMING_OK 0xff02 +#define REG_STATUS_BOOT_INTERRUPTS_EN 0xff03 +#define REG_STATUS_BOOT_R_PLL_DONE 0xff04 +#define REG_STATUS_BOOT_R_PLL_LOCKTIME_DONE 0xff05 +#define REG_STATUS_BOOT_DELAY_COUNT_DONE 0xff06 +#define REG_STATUS_BOOT_I_PLL_DONE 0xff07 +#define REG_STATUS_BOOT_I_PLL_LOCKTIME_DONE 0xff08 +#define REG_STATUS_BOOT_PLL_INIT_OK 0xff09 +#define REG_STATUS_BOOT_SENSOR_INIT_OK 0xff0a +#define REG_STATUS_BOOT_GPIO_SETTING_OK 0xff0b +#define REG_STATUS_BOOT_READ_CAL_DATA_OK 0xff0c +#define REG_STATUS_BOOT_STABLE_AE_AWB_OK 0xff0d +#define REG_STATUS_ISP_COMMAND_COMPLETED 0xffff +#define REG_STATUS_EXCEPTION_OCCURED 0xdead + +#define COMM_RESULT_OFFSET S5C73M3_REG(0x0009, 0x5000) + +#define COMM_IMG_OUTPUT 0x0902 +#define COMM_IMG_OUTPUT_HDR 0x0008 +#define COMM_IMG_OUTPUT_YUV 0x0009 +#define COMM_IMG_OUTPUT_INTERLEAVED 0x000d + +#define COMM_STILL_PRE_FLASH 0x0a00 +#define COMM_STILL_PRE_FLASH_FIRE 0x0000 +#define COMM_STILL_PRE_FLASH_NON_FIRED 0x0000 +#define COMM_STILL_PRE_FLASH_FIRED 0x0001 + +#define COMM_STILL_MAIN_FLASH 0x0a02 +#define COMM_STILL_MAIN_FLASH_CANCEL 0x0001 +#define COMM_STILL_MAIN_FLASH_FIRE 0x0002 + +#define COMM_ZOOM_STEP 0x0b00 + +#define COMM_IMAGE_EFFECT 0x0b0a +#define COMM_IMAGE_EFFECT_NONE 0x0001 +#define COMM_IMAGE_EFFECT_NEGATIVE 0x0002 +#define COMM_IMAGE_EFFECT_AQUA 0x0003 +#define COMM_IMAGE_EFFECT_SEPIA 0x0004 +#define COMM_IMAGE_EFFECT_MONO 0x0005 + +#define COMM_IMAGE_QUALITY 0x0b0c +#define COMM_IMAGE_QUALITY_SUPERFINE 0x0000 +#define COMM_IMAGE_QUALITY_FINE 0x0001 +#define COMM_IMAGE_QUALITY_NORMAL 0x0002 + +#define COMM_FLASH_MODE 0x0b0e +#define COMM_FLASH_MODE_OFF 0x0000 +#define COMM_FLASH_MODE_ON 0x0001 +#define COMM_FLASH_MODE_AUTO 0x0002 + +#define COMM_FLASH_STATUS 0x0b80 +#define COMM_FLASH_STATUS_OFF 0x0001 +#define COMM_FLASH_STATUS_ON 0x0002 +#define COMM_FLASH_STATUS_AUTO 0x0003 + +#define COMM_FLASH_TORCH 0x0b12 +#define COMM_FLASH_TORCH_OFF 0x0000 +#define COMM_FLASH_TORCH_ON 0x0001 + +#define COMM_AE_NEEDS_FLASH 0x0cba +#define COMM_AE_NEEDS_FLASH_OFF 0x0000 +#define COMM_AE_NEEDS_FLASH_ON 0x0001 + +#define COMM_CHG_MODE 0x0b10 +#define COMM_CHG_MODE_NEW 0x8000 +#define COMM_CHG_MODE_SUBSAMPLING_HALF 0x2000 +#define COMM_CHG_MODE_SUBSAMPLING_QUARTER 0x4000 + +#define COMM_CHG_MODE_YUV_320_240 0x0001 +#define COMM_CHG_MODE_YUV_640_480 0x0002 +#define COMM_CHG_MODE_YUV_880_720 0x0003 +#define COMM_CHG_MODE_YUV_960_720 0x0004 +#define COMM_CHG_MODE_YUV_1184_666 0x0005 +#define COMM_CHG_MODE_YUV_1280_720 0x0006 +#define COMM_CHG_MODE_YUV_1536_864 0x0007 +#define COMM_CHG_MODE_YUV_1600_1200 0x0008 +#define COMM_CHG_MODE_YUV_1632_1224 0x0009 +#define COMM_CHG_MODE_YUV_1920_1080 0x000a +#define COMM_CHG_MODE_YUV_1920_1440 0x000b +#define COMM_CHG_MODE_YUV_2304_1296 0x000c +#define COMM_CHG_MODE_YUV_3264_2448 0x000d +#define COMM_CHG_MODE_YUV_352_288 0x000e +#define COMM_CHG_MODE_YUV_1008_672 0x000f + +#define COMM_CHG_MODE_JPEG_640_480 0x0010 +#define COMM_CHG_MODE_JPEG_800_450 0x0020 +#define COMM_CHG_MODE_JPEG_800_600 0x0030 +#define COMM_CHG_MODE_JPEG_1280_720 0x0040 +#define COMM_CHG_MODE_JPEG_1280_960 0x0050 +#define COMM_CHG_MODE_JPEG_1600_900 0x0060 +#define COMM_CHG_MODE_JPEG_1600_1200 0x0070 +#define COMM_CHG_MODE_JPEG_2048_1152 0x0080 +#define COMM_CHG_MODE_JPEG_2048_1536 0x0090 +#define COMM_CHG_MODE_JPEG_2560_1440 0x00a0 +#define COMM_CHG_MODE_JPEG_2560_1920 0x00b0 +#define COMM_CHG_MODE_JPEG_3264_2176 0x00c0 +#define COMM_CHG_MODE_JPEG_1024_768 0x00d0 +#define COMM_CHG_MODE_JPEG_3264_1836 0x00e0 +#define COMM_CHG_MODE_JPEG_3264_2448 0x00f0 + +#define COMM_AF_CON 0x0e00 +#define COMM_AF_CON_STOP 0x0000 +#define COMM_AF_CON_SCAN 0x0001 /* Full Search */ +#define COMM_AF_CON_START 0x0002 /* Fast Search */ + +#define COMM_AF_CAL 0x0e06 +#define COMM_AF_TOUCH_AF 0x0e0a + +#define REG_AF_STATUS S5C73M3_REG(0x0009, 0x5e80) +#define REG_CAF_STATUS_FIND_SEARCH_DIR 0x0001 +#define REG_CAF_STATUS_FOCUSING 0x0002 +#define REG_CAF_STATUS_FOCUSED 0x0003 +#define REG_CAF_STATUS_UNFOCUSED 0x0004 +#define REG_AF_STATUS_INVALID 0x0010 +#define REG_AF_STATUS_FOCUSING 0x0020 +#define REG_AF_STATUS_FOCUSED 0x0030 +#define REG_AF_STATUS_UNFOCUSED 0x0040 + +#define REG_AF_TOUCH_POSITION S5C73M3_REG(0x0009, 0x5e8e) +#define COMM_AF_FACE_ZOOM 0x0e10 + +#define COMM_AF_MODE 0x0e02 +#define COMM_AF_MODE_NORMAL 0x0000 +#define COMM_AF_MODE_MACRO 0x0001 +#define COMM_AF_MODE_MOVIE_CAF_START 0x0002 +#define COMM_AF_MODE_MOVIE_CAF_STOP 0x0003 +#define COMM_AF_MODE_PREVIEW_CAF_START 0x0004 +#define COMM_AF_MODE_PREVIEW_CAF_STOP 0x0005 + +#define COMM_AF_SOFTLANDING 0x0e16 +#define COMM_AF_SOFTLANDING_ON 0x0000 +#define COMM_AF_SOFTLANDING_RES_COMPLETE 0x0001 + +#define COMM_FACE_DET 0x0e0c +#define COMM_FACE_DET_OFF 0x0000 +#define COMM_FACE_DET_ON 0x0001 + +#define COMM_FACE_DET_OSD 0x0e0e +#define COMM_FACE_DET_OSD_OFF 0x0000 +#define COMM_FACE_DET_OSD_ON 0x0001 + +#define COMM_AE_CON 0x0c00 +#define COMM_AE_STOP 0x0000 /* lock */ +#define COMM_AE_START 0x0001 /* unlock */ + +#define COMM_ISO 0x0c02 +#define COMM_ISO_AUTO 0x0000 +#define COMM_ISO_100 0x0001 +#define COMM_ISO_200 0x0002 +#define COMM_ISO_400 0x0003 +#define COMM_ISO_800 0x0004 +#define COMM_ISO_SPORTS 0x0005 +#define COMM_ISO_NIGHT 0x0006 +#define COMM_ISO_INDOOR 0x0007 + +/* 0x00000 (-2.0 EV)...0x0008 (2.0 EV), 0.5EV step */ +#define COMM_EV 0x0c04 + +#define COMM_METERING 0x0c06 +#define COMM_METERING_CENTER 0x0000 +#define COMM_METERING_SPOT 0x0001 +#define COMM_METERING_AVERAGE 0x0002 +#define COMM_METERING_SMART 0x0003 + +#define COMM_WDR 0x0c08 +#define COMM_WDR_OFF 0x0000 +#define COMM_WDR_ON 0x0001 + +#define COMM_FLICKER_MODE 0x0c12 +#define COMM_FLICKER_NONE 0x0000 +#define COMM_FLICKER_MANUAL_50HZ 0x0001 +#define COMM_FLICKER_MANUAL_60HZ 0x0002 +#define COMM_FLICKER_AUTO 0x0003 +#define COMM_FLICKER_AUTO_50HZ 0x0004 +#define COMM_FLICKER_AUTO_60HZ 0x0005 + +#define COMM_FRAME_RATE 0x0c1e +#define COMM_FRAME_RATE_AUTO_SET 0x0000 +#define COMM_FRAME_RATE_FIXED_30FPS 0x0002 +#define COMM_FRAME_RATE_FIXED_20FPS 0x0003 +#define COMM_FRAME_RATE_FIXED_15FPS 0x0004 +#define COMM_FRAME_RATE_FIXED_60FPS 0x0007 +#define COMM_FRAME_RATE_FIXED_120FPS 0x0008 +#define COMM_FRAME_RATE_FIXED_7FPS 0x0009 +#define COMM_FRAME_RATE_FIXED_10FPS 0x000a +#define COMM_FRAME_RATE_FIXED_90FPS 0x000b +#define COMM_FRAME_RATE_ANTI_SHAKE 0x0013 + +/* 0x0000...0x0004 -> sharpness: 0, 1, 2, -1, -2 */ +#define COMM_SHARPNESS 0x0c14 + +/* 0x0000...0x0004 -> saturation: 0, 1, 2, -1, -2 */ +#define COMM_SATURATION 0x0c16 + +/* 0x0000...0x0004 -> contrast: 0, 1, 2, -1, -2 */ +#define COMM_CONTRAST 0x0c18 + +#define COMM_SCENE_MODE 0x0c1a +#define COMM_SCENE_MODE_NONE 0x0000 +#define COMM_SCENE_MODE_PORTRAIT 0x0001 +#define COMM_SCENE_MODE_LANDSCAPE 0x0002 +#define COMM_SCENE_MODE_SPORTS 0x0003 +#define COMM_SCENE_MODE_INDOOR 0x0004 +#define COMM_SCENE_MODE_BEACH 0x0005 +#define COMM_SCENE_MODE_SUNSET 0x0006 +#define COMM_SCENE_MODE_DAWN 0x0007 +#define COMM_SCENE_MODE_FALL 0x0008 +#define COMM_SCENE_MODE_NIGHT 0x0009 +#define COMM_SCENE_MODE_AGAINST_LIGHT 0x000a +#define COMM_SCENE_MODE_FIRE 0x000b +#define COMM_SCENE_MODE_TEXT 0x000c +#define COMM_SCENE_MODE_CANDLE 0x000d + +#define COMM_AE_AUTO_BRACKET 0x0b14 +#define COMM_AE_AUTO_BRAKET_EV05 0x0080 +#define COMM_AE_AUTO_BRAKET_EV10 0x0100 +#define COMM_AE_AUTO_BRAKET_EV15 0x0180 +#define COMM_AE_AUTO_BRAKET_EV20 0x0200 + +#define COMM_SENSOR_STREAMING 0x090a +#define COMM_SENSOR_STREAMING_OFF 0x0000 +#define COMM_SENSOR_STREAMING_ON 0x0001 + +#define COMM_AWB_MODE 0x0d02 +#define COMM_AWB_MODE_INCANDESCENT 0x0000 +#define COMM_AWB_MODE_FLUORESCENT1 0x0001 +#define COMM_AWB_MODE_FLUORESCENT2 0x0002 +#define COMM_AWB_MODE_DAYLIGHT 0x0003 +#define COMM_AWB_MODE_CLOUDY 0x0004 +#define COMM_AWB_MODE_AUTO 0x0005 + +#define COMM_AWB_CON 0x0d00 +#define COMM_AWB_STOP 0x0000 /* lock */ +#define COMM_AWB_START 0x0001 /* unlock */ + +#define COMM_FW_UPDATE 0x0906 +#define COMM_FW_UPDATE_NOT_READY 0x0000 +#define COMM_FW_UPDATE_SUCCESS 0x0005 +#define COMM_FW_UPDATE_FAIL 0x0007 +#define COMM_FW_UPDATE_BUSY 0xffff + + +#define S5C73M3_MAX_SUPPLIES 6 +#define S5C73M3_DEFAULT_MCLK_FREQ 24000000U + +struct s5c73m3_ctrls { + struct v4l2_ctrl_handler handler; + struct { + /* exposure/exposure bias cluster */ + struct v4l2_ctrl *auto_exposure; + struct v4l2_ctrl *exposure_bias; + struct v4l2_ctrl *exposure_metering; + }; + struct { + /* iso/auto iso cluster */ + struct v4l2_ctrl *auto_iso; + struct v4l2_ctrl *iso; + }; + struct v4l2_ctrl *auto_wb; + struct { + /* continuous auto focus/auto focus cluster */ + struct v4l2_ctrl *focus_auto; + struct v4l2_ctrl *af_start; + struct v4l2_ctrl *af_stop; + struct v4l2_ctrl *af_status; + struct v4l2_ctrl *af_distance; + }; + + struct v4l2_ctrl *aaa_lock; + struct v4l2_ctrl *colorfx; + struct v4l2_ctrl *contrast; + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *sharpness; + struct v4l2_ctrl *zoom; + struct v4l2_ctrl *wdr; + struct v4l2_ctrl *stabilization; + struct v4l2_ctrl *jpeg_quality; + struct v4l2_ctrl *scene_mode; +}; + +enum s5c73m3_gpio_id { + STBY, + RSET, + GPIO_NUM, +}; + +enum s5c73m3_resolution_types { + RES_ISP, + RES_JPEG, +}; + +struct s5c73m3_interval { + u16 fps_reg; + struct v4l2_fract interval; + /* Maximum rectangle for the interval */ + struct v4l2_frmsize_discrete size; +}; + +struct s5c73m3 { + struct v4l2_subdev sensor_sd; + struct media_pad sensor_pads[S5C73M3_NUM_PADS]; + + struct v4l2_subdev oif_sd; + struct media_pad oif_pads[OIF_NUM_PADS]; + + struct spi_driver spidrv; + struct spi_device *spi_dev; + struct i2c_client *i2c_client; + u32 i2c_write_address; + u32 i2c_read_address; + + struct regulator_bulk_data supplies[S5C73M3_MAX_SUPPLIES]; + struct s5c73m3_gpio gpio[GPIO_NUM]; + + struct clk *clock; + + /* External master clock frequency */ + u32 mclk_frequency; + /* Video bus type - MIPI-CSI2/parallel */ + enum v4l2_mbus_type bus_type; + + const struct s5c73m3_frame_size *sensor_pix_size[2]; + const struct s5c73m3_frame_size *oif_pix_size[2]; + u32 mbus_code; + + const struct s5c73m3_interval *fiv; + + struct v4l2_mbus_frame_desc frame_desc; + /* protects the struct members below */ + struct mutex lock; + + struct s5c73m3_ctrls ctrls; + + u8 streaming:1; + u8 apply_fmt:1; + u8 apply_fiv:1; + u8 isp_ready:1; + + short power; + + char sensor_fw[S5C73M3_SENSOR_FW_LEN + 2]; + char sensor_type[S5C73M3_SENSOR_TYPE_LEN + 2]; + char fw_file_version[2]; + unsigned int fw_size; +}; + +struct s5c73m3_frame_size { + u32 width; + u32 height; + u8 reg_val; +}; + +extern int s5c73m3_dbg; + +int s5c73m3_register_spi_driver(struct s5c73m3 *state); +void s5c73m3_unregister_spi_driver(struct s5c73m3 *state); +int s5c73m3_spi_write(struct s5c73m3 *state, const void *addr, + const unsigned int len, const unsigned int tx_size); +int s5c73m3_spi_read(struct s5c73m3 *state, void *addr, + const unsigned int len, const unsigned int tx_size); + +int s5c73m3_read(struct s5c73m3 *state, u32 addr, u16 *data); +int s5c73m3_write(struct s5c73m3 *state, u32 addr, u16 data); +int s5c73m3_isp_command(struct s5c73m3 *state, u16 command, u16 data); +int s5c73m3_init_controls(struct s5c73m3 *state); + +static inline struct v4l2_subdev *ctrl_to_sensor_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct s5c73m3, + ctrls.handler)->sensor_sd; +} + +static inline struct s5c73m3 *sensor_sd_to_s5c73m3(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s5c73m3, sensor_sd); +} + +static inline struct s5c73m3 *oif_sd_to_s5c73m3(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s5c73m3, oif_sd); +} +#endif /* S5C73M3_H_ */ diff --git a/drivers/media/i2c/s5k4ecgx.c b/drivers/media/i2c/s5k4ecgx.c new file mode 100644 index 000000000..4e97309a6 --- /dev/null +++ b/drivers/media/i2c/s5k4ecgx.c @@ -0,0 +1,1030 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Samsung S5K4ECGX 1/4" 5Mp CMOS Image Sensor SoC + * with an Embedded Image Signal Processor. + * + * Copyright (C) 2012, Linaro, Sangwook Lee <sangwook.lee@linaro.org> + * Copyright (C) 2012, Insignal Co,. Ltd, Homin Lee <suapapa@insignal.co.kr> + * + * Based on s5k6aa and noon010pc30 driver + * Copyright (C) 2011, Samsung Electronics Co., Ltd. + */ + +#include <linux/clk.h> +#include <linux/crc32.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <asm/unaligned.h> + +#include <media/media-entity.h> +#include <media/i2c/s5k4ecgx.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-mediabus.h> +#include <media/v4l2-subdev.h> + +static int debug; +module_param(debug, int, 0644); + +#define S5K4ECGX_DRIVER_NAME "s5k4ecgx" +#define S5K4ECGX_FIRMWARE "s5k4ecgx.bin" + +/* Firmware revision information */ +#define REG_FW_REVISION 0x700001a6 +#define REG_FW_VERSION 0x700001a4 +#define S5K4ECGX_REVISION_1_1 0x11 +#define S5K4ECGX_FW_VERSION 0x4ec0 + +/* General purpose parameters */ +#define REG_USER_BRIGHTNESS 0x7000022c +#define REG_USER_CONTRAST 0x7000022e +#define REG_USER_SATURATION 0x70000230 + +#define REG_G_ENABLE_PREV 0x7000023e +#define REG_G_ENABLE_PREV_CHG 0x70000240 +#define REG_G_NEW_CFG_SYNC 0x7000024a +#define REG_G_PREV_IN_WIDTH 0x70000250 +#define REG_G_PREV_IN_HEIGHT 0x70000252 +#define REG_G_PREV_IN_XOFFS 0x70000254 +#define REG_G_PREV_IN_YOFFS 0x70000256 +#define REG_G_CAP_IN_WIDTH 0x70000258 +#define REG_G_CAP_IN_HEIGHT 0x7000025a +#define REG_G_CAP_IN_XOFFS 0x7000025c +#define REG_G_CAP_IN_YOFFS 0x7000025e +#define REG_G_INPUTS_CHANGE_REQ 0x70000262 +#define REG_G_ACTIVE_PREV_CFG 0x70000266 +#define REG_G_PREV_CFG_CHG 0x70000268 +#define REG_G_PREV_OPEN_AFTER_CH 0x7000026a + +/* Preview context register sets. n = 0...4. */ +#define PREG(n, x) ((n) * 0x30 + (x)) +#define REG_P_OUT_WIDTH(n) PREG(n, 0x700002a6) +#define REG_P_OUT_HEIGHT(n) PREG(n, 0x700002a8) +#define REG_P_FMT(n) PREG(n, 0x700002aa) +#define REG_P_PVI_MASK(n) PREG(n, 0x700002b4) +#define REG_P_FR_TIME_TYPE(n) PREG(n, 0x700002be) +#define FR_TIME_DYNAMIC 0 +#define FR_TIME_FIXED 1 +#define FR_TIME_FIXED_ACCURATE 2 +#define REG_P_FR_TIME_Q_TYPE(n) PREG(n, 0x700002c0) +#define FR_TIME_Q_DYNAMIC 0 +#define FR_TIME_Q_BEST_FRRATE 1 +#define FR_TIME_Q_BEST_QUALITY 2 + +/* Frame period in 0.1 ms units */ +#define REG_P_MAX_FR_TIME(n) PREG(n, 0x700002c2) +#define REG_P_MIN_FR_TIME(n) PREG(n, 0x700002c4) +#define US_TO_FR_TIME(__t) ((__t) / 100) +#define REG_P_PREV_MIRROR(n) PREG(n, 0x700002d0) +#define REG_P_CAP_MIRROR(n) PREG(n, 0x700002d2) + +#define REG_G_PREVZOOM_IN_WIDTH 0x70000494 +#define REG_G_PREVZOOM_IN_HEIGHT 0x70000496 +#define REG_G_PREVZOOM_IN_XOFFS 0x70000498 +#define REG_G_PREVZOOM_IN_YOFFS 0x7000049a +#define REG_G_CAPZOOM_IN_WIDTH 0x7000049c +#define REG_G_CAPZOOM_IN_HEIGHT 0x7000049e +#define REG_G_CAPZOOM_IN_XOFFS 0x700004a0 +#define REG_G_CAPZOOM_IN_YOFFS 0x700004a2 + +/* n = 0...4 */ +#define REG_USER_SHARPNESS(n) (0x70000a28 + (n) * 0xb6) + +/* Reduce sharpness range for user space API */ +#define SHARPNESS_DIV 8208 +#define TOK_TERM 0xffffffff + +/* + * FIXME: This is copied from s5k6aa, because of no information + * in the S5K4ECGX datasheet. + * H/W register Interface (0xd0000000 - 0xd0000fff) + */ +#define AHB_MSB_ADDR_PTR 0xfcfc +#define GEN_REG_OFFSH 0xd000 +#define REG_CMDWR_ADDRH 0x0028 +#define REG_CMDWR_ADDRL 0x002a +#define REG_CMDRD_ADDRH 0x002c +#define REG_CMDRD_ADDRL 0x002e +#define REG_CMDBUF0_ADDR 0x0f12 + +struct s5k4ecgx_frmsize { + struct v4l2_frmsize_discrete size; + /* Fixed sensor matrix crop rectangle */ + struct v4l2_rect input_window; +}; + +struct regval_list { + u32 addr; + u16 val; +}; + +/* + * TODO: currently only preview is supported and snapshot (capture) + * is not implemented yet + */ +static const struct s5k4ecgx_frmsize s5k4ecgx_prev_sizes[] = { + { + .size = { 176, 144 }, + .input_window = { 0x00, 0x00, 0x928, 0x780 }, + }, { + .size = { 352, 288 }, + .input_window = { 0x00, 0x00, 0x928, 0x780 }, + }, { + .size = { 640, 480 }, + .input_window = { 0x00, 0x00, 0xa00, 0x780 }, + }, { + .size = { 720, 480 }, + .input_window = { 0x00, 0x00, 0xa00, 0x6a8 }, + } +}; + +#define S5K4ECGX_NUM_PREV ARRAY_SIZE(s5k4ecgx_prev_sizes) + +struct s5k4ecgx_pixfmt { + u32 code; + u32 colorspace; + /* REG_TC_PCFG_Format register value */ + u16 reg_p_format; +}; + +/* By default value, output from sensor will be YUV422 0-255 */ +static const struct s5k4ecgx_pixfmt s5k4ecgx_formats[] = { + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG, 5 }, +}; + +static const char * const s5k4ecgx_supply_names[] = { + /* + * Usually 2.8V is used for analog power (vdda) + * and digital IO (vddio, vdddcore) + */ + "vdda", + "vddio", + "vddcore", + "vddreg", /* The internal s5k4ecgx regulator's supply (1.8V) */ +}; + +#define S5K4ECGX_NUM_SUPPLIES ARRAY_SIZE(s5k4ecgx_supply_names) + +enum s5k4ecgx_gpio_id { + STBY, + RSET, + GPIO_NUM, +}; + +struct s5k4ecgx { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler handler; + + struct s5k4ecgx_platform_data *pdata; + const struct s5k4ecgx_pixfmt *curr_pixfmt; + const struct s5k4ecgx_frmsize *curr_frmsize; + struct mutex lock; + u8 streaming; + u8 set_params; + + struct regulator_bulk_data supplies[S5K4ECGX_NUM_SUPPLIES]; + struct s5k4ecgx_gpio gpio[GPIO_NUM]; +}; + +static inline struct s5k4ecgx *to_s5k4ecgx(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s5k4ecgx, sd); +} + +static int s5k4ecgx_i2c_read(struct i2c_client *client, u16 addr, u16 *val) +{ + u8 wbuf[2] = { addr >> 8, addr & 0xff }; + struct i2c_msg msg[2]; + u8 rbuf[2]; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 2; + msg[0].buf = wbuf; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = rbuf; + + ret = i2c_transfer(client->adapter, msg, 2); + *val = be16_to_cpu(*((__be16 *)rbuf)); + + v4l2_dbg(4, debug, client, "i2c_read: 0x%04X : 0x%04x\n", addr, *val); + + return ret == 2 ? 0 : ret; +} + +static int s5k4ecgx_i2c_write(struct i2c_client *client, u16 addr, u16 val) +{ + u8 buf[4] = { addr >> 8, addr & 0xff, val >> 8, val & 0xff }; + + int ret = i2c_master_send(client, buf, 4); + v4l2_dbg(4, debug, client, "i2c_write: 0x%04x : 0x%04x\n", addr, val); + + return ret == 4 ? 0 : ret; +} + +static int s5k4ecgx_write(struct i2c_client *client, u32 addr, u16 val) +{ + u16 high = addr >> 16, low = addr & 0xffff; + int ret; + + v4l2_dbg(3, debug, client, "write: 0x%08x : 0x%04x\n", addr, val); + + ret = s5k4ecgx_i2c_write(client, REG_CMDWR_ADDRH, high); + if (!ret) + ret = s5k4ecgx_i2c_write(client, REG_CMDWR_ADDRL, low); + if (!ret) + ret = s5k4ecgx_i2c_write(client, REG_CMDBUF0_ADDR, val); + + return ret; +} + +static int s5k4ecgx_read(struct i2c_client *client, u32 addr, u16 *val) +{ + u16 high = addr >> 16, low = addr & 0xffff; + int ret; + + ret = s5k4ecgx_i2c_write(client, REG_CMDRD_ADDRH, high); + if (!ret) + ret = s5k4ecgx_i2c_write(client, REG_CMDRD_ADDRL, low); + if (!ret) + ret = s5k4ecgx_i2c_read(client, REG_CMDBUF0_ADDR, val); + + return ret; +} + +static int s5k4ecgx_read_fw_ver(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u16 hw_rev, fw_ver = 0; + int ret; + + ret = s5k4ecgx_read(client, REG_FW_VERSION, &fw_ver); + if (ret < 0 || fw_ver != S5K4ECGX_FW_VERSION) { + v4l2_err(sd, "FW version check failed!\n"); + return -ENODEV; + } + + ret = s5k4ecgx_read(client, REG_FW_REVISION, &hw_rev); + if (ret < 0) + return ret; + + v4l2_info(sd, "chip found FW ver: 0x%x, HW rev: 0x%x\n", + fw_ver, hw_rev); + return 0; +} + +static int s5k4ecgx_set_ahb_address(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + /* Set APB peripherals start address */ + ret = s5k4ecgx_i2c_write(client, AHB_MSB_ADDR_PTR, GEN_REG_OFFSH); + if (ret < 0) + return ret; + /* + * FIXME: This is copied from s5k6aa, because of no information + * in s5k4ecgx's datasheet. + * sw_reset is activated to put device into idle status + */ + ret = s5k4ecgx_i2c_write(client, 0x0010, 0x0001); + if (ret < 0) + return ret; + + ret = s5k4ecgx_i2c_write(client, 0x1030, 0x0000); + if (ret < 0) + return ret; + /* Halt ARM CPU */ + return s5k4ecgx_i2c_write(client, 0x0014, 0x0001); +} + +#define FW_CRC_SIZE 4 +/* Register address, value are 4, 2 bytes */ +#define FW_RECORD_SIZE 6 +/* + * The firmware has following format: + * < total number of records (4 bytes + 2 bytes padding) N >, + * < record 0 >, ..., < record N - 1 >, < CRC32-CCITT (4-bytes) >, + * where "record" is a 4-byte register address followed by 2-byte + * register value (little endian). + * The firmware generator can be found in following git repository: + * git://git.linaro.org/people/sangwook/fimc-v4l2-app.git + */ +static int s5k4ecgx_load_firmware(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + const struct firmware *fw; + const u8 *ptr; + int err, i, regs_num; + u32 addr, crc, crc_file, addr_inc = 0; + u16 val; + + err = request_firmware(&fw, S5K4ECGX_FIRMWARE, sd->v4l2_dev->dev); + if (err) { + v4l2_err(sd, "Failed to read firmware %s\n", S5K4ECGX_FIRMWARE); + return err; + } + regs_num = get_unaligned_le32(fw->data); + + v4l2_dbg(3, debug, sd, "FW: %s size %zu register sets %d\n", + S5K4ECGX_FIRMWARE, fw->size, regs_num); + + regs_num++; /* Add header */ + if (fw->size != regs_num * FW_RECORD_SIZE + FW_CRC_SIZE) { + err = -EINVAL; + goto fw_out; + } + crc_file = get_unaligned_le32(fw->data + regs_num * FW_RECORD_SIZE); + crc = crc32_le(~0, fw->data, regs_num * FW_RECORD_SIZE); + if (crc != crc_file) { + v4l2_err(sd, "FW: invalid crc (%#x:%#x)\n", crc, crc_file); + err = -EINVAL; + goto fw_out; + } + ptr = fw->data + FW_RECORD_SIZE; + for (i = 1; i < regs_num; i++) { + addr = get_unaligned_le32(ptr); + ptr += sizeof(u32); + val = get_unaligned_le16(ptr); + ptr += sizeof(u16); + if (addr - addr_inc != 2) + err = s5k4ecgx_write(client, addr, val); + else + err = s5k4ecgx_i2c_write(client, REG_CMDBUF0_ADDR, val); + if (err) + break; + addr_inc = addr; + } +fw_out: + release_firmware(fw); + return err; +} + +/* Set preview and capture input window */ +static int s5k4ecgx_set_input_window(struct i2c_client *c, + const struct v4l2_rect *r) +{ + int ret; + + ret = s5k4ecgx_write(c, REG_G_PREV_IN_WIDTH, r->width); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREV_IN_HEIGHT, r->height); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREV_IN_XOFFS, r->left); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREV_IN_YOFFS, r->top); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAP_IN_WIDTH, r->width); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAP_IN_HEIGHT, r->height); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAP_IN_XOFFS, r->left); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAP_IN_YOFFS, r->top); + + return ret; +} + +/* Set preview and capture zoom input window */ +static int s5k4ecgx_set_zoom_window(struct i2c_client *c, + const struct v4l2_rect *r) +{ + int ret; + + ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_WIDTH, r->width); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_HEIGHT, r->height); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_XOFFS, r->left); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_YOFFS, r->top); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_WIDTH, r->width); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_HEIGHT, r->height); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_XOFFS, r->left); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_YOFFS, r->top); + + return ret; +} + +static int s5k4ecgx_set_output_framefmt(struct s5k4ecgx *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + int ret; + + ret = s5k4ecgx_write(client, REG_P_OUT_WIDTH(0), + priv->curr_frmsize->size.width); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_OUT_HEIGHT(0), + priv->curr_frmsize->size.height); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_FMT(0), + priv->curr_pixfmt->reg_p_format); + return ret; +} + +static int s5k4ecgx_init_sensor(struct v4l2_subdev *sd) +{ + int ret; + + ret = s5k4ecgx_set_ahb_address(sd); + + /* The delay is from manufacturer's settings */ + msleep(100); + + if (!ret) + ret = s5k4ecgx_load_firmware(sd); + if (ret) + v4l2_err(sd, "Failed to write initial settings\n"); + + return ret; +} + +static int s5k4ecgx_gpio_set_value(struct s5k4ecgx *priv, int id, u32 val) +{ + if (!gpio_is_valid(priv->gpio[id].gpio)) + return 0; + gpio_set_value(priv->gpio[id].gpio, val); + + return 1; +} + +static int __s5k4ecgx_power_on(struct s5k4ecgx *priv) +{ + int ret; + + ret = regulator_bulk_enable(S5K4ECGX_NUM_SUPPLIES, priv->supplies); + if (ret) + return ret; + usleep_range(30, 50); + + /* The polarity of STBY is controlled by TSP */ + if (s5k4ecgx_gpio_set_value(priv, STBY, priv->gpio[STBY].level)) + usleep_range(30, 50); + + if (s5k4ecgx_gpio_set_value(priv, RSET, priv->gpio[RSET].level)) + usleep_range(30, 50); + + return 0; +} + +static int __s5k4ecgx_power_off(struct s5k4ecgx *priv) +{ + if (s5k4ecgx_gpio_set_value(priv, RSET, !priv->gpio[RSET].level)) + usleep_range(30, 50); + + if (s5k4ecgx_gpio_set_value(priv, STBY, !priv->gpio[STBY].level)) + usleep_range(30, 50); + + priv->streaming = 0; + + return regulator_bulk_disable(S5K4ECGX_NUM_SUPPLIES, priv->supplies); +} + +/* Find nearest matching image pixel size. */ +static int s5k4ecgx_try_frame_size(struct v4l2_mbus_framefmt *mf, + const struct s5k4ecgx_frmsize **size) +{ + unsigned int min_err = ~0; + int i = ARRAY_SIZE(s5k4ecgx_prev_sizes); + const struct s5k4ecgx_frmsize *fsize = &s5k4ecgx_prev_sizes[0], + *match = NULL; + + while (i--) { + int err = abs(fsize->size.width - mf->width) + + abs(fsize->size.height - mf->height); + if (err < min_err) { + min_err = err; + match = fsize; + } + fsize++; + } + if (match) { + mf->width = match->size.width; + mf->height = match->size.height; + if (size) + *size = match; + return 0; + } + + return -EINVAL; +} + +static int s5k4ecgx_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(s5k4ecgx_formats)) + return -EINVAL; + code->code = s5k4ecgx_formats[code->index].code; + + return 0; +} + +static int s5k4ecgx_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + struct v4l2_mbus_framefmt *mf; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + if (cfg) { + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + fmt->format = *mf; + } + return 0; + } + + mf = &fmt->format; + + mutex_lock(&priv->lock); + mf->width = priv->curr_frmsize->size.width; + mf->height = priv->curr_frmsize->size.height; + mf->code = priv->curr_pixfmt->code; + mf->colorspace = priv->curr_pixfmt->colorspace; + mf->field = V4L2_FIELD_NONE; + mutex_unlock(&priv->lock); + + return 0; +} + +static const struct s5k4ecgx_pixfmt *s5k4ecgx_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + int i = ARRAY_SIZE(s5k4ecgx_formats); + + while (--i) + if (mf->code == s5k4ecgx_formats[i].code) + break; + mf->code = s5k4ecgx_formats[i].code; + + return &s5k4ecgx_formats[i]; +} + +static int s5k4ecgx_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + const struct s5k4ecgx_frmsize *fsize = NULL; + const struct s5k4ecgx_pixfmt *pf; + struct v4l2_mbus_framefmt *mf; + int ret = 0; + + pf = s5k4ecgx_try_fmt(sd, &fmt->format); + s5k4ecgx_try_frame_size(&fmt->format, &fsize); + fmt->format.colorspace = V4L2_COLORSPACE_JPEG; + fmt->format.field = V4L2_FIELD_NONE; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + if (cfg) { + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + *mf = fmt->format; + } + return 0; + } + + mutex_lock(&priv->lock); + if (!priv->streaming) { + priv->curr_frmsize = fsize; + priv->curr_pixfmt = pf; + priv->set_params = 1; + } else { + ret = -EBUSY; + } + mutex_unlock(&priv->lock); + + return ret; +} + +static const struct v4l2_subdev_pad_ops s5k4ecgx_pad_ops = { + .enum_mbus_code = s5k4ecgx_enum_mbus_code, + .get_fmt = s5k4ecgx_get_fmt, + .set_fmt = s5k4ecgx_set_fmt, +}; + +/* + * V4L2 subdev controls + */ +static int s5k4ecgx_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = &container_of(ctrl->handler, struct s5k4ecgx, + handler)->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + unsigned int i; + int err = 0; + + v4l2_dbg(1, debug, sd, "ctrl: 0x%x, value: %d\n", ctrl->id, ctrl->val); + + mutex_lock(&priv->lock); + switch (ctrl->id) { + case V4L2_CID_CONTRAST: + err = s5k4ecgx_write(client, REG_USER_CONTRAST, ctrl->val); + break; + + case V4L2_CID_SATURATION: + err = s5k4ecgx_write(client, REG_USER_SATURATION, ctrl->val); + break; + + case V4L2_CID_SHARPNESS: + /* TODO: Revisit, is this setting for all presets ? */ + for (i = 0; i < 4 && !err; i++) + err = s5k4ecgx_write(client, REG_USER_SHARPNESS(i), + ctrl->val * SHARPNESS_DIV); + break; + + case V4L2_CID_BRIGHTNESS: + err = s5k4ecgx_write(client, REG_USER_BRIGHTNESS, ctrl->val); + break; + } + mutex_unlock(&priv->lock); + if (err < 0) + v4l2_err(sd, "Failed to write s_ctrl err %d\n", err); + + return err; +} + +static const struct v4l2_ctrl_ops s5k4ecgx_ctrl_ops = { + .s_ctrl = s5k4ecgx_s_ctrl, +}; + +/* + * Reading s5k4ecgx version information + */ +static int s5k4ecgx_registered(struct v4l2_subdev *sd) +{ + int ret; + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + + mutex_lock(&priv->lock); + ret = __s5k4ecgx_power_on(priv); + if (!ret) { + ret = s5k4ecgx_read_fw_ver(sd); + __s5k4ecgx_power_off(priv); + } + mutex_unlock(&priv->lock); + + return ret; +} + +/* + * V4L2 subdev internal operations + */ +static int s5k4ecgx_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *mf = v4l2_subdev_get_try_format(sd, fh->pad, 0); + + mf->width = s5k4ecgx_prev_sizes[0].size.width; + mf->height = s5k4ecgx_prev_sizes[0].size.height; + mf->code = s5k4ecgx_formats[0].code; + mf->colorspace = V4L2_COLORSPACE_JPEG; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static const struct v4l2_subdev_internal_ops s5k4ecgx_subdev_internal_ops = { + .registered = s5k4ecgx_registered, + .open = s5k4ecgx_open, +}; + +static int s5k4ecgx_s_power(struct v4l2_subdev *sd, int on) +{ + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + int ret; + + v4l2_dbg(1, debug, sd, "Switching %s\n", on ? "on" : "off"); + + if (on) { + ret = __s5k4ecgx_power_on(priv); + if (ret < 0) + return ret; + /* Time to stabilize sensor */ + msleep(100); + ret = s5k4ecgx_init_sensor(sd); + if (ret < 0) + __s5k4ecgx_power_off(priv); + else + priv->set_params = 1; + } else { + ret = __s5k4ecgx_power_off(priv); + } + + return ret; +} + +static int s5k4ecgx_log_status(struct v4l2_subdev *sd) +{ + v4l2_ctrl_handler_log_status(sd->ctrl_handler, sd->name); + + return 0; +} + +static const struct v4l2_subdev_core_ops s5k4ecgx_core_ops = { + .s_power = s5k4ecgx_s_power, + .log_status = s5k4ecgx_log_status, +}; + +static int __s5k4ecgx_s_params(struct s5k4ecgx *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + const struct v4l2_rect *crop_rect = &priv->curr_frmsize->input_window; + int ret; + + ret = s5k4ecgx_set_input_window(client, crop_rect); + if (!ret) + ret = s5k4ecgx_set_zoom_window(client, crop_rect); + if (!ret) + ret = s5k4ecgx_write(client, REG_G_INPUTS_CHANGE_REQ, 1); + if (!ret) + ret = s5k4ecgx_write(client, 0x70000a1e, 0x28); + if (!ret) + ret = s5k4ecgx_write(client, 0x70000ad4, 0x3c); + if (!ret) + ret = s5k4ecgx_set_output_framefmt(priv); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_PVI_MASK(0), 0x52); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_FR_TIME_TYPE(0), + FR_TIME_DYNAMIC); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_FR_TIME_Q_TYPE(0), + FR_TIME_Q_BEST_FRRATE); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_MIN_FR_TIME(0), + US_TO_FR_TIME(33300)); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_MAX_FR_TIME(0), + US_TO_FR_TIME(66600)); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_PREV_MIRROR(0), 0); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_CAP_MIRROR(0), 0); + if (!ret) + ret = s5k4ecgx_write(client, REG_G_ACTIVE_PREV_CFG, 0); + if (!ret) + ret = s5k4ecgx_write(client, REG_G_PREV_OPEN_AFTER_CH, 1); + if (!ret) + ret = s5k4ecgx_write(client, REG_G_NEW_CFG_SYNC, 1); + if (!ret) + ret = s5k4ecgx_write(client, REG_G_PREV_CFG_CHG, 1); + + return ret; +} + +static int __s5k4ecgx_s_stream(struct s5k4ecgx *priv, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + int ret; + + if (on && priv->set_params) { + ret = __s5k4ecgx_s_params(priv); + if (ret < 0) + return ret; + priv->set_params = 0; + } + /* + * This enables/disables preview stream only. Capture requests + * are not supported yet. + */ + ret = s5k4ecgx_write(client, REG_G_ENABLE_PREV, on); + if (ret < 0) + return ret; + return s5k4ecgx_write(client, REG_G_ENABLE_PREV_CHG, 1); +} + +static int s5k4ecgx_s_stream(struct v4l2_subdev *sd, int on) +{ + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + int ret = 0; + + v4l2_dbg(1, debug, sd, "Turn streaming %s\n", on ? "on" : "off"); + + mutex_lock(&priv->lock); + + if (priv->streaming == !on) { + ret = __s5k4ecgx_s_stream(priv, on); + if (!ret) + priv->streaming = on & 1; + } + + mutex_unlock(&priv->lock); + return ret; +} + +static const struct v4l2_subdev_video_ops s5k4ecgx_video_ops = { + .s_stream = s5k4ecgx_s_stream, +}; + +static const struct v4l2_subdev_ops s5k4ecgx_ops = { + .core = &s5k4ecgx_core_ops, + .pad = &s5k4ecgx_pad_ops, + .video = &s5k4ecgx_video_ops, +}; + +/* + * GPIO setup + */ +static int s5k4ecgx_config_gpio(int nr, int val, const char *name) +{ + unsigned long flags = val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + int ret; + + if (!gpio_is_valid(nr)) + return 0; + ret = gpio_request_one(nr, flags, name); + if (!ret) + gpio_export(nr, 0); + + return ret; +} + +static void s5k4ecgx_free_gpios(struct s5k4ecgx *priv) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(priv->gpio); i++) { + if (!gpio_is_valid(priv->gpio[i].gpio)) + continue; + gpio_free(priv->gpio[i].gpio); + priv->gpio[i].gpio = -EINVAL; + } +} + +static int s5k4ecgx_config_gpios(struct s5k4ecgx *priv, + const struct s5k4ecgx_platform_data *pdata) +{ + const struct s5k4ecgx_gpio *gpio = &pdata->gpio_stby; + int ret; + + priv->gpio[STBY].gpio = -EINVAL; + priv->gpio[RSET].gpio = -EINVAL; + + ret = s5k4ecgx_config_gpio(gpio->gpio, gpio->level, "S5K4ECGX_STBY"); + + if (ret) { + s5k4ecgx_free_gpios(priv); + return ret; + } + priv->gpio[STBY] = *gpio; + if (gpio_is_valid(gpio->gpio)) + gpio_set_value(gpio->gpio, 0); + + gpio = &pdata->gpio_reset; + + ret = s5k4ecgx_config_gpio(gpio->gpio, gpio->level, "S5K4ECGX_RST"); + if (ret) { + s5k4ecgx_free_gpios(priv); + return ret; + } + priv->gpio[RSET] = *gpio; + if (gpio_is_valid(gpio->gpio)) + gpio_set_value(gpio->gpio, 0); + + return 0; +} + +static int s5k4ecgx_init_v4l2_ctrls(struct s5k4ecgx *priv) +{ + const struct v4l2_ctrl_ops *ops = &s5k4ecgx_ctrl_ops; + struct v4l2_ctrl_handler *hdl = &priv->handler; + int ret; + + ret = v4l2_ctrl_handler_init(hdl, 4); + if (ret) + return ret; + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -208, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, -127, 127, 1, 0); + + /* Sharpness default is 24612, and then (24612/SHARPNESS_DIV) = 2 */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SHARPNESS, -32704/SHARPNESS_DIV, + 24612/SHARPNESS_DIV, 1, 2); + if (hdl->error) { + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + priv->sd.ctrl_handler = hdl; + + return 0; +}; + +static int s5k4ecgx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct s5k4ecgx_platform_data *pdata = client->dev.platform_data; + struct v4l2_subdev *sd; + struct s5k4ecgx *priv; + int ret, i; + + if (pdata == NULL) { + dev_err(&client->dev, "platform data is missing!\n"); + return -EINVAL; + } + + priv = devm_kzalloc(&client->dev, sizeof(struct s5k4ecgx), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->lock); + priv->streaming = 0; + + sd = &priv->sd; + /* Registering subdev */ + v4l2_i2c_subdev_init(sd, client, &s5k4ecgx_ops); + /* Static name; NEVER use in new drivers! */ + strscpy(sd->name, S5K4ECGX_DRIVER_NAME, sizeof(sd->name)); + + sd->internal_ops = &s5k4ecgx_subdev_internal_ops; + /* Support v4l2 sub-device user space API */ + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + priv->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &priv->pad); + if (ret) + return ret; + + ret = s5k4ecgx_config_gpios(priv, pdata); + if (ret) { + dev_err(&client->dev, "Failed to set gpios\n"); + goto out_err1; + } + for (i = 0; i < S5K4ECGX_NUM_SUPPLIES; i++) + priv->supplies[i].supply = s5k4ecgx_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, S5K4ECGX_NUM_SUPPLIES, + priv->supplies); + if (ret) { + dev_err(&client->dev, "Failed to get regulators\n"); + goto out_err2; + } + ret = s5k4ecgx_init_v4l2_ctrls(priv); + if (ret) + goto out_err2; + + priv->curr_pixfmt = &s5k4ecgx_formats[0]; + priv->curr_frmsize = &s5k4ecgx_prev_sizes[0]; + + return 0; + +out_err2: + s5k4ecgx_free_gpios(priv); +out_err1: + media_entity_cleanup(&priv->sd.entity); + + return ret; +} + +static int s5k4ecgx_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + + mutex_destroy(&priv->lock); + s5k4ecgx_free_gpios(priv); + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&priv->handler); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct i2c_device_id s5k4ecgx_id[] = { + { S5K4ECGX_DRIVER_NAME, 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, s5k4ecgx_id); + +static struct i2c_driver v4l2_i2c_driver = { + .driver = { + .name = S5K4ECGX_DRIVER_NAME, + }, + .probe = s5k4ecgx_probe, + .remove = s5k4ecgx_remove, + .id_table = s5k4ecgx_id, +}; + +module_i2c_driver(v4l2_i2c_driver); + +MODULE_DESCRIPTION("Samsung S5K4ECGX 5MP SOC camera"); +MODULE_AUTHOR("Sangwook Lee <sangwook.lee@linaro.org>"); +MODULE_AUTHOR("Seok-Young Jang <quartz.jang@samsung.com>"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(S5K4ECGX_FIRMWARE); diff --git a/drivers/media/i2c/s5k5baf.c b/drivers/media/i2c/s5k5baf.c new file mode 100644 index 000000000..ec65a8e08 --- /dev/null +++ b/drivers/media/i2c/s5k5baf.c @@ -0,0 +1,2056 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Samsung S5K5BAF UXGA 1/5" 2M CMOS Image Sensor + * with embedded SoC ISP. + * + * Copyright (C) 2013, Samsung Electronics Co., Ltd. + * Andrzej Hajda <a.hajda@samsung.com> + * + * Based on S5K6AA driver authored by Sylwester Nawrocki + * Copyright (C) 2013, Samsung Electronics Co., Ltd. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/media.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-mediabus.h> +#include <media/v4l2-fwnode.h> + +static int debug; +module_param(debug, int, 0644); + +#define S5K5BAF_DRIVER_NAME "s5k5baf" +#define S5K5BAF_DEFAULT_MCLK_FREQ 24000000U +#define S5K5BAF_CLK_NAME "mclk" + +#define S5K5BAF_FW_FILENAME "s5k5baf-cfg.bin" +#define S5K5BAF_FW_TAG "SF00" +#define S5K5BAG_FW_TAG_LEN 2 +#define S5K5BAG_FW_MAX_COUNT 16 + +#define S5K5BAF_CIS_WIDTH 1600 +#define S5K5BAF_CIS_HEIGHT 1200 +#define S5K5BAF_WIN_WIDTH_MIN 8 +#define S5K5BAF_WIN_HEIGHT_MIN 8 +#define S5K5BAF_GAIN_RED_DEF 127 +#define S5K5BAF_GAIN_GREEN_DEF 95 +#define S5K5BAF_GAIN_BLUE_DEF 180 +/* Default number of MIPI CSI-2 data lanes used */ +#define S5K5BAF_DEF_NUM_LANES 1 + +#define AHB_MSB_ADDR_PTR 0xfcfc + +/* + * Register interface pages (the most significant word of the address) + */ +#define PAGE_IF_HW 0xd000 +#define PAGE_IF_SW 0x7000 + +/* + * H/W register Interface (PAGE_IF_HW) + */ +#define REG_SW_LOAD_COMPLETE 0x0014 +#define REG_CMDWR_PAGE 0x0028 +#define REG_CMDWR_ADDR 0x002a +#define REG_CMDRD_PAGE 0x002c +#define REG_CMDRD_ADDR 0x002e +#define REG_CMD_BUF 0x0f12 +#define REG_SET_HOST_INT 0x1000 +#define REG_CLEAR_HOST_INT 0x1030 +#define REG_PATTERN_SET 0x3100 +#define REG_PATTERN_WIDTH 0x3118 +#define REG_PATTERN_HEIGHT 0x311a +#define REG_PATTERN_PARAM 0x311c + +/* + * S/W register interface (PAGE_IF_SW) + */ + +/* Firmware revision information */ +#define REG_FW_APIVER 0x012e +#define S5K5BAF_FW_APIVER 0x0001 +#define REG_FW_REVISION 0x0130 +#define REG_FW_SENSOR_ID 0x0152 + +/* Initialization parameters */ +/* Master clock frequency in KHz */ +#define REG_I_INCLK_FREQ_L 0x01b8 +#define REG_I_INCLK_FREQ_H 0x01ba +#define MIN_MCLK_FREQ_KHZ 6000U +#define MAX_MCLK_FREQ_KHZ 48000U +#define REG_I_USE_NPVI_CLOCKS 0x01c6 +#define NPVI_CLOCKS 1 +#define REG_I_USE_NMIPI_CLOCKS 0x01c8 +#define NMIPI_CLOCKS 1 +#define REG_I_BLOCK_INTERNAL_PLL_CALC 0x01ca + +/* Clock configurations, n = 0..2. REG_I_* frequency unit is 4 kHz. */ +#define REG_I_OPCLK_4KHZ(n) ((n) * 6 + 0x01cc) +#define REG_I_MIN_OUTRATE_4KHZ(n) ((n) * 6 + 0x01ce) +#define REG_I_MAX_OUTRATE_4KHZ(n) ((n) * 6 + 0x01d0) +#define SCLK_PVI_FREQ 24000 +#define SCLK_MIPI_FREQ 48000 +#define PCLK_MIN_FREQ 6000 +#define PCLK_MAX_FREQ 48000 +#define REG_I_USE_REGS_API 0x01de +#define REG_I_INIT_PARAMS_UPDATED 0x01e0 +#define REG_I_ERROR_INFO 0x01e2 + +/* General purpose parameters */ +#define REG_USER_BRIGHTNESS 0x01e4 +#define REG_USER_CONTRAST 0x01e6 +#define REG_USER_SATURATION 0x01e8 +#define REG_USER_SHARPBLUR 0x01ea + +#define REG_G_SPEC_EFFECTS 0x01ee +#define REG_G_ENABLE_PREV 0x01f0 +#define REG_G_ENABLE_PREV_CHG 0x01f2 +#define REG_G_NEW_CFG_SYNC 0x01f8 +#define REG_G_PREVREQ_IN_WIDTH 0x01fa +#define REG_G_PREVREQ_IN_HEIGHT 0x01fc +#define REG_G_PREVREQ_IN_XOFFS 0x01fe +#define REG_G_PREVREQ_IN_YOFFS 0x0200 +#define REG_G_PREVZOOM_IN_WIDTH 0x020a +#define REG_G_PREVZOOM_IN_HEIGHT 0x020c +#define REG_G_PREVZOOM_IN_XOFFS 0x020e +#define REG_G_PREVZOOM_IN_YOFFS 0x0210 +#define REG_G_INPUTS_CHANGE_REQ 0x021a +#define REG_G_ACTIVE_PREV_CFG 0x021c +#define REG_G_PREV_CFG_CHG 0x021e +#define REG_G_PREV_OPEN_AFTER_CH 0x0220 +#define REG_G_PREV_CFG_ERROR 0x0222 +#define CFG_ERROR_RANGE 0x0b +#define REG_G_PREV_CFG_BYPASS_CHANGED 0x022a +#define REG_G_ACTUAL_P_FR_TIME 0x023a +#define REG_G_ACTUAL_P_OUT_RATE 0x023c +#define REG_G_ACTUAL_C_FR_TIME 0x023e +#define REG_G_ACTUAL_C_OUT_RATE 0x0240 + +/* Preview control section. n = 0...4. */ +#define PREG(n, x) ((n) * 0x26 + x) +#define REG_P_OUT_WIDTH(n) PREG(n, 0x0242) +#define REG_P_OUT_HEIGHT(n) PREG(n, 0x0244) +#define REG_P_FMT(n) PREG(n, 0x0246) +#define REG_P_MAX_OUT_RATE(n) PREG(n, 0x0248) +#define REG_P_MIN_OUT_RATE(n) PREG(n, 0x024a) +#define REG_P_PVI_MASK(n) PREG(n, 0x024c) +#define PVI_MASK_MIPI 0x52 +#define REG_P_CLK_INDEX(n) PREG(n, 0x024e) +#define CLK_PVI_INDEX 0 +#define CLK_MIPI_INDEX NPVI_CLOCKS +#define REG_P_FR_RATE_TYPE(n) PREG(n, 0x0250) +#define FR_RATE_DYNAMIC 0 +#define FR_RATE_FIXED 1 +#define FR_RATE_FIXED_ACCURATE 2 +#define REG_P_FR_RATE_Q_TYPE(n) PREG(n, 0x0252) +#define FR_RATE_Q_DYNAMIC 0 +#define FR_RATE_Q_BEST_FRRATE 1 /* Binning enabled */ +#define FR_RATE_Q_BEST_QUALITY 2 /* Binning disabled */ +/* Frame period in 0.1 ms units */ +#define REG_P_MAX_FR_TIME(n) PREG(n, 0x0254) +#define REG_P_MIN_FR_TIME(n) PREG(n, 0x0256) +#define S5K5BAF_MIN_FR_TIME 333 /* x100 us */ +#define S5K5BAF_MAX_FR_TIME 6500 /* x100 us */ +/* The below 5 registers are for "device correction" values */ +#define REG_P_SATURATION(n) PREG(n, 0x0258) +#define REG_P_SHARP_BLUR(n) PREG(n, 0x025a) +#define REG_P_GLAMOUR(n) PREG(n, 0x025c) +#define REG_P_COLORTEMP(n) PREG(n, 0x025e) +#define REG_P_GAMMA_INDEX(n) PREG(n, 0x0260) +#define REG_P_PREV_MIRROR(n) PREG(n, 0x0262) +#define REG_P_CAP_MIRROR(n) PREG(n, 0x0264) +#define REG_P_CAP_ROTATION(n) PREG(n, 0x0266) + +/* Extended image property controls */ +/* Exposure time in 10 us units */ +#define REG_SF_USR_EXPOSURE_L 0x03bc +#define REG_SF_USR_EXPOSURE_H 0x03be +#define REG_SF_USR_EXPOSURE_CHG 0x03c0 +#define REG_SF_USR_TOT_GAIN 0x03c2 +#define REG_SF_USR_TOT_GAIN_CHG 0x03c4 +#define REG_SF_RGAIN 0x03c6 +#define REG_SF_RGAIN_CHG 0x03c8 +#define REG_SF_GGAIN 0x03ca +#define REG_SF_GGAIN_CHG 0x03cc +#define REG_SF_BGAIN 0x03ce +#define REG_SF_BGAIN_CHG 0x03d0 +#define REG_SF_WBGAIN_CHG 0x03d2 +#define REG_SF_FLICKER_QUANT 0x03d4 +#define REG_SF_FLICKER_QUANT_CHG 0x03d6 + +/* Output interface (parallel/MIPI) setup */ +#define REG_OIF_EN_MIPI_LANES 0x03f2 +#define REG_OIF_EN_PACKETS 0x03f4 +#define EN_PACKETS_CSI2 0xc3 +#define REG_OIF_CFG_CHG 0x03f6 + +/* Auto-algorithms enable mask */ +#define REG_DBG_AUTOALG_EN 0x03f8 +#define AALG_ALL_EN BIT(0) +#define AALG_AE_EN BIT(1) +#define AALG_DIVLEI_EN BIT(2) +#define AALG_WB_EN BIT(3) +#define AALG_USE_WB_FOR_ISP BIT(4) +#define AALG_FLICKER_EN BIT(5) +#define AALG_FIT_EN BIT(6) +#define AALG_WRHW_EN BIT(7) + +/* Pointers to color correction matrices */ +#define REG_PTR_CCM_HORIZON 0x06d0 +#define REG_PTR_CCM_INCANDESCENT 0x06d4 +#define REG_PTR_CCM_WARM_WHITE 0x06d8 +#define REG_PTR_CCM_COOL_WHITE 0x06dc +#define REG_PTR_CCM_DL50 0x06e0 +#define REG_PTR_CCM_DL65 0x06e4 +#define REG_PTR_CCM_OUTDOOR 0x06ec + +#define REG_ARR_CCM(n) (0x2800 + 36 * (n)) + +static const char * const s5k5baf_supply_names[] = { + "vdda", /* Analog power supply 2.8V (2.6V to 3.0V) */ + "vddreg", /* Regulator input power supply 1.8V (1.7V to 1.9V) + or 2.8V (2.6V to 3.0) */ + "vddio", /* I/O power supply 1.8V (1.65V to 1.95V) + or 2.8V (2.5V to 3.1V) */ +}; +#define S5K5BAF_NUM_SUPPLIES ARRAY_SIZE(s5k5baf_supply_names) + +struct s5k5baf_gpio { + int gpio; + int level; +}; + +enum s5k5baf_gpio_id { + STBY, + RSET, + NUM_GPIOS, +}; + +#define PAD_CIS 0 +#define PAD_OUT 1 +#define NUM_CIS_PADS 1 +#define NUM_ISP_PADS 2 + +struct s5k5baf_pixfmt { + u32 code; + u32 colorspace; + /* REG_P_FMT(x) register value */ + u16 reg_p_fmt; +}; + +struct s5k5baf_ctrls { + struct v4l2_ctrl_handler handler; + struct { /* Auto / manual white balance cluster */ + struct v4l2_ctrl *awb; + struct v4l2_ctrl *gain_red; + struct v4l2_ctrl *gain_blue; + }; + struct { /* Mirror cluster */ + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + struct { /* Auto exposure / manual exposure and gain cluster */ + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; + }; +}; + +enum { + S5K5BAF_FW_ID_PATCH, + S5K5BAF_FW_ID_CCM, + S5K5BAF_FW_ID_CIS, +}; + +struct s5k5baf_fw { + u16 count; + struct { + u16 id; + u16 offset; + } seq[]; +}; + +struct s5k5baf { + struct s5k5baf_gpio gpios[NUM_GPIOS]; + enum v4l2_mbus_type bus_type; + u8 nlanes; + struct regulator_bulk_data supplies[S5K5BAF_NUM_SUPPLIES]; + + struct clk *clock; + u32 mclk_frequency; + + struct s5k5baf_fw *fw; + + struct v4l2_subdev cis_sd; + struct media_pad cis_pad; + + struct v4l2_subdev sd; + struct media_pad pads[NUM_ISP_PADS]; + + /* protects the struct members below */ + struct mutex lock; + + int error; + + struct v4l2_rect crop_sink; + struct v4l2_rect compose; + struct v4l2_rect crop_source; + /* index to s5k5baf_formats array */ + int pixfmt; + /* actual frame interval in 100us */ + u16 fiv; + /* requested frame interval in 100us */ + u16 req_fiv; + /* cache for REG_DBG_AUTOALG_EN register */ + u16 auto_alg; + + struct s5k5baf_ctrls ctrls; + + unsigned int streaming:1; + unsigned int apply_cfg:1; + unsigned int apply_crop:1; + unsigned int valid_auto_alg:1; + unsigned int power; +}; + +static const struct s5k5baf_pixfmt s5k5baf_formats[] = { + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_COLORSPACE_JPEG, 5 }, + /* range 16-240 */ + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_COLORSPACE_REC709, 6 }, + { MEDIA_BUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_JPEG, 0 }, +}; + +static struct v4l2_rect s5k5baf_cis_rect = { + 0, 0, S5K5BAF_CIS_WIDTH, S5K5BAF_CIS_HEIGHT +}; + +/* Setfile contains set of I2C command sequences. Each sequence has its ID. + * setfile format: + * u8 magic[4]; + * u16 count; number of sequences + * struct { + * u16 id; sequence id + * u16 offset; sequence offset in data array + * } seq[count]; + * u16 data[*]; array containing sequences + * + */ +static int s5k5baf_fw_parse(struct device *dev, struct s5k5baf_fw **fw, + size_t count, const __le16 *data) +{ + struct s5k5baf_fw *f; + u16 *d, i, *end; + int ret; + + if (count < S5K5BAG_FW_TAG_LEN + 1) { + dev_err(dev, "firmware file too short (%zu)\n", count); + return -EINVAL; + } + + ret = memcmp(data, S5K5BAF_FW_TAG, S5K5BAG_FW_TAG_LEN * sizeof(u16)); + if (ret != 0) { + dev_err(dev, "invalid firmware magic number\n"); + return -EINVAL; + } + + data += S5K5BAG_FW_TAG_LEN; + count -= S5K5BAG_FW_TAG_LEN; + + d = devm_kcalloc(dev, count, sizeof(u16), GFP_KERNEL); + if (!d) + return -ENOMEM; + + for (i = 0; i < count; ++i) + d[i] = le16_to_cpu(data[i]); + + f = (struct s5k5baf_fw *)d; + if (count < 1 + 2 * f->count) { + dev_err(dev, "invalid firmware header (count=%d size=%zu)\n", + f->count, 2 * (count + S5K5BAG_FW_TAG_LEN)); + return -EINVAL; + } + end = d + count; + d += 1 + 2 * f->count; + + for (i = 0; i < f->count; ++i) { + if (f->seq[i].offset + d <= end) + continue; + dev_err(dev, "invalid firmware header (seq=%d)\n", i); + return -EINVAL; + } + + *fw = f; + + return 0; +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct s5k5baf, ctrls.handler)->sd; +} + +static inline bool s5k5baf_is_cis_subdev(struct v4l2_subdev *sd) +{ + return sd->entity.function == MEDIA_ENT_F_CAM_SENSOR; +} + +static inline struct s5k5baf *to_s5k5baf(struct v4l2_subdev *sd) +{ + if (s5k5baf_is_cis_subdev(sd)) + return container_of(sd, struct s5k5baf, cis_sd); + else + return container_of(sd, struct s5k5baf, sd); +} + +static u16 s5k5baf_i2c_read(struct s5k5baf *state, u16 addr) +{ + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + __be16 w, r; + u16 res; + struct i2c_msg msg[] = { + { .addr = c->addr, .flags = 0, + .len = 2, .buf = (u8 *)&w }, + { .addr = c->addr, .flags = I2C_M_RD, + .len = 2, .buf = (u8 *)&r }, + }; + int ret; + + if (state->error) + return 0; + + w = cpu_to_be16(addr); + ret = i2c_transfer(c->adapter, msg, 2); + res = be16_to_cpu(r); + + v4l2_dbg(3, debug, c, "i2c_read: 0x%04x : 0x%04x\n", addr, res); + + if (ret != 2) { + v4l2_err(c, "i2c_read: error during transfer (%d)\n", ret); + state->error = ret; + } + return res; +} + +static void s5k5baf_i2c_write(struct s5k5baf *state, u16 addr, u16 val) +{ + u8 buf[4] = { addr >> 8, addr & 0xFF, val >> 8, val & 0xFF }; + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + int ret; + + if (state->error) + return; + + ret = i2c_master_send(c, buf, 4); + v4l2_dbg(3, debug, c, "i2c_write: 0x%04x : 0x%04x\n", addr, val); + + if (ret != 4) { + v4l2_err(c, "i2c_write: error during transfer (%d)\n", ret); + state->error = ret; + } +} + +static u16 s5k5baf_read(struct s5k5baf *state, u16 addr) +{ + s5k5baf_i2c_write(state, REG_CMDRD_ADDR, addr); + return s5k5baf_i2c_read(state, REG_CMD_BUF); +} + +static void s5k5baf_write(struct s5k5baf *state, u16 addr, u16 val) +{ + s5k5baf_i2c_write(state, REG_CMDWR_ADDR, addr); + s5k5baf_i2c_write(state, REG_CMD_BUF, val); +} + +static void s5k5baf_write_arr_seq(struct s5k5baf *state, u16 addr, + u16 count, const u16 *seq) +{ + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + __be16 buf[65]; + + s5k5baf_i2c_write(state, REG_CMDWR_ADDR, addr); + if (state->error) + return; + + v4l2_dbg(3, debug, c, "i2c_write_seq(count=%d): %*ph\n", count, + min(2 * count, 64), seq); + + buf[0] = cpu_to_be16(REG_CMD_BUF); + + while (count > 0) { + int n = min_t(int, count, ARRAY_SIZE(buf) - 1); + int ret, i; + + for (i = 1; i <= n; ++i) + buf[i] = cpu_to_be16(*seq++); + + i *= 2; + ret = i2c_master_send(c, (char *)buf, i); + if (ret != i) { + v4l2_err(c, "i2c_write_seq: error during transfer (%d)\n", ret); + state->error = ret; + break; + } + + count -= n; + } +} + +#define s5k5baf_write_seq(state, addr, seq...) \ + s5k5baf_write_arr_seq(state, addr, sizeof((char[]){ seq }), \ + (const u16 []){ seq }); + +/* add items count at the beginning of the list */ +#define NSEQ(seq...) sizeof((char[]){ seq }), seq + +/* + * s5k5baf_write_nseq() - Writes sequences of values to sensor memory via i2c + * @nseq: sequence of u16 words in format: + * (N, address, value[1]...value[N-1])*,0 + * Ex.: + * u16 seq[] = { NSEQ(0x4000, 1, 1), NSEQ(0x4010, 640, 480), 0 }; + * ret = s5k5baf_write_nseq(c, seq); + */ +static void s5k5baf_write_nseq(struct s5k5baf *state, const u16 *nseq) +{ + int count; + + while ((count = *nseq++)) { + u16 addr = *nseq++; + --count; + + s5k5baf_write_arr_seq(state, addr, count, nseq); + nseq += count; + } +} + +static void s5k5baf_synchronize(struct s5k5baf *state, int timeout, u16 addr) +{ + unsigned long end = jiffies + msecs_to_jiffies(timeout); + u16 reg; + + s5k5baf_write(state, addr, 1); + do { + reg = s5k5baf_read(state, addr); + if (state->error || !reg) + return; + usleep_range(5000, 10000); + } while (time_is_after_jiffies(end)); + + v4l2_err(&state->sd, "timeout on register synchronize (%#x)\n", addr); + state->error = -ETIMEDOUT; +} + +static u16 *s5k5baf_fw_get_seq(struct s5k5baf *state, u16 seq_id) +{ + struct s5k5baf_fw *fw = state->fw; + u16 *data; + int i; + + if (fw == NULL) + return NULL; + + data = &fw->seq[0].id + 2 * fw->count; + + for (i = 0; i < fw->count; ++i) { + if (fw->seq[i].id == seq_id) + return data + fw->seq[i].offset; + } + + return NULL; +} + +static void s5k5baf_hw_patch(struct s5k5baf *state) +{ + u16 *seq = s5k5baf_fw_get_seq(state, S5K5BAF_FW_ID_PATCH); + + if (seq) + s5k5baf_write_nseq(state, seq); +} + +static void s5k5baf_hw_set_clocks(struct s5k5baf *state) +{ + unsigned long mclk = state->mclk_frequency / 1000; + u16 status; + static const u16 nseq_clk_cfg[] = { + NSEQ(REG_I_USE_NPVI_CLOCKS, + NPVI_CLOCKS, NMIPI_CLOCKS, 0, + SCLK_PVI_FREQ / 4, PCLK_MIN_FREQ / 4, PCLK_MAX_FREQ / 4, + SCLK_MIPI_FREQ / 4, PCLK_MIN_FREQ / 4, PCLK_MAX_FREQ / 4), + NSEQ(REG_I_USE_REGS_API, 1), + 0 + }; + + s5k5baf_write_seq(state, REG_I_INCLK_FREQ_L, mclk & 0xffff, mclk >> 16); + s5k5baf_write_nseq(state, nseq_clk_cfg); + + s5k5baf_synchronize(state, 250, REG_I_INIT_PARAMS_UPDATED); + status = s5k5baf_read(state, REG_I_ERROR_INFO); + if (!state->error && status) { + v4l2_err(&state->sd, "error configuring PLL (%d)\n", status); + state->error = -EINVAL; + } +} + +/* set custom color correction matrices for various illuminations */ +static void s5k5baf_hw_set_ccm(struct s5k5baf *state) +{ + u16 *seq = s5k5baf_fw_get_seq(state, S5K5BAF_FW_ID_CCM); + + if (seq) + s5k5baf_write_nseq(state, seq); +} + +/* CIS sensor tuning, based on undocumented android driver code */ +static void s5k5baf_hw_set_cis(struct s5k5baf *state) +{ + u16 *seq = s5k5baf_fw_get_seq(state, S5K5BAF_FW_ID_CIS); + + if (!seq) + return; + + s5k5baf_i2c_write(state, REG_CMDWR_PAGE, PAGE_IF_HW); + s5k5baf_write_nseq(state, seq); + s5k5baf_i2c_write(state, REG_CMDWR_PAGE, PAGE_IF_SW); +} + +static void s5k5baf_hw_sync_cfg(struct s5k5baf *state) +{ + s5k5baf_write(state, REG_G_PREV_CFG_CHG, 1); + if (state->apply_crop) { + s5k5baf_write(state, REG_G_INPUTS_CHANGE_REQ, 1); + s5k5baf_write(state, REG_G_PREV_CFG_BYPASS_CHANGED, 1); + } + s5k5baf_synchronize(state, 500, REG_G_NEW_CFG_SYNC); +} +/* Set horizontal and vertical image flipping */ +static void s5k5baf_hw_set_mirror(struct s5k5baf *state) +{ + u16 flip = state->ctrls.vflip->val | (state->ctrls.vflip->val << 1); + + s5k5baf_write(state, REG_P_PREV_MIRROR(0), flip); + if (state->streaming) + s5k5baf_hw_sync_cfg(state); +} + +static void s5k5baf_hw_set_alg(struct s5k5baf *state, u16 alg, bool enable) +{ + u16 cur_alg, new_alg; + + if (!state->valid_auto_alg) + cur_alg = s5k5baf_read(state, REG_DBG_AUTOALG_EN); + else + cur_alg = state->auto_alg; + + new_alg = enable ? (cur_alg | alg) : (cur_alg & ~alg); + + if (new_alg != cur_alg) + s5k5baf_write(state, REG_DBG_AUTOALG_EN, new_alg); + + if (state->error) + return; + + state->valid_auto_alg = 1; + state->auto_alg = new_alg; +} + +/* Configure auto/manual white balance and R/G/B gains */ +static void s5k5baf_hw_set_awb(struct s5k5baf *state, int awb) +{ + struct s5k5baf_ctrls *ctrls = &state->ctrls; + + if (!awb) + s5k5baf_write_seq(state, REG_SF_RGAIN, + ctrls->gain_red->val, 1, + S5K5BAF_GAIN_GREEN_DEF, 1, + ctrls->gain_blue->val, 1, + 1); + + s5k5baf_hw_set_alg(state, AALG_WB_EN, awb); +} + +/* Program FW with exposure time, 'exposure' in us units */ +static void s5k5baf_hw_set_user_exposure(struct s5k5baf *state, int exposure) +{ + unsigned int time = exposure / 10; + + s5k5baf_write_seq(state, REG_SF_USR_EXPOSURE_L, + time & 0xffff, time >> 16, 1); +} + +static void s5k5baf_hw_set_user_gain(struct s5k5baf *state, int gain) +{ + s5k5baf_write_seq(state, REG_SF_USR_TOT_GAIN, gain, 1); +} + +/* Set auto/manual exposure and total gain */ +static void s5k5baf_hw_set_auto_exposure(struct s5k5baf *state, int value) +{ + if (value == V4L2_EXPOSURE_AUTO) { + s5k5baf_hw_set_alg(state, AALG_AE_EN | AALG_DIVLEI_EN, true); + } else { + unsigned int exp_time = state->ctrls.exposure->val; + + s5k5baf_hw_set_user_exposure(state, exp_time); + s5k5baf_hw_set_user_gain(state, state->ctrls.gain->val); + s5k5baf_hw_set_alg(state, AALG_AE_EN | AALG_DIVLEI_EN, false); + } +} + +static void s5k5baf_hw_set_anti_flicker(struct s5k5baf *state, int v) +{ + if (v == V4L2_CID_POWER_LINE_FREQUENCY_AUTO) { + s5k5baf_hw_set_alg(state, AALG_FLICKER_EN, true); + } else { + /* The V4L2_CID_LINE_FREQUENCY control values match + * the register values */ + s5k5baf_write_seq(state, REG_SF_FLICKER_QUANT, v, 1); + s5k5baf_hw_set_alg(state, AALG_FLICKER_EN, false); + } +} + +static void s5k5baf_hw_set_colorfx(struct s5k5baf *state, int val) +{ + static const u16 colorfx[] = { + [V4L2_COLORFX_NONE] = 0, + [V4L2_COLORFX_BW] = 1, + [V4L2_COLORFX_NEGATIVE] = 2, + [V4L2_COLORFX_SEPIA] = 3, + [V4L2_COLORFX_SKY_BLUE] = 4, + [V4L2_COLORFX_SKETCH] = 5, + }; + + s5k5baf_write(state, REG_G_SPEC_EFFECTS, colorfx[val]); +} + +static int s5k5baf_find_pixfmt(struct v4l2_mbus_framefmt *mf) +{ + int i, c = -1; + + for (i = 0; i < ARRAY_SIZE(s5k5baf_formats); i++) { + if (mf->colorspace != s5k5baf_formats[i].colorspace) + continue; + if (mf->code == s5k5baf_formats[i].code) + return i; + if (c < 0) + c = i; + } + return (c < 0) ? 0 : c; +} + +static int s5k5baf_clear_error(struct s5k5baf *state) +{ + int ret = state->error; + + state->error = 0; + return ret; +} + +static int s5k5baf_hw_set_video_bus(struct s5k5baf *state) +{ + u16 en_pkts; + + if (state->bus_type == V4L2_MBUS_CSI2_DPHY) + en_pkts = EN_PACKETS_CSI2; + else + en_pkts = 0; + + s5k5baf_write_seq(state, REG_OIF_EN_MIPI_LANES, + state->nlanes, en_pkts, 1); + + return s5k5baf_clear_error(state); +} + +static u16 s5k5baf_get_cfg_error(struct s5k5baf *state) +{ + u16 err = s5k5baf_read(state, REG_G_PREV_CFG_ERROR); + if (err) + s5k5baf_write(state, REG_G_PREV_CFG_ERROR, 0); + return err; +} + +static void s5k5baf_hw_set_fiv(struct s5k5baf *state, u16 fiv) +{ + s5k5baf_write(state, REG_P_MAX_FR_TIME(0), fiv); + s5k5baf_hw_sync_cfg(state); +} + +static void s5k5baf_hw_find_min_fiv(struct s5k5baf *state) +{ + u16 err, fiv; + int n; + + fiv = s5k5baf_read(state, REG_G_ACTUAL_P_FR_TIME); + if (state->error) + return; + + for (n = 5; n > 0; --n) { + s5k5baf_hw_set_fiv(state, fiv); + err = s5k5baf_get_cfg_error(state); + if (state->error) + return; + switch (err) { + case CFG_ERROR_RANGE: + ++fiv; + break; + case 0: + state->fiv = fiv; + v4l2_info(&state->sd, + "found valid frame interval: %d00us\n", fiv); + return; + default: + v4l2_err(&state->sd, + "error setting frame interval: %d\n", err); + state->error = -EINVAL; + } + } + v4l2_err(&state->sd, "cannot find correct frame interval\n"); + state->error = -ERANGE; +} + +static void s5k5baf_hw_validate_cfg(struct s5k5baf *state) +{ + u16 err; + + err = s5k5baf_get_cfg_error(state); + if (state->error) + return; + + switch (err) { + case 0: + state->apply_cfg = 1; + return; + case CFG_ERROR_RANGE: + s5k5baf_hw_find_min_fiv(state); + if (!state->error) + state->apply_cfg = 1; + return; + default: + v4l2_err(&state->sd, + "error setting format: %d\n", err); + state->error = -EINVAL; + } +} + +static void s5k5baf_rescale(struct v4l2_rect *r, const struct v4l2_rect *v, + const struct v4l2_rect *n, + const struct v4l2_rect *d) +{ + r->left = v->left * n->width / d->width; + r->top = v->top * n->height / d->height; + r->width = v->width * n->width / d->width; + r->height = v->height * n->height / d->height; +} + +static int s5k5baf_hw_set_crop_rects(struct s5k5baf *state) +{ + struct v4l2_rect *p, r; + u16 err; + int ret; + + p = &state->crop_sink; + s5k5baf_write_seq(state, REG_G_PREVREQ_IN_WIDTH, p->width, p->height, + p->left, p->top); + + s5k5baf_rescale(&r, &state->crop_source, &state->crop_sink, + &state->compose); + s5k5baf_write_seq(state, REG_G_PREVZOOM_IN_WIDTH, r.width, r.height, + r.left, r.top); + + s5k5baf_synchronize(state, 500, REG_G_INPUTS_CHANGE_REQ); + s5k5baf_synchronize(state, 500, REG_G_PREV_CFG_BYPASS_CHANGED); + err = s5k5baf_get_cfg_error(state); + ret = s5k5baf_clear_error(state); + if (ret < 0) + return ret; + + switch (err) { + case 0: + break; + case CFG_ERROR_RANGE: + /* retry crop with frame interval set to max */ + s5k5baf_hw_set_fiv(state, S5K5BAF_MAX_FR_TIME); + err = s5k5baf_get_cfg_error(state); + ret = s5k5baf_clear_error(state); + if (ret < 0) + return ret; + if (err) { + v4l2_err(&state->sd, + "crop error on max frame interval: %d\n", err); + state->error = -EINVAL; + } + s5k5baf_hw_set_fiv(state, state->req_fiv); + s5k5baf_hw_validate_cfg(state); + break; + default: + v4l2_err(&state->sd, "crop error: %d\n", err); + return -EINVAL; + } + + if (!state->apply_cfg) + return 0; + + p = &state->crop_source; + s5k5baf_write_seq(state, REG_P_OUT_WIDTH(0), p->width, p->height); + s5k5baf_hw_set_fiv(state, state->req_fiv); + s5k5baf_hw_validate_cfg(state); + + return s5k5baf_clear_error(state); +} + +static void s5k5baf_hw_set_config(struct s5k5baf *state) +{ + u16 reg_fmt = s5k5baf_formats[state->pixfmt].reg_p_fmt; + struct v4l2_rect *r = &state->crop_source; + + s5k5baf_write_seq(state, REG_P_OUT_WIDTH(0), + r->width, r->height, reg_fmt, + PCLK_MAX_FREQ >> 2, PCLK_MIN_FREQ >> 2, + PVI_MASK_MIPI, CLK_MIPI_INDEX, + FR_RATE_FIXED, FR_RATE_Q_DYNAMIC, + state->req_fiv, S5K5BAF_MIN_FR_TIME); + s5k5baf_hw_sync_cfg(state); + s5k5baf_hw_validate_cfg(state); +} + + +static void s5k5baf_hw_set_test_pattern(struct s5k5baf *state, int id) +{ + s5k5baf_i2c_write(state, REG_PATTERN_WIDTH, 800); + s5k5baf_i2c_write(state, REG_PATTERN_HEIGHT, 511); + s5k5baf_i2c_write(state, REG_PATTERN_PARAM, 0); + s5k5baf_i2c_write(state, REG_PATTERN_SET, id); +} + +static void s5k5baf_gpio_assert(struct s5k5baf *state, int id) +{ + struct s5k5baf_gpio *gpio = &state->gpios[id]; + + gpio_set_value(gpio->gpio, gpio->level); +} + +static void s5k5baf_gpio_deassert(struct s5k5baf *state, int id) +{ + struct s5k5baf_gpio *gpio = &state->gpios[id]; + + gpio_set_value(gpio->gpio, !gpio->level); +} + +static int s5k5baf_power_on(struct s5k5baf *state) +{ + int ret; + + ret = regulator_bulk_enable(S5K5BAF_NUM_SUPPLIES, state->supplies); + if (ret < 0) + goto err; + + ret = clk_set_rate(state->clock, state->mclk_frequency); + if (ret < 0) + goto err_reg_dis; + + ret = clk_prepare_enable(state->clock); + if (ret < 0) + goto err_reg_dis; + + v4l2_dbg(1, debug, &state->sd, "clock frequency: %ld\n", + clk_get_rate(state->clock)); + + s5k5baf_gpio_deassert(state, STBY); + usleep_range(50, 100); + s5k5baf_gpio_deassert(state, RSET); + return 0; + +err_reg_dis: + regulator_bulk_disable(S5K5BAF_NUM_SUPPLIES, state->supplies); +err: + v4l2_err(&state->sd, "%s() failed (%d)\n", __func__, ret); + return ret; +} + +static int s5k5baf_power_off(struct s5k5baf *state) +{ + int ret; + + state->streaming = 0; + state->apply_cfg = 0; + state->apply_crop = 0; + + s5k5baf_gpio_assert(state, RSET); + s5k5baf_gpio_assert(state, STBY); + + if (!IS_ERR(state->clock)) + clk_disable_unprepare(state->clock); + + ret = regulator_bulk_disable(S5K5BAF_NUM_SUPPLIES, + state->supplies); + if (ret < 0) + v4l2_err(&state->sd, "failed to disable regulators\n"); + + return 0; +} + +static void s5k5baf_hw_init(struct s5k5baf *state) +{ + s5k5baf_i2c_write(state, AHB_MSB_ADDR_PTR, PAGE_IF_HW); + s5k5baf_i2c_write(state, REG_CLEAR_HOST_INT, 0); + s5k5baf_i2c_write(state, REG_SW_LOAD_COMPLETE, 1); + s5k5baf_i2c_write(state, REG_CMDRD_PAGE, PAGE_IF_SW); + s5k5baf_i2c_write(state, REG_CMDWR_PAGE, PAGE_IF_SW); +} + +/* + * V4L2 subdev core and video operations + */ + +static void s5k5baf_initialize_data(struct s5k5baf *state) +{ + state->pixfmt = 0; + state->req_fiv = 10000 / 15; + state->fiv = state->req_fiv; + state->valid_auto_alg = 0; +} + +static int s5k5baf_load_setfile(struct s5k5baf *state) +{ + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, S5K5BAF_FW_FILENAME, &c->dev); + if (ret < 0) { + dev_warn(&c->dev, "firmware file (%s) not loaded\n", + S5K5BAF_FW_FILENAME); + return ret; + } + + ret = s5k5baf_fw_parse(&c->dev, &state->fw, fw->size / 2, + (__le16 *)fw->data); + + release_firmware(fw); + + return ret; +} + +static int s5k5baf_set_power(struct v4l2_subdev *sd, int on) +{ + struct s5k5baf *state = to_s5k5baf(sd); + int ret = 0; + + mutex_lock(&state->lock); + + if (state->power != !on) + goto out; + + if (on) { + if (state->fw == NULL) + s5k5baf_load_setfile(state); + + s5k5baf_initialize_data(state); + ret = s5k5baf_power_on(state); + if (ret < 0) + goto out; + + s5k5baf_hw_init(state); + s5k5baf_hw_patch(state); + s5k5baf_i2c_write(state, REG_SET_HOST_INT, 1); + s5k5baf_hw_set_clocks(state); + + ret = s5k5baf_hw_set_video_bus(state); + if (ret < 0) + goto out; + + s5k5baf_hw_set_cis(state); + s5k5baf_hw_set_ccm(state); + + ret = s5k5baf_clear_error(state); + if (!ret) + state->power++; + } else { + s5k5baf_power_off(state); + state->power--; + } + +out: + mutex_unlock(&state->lock); + + if (!ret && on) + ret = v4l2_ctrl_handler_setup(&state->ctrls.handler); + + return ret; +} + +static void s5k5baf_hw_set_stream(struct s5k5baf *state, int enable) +{ + s5k5baf_write_seq(state, REG_G_ENABLE_PREV, enable, 1); +} + +static int s5k5baf_s_stream(struct v4l2_subdev *sd, int on) +{ + struct s5k5baf *state = to_s5k5baf(sd); + int ret; + + mutex_lock(&state->lock); + + if (state->streaming == !!on) { + ret = 0; + goto out; + } + + if (on) { + s5k5baf_hw_set_config(state); + ret = s5k5baf_hw_set_crop_rects(state); + if (ret < 0) + goto out; + s5k5baf_hw_set_stream(state, 1); + s5k5baf_i2c_write(state, 0xb0cc, 0x000b); + } else { + s5k5baf_hw_set_stream(state, 0); + } + ret = s5k5baf_clear_error(state); + if (!ret) + state->streaming = !state->streaming; + +out: + mutex_unlock(&state->lock); + + return ret; +} + +static int s5k5baf_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct s5k5baf *state = to_s5k5baf(sd); + + mutex_lock(&state->lock); + fi->interval.numerator = state->fiv; + fi->interval.denominator = 10000; + mutex_unlock(&state->lock); + + return 0; +} + +static void s5k5baf_set_frame_interval(struct s5k5baf *state, + struct v4l2_subdev_frame_interval *fi) +{ + struct v4l2_fract *i = &fi->interval; + + if (fi->interval.denominator == 0) + state->req_fiv = S5K5BAF_MAX_FR_TIME; + else + state->req_fiv = clamp_t(u32, + i->numerator * 10000 / i->denominator, + S5K5BAF_MIN_FR_TIME, + S5K5BAF_MAX_FR_TIME); + + state->fiv = state->req_fiv; + if (state->apply_cfg) { + s5k5baf_hw_set_fiv(state, state->req_fiv); + s5k5baf_hw_validate_cfg(state); + } + *i = (struct v4l2_fract){ state->fiv, 10000 }; + if (state->fiv == state->req_fiv) + v4l2_info(&state->sd, "frame interval changed to %d00us\n", + state->fiv); +} + +static int s5k5baf_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct s5k5baf *state = to_s5k5baf(sd); + + mutex_lock(&state->lock); + s5k5baf_set_frame_interval(state, fi); + mutex_unlock(&state->lock); + return 0; +} + +/* + * V4L2 subdev pad level and video operations + */ +static int s5k5baf_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + if (fie->index > S5K5BAF_MAX_FR_TIME - S5K5BAF_MIN_FR_TIME || + fie->pad != PAD_CIS) + return -EINVAL; + + v4l_bound_align_image(&fie->width, S5K5BAF_WIN_WIDTH_MIN, + S5K5BAF_CIS_WIDTH, 1, + &fie->height, S5K5BAF_WIN_HEIGHT_MIN, + S5K5BAF_CIS_HEIGHT, 1, 0); + + fie->interval.numerator = S5K5BAF_MIN_FR_TIME + fie->index; + fie->interval.denominator = 10000; + + return 0; +} + +static int s5k5baf_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad == PAD_CIS) { + if (code->index > 0) + return -EINVAL; + code->code = MEDIA_BUS_FMT_FIXED; + return 0; + } + + if (code->index >= ARRAY_SIZE(s5k5baf_formats)) + return -EINVAL; + + code->code = s5k5baf_formats[code->index].code; + return 0; +} + +static int s5k5baf_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + int i; + + if (fse->index > 0) + return -EINVAL; + + if (fse->pad == PAD_CIS) { + fse->code = MEDIA_BUS_FMT_FIXED; + fse->min_width = S5K5BAF_CIS_WIDTH; + fse->max_width = S5K5BAF_CIS_WIDTH; + fse->min_height = S5K5BAF_CIS_HEIGHT; + fse->max_height = S5K5BAF_CIS_HEIGHT; + return 0; + } + + i = ARRAY_SIZE(s5k5baf_formats); + while (--i) + if (fse->code == s5k5baf_formats[i].code) + break; + fse->code = s5k5baf_formats[i].code; + fse->min_width = S5K5BAF_WIN_WIDTH_MIN; + fse->max_width = S5K5BAF_CIS_WIDTH; + fse->max_height = S5K5BAF_WIN_HEIGHT_MIN; + fse->min_height = S5K5BAF_CIS_HEIGHT; + + return 0; +} + +static void s5k5baf_try_cis_format(struct v4l2_mbus_framefmt *mf) +{ + mf->width = S5K5BAF_CIS_WIDTH; + mf->height = S5K5BAF_CIS_HEIGHT; + mf->code = MEDIA_BUS_FMT_FIXED; + mf->colorspace = V4L2_COLORSPACE_JPEG; + mf->field = V4L2_FIELD_NONE; +} + +static int s5k5baf_try_isp_format(struct v4l2_mbus_framefmt *mf) +{ + int pixfmt; + + v4l_bound_align_image(&mf->width, S5K5BAF_WIN_WIDTH_MIN, + S5K5BAF_CIS_WIDTH, 1, + &mf->height, S5K5BAF_WIN_HEIGHT_MIN, + S5K5BAF_CIS_HEIGHT, 1, 0); + + pixfmt = s5k5baf_find_pixfmt(mf); + + mf->colorspace = s5k5baf_formats[pixfmt].colorspace; + mf->code = s5k5baf_formats[pixfmt].code; + mf->field = V4L2_FIELD_NONE; + + return pixfmt; +} + +static int s5k5baf_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct s5k5baf *state = to_s5k5baf(sd); + const struct s5k5baf_pixfmt *pixfmt; + struct v4l2_mbus_framefmt *mf; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + fmt->format = *mf; + return 0; + } + + mf = &fmt->format; + if (fmt->pad == PAD_CIS) { + s5k5baf_try_cis_format(mf); + return 0; + } + mf->field = V4L2_FIELD_NONE; + mutex_lock(&state->lock); + pixfmt = &s5k5baf_formats[state->pixfmt]; + mf->width = state->crop_source.width; + mf->height = state->crop_source.height; + mf->code = pixfmt->code; + mf->colorspace = pixfmt->colorspace; + mutex_unlock(&state->lock); + + return 0; +} + +static int s5k5baf_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mf = &fmt->format; + struct s5k5baf *state = to_s5k5baf(sd); + const struct s5k5baf_pixfmt *pixfmt; + int ret = 0; + + mf->field = V4L2_FIELD_NONE; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = *mf; + return 0; + } + + if (fmt->pad == PAD_CIS) { + s5k5baf_try_cis_format(mf); + return 0; + } + + mutex_lock(&state->lock); + + if (state->streaming) { + mutex_unlock(&state->lock); + return -EBUSY; + } + + state->pixfmt = s5k5baf_try_isp_format(mf); + pixfmt = &s5k5baf_formats[state->pixfmt]; + mf->code = pixfmt->code; + mf->colorspace = pixfmt->colorspace; + mf->width = state->crop_source.width; + mf->height = state->crop_source.height; + + mutex_unlock(&state->lock); + return ret; +} + +enum selection_rect { R_CIS, R_CROP_SINK, R_COMPOSE, R_CROP_SOURCE, R_INVALID }; + +static enum selection_rect s5k5baf_get_sel_rect(u32 pad, u32 target) +{ + switch (target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + return pad ? R_COMPOSE : R_CIS; + case V4L2_SEL_TGT_CROP: + return pad ? R_CROP_SOURCE : R_CROP_SINK; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + return pad ? R_INVALID : R_CROP_SINK; + case V4L2_SEL_TGT_COMPOSE: + return pad ? R_INVALID : R_COMPOSE; + default: + return R_INVALID; + } +} + +static int s5k5baf_is_bound_target(u32 target) +{ + return target == V4L2_SEL_TGT_CROP_BOUNDS || + target == V4L2_SEL_TGT_COMPOSE_BOUNDS; +} + +static int s5k5baf_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + enum selection_rect rtype; + struct s5k5baf *state = to_s5k5baf(sd); + + rtype = s5k5baf_get_sel_rect(sel->pad, sel->target); + + switch (rtype) { + case R_INVALID: + return -EINVAL; + case R_CIS: + sel->r = s5k5baf_cis_rect; + return 0; + default: + break; + } + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + if (rtype == R_COMPOSE) + sel->r = *v4l2_subdev_get_try_compose(sd, cfg, sel->pad); + else + sel->r = *v4l2_subdev_get_try_crop(sd, cfg, sel->pad); + return 0; + } + + mutex_lock(&state->lock); + switch (rtype) { + case R_CROP_SINK: + sel->r = state->crop_sink; + break; + case R_COMPOSE: + sel->r = state->compose; + break; + case R_CROP_SOURCE: + sel->r = state->crop_source; + break; + default: + break; + } + if (s5k5baf_is_bound_target(sel->target)) { + sel->r.left = 0; + sel->r.top = 0; + } + mutex_unlock(&state->lock); + + return 0; +} + +/* bounds range [start, start+len) to [0, max) and aligns to 2 */ +static void s5k5baf_bound_range(u32 *start, u32 *len, u32 max) +{ + if (*len > max) + *len = max; + if (*start + *len > max) + *start = max - *len; + *start &= ~1; + *len &= ~1; + if (*len < S5K5BAF_WIN_WIDTH_MIN) + *len = S5K5BAF_WIN_WIDTH_MIN; +} + +static void s5k5baf_bound_rect(struct v4l2_rect *r, u32 width, u32 height) +{ + s5k5baf_bound_range(&r->left, &r->width, width); + s5k5baf_bound_range(&r->top, &r->height, height); +} + +static void s5k5baf_set_rect_and_adjust(struct v4l2_rect **rects, + enum selection_rect first, + struct v4l2_rect *v) +{ + struct v4l2_rect *r, *br; + enum selection_rect i = first; + + *rects[first] = *v; + do { + r = rects[i]; + br = rects[i - 1]; + s5k5baf_bound_rect(r, br->width, br->height); + } while (++i != R_INVALID); + *v = *rects[first]; +} + +static bool s5k5baf_cmp_rect(const struct v4l2_rect *r1, + const struct v4l2_rect *r2) +{ + return !memcmp(r1, r2, sizeof(*r1)); +} + +static int s5k5baf_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + static enum selection_rect rtype; + struct s5k5baf *state = to_s5k5baf(sd); + struct v4l2_rect **rects; + int ret = 0; + + rtype = s5k5baf_get_sel_rect(sel->pad, sel->target); + if (rtype == R_INVALID || s5k5baf_is_bound_target(sel->target)) + return -EINVAL; + + /* allow only scaling on compose */ + if (rtype == R_COMPOSE) { + sel->r.left = 0; + sel->r.top = 0; + } + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + rects = (struct v4l2_rect * []) { + &s5k5baf_cis_rect, + v4l2_subdev_get_try_crop(sd, cfg, PAD_CIS), + v4l2_subdev_get_try_compose(sd, cfg, PAD_CIS), + v4l2_subdev_get_try_crop(sd, cfg, PAD_OUT) + }; + s5k5baf_set_rect_and_adjust(rects, rtype, &sel->r); + return 0; + } + + rects = (struct v4l2_rect * []) { + &s5k5baf_cis_rect, + &state->crop_sink, + &state->compose, + &state->crop_source + }; + mutex_lock(&state->lock); + if (state->streaming) { + /* adjust sel->r to avoid output resolution change */ + if (rtype < R_CROP_SOURCE) { + if (sel->r.width < state->crop_source.width) + sel->r.width = state->crop_source.width; + if (sel->r.height < state->crop_source.height) + sel->r.height = state->crop_source.height; + } else { + sel->r.width = state->crop_source.width; + sel->r.height = state->crop_source.height; + } + } + s5k5baf_set_rect_and_adjust(rects, rtype, &sel->r); + if (!s5k5baf_cmp_rect(&state->crop_sink, &s5k5baf_cis_rect) || + !s5k5baf_cmp_rect(&state->compose, &s5k5baf_cis_rect)) + state->apply_crop = 1; + if (state->streaming) + ret = s5k5baf_hw_set_crop_rects(state); + mutex_unlock(&state->lock); + + return ret; +} + +static const struct v4l2_subdev_pad_ops s5k5baf_cis_pad_ops = { + .enum_mbus_code = s5k5baf_enum_mbus_code, + .enum_frame_size = s5k5baf_enum_frame_size, + .get_fmt = s5k5baf_get_fmt, + .set_fmt = s5k5baf_set_fmt, +}; + +static const struct v4l2_subdev_pad_ops s5k5baf_pad_ops = { + .enum_mbus_code = s5k5baf_enum_mbus_code, + .enum_frame_size = s5k5baf_enum_frame_size, + .enum_frame_interval = s5k5baf_enum_frame_interval, + .get_fmt = s5k5baf_get_fmt, + .set_fmt = s5k5baf_set_fmt, + .get_selection = s5k5baf_get_selection, + .set_selection = s5k5baf_set_selection, +}; + +static const struct v4l2_subdev_video_ops s5k5baf_video_ops = { + .g_frame_interval = s5k5baf_g_frame_interval, + .s_frame_interval = s5k5baf_s_frame_interval, + .s_stream = s5k5baf_s_stream, +}; + +/* + * V4L2 subdev controls + */ + +static int s5k5baf_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct s5k5baf *state = to_s5k5baf(sd); + int ret; + + v4l2_dbg(1, debug, sd, "ctrl: %s, value: %d\n", ctrl->name, ctrl->val); + + mutex_lock(&state->lock); + + if (state->power == 0) + goto unlock; + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + s5k5baf_hw_set_awb(state, ctrl->val); + break; + + case V4L2_CID_BRIGHTNESS: + s5k5baf_write(state, REG_USER_BRIGHTNESS, ctrl->val); + break; + + case V4L2_CID_COLORFX: + s5k5baf_hw_set_colorfx(state, ctrl->val); + break; + + case V4L2_CID_CONTRAST: + s5k5baf_write(state, REG_USER_CONTRAST, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + s5k5baf_hw_set_auto_exposure(state, ctrl->val); + break; + + case V4L2_CID_HFLIP: + s5k5baf_hw_set_mirror(state); + break; + + case V4L2_CID_POWER_LINE_FREQUENCY: + s5k5baf_hw_set_anti_flicker(state, ctrl->val); + break; + + case V4L2_CID_SATURATION: + s5k5baf_write(state, REG_USER_SATURATION, ctrl->val); + break; + + case V4L2_CID_SHARPNESS: + s5k5baf_write(state, REG_USER_SHARPBLUR, ctrl->val); + break; + + case V4L2_CID_WHITE_BALANCE_TEMPERATURE: + s5k5baf_write(state, REG_P_COLORTEMP(0), ctrl->val); + if (state->apply_cfg) + s5k5baf_hw_sync_cfg(state); + break; + + case V4L2_CID_TEST_PATTERN: + s5k5baf_hw_set_test_pattern(state, ctrl->val); + break; + } +unlock: + ret = s5k5baf_clear_error(state); + mutex_unlock(&state->lock); + return ret; +} + +static const struct v4l2_ctrl_ops s5k5baf_ctrl_ops = { + .s_ctrl = s5k5baf_s_ctrl, +}; + +static const char * const s5k5baf_test_pattern_menu[] = { + "Disabled", + "Blank", + "Bars", + "Gradients", + "Textile", + "Textile2", + "Squares" +}; + +static int s5k5baf_initialize_ctrls(struct s5k5baf *state) +{ + const struct v4l2_ctrl_ops *ops = &s5k5baf_ctrl_ops; + struct s5k5baf_ctrls *ctrls = &state->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int ret; + + ret = v4l2_ctrl_handler_init(hdl, 16); + if (ret < 0) { + v4l2_err(&state->sd, "cannot init ctrl handler (%d)\n", ret); + return ret; + } + + /* Auto white balance cluster */ + ctrls->awb = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + ctrls->gain_red = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, + 0, 255, 1, S5K5BAF_GAIN_RED_DEF); + ctrls->gain_blue = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, + 0, 255, 1, S5K5BAF_GAIN_BLUE_DEF); + v4l2_ctrl_auto_cluster(3, &ctrls->awb, 0, false); + + ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_cluster(2, &ctrls->hflip); + + ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, V4L2_EXPOSURE_AUTO); + /* Exposure time: x 1 us */ + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + 0, 6000000U, 1, 100000U); + /* Total gain: 256 <=> 1x */ + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, + 0, 256, 1, 256); + v4l2_ctrl_auto_cluster(3, &ctrls->auto_exp, 0, false); + + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO); + + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_COLORFX, + V4L2_COLORFX_SKY_BLUE, ~0x6f, V4L2_COLORFX_NONE); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_WHITE_BALANCE_TEMPERATURE, + 0, 256, 1, 0); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SHARPNESS, -127, 127, 1, 0); + + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(s5k5baf_test_pattern_menu) - 1, + 0, 0, s5k5baf_test_pattern_menu); + + if (hdl->error) { + v4l2_err(&state->sd, "error creating controls (%d)\n", + hdl->error); + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + state->sd.ctrl_handler = hdl; + return 0; +} + +/* + * V4L2 subdev internal operations + */ +static int s5k5baf_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_get_try_format(sd, fh->pad, PAD_CIS); + s5k5baf_try_cis_format(mf); + + if (s5k5baf_is_cis_subdev(sd)) + return 0; + + mf = v4l2_subdev_get_try_format(sd, fh->pad, PAD_OUT); + mf->colorspace = s5k5baf_formats[0].colorspace; + mf->code = s5k5baf_formats[0].code; + mf->width = s5k5baf_cis_rect.width; + mf->height = s5k5baf_cis_rect.height; + mf->field = V4L2_FIELD_NONE; + + *v4l2_subdev_get_try_crop(sd, fh->pad, PAD_CIS) = s5k5baf_cis_rect; + *v4l2_subdev_get_try_compose(sd, fh->pad, PAD_CIS) = s5k5baf_cis_rect; + *v4l2_subdev_get_try_crop(sd, fh->pad, PAD_OUT) = s5k5baf_cis_rect; + + return 0; +} + +static int s5k5baf_check_fw_revision(struct s5k5baf *state) +{ + u16 api_ver = 0, fw_rev = 0, s_id = 0; + int ret; + + api_ver = s5k5baf_read(state, REG_FW_APIVER); + fw_rev = s5k5baf_read(state, REG_FW_REVISION) & 0xff; + s_id = s5k5baf_read(state, REG_FW_SENSOR_ID); + ret = s5k5baf_clear_error(state); + if (ret < 0) + return ret; + + v4l2_info(&state->sd, "FW API=%#x, revision=%#x sensor_id=%#x\n", + api_ver, fw_rev, s_id); + + if (api_ver != S5K5BAF_FW_APIVER) { + v4l2_err(&state->sd, "FW API version not supported\n"); + return -ENODEV; + } + + return 0; +} + +static int s5k5baf_registered(struct v4l2_subdev *sd) +{ + struct s5k5baf *state = to_s5k5baf(sd); + int ret; + + ret = v4l2_device_register_subdev(sd->v4l2_dev, &state->cis_sd); + if (ret < 0) + v4l2_err(sd, "failed to register subdev %s\n", + state->cis_sd.name); + else + ret = media_create_pad_link(&state->cis_sd.entity, PAD_CIS, + &state->sd.entity, PAD_CIS, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + return ret; +} + +static void s5k5baf_unregistered(struct v4l2_subdev *sd) +{ + struct s5k5baf *state = to_s5k5baf(sd); + v4l2_device_unregister_subdev(&state->cis_sd); +} + +static const struct v4l2_subdev_ops s5k5baf_cis_subdev_ops = { + .pad = &s5k5baf_cis_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops s5k5baf_cis_subdev_internal_ops = { + .open = s5k5baf_open, +}; + +static const struct v4l2_subdev_internal_ops s5k5baf_subdev_internal_ops = { + .registered = s5k5baf_registered, + .unregistered = s5k5baf_unregistered, + .open = s5k5baf_open, +}; + +static const struct v4l2_subdev_core_ops s5k5baf_core_ops = { + .s_power = s5k5baf_set_power, + .log_status = v4l2_ctrl_subdev_log_status, +}; + +static const struct v4l2_subdev_ops s5k5baf_subdev_ops = { + .core = &s5k5baf_core_ops, + .pad = &s5k5baf_pad_ops, + .video = &s5k5baf_video_ops, +}; + +static int s5k5baf_configure_gpios(struct s5k5baf *state) +{ + static const char * const name[] = { "S5K5BAF_STBY", "S5K5BAF_RST" }; + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + struct s5k5baf_gpio *g = state->gpios; + int ret, i; + + for (i = 0; i < NUM_GPIOS; ++i) { + int flags = GPIOF_DIR_OUT; + if (g[i].level) + flags |= GPIOF_INIT_HIGH; + ret = devm_gpio_request_one(&c->dev, g[i].gpio, flags, name[i]); + if (ret < 0) { + v4l2_err(c, "failed to request gpio %s\n", name[i]); + return ret; + } + } + return 0; +} + +static int s5k5baf_parse_gpios(struct s5k5baf_gpio *gpios, struct device *dev) +{ + static const char * const names[] = { + "stbyn-gpios", + "rstn-gpios", + }; + struct device_node *node = dev->of_node; + enum of_gpio_flags flags; + int ret, i; + + for (i = 0; i < NUM_GPIOS; ++i) { + ret = of_get_named_gpio_flags(node, names[i], 0, &flags); + if (ret < 0) { + dev_err(dev, "no %s GPIO pin provided\n", names[i]); + return ret; + } + gpios[i].gpio = ret; + gpios[i].level = !(flags & OF_GPIO_ACTIVE_LOW); + } + + return 0; +} + +static int s5k5baf_parse_device_node(struct s5k5baf *state, struct device *dev) +{ + struct device_node *node = dev->of_node; + struct device_node *node_ep; + struct v4l2_fwnode_endpoint ep = { .bus_type = 0 }; + int ret; + + if (!node) { + dev_err(dev, "no device-tree node provided\n"); + return -EINVAL; + } + + ret = of_property_read_u32(node, "clock-frequency", + &state->mclk_frequency); + if (ret < 0) { + state->mclk_frequency = S5K5BAF_DEFAULT_MCLK_FREQ; + dev_info(dev, "using default %u Hz clock frequency\n", + state->mclk_frequency); + } + + ret = s5k5baf_parse_gpios(state->gpios, dev); + if (ret < 0) + return ret; + + node_ep = of_graph_get_next_endpoint(node, NULL); + if (!node_ep) { + dev_err(dev, "no endpoint defined at node %pOF\n", node); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node_ep), &ep); + of_node_put(node_ep); + if (ret) + return ret; + + state->bus_type = ep.bus_type; + + switch (state->bus_type) { + case V4L2_MBUS_CSI2_DPHY: + state->nlanes = ep.bus.mipi_csi2.num_data_lanes; + break; + case V4L2_MBUS_PARALLEL: + break; + default: + dev_err(dev, "unsupported bus in endpoint defined at node %pOF\n", + node); + return -EINVAL; + } + + return 0; +} + +static int s5k5baf_configure_subdevs(struct s5k5baf *state, + struct i2c_client *c) +{ + struct v4l2_subdev *sd; + int ret; + + sd = &state->cis_sd; + v4l2_subdev_init(sd, &s5k5baf_cis_subdev_ops); + sd->owner = THIS_MODULE; + v4l2_set_subdevdata(sd, state); + snprintf(sd->name, sizeof(sd->name), "S5K5BAF-CIS %d-%04x", + i2c_adapter_id(c->adapter), c->addr); + + sd->internal_ops = &s5k5baf_cis_subdev_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + state->cis_pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, NUM_CIS_PADS, &state->cis_pad); + if (ret < 0) + goto err; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, c, &s5k5baf_subdev_ops); + snprintf(sd->name, sizeof(sd->name), "S5K5BAF-ISP %d-%04x", + i2c_adapter_id(c->adapter), c->addr); + + sd->internal_ops = &s5k5baf_subdev_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + state->pads[PAD_CIS].flags = MEDIA_PAD_FL_SINK; + state->pads[PAD_OUT].flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN; + ret = media_entity_pads_init(&sd->entity, NUM_ISP_PADS, state->pads); + + if (!ret) + return 0; + + media_entity_cleanup(&state->cis_sd.entity); +err: + dev_err(&c->dev, "cannot init media entity %s\n", sd->name); + return ret; +} + +static int s5k5baf_configure_regulators(struct s5k5baf *state) +{ + struct i2c_client *c = v4l2_get_subdevdata(&state->sd); + int ret; + int i; + + for (i = 0; i < S5K5BAF_NUM_SUPPLIES; i++) + state->supplies[i].supply = s5k5baf_supply_names[i]; + + ret = devm_regulator_bulk_get(&c->dev, S5K5BAF_NUM_SUPPLIES, + state->supplies); + if (ret < 0) + v4l2_err(c, "failed to get regulators\n"); + return ret; +} + +static int s5k5baf_probe(struct i2c_client *c) +{ + struct s5k5baf *state; + int ret; + + state = devm_kzalloc(&c->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mutex_init(&state->lock); + state->crop_sink = s5k5baf_cis_rect; + state->compose = s5k5baf_cis_rect; + state->crop_source = s5k5baf_cis_rect; + + ret = s5k5baf_parse_device_node(state, &c->dev); + if (ret < 0) + return ret; + + ret = s5k5baf_configure_subdevs(state, c); + if (ret < 0) + return ret; + + ret = s5k5baf_configure_gpios(state); + if (ret < 0) + goto err_me; + + ret = s5k5baf_configure_regulators(state); + if (ret < 0) + goto err_me; + + state->clock = devm_clk_get(state->sd.dev, S5K5BAF_CLK_NAME); + if (IS_ERR(state->clock)) { + ret = -EPROBE_DEFER; + goto err_me; + } + + ret = s5k5baf_power_on(state); + if (ret < 0) { + ret = -EPROBE_DEFER; + goto err_me; + } + s5k5baf_hw_init(state); + ret = s5k5baf_check_fw_revision(state); + + s5k5baf_power_off(state); + if (ret < 0) + goto err_me; + + ret = s5k5baf_initialize_ctrls(state); + if (ret < 0) + goto err_me; + + ret = v4l2_async_register_subdev(&state->sd); + if (ret < 0) + goto err_ctrl; + + return 0; + +err_ctrl: + v4l2_ctrl_handler_free(state->sd.ctrl_handler); +err_me: + media_entity_cleanup(&state->sd.entity); + media_entity_cleanup(&state->cis_sd.entity); + return ret; +} + +static int s5k5baf_remove(struct i2c_client *c) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct s5k5baf *state = to_s5k5baf(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + media_entity_cleanup(&sd->entity); + + sd = &state->cis_sd; + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct i2c_device_id s5k5baf_id[] = { + { S5K5BAF_DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, s5k5baf_id); + +static const struct of_device_id s5k5baf_of_match[] = { + { .compatible = "samsung,s5k5baf" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5k5baf_of_match); + +static struct i2c_driver s5k5baf_i2c_driver = { + .driver = { + .of_match_table = s5k5baf_of_match, + .name = S5K5BAF_DRIVER_NAME + }, + .probe_new = s5k5baf_probe, + .remove = s5k5baf_remove, + .id_table = s5k5baf_id, +}; + +module_i2c_driver(s5k5baf_i2c_driver); + +MODULE_DESCRIPTION("Samsung S5K5BAF(X) UXGA camera driver"); +MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/s5k6a3.c b/drivers/media/i2c/s5k6a3.c new file mode 100644 index 000000000..f26c168ef --- /dev/null +++ b/drivers/media/i2c/s5k6a3.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung S5K6A3 image sensor driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <media/v4l2-async.h> +#include <media/v4l2-subdev.h> + +#define S5K6A3_SENSOR_MAX_WIDTH 1412 +#define S5K6A3_SENSOR_MAX_HEIGHT 1412 +#define S5K6A3_SENSOR_MIN_WIDTH 32 +#define S5K6A3_SENSOR_MIN_HEIGHT 32 + +#define S5K6A3_DEFAULT_WIDTH 1296 +#define S5K6A3_DEFAULT_HEIGHT 732 + +#define S5K6A3_DRV_NAME "S5K6A3" +#define S5K6A3_CLK_NAME "extclk" +#define S5K6A3_DEFAULT_CLK_FREQ 24000000U + +enum { + S5K6A3_SUPP_VDDA, + S5K6A3_SUPP_VDDIO, + S5K6A3_SUPP_AFVDD, + S5K6A3_NUM_SUPPLIES, +}; + +/** + * struct s5k6a3 - fimc-is sensor data structure + * @dev: pointer to this I2C client device structure + * @subdev: the image sensor's v4l2 subdev + * @pad: subdev media source pad + * @supplies: image sensor's voltage regulator supplies + * @gpio_reset: GPIO connected to the sensor's reset pin + * @lock: mutex protecting the structure's members below + * @format: media bus format at the sensor's source pad + * @clock: pointer to &struct clk. + * @clock_frequency: clock frequency + * @power_count: stores state if device is powered + */ +struct s5k6a3 { + struct device *dev; + struct v4l2_subdev subdev; + struct media_pad pad; + struct regulator_bulk_data supplies[S5K6A3_NUM_SUPPLIES]; + int gpio_reset; + struct mutex lock; + struct v4l2_mbus_framefmt format; + struct clk *clock; + u32 clock_frequency; + int power_count; +}; + +static const char * const s5k6a3_supply_names[] = { + [S5K6A3_SUPP_VDDA] = "svdda", + [S5K6A3_SUPP_VDDIO] = "svddio", + [S5K6A3_SUPP_AFVDD] = "afvdd", +}; + +static inline struct s5k6a3 *sd_to_s5k6a3(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s5k6a3, subdev); +} + +static const struct v4l2_mbus_framefmt s5k6a3_formats[] = { + { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .colorspace = V4L2_COLORSPACE_SRGB, + .field = V4L2_FIELD_NONE, + } +}; + +static const struct v4l2_mbus_framefmt *find_sensor_format( + struct v4l2_mbus_framefmt *mf) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s5k6a3_formats); i++) + if (mf->code == s5k6a3_formats[i].code) + return &s5k6a3_formats[i]; + + return &s5k6a3_formats[0]; +} + +static int s5k6a3_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(s5k6a3_formats)) + return -EINVAL; + + code->code = s5k6a3_formats[code->index].code; + return 0; +} + +static void s5k6a3_try_format(struct v4l2_mbus_framefmt *mf) +{ + const struct v4l2_mbus_framefmt *fmt; + + fmt = find_sensor_format(mf); + mf->code = fmt->code; + mf->field = V4L2_FIELD_NONE; + v4l_bound_align_image(&mf->width, S5K6A3_SENSOR_MIN_WIDTH, + S5K6A3_SENSOR_MAX_WIDTH, 0, + &mf->height, S5K6A3_SENSOR_MIN_HEIGHT, + S5K6A3_SENSOR_MAX_HEIGHT, 0, 0); +} + +static struct v4l2_mbus_framefmt *__s5k6a3_get_format( + struct s5k6a3 *sensor, struct v4l2_subdev_pad_config *cfg, + u32 pad, enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return cfg ? v4l2_subdev_get_try_format(&sensor->subdev, cfg, pad) : NULL; + + return &sensor->format; +} + +static int s5k6a3_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct s5k6a3 *sensor = sd_to_s5k6a3(sd); + struct v4l2_mbus_framefmt *mf; + + s5k6a3_try_format(&fmt->format); + + mf = __s5k6a3_get_format(sensor, cfg, fmt->pad, fmt->which); + if (mf) { + mutex_lock(&sensor->lock); + *mf = fmt->format; + mutex_unlock(&sensor->lock); + } + return 0; +} + +static int s5k6a3_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct s5k6a3 *sensor = sd_to_s5k6a3(sd); + struct v4l2_mbus_framefmt *mf; + + mf = __s5k6a3_get_format(sensor, cfg, fmt->pad, fmt->which); + + mutex_lock(&sensor->lock); + fmt->format = *mf; + mutex_unlock(&sensor->lock); + return 0; +} + +static const struct v4l2_subdev_pad_ops s5k6a3_pad_ops = { + .enum_mbus_code = s5k6a3_enum_mbus_code, + .get_fmt = s5k6a3_get_fmt, + .set_fmt = s5k6a3_set_fmt, +}; + +static int s5k6a3_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format = v4l2_subdev_get_try_format(sd, fh->pad, 0); + + *format = s5k6a3_formats[0]; + format->width = S5K6A3_DEFAULT_WIDTH; + format->height = S5K6A3_DEFAULT_HEIGHT; + + return 0; +} + +static const struct v4l2_subdev_internal_ops s5k6a3_sd_internal_ops = { + .open = s5k6a3_open, +}; + +static int __s5k6a3_power_on(struct s5k6a3 *sensor) +{ + int i = S5K6A3_SUPP_VDDA; + int ret; + + ret = clk_set_rate(sensor->clock, sensor->clock_frequency); + if (ret < 0) + return ret; + + ret = pm_runtime_get(sensor->dev); + if (ret < 0) + goto error_rpm_put; + + ret = regulator_enable(sensor->supplies[i].consumer); + if (ret < 0) + goto error_rpm_put; + + ret = clk_prepare_enable(sensor->clock); + if (ret < 0) + goto error_reg_dis; + + for (i++; i < S5K6A3_NUM_SUPPLIES; i++) { + ret = regulator_enable(sensor->supplies[i].consumer); + if (ret < 0) + goto error_reg_dis; + } + + gpio_set_value(sensor->gpio_reset, 1); + usleep_range(600, 800); + gpio_set_value(sensor->gpio_reset, 0); + usleep_range(600, 800); + gpio_set_value(sensor->gpio_reset, 1); + + /* Delay needed for the sensor initialization */ + msleep(20); + return 0; + +error_reg_dis: + for (--i; i >= 0; --i) + regulator_disable(sensor->supplies[i].consumer); +error_rpm_put: + pm_runtime_put(sensor->dev); + return ret; +} + +static int __s5k6a3_power_off(struct s5k6a3 *sensor) +{ + int i; + + gpio_set_value(sensor->gpio_reset, 0); + + for (i = S5K6A3_NUM_SUPPLIES - 1; i >= 0; i--) + regulator_disable(sensor->supplies[i].consumer); + + clk_disable_unprepare(sensor->clock); + pm_runtime_put(sensor->dev); + return 0; +} + +static int s5k6a3_s_power(struct v4l2_subdev *sd, int on) +{ + struct s5k6a3 *sensor = sd_to_s5k6a3(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + if (sensor->power_count == !on) { + if (on) + ret = __s5k6a3_power_on(sensor); + else + ret = __s5k6a3_power_off(sensor); + + if (ret == 0) + sensor->power_count += on ? 1 : -1; + } + + mutex_unlock(&sensor->lock); + return ret; +} + +static const struct v4l2_subdev_core_ops s5k6a3_core_ops = { + .s_power = s5k6a3_s_power, +}; + +static const struct v4l2_subdev_ops s5k6a3_subdev_ops = { + .core = &s5k6a3_core_ops, + .pad = &s5k6a3_pad_ops, +}; + +static int s5k6a3_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct s5k6a3 *sensor; + struct v4l2_subdev *sd; + int gpio, i, ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + mutex_init(&sensor->lock); + sensor->gpio_reset = -EINVAL; + sensor->clock = ERR_PTR(-EINVAL); + sensor->dev = dev; + + sensor->clock = devm_clk_get(sensor->dev, S5K6A3_CLK_NAME); + if (IS_ERR(sensor->clock)) + return PTR_ERR(sensor->clock); + + gpio = of_get_gpio_flags(dev->of_node, 0, NULL); + if (!gpio_is_valid(gpio)) + return gpio; + + ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_LOW, + S5K6A3_DRV_NAME); + if (ret < 0) + return ret; + + sensor->gpio_reset = gpio; + + if (of_property_read_u32(dev->of_node, "clock-frequency", + &sensor->clock_frequency)) { + sensor->clock_frequency = S5K6A3_DEFAULT_CLK_FREQ; + dev_info(dev, "using default %u Hz clock frequency\n", + sensor->clock_frequency); + } + + for (i = 0; i < S5K6A3_NUM_SUPPLIES; i++) + sensor->supplies[i].supply = s5k6a3_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, S5K6A3_NUM_SUPPLIES, + sensor->supplies); + if (ret < 0) + return ret; + + sd = &sensor->subdev; + v4l2_i2c_subdev_init(sd, client, &s5k6a3_subdev_ops); + sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->internal_ops = &s5k6a3_sd_internal_ops; + + sensor->format.code = s5k6a3_formats[0].code; + sensor->format.width = S5K6A3_DEFAULT_WIDTH; + sensor->format.height = S5K6A3_DEFAULT_HEIGHT; + + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad); + if (ret < 0) + return ret; + + pm_runtime_no_callbacks(dev); + pm_runtime_enable(dev); + + ret = v4l2_async_register_subdev(sd); + + if (ret < 0) { + pm_runtime_disable(&client->dev); + media_entity_cleanup(&sd->entity); + } + + return ret; +} + +static int s5k6a3_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + pm_runtime_disable(&client->dev); + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + return 0; +} + +static const struct i2c_device_id s5k6a3_ids[] = { + { } +}; +MODULE_DEVICE_TABLE(i2c, s5k6a3_ids); + +#ifdef CONFIG_OF +static const struct of_device_id s5k6a3_of_match[] = { + { .compatible = "samsung,s5k6a3" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s5k6a3_of_match); +#endif + +static struct i2c_driver s5k6a3_driver = { + .driver = { + .of_match_table = of_match_ptr(s5k6a3_of_match), + .name = S5K6A3_DRV_NAME, + }, + .probe_new = s5k6a3_probe, + .remove = s5k6a3_remove, + .id_table = s5k6a3_ids, +}; + +module_i2c_driver(s5k6a3_driver); + +MODULE_DESCRIPTION("S5K6A3 image sensor subdev driver"); +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/s5k6aa.c b/drivers/media/i2c/s5k6aa.c new file mode 100644 index 000000000..6516e205e --- /dev/null +++ b/drivers/media/i2c/s5k6aa.c @@ -0,0 +1,1650 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Samsung S5K6AAFX SXGA 1/6" 1.3M CMOS Image Sensor + * with embedded SoC ISP. + * + * Copyright (C) 2011, Samsung Electronics Co., Ltd. + * Sylwester Nawrocki <s.nawrocki@samsung.com> + * + * Based on a driver authored by Dongsoo Nathaniel Kim. + * Copyright (C) 2009, Dongsoo Nathaniel Kim <dongsoo45.kim@samsung.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/media.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-mediabus.h> +#include <media/i2c/s5k6aa.h> + +static int debug; +module_param(debug, int, 0644); + +#define DRIVER_NAME "S5K6AA" + +/* The token to indicate array termination */ +#define S5K6AA_TERM 0xffff +#define S5K6AA_OUT_WIDTH_DEF 640 +#define S5K6AA_OUT_HEIGHT_DEF 480 +#define S5K6AA_WIN_WIDTH_MAX 1280 +#define S5K6AA_WIN_HEIGHT_MAX 1024 +#define S5K6AA_WIN_WIDTH_MIN 8 +#define S5K6AA_WIN_HEIGHT_MIN 8 + +/* + * H/W register Interface (0xD0000000 - 0xD0000FFF) + */ +#define AHB_MSB_ADDR_PTR 0xfcfc +#define GEN_REG_OFFSH 0xd000 +#define REG_CMDWR_ADDRH 0x0028 +#define REG_CMDWR_ADDRL 0x002a +#define REG_CMDRD_ADDRH 0x002c +#define REG_CMDRD_ADDRL 0x002e +#define REG_CMDBUF0_ADDR 0x0f12 +#define REG_CMDBUF1_ADDR 0x0f10 + +/* + * Host S/W Register interface (0x70000000 - 0x70002000) + * The value of the two most significant address bytes is 0x7000, + * (HOST_SWIF_OFFS_H). The register addresses below specify 2 LSBs. + */ +#define HOST_SWIF_OFFSH 0x7000 + +/* Initialization parameters */ +/* Master clock frequency in KHz */ +#define REG_I_INCLK_FREQ_L 0x01b8 +#define REG_I_INCLK_FREQ_H 0x01ba +#define MIN_MCLK_FREQ_KHZ 6000U +#define MAX_MCLK_FREQ_KHZ 27000U +#define REG_I_USE_NPVI_CLOCKS 0x01c6 +#define REG_I_USE_NMIPI_CLOCKS 0x01c8 + +/* Clock configurations, n = 0..2. REG_I_* frequency unit is 4 kHz. */ +#define REG_I_OPCLK_4KHZ(n) ((n) * 6 + 0x01cc) +#define REG_I_MIN_OUTRATE_4KHZ(n) ((n) * 6 + 0x01ce) +#define REG_I_MAX_OUTRATE_4KHZ(n) ((n) * 6 + 0x01d0) +#define SYS_PLL_OUT_FREQ (48000000 / 4000) +#define PCLK_FREQ_MIN (24000000 / 4000) +#define PCLK_FREQ_MAX (48000000 / 4000) +#define REG_I_INIT_PARAMS_UPDATED 0x01e0 +#define REG_I_ERROR_INFO 0x01e2 + +/* General purpose parameters */ +#define REG_USER_BRIGHTNESS 0x01e4 +#define REG_USER_CONTRAST 0x01e6 +#define REG_USER_SATURATION 0x01e8 +#define REG_USER_SHARPBLUR 0x01ea + +#define REG_G_SPEC_EFFECTS 0x01ee +#define REG_G_ENABLE_PREV 0x01f0 +#define REG_G_ENABLE_PREV_CHG 0x01f2 +#define REG_G_NEW_CFG_SYNC 0x01f8 +#define REG_G_PREVZOOM_IN_WIDTH 0x020a +#define REG_G_PREVZOOM_IN_HEIGHT 0x020c +#define REG_G_PREVZOOM_IN_XOFFS 0x020e +#define REG_G_PREVZOOM_IN_YOFFS 0x0210 +#define REG_G_INPUTS_CHANGE_REQ 0x021a +#define REG_G_ACTIVE_PREV_CFG 0x021c +#define REG_G_PREV_CFG_CHG 0x021e +#define REG_G_PREV_OPEN_AFTER_CH 0x0220 +#define REG_G_PREV_CFG_ERROR 0x0222 + +/* Preview control section. n = 0...4. */ +#define PREG(n, x) ((n) * 0x26 + x) +#define REG_P_OUT_WIDTH(n) PREG(n, 0x0242) +#define REG_P_OUT_HEIGHT(n) PREG(n, 0x0244) +#define REG_P_FMT(n) PREG(n, 0x0246) +#define REG_P_MAX_OUT_RATE(n) PREG(n, 0x0248) +#define REG_P_MIN_OUT_RATE(n) PREG(n, 0x024a) +#define REG_P_PVI_MASK(n) PREG(n, 0x024c) +#define REG_P_CLK_INDEX(n) PREG(n, 0x024e) +#define REG_P_FR_RATE_TYPE(n) PREG(n, 0x0250) +#define FR_RATE_DYNAMIC 0 +#define FR_RATE_FIXED 1 +#define FR_RATE_FIXED_ACCURATE 2 +#define REG_P_FR_RATE_Q_TYPE(n) PREG(n, 0x0252) +#define FR_RATE_Q_BEST_FRRATE 1 /* Binning enabled */ +#define FR_RATE_Q_BEST_QUALITY 2 /* Binning disabled */ +/* Frame period in 0.1 ms units */ +#define REG_P_MAX_FR_TIME(n) PREG(n, 0x0254) +#define REG_P_MIN_FR_TIME(n) PREG(n, 0x0256) +/* Conversion to REG_P_[MAX/MIN]_FR_TIME value; __t: time in us */ +#define US_TO_FR_TIME(__t) ((__t) / 100) +#define S5K6AA_MIN_FR_TIME 33300 /* us */ +#define S5K6AA_MAX_FR_TIME 650000 /* us */ +#define S5K6AA_MAX_HIGHRES_FR_TIME 666 /* x100 us */ +/* The below 5 registers are for "device correction" values */ +#define REG_P_COLORTEMP(n) PREG(n, 0x025e) +#define REG_P_PREV_MIRROR(n) PREG(n, 0x0262) + +/* Extended image property controls */ +/* Exposure time in 10 us units */ +#define REG_SF_USR_EXPOSURE_L 0x03c6 +#define REG_SF_USR_EXPOSURE_H 0x03c8 +#define REG_SF_USR_EXPOSURE_CHG 0x03ca +#define REG_SF_USR_TOT_GAIN 0x03cc +#define REG_SF_USR_TOT_GAIN_CHG 0x03ce +#define REG_SF_RGAIN 0x03d0 +#define REG_SF_RGAIN_CHG 0x03d2 +#define REG_SF_GGAIN 0x03d4 +#define REG_SF_GGAIN_CHG 0x03d6 +#define REG_SF_BGAIN 0x03d8 +#define REG_SF_BGAIN_CHG 0x03da +#define REG_SF_FLICKER_QUANT 0x03dc +#define REG_SF_FLICKER_QUANT_CHG 0x03de + +/* Output interface (parallel/MIPI) setup */ +#define REG_OIF_EN_MIPI_LANES 0x03fa +#define REG_OIF_EN_PACKETS 0x03fc +#define REG_OIF_CFG_CHG 0x03fe + +/* Auto-algorithms enable mask */ +#define REG_DBG_AUTOALG_EN 0x0400 +#define AALG_ALL_EN_MASK (1 << 0) +#define AALG_AE_EN_MASK (1 << 1) +#define AALG_DIVLEI_EN_MASK (1 << 2) +#define AALG_WB_EN_MASK (1 << 3) +#define AALG_FLICKER_EN_MASK (1 << 5) +#define AALG_FIT_EN_MASK (1 << 6) +#define AALG_WRHW_EN_MASK (1 << 7) + +/* Firmware revision information */ +#define REG_FW_APIVER 0x012e +#define S5K6AAFX_FW_APIVER 0x0001 +#define REG_FW_REVISION 0x0130 + +/* For now we use only one user configuration register set */ +#define S5K6AA_MAX_PRESETS 1 + +static const char * const s5k6aa_supply_names[] = { + "vdd_core", /* Digital core supply 1.5V (1.4V to 1.6V) */ + "vdda", /* Analog power supply 2.8V (2.6V to 3.0V) */ + "vdd_reg", /* Regulator input power 1.8V (1.7V to 1.9V) + or 2.8V (2.6V to 3.0) */ + "vddio", /* I/O supply 1.8V (1.65V to 1.95V) + or 2.8V (2.5V to 3.1V) */ +}; +#define S5K6AA_NUM_SUPPLIES ARRAY_SIZE(s5k6aa_supply_names) + +enum s5k6aa_gpio_id { + STBY, + RSET, + GPIO_NUM, +}; + +struct s5k6aa_regval { + u16 addr; + u16 val; +}; + +struct s5k6aa_pixfmt { + u32 code; + u32 colorspace; + /* REG_P_FMT(x) register value */ + u16 reg_p_fmt; +}; + +struct s5k6aa_preset { + /* output pixel format and resolution */ + struct v4l2_mbus_framefmt mbus_fmt; + u8 clk_id; + u8 index; +}; + +struct s5k6aa_ctrls { + struct v4l2_ctrl_handler handler; + /* Auto / manual white balance cluster */ + struct v4l2_ctrl *awb; + struct v4l2_ctrl *gain_red; + struct v4l2_ctrl *gain_blue; + struct v4l2_ctrl *gain_green; + /* Mirror cluster */ + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + /* Auto exposure / manual exposure and gain cluster */ + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; +}; + +struct s5k6aa_interval { + u16 reg_fr_time; + struct v4l2_fract interval; + /* Maximum rectangle for the interval */ + struct v4l2_frmsize_discrete size; +}; + +struct s5k6aa { + struct v4l2_subdev sd; + struct media_pad pad; + + enum v4l2_mbus_type bus_type; + u8 mipi_lanes; + + int (*s_power)(int enable); + struct regulator_bulk_data supplies[S5K6AA_NUM_SUPPLIES]; + struct s5k6aa_gpio gpio[GPIO_NUM]; + + /* external master clock frequency */ + unsigned long mclk_frequency; + /* ISP internal master clock frequency */ + u16 clk_fop; + /* output pixel clock frequency range */ + u16 pclk_fmin; + u16 pclk_fmax; + + unsigned int inv_hflip:1; + unsigned int inv_vflip:1; + + /* protects the struct members below */ + struct mutex lock; + + /* sensor matrix scan window */ + struct v4l2_rect ccd_rect; + + struct s5k6aa_ctrls ctrls; + struct s5k6aa_preset presets[S5K6AA_MAX_PRESETS]; + struct s5k6aa_preset *preset; + const struct s5k6aa_interval *fiv; + + unsigned int streaming:1; + unsigned int apply_cfg:1; + unsigned int apply_crop:1; + unsigned int power; +}; + +static struct s5k6aa_regval s5k6aa_analog_config[] = { + /* Analog settings */ + { 0x112a, 0x0000 }, { 0x1132, 0x0000 }, + { 0x113e, 0x0000 }, { 0x115c, 0x0000 }, + { 0x1164, 0x0000 }, { 0x1174, 0x0000 }, + { 0x1178, 0x0000 }, { 0x077a, 0x0000 }, + { 0x077c, 0x0000 }, { 0x077e, 0x0000 }, + { 0x0780, 0x0000 }, { 0x0782, 0x0000 }, + { 0x0784, 0x0000 }, { 0x0786, 0x0000 }, + { 0x0788, 0x0000 }, { 0x07a2, 0x0000 }, + { 0x07a4, 0x0000 }, { 0x07a6, 0x0000 }, + { 0x07a8, 0x0000 }, { 0x07b6, 0x0000 }, + { 0x07b8, 0x0002 }, { 0x07ba, 0x0004 }, + { 0x07bc, 0x0004 }, { 0x07be, 0x0005 }, + { 0x07c0, 0x0005 }, { S5K6AA_TERM, 0 }, +}; + +/* TODO: Add RGB888 and Bayer format */ +static const struct s5k6aa_pixfmt s5k6aa_formats[] = { + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG, 5 }, + /* range 16-240 */ + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_REC709, 6 }, + { MEDIA_BUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_JPEG, 0 }, +}; + +static const struct s5k6aa_interval s5k6aa_intervals[] = { + { 1000, {10000, 1000000}, {1280, 1024} }, /* 10 fps */ + { 666, {15000, 1000000}, {1280, 1024} }, /* 15 fps */ + { 500, {20000, 1000000}, {1280, 720} }, /* 20 fps */ + { 400, {25000, 1000000}, {640, 480} }, /* 25 fps */ + { 333, {33300, 1000000}, {640, 480} }, /* 30 fps */ +}; + +#define S5K6AA_INTERVAL_DEF_INDEX 1 /* 15 fps */ + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct s5k6aa, ctrls.handler)->sd; +} + +static inline struct s5k6aa *to_s5k6aa(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s5k6aa, sd); +} + +/* Set initial values for all preview presets */ +static void s5k6aa_presets_data_init(struct s5k6aa *s5k6aa) +{ + struct s5k6aa_preset *preset = &s5k6aa->presets[0]; + int i; + + for (i = 0; i < S5K6AA_MAX_PRESETS; i++) { + preset->mbus_fmt.width = S5K6AA_OUT_WIDTH_DEF; + preset->mbus_fmt.height = S5K6AA_OUT_HEIGHT_DEF; + preset->mbus_fmt.code = s5k6aa_formats[0].code; + preset->index = i; + preset->clk_id = 0; + preset++; + } + + s5k6aa->fiv = &s5k6aa_intervals[S5K6AA_INTERVAL_DEF_INDEX]; + s5k6aa->preset = &s5k6aa->presets[0]; +} + +static int s5k6aa_i2c_read(struct i2c_client *client, u16 addr, u16 *val) +{ + u8 wbuf[2] = {addr >> 8, addr & 0xFF}; + struct i2c_msg msg[2]; + u8 rbuf[2]; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 2; + msg[0].buf = wbuf; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = rbuf; + + ret = i2c_transfer(client->adapter, msg, 2); + *val = be16_to_cpu(*((__be16 *)rbuf)); + + v4l2_dbg(3, debug, client, "i2c_read: 0x%04X : 0x%04x\n", addr, *val); + + return ret == 2 ? 0 : ret; +} + +static int s5k6aa_i2c_write(struct i2c_client *client, u16 addr, u16 val) +{ + u8 buf[4] = {addr >> 8, addr & 0xFF, val >> 8, val & 0xFF}; + + int ret = i2c_master_send(client, buf, 4); + v4l2_dbg(3, debug, client, "i2c_write: 0x%04X : 0x%04x\n", addr, val); + + return ret == 4 ? 0 : ret; +} + +/* The command register write, assumes Command_Wr_addH = 0x7000. */ +static int s5k6aa_write(struct i2c_client *c, u16 addr, u16 val) +{ + int ret = s5k6aa_i2c_write(c, REG_CMDWR_ADDRL, addr); + if (ret) + return ret; + return s5k6aa_i2c_write(c, REG_CMDBUF0_ADDR, val); +} + +/* The command register read, assumes Command_Rd_addH = 0x7000. */ +static int s5k6aa_read(struct i2c_client *client, u16 addr, u16 *val) +{ + int ret = s5k6aa_i2c_write(client, REG_CMDRD_ADDRL, addr); + if (ret) + return ret; + return s5k6aa_i2c_read(client, REG_CMDBUF0_ADDR, val); +} + +static int s5k6aa_write_array(struct v4l2_subdev *sd, + const struct s5k6aa_regval *msg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u16 addr_incr = 0; + int ret = 0; + + while (msg->addr != S5K6AA_TERM) { + if (addr_incr != 2) + ret = s5k6aa_i2c_write(client, REG_CMDWR_ADDRL, + msg->addr); + if (ret) + break; + ret = s5k6aa_i2c_write(client, REG_CMDBUF0_ADDR, msg->val); + if (ret) + break; + /* Assume that msg->addr is always less than 0xfffc */ + addr_incr = (msg + 1)->addr - msg->addr; + msg++; + } + + return ret; +} + +/* Configure the AHB high address bytes for GTG registers access */ +static int s5k6aa_set_ahb_address(struct i2c_client *client) +{ + int ret = s5k6aa_i2c_write(client, AHB_MSB_ADDR_PTR, GEN_REG_OFFSH); + if (ret) + return ret; + ret = s5k6aa_i2c_write(client, REG_CMDRD_ADDRH, HOST_SWIF_OFFSH); + if (ret) + return ret; + return s5k6aa_i2c_write(client, REG_CMDWR_ADDRH, HOST_SWIF_OFFSH); +} + +/** + * s5k6aa_configure_pixel_clock - apply ISP main clock/PLL configuration + * @s5k6aa: pointer to &struct s5k6aa describing the device + * + * Configure the internal ISP PLL for the required output frequency. + * Locking: called with s5k6aa.lock mutex held. + */ +static int s5k6aa_configure_pixel_clocks(struct s5k6aa *s5k6aa) +{ + struct i2c_client *c = v4l2_get_subdevdata(&s5k6aa->sd); + unsigned long fmclk = s5k6aa->mclk_frequency / 1000; + u16 status; + int ret; + + if (WARN(fmclk < MIN_MCLK_FREQ_KHZ || fmclk > MAX_MCLK_FREQ_KHZ, + "Invalid clock frequency: %ld\n", fmclk)) + return -EINVAL; + + s5k6aa->pclk_fmin = PCLK_FREQ_MIN; + s5k6aa->pclk_fmax = PCLK_FREQ_MAX; + s5k6aa->clk_fop = SYS_PLL_OUT_FREQ; + + /* External input clock frequency in kHz */ + ret = s5k6aa_write(c, REG_I_INCLK_FREQ_H, fmclk >> 16); + if (!ret) + ret = s5k6aa_write(c, REG_I_INCLK_FREQ_L, fmclk & 0xFFFF); + if (!ret) + ret = s5k6aa_write(c, REG_I_USE_NPVI_CLOCKS, 1); + /* Internal PLL frequency */ + if (!ret) + ret = s5k6aa_write(c, REG_I_OPCLK_4KHZ(0), s5k6aa->clk_fop); + if (!ret) + ret = s5k6aa_write(c, REG_I_MIN_OUTRATE_4KHZ(0), + s5k6aa->pclk_fmin); + if (!ret) + ret = s5k6aa_write(c, REG_I_MAX_OUTRATE_4KHZ(0), + s5k6aa->pclk_fmax); + if (!ret) + ret = s5k6aa_write(c, REG_I_INIT_PARAMS_UPDATED, 1); + if (!ret) + ret = s5k6aa_read(c, REG_I_ERROR_INFO, &status); + + return ret ? ret : (status ? -EINVAL : 0); +} + +/* Set horizontal and vertical image flipping */ +static int s5k6aa_set_mirror(struct s5k6aa *s5k6aa, int horiz_flip) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + int index = s5k6aa->preset->index; + + unsigned int vflip = s5k6aa->ctrls.vflip->val ^ s5k6aa->inv_vflip; + unsigned int flip = (horiz_flip ^ s5k6aa->inv_hflip) | (vflip << 1); + + return s5k6aa_write(client, REG_P_PREV_MIRROR(index), flip); +} + +/* Configure auto/manual white balance and R/G/B gains */ +static int s5k6aa_set_awb(struct s5k6aa *s5k6aa, int awb) +{ + struct i2c_client *c = v4l2_get_subdevdata(&s5k6aa->sd); + struct s5k6aa_ctrls *ctrls = &s5k6aa->ctrls; + u16 reg; + + int ret = s5k6aa_read(c, REG_DBG_AUTOALG_EN, ®); + + if (!ret && !awb) { + ret = s5k6aa_write(c, REG_SF_RGAIN, ctrls->gain_red->val); + if (!ret) + ret = s5k6aa_write(c, REG_SF_RGAIN_CHG, 1); + if (ret) + return ret; + + ret = s5k6aa_write(c, REG_SF_GGAIN, ctrls->gain_green->val); + if (!ret) + ret = s5k6aa_write(c, REG_SF_GGAIN_CHG, 1); + if (ret) + return ret; + + ret = s5k6aa_write(c, REG_SF_BGAIN, ctrls->gain_blue->val); + if (!ret) + ret = s5k6aa_write(c, REG_SF_BGAIN_CHG, 1); + } + if (!ret) { + reg = awb ? reg | AALG_WB_EN_MASK : reg & ~AALG_WB_EN_MASK; + ret = s5k6aa_write(c, REG_DBG_AUTOALG_EN, reg); + } + + return ret; +} + +/* Program FW with exposure time, 'exposure' in us units */ +static int s5k6aa_set_user_exposure(struct i2c_client *client, int exposure) +{ + unsigned int time = exposure / 10; + + int ret = s5k6aa_write(client, REG_SF_USR_EXPOSURE_L, time & 0xffff); + if (!ret) + ret = s5k6aa_write(client, REG_SF_USR_EXPOSURE_H, time >> 16); + if (ret) + return ret; + return s5k6aa_write(client, REG_SF_USR_EXPOSURE_CHG, 1); +} + +static int s5k6aa_set_user_gain(struct i2c_client *client, int gain) +{ + int ret = s5k6aa_write(client, REG_SF_USR_TOT_GAIN, gain); + if (ret) + return ret; + return s5k6aa_write(client, REG_SF_USR_TOT_GAIN_CHG, 1); +} + +/* Set auto/manual exposure and total gain */ +static int s5k6aa_set_auto_exposure(struct s5k6aa *s5k6aa, int value) +{ + struct i2c_client *c = v4l2_get_subdevdata(&s5k6aa->sd); + unsigned int exp_time = s5k6aa->ctrls.exposure->val; + u16 auto_alg; + + int ret = s5k6aa_read(c, REG_DBG_AUTOALG_EN, &auto_alg); + if (ret) + return ret; + + v4l2_dbg(1, debug, c, "man_exp: %d, auto_exp: %d, a_alg: 0x%x\n", + exp_time, value, auto_alg); + + if (value == V4L2_EXPOSURE_AUTO) { + auto_alg |= AALG_AE_EN_MASK | AALG_DIVLEI_EN_MASK; + } else { + ret = s5k6aa_set_user_exposure(c, exp_time); + if (ret) + return ret; + ret = s5k6aa_set_user_gain(c, s5k6aa->ctrls.gain->val); + if (ret) + return ret; + auto_alg &= ~(AALG_AE_EN_MASK | AALG_DIVLEI_EN_MASK); + } + + return s5k6aa_write(c, REG_DBG_AUTOALG_EN, auto_alg); +} + +static int s5k6aa_set_anti_flicker(struct s5k6aa *s5k6aa, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + u16 auto_alg; + int ret; + + ret = s5k6aa_read(client, REG_DBG_AUTOALG_EN, &auto_alg); + if (ret) + return ret; + + if (value == V4L2_CID_POWER_LINE_FREQUENCY_AUTO) { + auto_alg |= AALG_FLICKER_EN_MASK; + } else { + auto_alg &= ~AALG_FLICKER_EN_MASK; + /* The V4L2_CID_LINE_FREQUENCY control values match + * the register values */ + ret = s5k6aa_write(client, REG_SF_FLICKER_QUANT, value); + if (ret) + return ret; + ret = s5k6aa_write(client, REG_SF_FLICKER_QUANT_CHG, 1); + if (ret) + return ret; + } + + return s5k6aa_write(client, REG_DBG_AUTOALG_EN, auto_alg); +} + +static int s5k6aa_set_colorfx(struct s5k6aa *s5k6aa, int val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + static const struct v4l2_control colorfx[] = { + { V4L2_COLORFX_NONE, 0 }, + { V4L2_COLORFX_BW, 1 }, + { V4L2_COLORFX_NEGATIVE, 2 }, + { V4L2_COLORFX_SEPIA, 3 }, + { V4L2_COLORFX_SKY_BLUE, 4 }, + { V4L2_COLORFX_SKETCH, 5 }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(colorfx); i++) { + if (colorfx[i].id == val) + return s5k6aa_write(client, REG_G_SPEC_EFFECTS, + colorfx[i].value); + } + return -EINVAL; +} + +static int s5k6aa_preview_config_status(struct i2c_client *client) +{ + u16 error = 0; + int ret = s5k6aa_read(client, REG_G_PREV_CFG_ERROR, &error); + + v4l2_dbg(1, debug, client, "error: 0x%x (%d)\n", error, ret); + return ret ? ret : (error ? -EINVAL : 0); +} + +static int s5k6aa_get_pixfmt_index(struct s5k6aa *s5k6aa, + struct v4l2_mbus_framefmt *mf) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(s5k6aa_formats); i++) + if (mf->colorspace == s5k6aa_formats[i].colorspace && + mf->code == s5k6aa_formats[i].code) + return i; + return 0; +} + +static int s5k6aa_set_output_framefmt(struct s5k6aa *s5k6aa, + struct s5k6aa_preset *preset) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + int fmt_index = s5k6aa_get_pixfmt_index(s5k6aa, &preset->mbus_fmt); + int ret; + + ret = s5k6aa_write(client, REG_P_OUT_WIDTH(preset->index), + preset->mbus_fmt.width); + if (!ret) + ret = s5k6aa_write(client, REG_P_OUT_HEIGHT(preset->index), + preset->mbus_fmt.height); + if (!ret) + ret = s5k6aa_write(client, REG_P_FMT(preset->index), + s5k6aa_formats[fmt_index].reg_p_fmt); + return ret; +} + +static int s5k6aa_set_input_params(struct s5k6aa *s5k6aa) +{ + struct i2c_client *c = v4l2_get_subdevdata(&s5k6aa->sd); + struct v4l2_rect *r = &s5k6aa->ccd_rect; + int ret; + + ret = s5k6aa_write(c, REG_G_PREVZOOM_IN_WIDTH, r->width); + if (!ret) + ret = s5k6aa_write(c, REG_G_PREVZOOM_IN_HEIGHT, r->height); + if (!ret) + ret = s5k6aa_write(c, REG_G_PREVZOOM_IN_XOFFS, r->left); + if (!ret) + ret = s5k6aa_write(c, REG_G_PREVZOOM_IN_YOFFS, r->top); + if (!ret) + ret = s5k6aa_write(c, REG_G_INPUTS_CHANGE_REQ, 1); + if (!ret) + s5k6aa->apply_crop = 0; + + return ret; +} + +/** + * s5k6aa_configure_video_bus - configure the video output interface + * @s5k6aa: pointer to &struct s5k6aa describing the device + * @bus_type: video bus type: parallel or MIPI-CSI + * @nlanes: number of MIPI lanes to be used (MIPI-CSI only) + * + * Note: Only parallel bus operation has been tested. + */ +static int s5k6aa_configure_video_bus(struct s5k6aa *s5k6aa, + enum v4l2_mbus_type bus_type, int nlanes) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + u16 cfg = 0; + int ret; + + /* + * TODO: The sensor is supposed to support BT.601 and BT.656 + * but there is nothing indicating how to switch between both + * in the datasheet. For now default BT.601 interface is assumed. + */ + if (bus_type == V4L2_MBUS_CSI2_DPHY) + cfg = nlanes; + else if (bus_type != V4L2_MBUS_PARALLEL) + return -EINVAL; + + ret = s5k6aa_write(client, REG_OIF_EN_MIPI_LANES, cfg); + if (ret) + return ret; + return s5k6aa_write(client, REG_OIF_CFG_CHG, 1); +} + +/* This function should be called when switching to new user configuration set*/ +static int s5k6aa_new_config_sync(struct i2c_client *client, int timeout, + int cid) +{ + unsigned long end = jiffies + msecs_to_jiffies(timeout); + u16 reg = 1; + int ret; + + ret = s5k6aa_write(client, REG_G_ACTIVE_PREV_CFG, cid); + if (!ret) + ret = s5k6aa_write(client, REG_G_PREV_CFG_CHG, 1); + if (!ret) + ret = s5k6aa_write(client, REG_G_NEW_CFG_SYNC, 1); + if (timeout == 0) + return ret; + + while (ret >= 0 && time_is_after_jiffies(end)) { + ret = s5k6aa_read(client, REG_G_NEW_CFG_SYNC, ®); + if (!reg) + return 0; + usleep_range(1000, 5000); + } + return ret ? ret : -ETIMEDOUT; +} + +/** + * s5k6aa_set_prev_config - write user preview register set + * @s5k6aa: pointer to &struct s5k6aa describing the device + * @preset: s5kaa preset to be applied + * + * Configure output resolution and color format, pixel clock + * frequency range, device frame rate type and frame period range. + */ +static int s5k6aa_set_prev_config(struct s5k6aa *s5k6aa, + struct s5k6aa_preset *preset) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + int idx = preset->index; + u16 frame_rate_q; + int ret; + + if (s5k6aa->fiv->reg_fr_time >= S5K6AA_MAX_HIGHRES_FR_TIME) + frame_rate_q = FR_RATE_Q_BEST_FRRATE; + else + frame_rate_q = FR_RATE_Q_BEST_QUALITY; + + ret = s5k6aa_set_output_framefmt(s5k6aa, preset); + if (!ret) + ret = s5k6aa_write(client, REG_P_MAX_OUT_RATE(idx), + s5k6aa->pclk_fmax); + if (!ret) + ret = s5k6aa_write(client, REG_P_MIN_OUT_RATE(idx), + s5k6aa->pclk_fmin); + if (!ret) + ret = s5k6aa_write(client, REG_P_CLK_INDEX(idx), + preset->clk_id); + if (!ret) + ret = s5k6aa_write(client, REG_P_FR_RATE_TYPE(idx), + FR_RATE_DYNAMIC); + if (!ret) + ret = s5k6aa_write(client, REG_P_FR_RATE_Q_TYPE(idx), + frame_rate_q); + if (!ret) + ret = s5k6aa_write(client, REG_P_MAX_FR_TIME(idx), + s5k6aa->fiv->reg_fr_time + 33); + if (!ret) + ret = s5k6aa_write(client, REG_P_MIN_FR_TIME(idx), + s5k6aa->fiv->reg_fr_time - 33); + if (!ret) + ret = s5k6aa_new_config_sync(client, 250, idx); + if (!ret) + ret = s5k6aa_preview_config_status(client); + if (!ret) + s5k6aa->apply_cfg = 0; + + v4l2_dbg(1, debug, client, "Frame interval: %d +/- 3.3ms. (%d)\n", + s5k6aa->fiv->reg_fr_time, ret); + return ret; +} + +/** + * s5k6aa_initialize_isp - basic ISP MCU initialization + * @sd: pointer to V4L2 sub-device descriptor + * + * Configure AHB addresses for registers read/write; configure PLLs for + * required output pixel clock. The ISP power supply needs to be already + * enabled, with an optional H/W reset. + * Locking: called with s5k6aa.lock mutex held. + */ +static int s5k6aa_initialize_isp(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int ret; + + s5k6aa->apply_crop = 1; + s5k6aa->apply_cfg = 1; + msleep(100); + + ret = s5k6aa_set_ahb_address(client); + if (ret) + return ret; + ret = s5k6aa_configure_video_bus(s5k6aa, s5k6aa->bus_type, + s5k6aa->mipi_lanes); + if (ret) + return ret; + ret = s5k6aa_write_array(sd, s5k6aa_analog_config); + if (ret) + return ret; + msleep(20); + + return s5k6aa_configure_pixel_clocks(s5k6aa); +} + +static int s5k6aa_gpio_set_value(struct s5k6aa *priv, int id, u32 val) +{ + if (!gpio_is_valid(priv->gpio[id].gpio)) + return 0; + gpio_set_value(priv->gpio[id].gpio, !!val); + return 1; +} + +static int s5k6aa_gpio_assert(struct s5k6aa *priv, int id) +{ + return s5k6aa_gpio_set_value(priv, id, priv->gpio[id].level); +} + +static int s5k6aa_gpio_deassert(struct s5k6aa *priv, int id) +{ + return s5k6aa_gpio_set_value(priv, id, !priv->gpio[id].level); +} + +static int __s5k6aa_power_on(struct s5k6aa *s5k6aa) +{ + int ret; + + ret = regulator_bulk_enable(S5K6AA_NUM_SUPPLIES, s5k6aa->supplies); + if (ret) + return ret; + if (s5k6aa_gpio_deassert(s5k6aa, STBY)) + usleep_range(150, 200); + + if (s5k6aa->s_power) + ret = s5k6aa->s_power(1); + usleep_range(4000, 5000); + + if (s5k6aa_gpio_deassert(s5k6aa, RSET)) + msleep(20); + + return ret; +} + +static int __s5k6aa_power_off(struct s5k6aa *s5k6aa) +{ + int ret; + + if (s5k6aa_gpio_assert(s5k6aa, RSET)) + usleep_range(100, 150); + + if (s5k6aa->s_power) { + ret = s5k6aa->s_power(0); + if (ret) + return ret; + } + if (s5k6aa_gpio_assert(s5k6aa, STBY)) + usleep_range(50, 100); + s5k6aa->streaming = 0; + + return regulator_bulk_disable(S5K6AA_NUM_SUPPLIES, s5k6aa->supplies); +} + +/* + * V4L2 subdev core and video operations + */ +static int s5k6aa_set_power(struct v4l2_subdev *sd, int on) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int ret = 0; + + mutex_lock(&s5k6aa->lock); + + if (s5k6aa->power == !on) { + if (on) { + ret = __s5k6aa_power_on(s5k6aa); + if (!ret) + ret = s5k6aa_initialize_isp(sd); + } else { + ret = __s5k6aa_power_off(s5k6aa); + } + + if (!ret) + s5k6aa->power += on ? 1 : -1; + } + + mutex_unlock(&s5k6aa->lock); + + if (!on || ret || s5k6aa->power != 1) + return ret; + + return v4l2_ctrl_handler_setup(sd->ctrl_handler); +} + +static int __s5k6aa_stream(struct s5k6aa *s5k6aa, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + int ret = 0; + + ret = s5k6aa_write(client, REG_G_ENABLE_PREV, enable); + if (!ret) + ret = s5k6aa_write(client, REG_G_ENABLE_PREV_CHG, 1); + if (!ret) + s5k6aa->streaming = enable; + + return ret; +} + +static int s5k6aa_s_stream(struct v4l2_subdev *sd, int on) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int ret = 0; + + mutex_lock(&s5k6aa->lock); + + if (s5k6aa->streaming == !on) { + if (!ret && s5k6aa->apply_cfg) + ret = s5k6aa_set_prev_config(s5k6aa, s5k6aa->preset); + if (s5k6aa->apply_crop) + ret = s5k6aa_set_input_params(s5k6aa); + if (!ret) + ret = __s5k6aa_stream(s5k6aa, !!on); + } + mutex_unlock(&s5k6aa->lock); + + return ret; +} + +static int s5k6aa_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + + mutex_lock(&s5k6aa->lock); + fi->interval = s5k6aa->fiv->interval; + mutex_unlock(&s5k6aa->lock); + + return 0; +} + +static int __s5k6aa_set_frame_interval(struct s5k6aa *s5k6aa, + struct v4l2_subdev_frame_interval *fi) +{ + struct v4l2_mbus_framefmt *mbus_fmt = &s5k6aa->preset->mbus_fmt; + const struct s5k6aa_interval *fiv = &s5k6aa_intervals[0]; + unsigned int err, min_err = UINT_MAX; + unsigned int i, fr_time; + + if (fi->interval.denominator == 0) + return -EINVAL; + + fr_time = fi->interval.numerator * 10000 / fi->interval.denominator; + + for (i = 0; i < ARRAY_SIZE(s5k6aa_intervals); i++) { + const struct s5k6aa_interval *iv = &s5k6aa_intervals[i]; + + if (mbus_fmt->width > iv->size.width || + mbus_fmt->height > iv->size.height) + continue; + + err = abs(iv->reg_fr_time - fr_time); + if (err < min_err) { + fiv = iv; + min_err = err; + } + } + s5k6aa->fiv = fiv; + + v4l2_dbg(1, debug, &s5k6aa->sd, "Changed frame interval to %d us\n", + fiv->reg_fr_time * 100); + return 0; +} + +static int s5k6aa_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int ret; + + v4l2_dbg(1, debug, sd, "Setting %d/%d frame interval\n", + fi->interval.numerator, fi->interval.denominator); + + mutex_lock(&s5k6aa->lock); + ret = __s5k6aa_set_frame_interval(s5k6aa, fi); + s5k6aa->apply_cfg = 1; + + mutex_unlock(&s5k6aa->lock); + return ret; +} + +/* + * V4L2 subdev pad level and video operations + */ +static int s5k6aa_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + const struct s5k6aa_interval *fi; + int ret = 0; + + if (fie->index >= ARRAY_SIZE(s5k6aa_intervals)) + return -EINVAL; + + v4l_bound_align_image(&fie->width, S5K6AA_WIN_WIDTH_MIN, + S5K6AA_WIN_WIDTH_MAX, 1, + &fie->height, S5K6AA_WIN_HEIGHT_MIN, + S5K6AA_WIN_HEIGHT_MAX, 1, 0); + + mutex_lock(&s5k6aa->lock); + fi = &s5k6aa_intervals[fie->index]; + if (fie->width > fi->size.width || fie->height > fi->size.height) + ret = -EINVAL; + else + fie->interval = fi->interval; + mutex_unlock(&s5k6aa->lock); + + return ret; +} + +static int s5k6aa_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(s5k6aa_formats)) + return -EINVAL; + + code->code = s5k6aa_formats[code->index].code; + return 0; +} + +static int s5k6aa_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + int i = ARRAY_SIZE(s5k6aa_formats); + + if (fse->index > 0) + return -EINVAL; + + while (--i) + if (fse->code == s5k6aa_formats[i].code) + break; + + fse->code = s5k6aa_formats[i].code; + fse->min_width = S5K6AA_WIN_WIDTH_MIN; + fse->max_width = S5K6AA_WIN_WIDTH_MAX; + fse->max_height = S5K6AA_WIN_HEIGHT_MIN; + fse->min_height = S5K6AA_WIN_HEIGHT_MAX; + + return 0; +} + +static struct v4l2_rect * +__s5k6aa_get_crop_rect(struct s5k6aa *s5k6aa, struct v4l2_subdev_pad_config *cfg, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) + return &s5k6aa->ccd_rect; + + WARN_ON(which != V4L2_SUBDEV_FORMAT_TRY); + return v4l2_subdev_get_try_crop(&s5k6aa->sd, cfg, 0); +} + +static void s5k6aa_try_format(struct s5k6aa *s5k6aa, + struct v4l2_mbus_framefmt *mf) +{ + unsigned int index; + + v4l_bound_align_image(&mf->width, S5K6AA_WIN_WIDTH_MIN, + S5K6AA_WIN_WIDTH_MAX, 1, + &mf->height, S5K6AA_WIN_HEIGHT_MIN, + S5K6AA_WIN_HEIGHT_MAX, 1, 0); + + if (mf->colorspace != V4L2_COLORSPACE_JPEG && + mf->colorspace != V4L2_COLORSPACE_REC709) + mf->colorspace = V4L2_COLORSPACE_JPEG; + + index = s5k6aa_get_pixfmt_index(s5k6aa, mf); + + mf->colorspace = s5k6aa_formats[index].colorspace; + mf->code = s5k6aa_formats[index].code; + mf->field = V4L2_FIELD_NONE; +} + +static int s5k6aa_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + struct v4l2_mbus_framefmt *mf; + + memset(fmt->reserved, 0, sizeof(fmt->reserved)); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + fmt->format = *mf; + return 0; + } + + mutex_lock(&s5k6aa->lock); + fmt->format = s5k6aa->preset->mbus_fmt; + mutex_unlock(&s5k6aa->lock); + + return 0; +} + +static int s5k6aa_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + struct s5k6aa_preset *preset = s5k6aa->preset; + struct v4l2_mbus_framefmt *mf; + struct v4l2_rect *crop; + int ret = 0; + + mutex_lock(&s5k6aa->lock); + s5k6aa_try_format(s5k6aa, &fmt->format); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + crop = v4l2_subdev_get_try_crop(sd, cfg, 0); + } else { + if (s5k6aa->streaming) { + ret = -EBUSY; + } else { + mf = &preset->mbus_fmt; + crop = &s5k6aa->ccd_rect; + s5k6aa->apply_cfg = 1; + } + } + + if (ret == 0) { + struct v4l2_subdev_frame_interval fiv = { + .interval = {0, 1} + }; + + *mf = fmt->format; + /* + * Make sure the crop window is valid, i.e. its size is + * greater than the output window, as the ISP supports + * only down-scaling. + */ + crop->width = clamp_t(unsigned int, crop->width, mf->width, + S5K6AA_WIN_WIDTH_MAX); + crop->height = clamp_t(unsigned int, crop->height, mf->height, + S5K6AA_WIN_HEIGHT_MAX); + crop->left = clamp_t(unsigned int, crop->left, 0, + S5K6AA_WIN_WIDTH_MAX - crop->width); + crop->top = clamp_t(unsigned int, crop->top, 0, + S5K6AA_WIN_HEIGHT_MAX - crop->height); + + /* Reset to minimum possible frame interval */ + ret = __s5k6aa_set_frame_interval(s5k6aa, &fiv); + } + mutex_unlock(&s5k6aa->lock); + + return ret; +} + +static int s5k6aa_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + struct v4l2_rect *rect; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + memset(sel->reserved, 0, sizeof(sel->reserved)); + + mutex_lock(&s5k6aa->lock); + rect = __s5k6aa_get_crop_rect(s5k6aa, cfg, sel->which); + sel->r = *rect; + mutex_unlock(&s5k6aa->lock); + + v4l2_dbg(1, debug, sd, "Current crop rectangle: (%d,%d)/%dx%d\n", + rect->left, rect->top, rect->width, rect->height); + + return 0; +} + +static int s5k6aa_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + struct v4l2_mbus_framefmt *mf; + unsigned int max_x, max_y; + struct v4l2_rect *crop_r; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + mutex_lock(&s5k6aa->lock); + crop_r = __s5k6aa_get_crop_rect(s5k6aa, cfg, sel->which); + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + mf = &s5k6aa->preset->mbus_fmt; + s5k6aa->apply_crop = 1; + } else { + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + } + v4l_bound_align_image(&sel->r.width, mf->width, + S5K6AA_WIN_WIDTH_MAX, 1, + &sel->r.height, mf->height, + S5K6AA_WIN_HEIGHT_MAX, 1, 0); + + max_x = (S5K6AA_WIN_WIDTH_MAX - sel->r.width) & ~1; + max_y = (S5K6AA_WIN_HEIGHT_MAX - sel->r.height) & ~1; + + sel->r.left = clamp_t(unsigned int, sel->r.left, 0, max_x); + sel->r.top = clamp_t(unsigned int, sel->r.top, 0, max_y); + + *crop_r = sel->r; + + mutex_unlock(&s5k6aa->lock); + + v4l2_dbg(1, debug, sd, "Set crop rectangle: (%d,%d)/%dx%d\n", + crop_r->left, crop_r->top, crop_r->width, crop_r->height); + + return 0; +} + +static const struct v4l2_subdev_pad_ops s5k6aa_pad_ops = { + .enum_mbus_code = s5k6aa_enum_mbus_code, + .enum_frame_size = s5k6aa_enum_frame_size, + .enum_frame_interval = s5k6aa_enum_frame_interval, + .get_fmt = s5k6aa_get_fmt, + .set_fmt = s5k6aa_set_fmt, + .get_selection = s5k6aa_get_selection, + .set_selection = s5k6aa_set_selection, +}; + +static const struct v4l2_subdev_video_ops s5k6aa_video_ops = { + .g_frame_interval = s5k6aa_g_frame_interval, + .s_frame_interval = s5k6aa_s_frame_interval, + .s_stream = s5k6aa_s_stream, +}; + +/* + * V4L2 subdev controls + */ + +static int s5k6aa_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int idx, err = 0; + + v4l2_dbg(1, debug, sd, "ctrl: 0x%x, value: %d\n", ctrl->id, ctrl->val); + + mutex_lock(&s5k6aa->lock); + /* + * If the device is not powered up by the host driver do + * not apply any controls to H/W at this time. Instead + * the controls will be restored right after power-up. + */ + if (s5k6aa->power == 0) + goto unlock; + idx = s5k6aa->preset->index; + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + err = s5k6aa_set_awb(s5k6aa, ctrl->val); + break; + + case V4L2_CID_BRIGHTNESS: + err = s5k6aa_write(client, REG_USER_BRIGHTNESS, ctrl->val); + break; + + case V4L2_CID_COLORFX: + err = s5k6aa_set_colorfx(s5k6aa, ctrl->val); + break; + + case V4L2_CID_CONTRAST: + err = s5k6aa_write(client, REG_USER_CONTRAST, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + err = s5k6aa_set_auto_exposure(s5k6aa, ctrl->val); + break; + + case V4L2_CID_HFLIP: + err = s5k6aa_set_mirror(s5k6aa, ctrl->val); + if (err) + break; + err = s5k6aa_write(client, REG_G_PREV_CFG_CHG, 1); + break; + + case V4L2_CID_POWER_LINE_FREQUENCY: + err = s5k6aa_set_anti_flicker(s5k6aa, ctrl->val); + break; + + case V4L2_CID_SATURATION: + err = s5k6aa_write(client, REG_USER_SATURATION, ctrl->val); + break; + + case V4L2_CID_SHARPNESS: + err = s5k6aa_write(client, REG_USER_SHARPBLUR, ctrl->val); + break; + + case V4L2_CID_WHITE_BALANCE_TEMPERATURE: + err = s5k6aa_write(client, REG_P_COLORTEMP(idx), ctrl->val); + if (err) + break; + err = s5k6aa_write(client, REG_G_PREV_CFG_CHG, 1); + break; + } +unlock: + mutex_unlock(&s5k6aa->lock); + return err; +} + +static const struct v4l2_ctrl_ops s5k6aa_ctrl_ops = { + .s_ctrl = s5k6aa_s_ctrl, +}; + +static int s5k6aa_log_status(struct v4l2_subdev *sd) +{ + v4l2_ctrl_handler_log_status(sd->ctrl_handler, sd->name); + return 0; +} + +#define V4L2_CID_RED_GAIN (V4L2_CTRL_CLASS_CAMERA | 0x1001) +#define V4L2_CID_GREEN_GAIN (V4L2_CTRL_CLASS_CAMERA | 0x1002) +#define V4L2_CID_BLUE_GAIN (V4L2_CTRL_CLASS_CAMERA | 0x1003) + +static const struct v4l2_ctrl_config s5k6aa_ctrls[] = { + { + .ops = &s5k6aa_ctrl_ops, + .id = V4L2_CID_RED_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain, Red", + .min = 0, + .max = 256, + .def = 127, + .step = 1, + }, { + .ops = &s5k6aa_ctrl_ops, + .id = V4L2_CID_GREEN_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain, Green", + .min = 0, + .max = 256, + .def = 127, + .step = 1, + }, { + .ops = &s5k6aa_ctrl_ops, + .id = V4L2_CID_BLUE_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain, Blue", + .min = 0, + .max = 256, + .def = 127, + .step = 1, + }, +}; + +static int s5k6aa_initialize_ctrls(struct s5k6aa *s5k6aa) +{ + const struct v4l2_ctrl_ops *ops = &s5k6aa_ctrl_ops; + struct s5k6aa_ctrls *ctrls = &s5k6aa->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + + int ret = v4l2_ctrl_handler_init(hdl, 16); + if (ret) + return ret; + /* Auto white balance cluster */ + ctrls->awb = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + ctrls->gain_red = v4l2_ctrl_new_custom(hdl, &s5k6aa_ctrls[0], NULL); + ctrls->gain_green = v4l2_ctrl_new_custom(hdl, &s5k6aa_ctrls[1], NULL); + ctrls->gain_blue = v4l2_ctrl_new_custom(hdl, &s5k6aa_ctrls[2], NULL); + v4l2_ctrl_auto_cluster(4, &ctrls->awb, 0, false); + + ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_cluster(2, &ctrls->hflip); + + ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, V4L2_EXPOSURE_AUTO); + /* Exposure time: x 1 us */ + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + 0, 6000000U, 1, 100000U); + /* Total gain: 256 <=> 1x */ + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, + 0, 256, 1, 256); + v4l2_ctrl_auto_cluster(3, &ctrls->auto_exp, 0, false); + + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO); + + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_COLORFX, + V4L2_COLORFX_SKY_BLUE, ~0x6f, V4L2_COLORFX_NONE); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_WHITE_BALANCE_TEMPERATURE, + 0, 256, 1, 0); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SHARPNESS, -127, 127, 1, 0); + + if (hdl->error) { + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + s5k6aa->sd.ctrl_handler = hdl; + return 0; +} + +/* + * V4L2 subdev internal operations + */ +static int s5k6aa_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format = v4l2_subdev_get_try_format(sd, fh->pad, 0); + struct v4l2_rect *crop = v4l2_subdev_get_try_crop(sd, fh->pad, 0); + + format->colorspace = s5k6aa_formats[0].colorspace; + format->code = s5k6aa_formats[0].code; + format->width = S5K6AA_OUT_WIDTH_DEF; + format->height = S5K6AA_OUT_HEIGHT_DEF; + format->field = V4L2_FIELD_NONE; + + crop->width = S5K6AA_WIN_WIDTH_MAX; + crop->height = S5K6AA_WIN_HEIGHT_MAX; + crop->left = 0; + crop->top = 0; + + return 0; +} + +static int s5k6aa_check_fw_revision(struct s5k6aa *s5k6aa) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + u16 api_ver = 0, fw_rev = 0; + + int ret = s5k6aa_set_ahb_address(client); + + if (!ret) + ret = s5k6aa_read(client, REG_FW_APIVER, &api_ver); + if (!ret) + ret = s5k6aa_read(client, REG_FW_REVISION, &fw_rev); + if (ret) { + v4l2_err(&s5k6aa->sd, "FW revision check failed!\n"); + return ret; + } + + v4l2_info(&s5k6aa->sd, "FW API ver.: 0x%X, FW rev.: 0x%X\n", + api_ver, fw_rev); + + return api_ver == S5K6AAFX_FW_APIVER ? 0 : -ENODEV; +} + +static int s5k6aa_registered(struct v4l2_subdev *sd) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int ret; + + mutex_lock(&s5k6aa->lock); + ret = __s5k6aa_power_on(s5k6aa); + if (!ret) { + msleep(100); + ret = s5k6aa_check_fw_revision(s5k6aa); + __s5k6aa_power_off(s5k6aa); + } + mutex_unlock(&s5k6aa->lock); + + return ret; +} + +static const struct v4l2_subdev_internal_ops s5k6aa_subdev_internal_ops = { + .registered = s5k6aa_registered, + .open = s5k6aa_open, +}; + +static const struct v4l2_subdev_core_ops s5k6aa_core_ops = { + .s_power = s5k6aa_set_power, + .log_status = s5k6aa_log_status, +}; + +static const struct v4l2_subdev_ops s5k6aa_subdev_ops = { + .core = &s5k6aa_core_ops, + .pad = &s5k6aa_pad_ops, + .video = &s5k6aa_video_ops, +}; + +/* + * GPIO setup + */ + +static int s5k6aa_configure_gpios(struct s5k6aa *s5k6aa, + const struct s5k6aa_platform_data *pdata) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + const struct s5k6aa_gpio *gpio; + unsigned long flags; + int ret; + + s5k6aa->gpio[STBY].gpio = -EINVAL; + s5k6aa->gpio[RSET].gpio = -EINVAL; + + gpio = &pdata->gpio_stby; + if (gpio_is_valid(gpio->gpio)) { + flags = (gpio->level ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW) + | GPIOF_EXPORT; + ret = devm_gpio_request_one(&client->dev, gpio->gpio, flags, + "S5K6AA_STBY"); + if (ret < 0) + return ret; + + s5k6aa->gpio[STBY] = *gpio; + } + + gpio = &pdata->gpio_reset; + if (gpio_is_valid(gpio->gpio)) { + flags = (gpio->level ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW) + | GPIOF_EXPORT; + ret = devm_gpio_request_one(&client->dev, gpio->gpio, flags, + "S5K6AA_RST"); + if (ret < 0) + return ret; + + s5k6aa->gpio[RSET] = *gpio; + } + + return 0; +} + +static int s5k6aa_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct s5k6aa_platform_data *pdata = client->dev.platform_data; + struct v4l2_subdev *sd; + struct s5k6aa *s5k6aa; + int i, ret; + + if (pdata == NULL) { + dev_err(&client->dev, "Platform data not specified\n"); + return -EINVAL; + } + + if (pdata->mclk_frequency == 0) { + dev_err(&client->dev, "MCLK frequency not specified\n"); + return -EINVAL; + } + + s5k6aa = devm_kzalloc(&client->dev, sizeof(*s5k6aa), GFP_KERNEL); + if (!s5k6aa) + return -ENOMEM; + + mutex_init(&s5k6aa->lock); + + s5k6aa->mclk_frequency = pdata->mclk_frequency; + s5k6aa->bus_type = pdata->bus_type; + s5k6aa->mipi_lanes = pdata->nlanes; + s5k6aa->s_power = pdata->set_power; + s5k6aa->inv_hflip = pdata->horiz_flip; + s5k6aa->inv_vflip = pdata->vert_flip; + + sd = &s5k6aa->sd; + v4l2_i2c_subdev_init(sd, client, &s5k6aa_subdev_ops); + /* Static name; NEVER use in new drivers! */ + strscpy(sd->name, DRIVER_NAME, sizeof(sd->name)); + + sd->internal_ops = &s5k6aa_subdev_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + s5k6aa->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &s5k6aa->pad); + if (ret) + return ret; + + ret = s5k6aa_configure_gpios(s5k6aa, pdata); + if (ret) + goto out_err; + + for (i = 0; i < S5K6AA_NUM_SUPPLIES; i++) + s5k6aa->supplies[i].supply = s5k6aa_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, S5K6AA_NUM_SUPPLIES, + s5k6aa->supplies); + if (ret) { + dev_err(&client->dev, "Failed to get regulators\n"); + goto out_err; + } + + ret = s5k6aa_initialize_ctrls(s5k6aa); + if (ret) + goto out_err; + + s5k6aa_presets_data_init(s5k6aa); + + s5k6aa->ccd_rect.width = S5K6AA_WIN_WIDTH_MAX; + s5k6aa->ccd_rect.height = S5K6AA_WIN_HEIGHT_MAX; + s5k6aa->ccd_rect.left = 0; + s5k6aa->ccd_rect.top = 0; + + return 0; + +out_err: + media_entity_cleanup(&s5k6aa->sd.entity); + return ret; +} + +static int s5k6aa_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct i2c_device_id s5k6aa_id[] = { + { DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, s5k6aa_id); + + +static struct i2c_driver s5k6aa_i2c_driver = { + .driver = { + .name = DRIVER_NAME + }, + .probe = s5k6aa_probe, + .remove = s5k6aa_remove, + .id_table = s5k6aa_id, +}; + +module_i2c_driver(s5k6aa_i2c_driver); + +MODULE_DESCRIPTION("Samsung S5K6AA(FX) SXGA camera driver"); +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/saa6588.c b/drivers/media/i2c/saa6588.c new file mode 100644 index 000000000..d1e0716bd --- /dev/null +++ b/drivers/media/i2c/saa6588.c @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Driver for SAA6588 RDS decoder + + (c) 2005 Hans J. Koch + +*/ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev2.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/wait.h> +#include <linux/uaccess.h> + +#include <media/i2c/saa6588.h> +#include <media/v4l2-device.h> + + +/* insmod options */ +static unsigned int debug; +static unsigned int xtal; +static unsigned int mmbs; +static unsigned int plvl; +static unsigned int bufblocks = 100; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages"); +module_param(xtal, int, 0); +MODULE_PARM_DESC(xtal, "select oscillator frequency (0..3), default 0"); +module_param(mmbs, int, 0); +MODULE_PARM_DESC(mmbs, "enable MMBS mode: 0=off (default), 1=on"); +module_param(plvl, int, 0); +MODULE_PARM_DESC(plvl, "select pause level (0..3), default 0"); +module_param(bufblocks, int, 0); +MODULE_PARM_DESC(bufblocks, "number of buffered blocks, default 100"); + +MODULE_DESCRIPTION("v4l2 driver module for SAA6588 RDS decoder"); +MODULE_AUTHOR("Hans J. Koch <koch@hjk-az.de>"); + +MODULE_LICENSE("GPL"); + +/* ---------------------------------------------------------------------- */ + +#define UNSET (-1U) +#define PREFIX "saa6588: " +#define dprintk if (debug) printk + +struct saa6588 { + struct v4l2_subdev sd; + struct delayed_work work; + spinlock_t lock; + unsigned char *buffer; + unsigned int buf_size; + unsigned int rd_index; + unsigned int wr_index; + unsigned int block_count; + unsigned char last_blocknum; + wait_queue_head_t read_queue; + int data_available_for_read; + u8 sync; +}; + +static inline struct saa6588 *to_saa6588(struct v4l2_subdev *sd) +{ + return container_of(sd, struct saa6588, sd); +} + +/* ---------------------------------------------------------------------- */ + +/* + * SAA6588 defines + */ + +/* Initialization and mode control byte (0w) */ + +/* bit 0+1 (DAC0/DAC1) */ +#define cModeStandard 0x00 +#define cModeFastPI 0x01 +#define cModeReducedRequest 0x02 +#define cModeInvalid 0x03 + +/* bit 2 (RBDS) */ +#define cProcessingModeRDS 0x00 +#define cProcessingModeRBDS 0x04 + +/* bit 3+4 (SYM0/SYM1) */ +#define cErrCorrectionNone 0x00 +#define cErrCorrection2Bits 0x08 +#define cErrCorrection5Bits 0x10 +#define cErrCorrectionNoneRBDS 0x18 + +/* bit 5 (NWSY) */ +#define cSyncNormal 0x00 +#define cSyncRestart 0x20 + +/* bit 6 (TSQD) */ +#define cSigQualityDetectOFF 0x00 +#define cSigQualityDetectON 0x40 + +/* bit 7 (SQCM) */ +#define cSigQualityTriggered 0x00 +#define cSigQualityContinous 0x80 + +/* Pause level and flywheel control byte (1w) */ + +/* bits 0..5 (FEB0..FEB5) */ +#define cFlywheelMaxBlocksMask 0x3F +#define cFlywheelDefault 0x20 + +/* bits 6+7 (PL0/PL1) */ +#define cPauseLevel_11mV 0x00 +#define cPauseLevel_17mV 0x40 +#define cPauseLevel_27mV 0x80 +#define cPauseLevel_43mV 0xC0 + +/* Pause time/oscillator frequency/quality detector control byte (1w) */ + +/* bits 0..4 (SQS0..SQS4) */ +#define cQualityDetectSensMask 0x1F +#define cQualityDetectDefault 0x0F + +/* bit 5 (SOSC) */ +#define cSelectOscFreqOFF 0x00 +#define cSelectOscFreqON 0x20 + +/* bit 6+7 (PTF0/PTF1) */ +#define cOscFreq_4332kHz 0x00 +#define cOscFreq_8664kHz 0x40 +#define cOscFreq_12996kHz 0x80 +#define cOscFreq_17328kHz 0xC0 + +/* ---------------------------------------------------------------------- */ + +static bool block_from_buf(struct saa6588 *s, unsigned char *buf) +{ + int i; + + if (s->rd_index == s->wr_index) { + if (debug > 2) + dprintk(PREFIX "Read: buffer empty.\n"); + return false; + } + + if (debug > 2) { + dprintk(PREFIX "Read: "); + for (i = s->rd_index; i < s->rd_index + 3; i++) + dprintk("0x%02x ", s->buffer[i]); + } + + memcpy(buf, &s->buffer[s->rd_index], 3); + + s->rd_index += 3; + if (s->rd_index >= s->buf_size) + s->rd_index = 0; + s->block_count--; + + if (debug > 2) + dprintk("%d blocks total.\n", s->block_count); + + return true; +} + +static void read_from_buf(struct saa6588 *s, struct saa6588_command *a) +{ + unsigned char __user *buf_ptr = a->buffer; + unsigned char buf[3]; + unsigned long flags; + unsigned int rd_blocks; + unsigned int i; + + a->result = 0; + if (!a->buffer) + return; + + while (!a->nonblocking && !s->data_available_for_read) { + int ret = wait_event_interruptible(s->read_queue, + s->data_available_for_read); + if (ret == -ERESTARTSYS) { + a->result = -EINTR; + return; + } + } + + rd_blocks = a->block_count; + spin_lock_irqsave(&s->lock, flags); + if (rd_blocks > s->block_count) + rd_blocks = s->block_count; + spin_unlock_irqrestore(&s->lock, flags); + + if (!rd_blocks) + return; + + for (i = 0; i < rd_blocks; i++) { + bool got_block; + + spin_lock_irqsave(&s->lock, flags); + got_block = block_from_buf(s, buf); + spin_unlock_irqrestore(&s->lock, flags); + if (!got_block) + break; + if (copy_to_user(buf_ptr, buf, 3)) { + a->result = -EFAULT; + return; + } + buf_ptr += 3; + a->result += 3; + } + spin_lock_irqsave(&s->lock, flags); + s->data_available_for_read = (s->block_count > 0); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void block_to_buf(struct saa6588 *s, unsigned char *blockbuf) +{ + unsigned int i; + + if (debug > 3) + dprintk(PREFIX "New block: "); + + for (i = 0; i < 3; ++i) { + if (debug > 3) + dprintk("0x%02x ", blockbuf[i]); + s->buffer[s->wr_index] = blockbuf[i]; + s->wr_index++; + } + + if (s->wr_index >= s->buf_size) + s->wr_index = 0; + + if (s->wr_index == s->rd_index) { + s->rd_index += 3; + if (s->rd_index >= s->buf_size) + s->rd_index = 0; + } else + s->block_count++; + + if (debug > 3) + dprintk("%d blocks total.\n", s->block_count); +} + +static void saa6588_i2c_poll(struct saa6588 *s) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s->sd); + unsigned long flags; + unsigned char tmpbuf[6]; + unsigned char blocknum; + unsigned char tmp; + + /* Although we only need 3 bytes, we have to read at least 6. + SAA6588 returns garbage otherwise. */ + if (6 != i2c_master_recv(client, &tmpbuf[0], 6)) { + if (debug > 1) + dprintk(PREFIX "read error!\n"); + return; + } + + s->sync = tmpbuf[0] & 0x10; + if (!s->sync) + return; + blocknum = tmpbuf[0] >> 5; + if (blocknum == s->last_blocknum) { + if (debug > 3) + dprintk("Saw block %d again.\n", blocknum); + return; + } + + s->last_blocknum = blocknum; + + /* + Byte order according to v4l2 specification: + + Byte 0: Least Significant Byte of RDS Block + Byte 1: Most Significant Byte of RDS Block + Byte 2 Bit 7: Error bit. Indicates that an uncorrectable error + occurred during reception of this block. + Bit 6: Corrected bit. Indicates that an error was + corrected for this data block. + Bits 5-3: Same as bits 0-2. + Bits 2-0: Block number. + + SAA6588 byte order is Status-MSB-LSB, so we have to swap the + first and the last of the 3 bytes block. + */ + + swap(tmpbuf[2], tmpbuf[0]); + + /* Map 'Invalid block E' to 'Invalid Block' */ + if (blocknum == 6) + blocknum = V4L2_RDS_BLOCK_INVALID; + /* And if are not in mmbs mode, then 'Block E' is also mapped + to 'Invalid Block'. As far as I can tell MMBS is discontinued, + and if there is ever a need to support E blocks, then please + contact the linux-media mailinglist. */ + else if (!mmbs && blocknum == 5) + blocknum = V4L2_RDS_BLOCK_INVALID; + tmp = blocknum; + tmp |= blocknum << 3; /* Received offset == Offset Name (OK ?) */ + if ((tmpbuf[2] & 0x03) == 0x03) + tmp |= V4L2_RDS_BLOCK_ERROR; /* uncorrectable error */ + else if ((tmpbuf[2] & 0x03) != 0x00) + tmp |= V4L2_RDS_BLOCK_CORRECTED; /* corrected error */ + tmpbuf[2] = tmp; /* Is this enough ? Should we also check other bits ? */ + + spin_lock_irqsave(&s->lock, flags); + block_to_buf(s, tmpbuf); + spin_unlock_irqrestore(&s->lock, flags); + s->data_available_for_read = 1; + wake_up_interruptible(&s->read_queue); +} + +static void saa6588_work(struct work_struct *work) +{ + struct saa6588 *s = container_of(work, struct saa6588, work.work); + + saa6588_i2c_poll(s); + schedule_delayed_work(&s->work, msecs_to_jiffies(20)); +} + +static void saa6588_configure(struct saa6588 *s) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s->sd); + unsigned char buf[3]; + int rc; + + buf[0] = cSyncRestart; + if (mmbs) + buf[0] |= cProcessingModeRBDS; + + buf[1] = cFlywheelDefault; + switch (plvl) { + case 0: + buf[1] |= cPauseLevel_11mV; + break; + case 1: + buf[1] |= cPauseLevel_17mV; + break; + case 2: + buf[1] |= cPauseLevel_27mV; + break; + case 3: + buf[1] |= cPauseLevel_43mV; + break; + default: /* nothing */ + break; + } + + buf[2] = cQualityDetectDefault | cSelectOscFreqON; + + switch (xtal) { + case 0: + buf[2] |= cOscFreq_4332kHz; + break; + case 1: + buf[2] |= cOscFreq_8664kHz; + break; + case 2: + buf[2] |= cOscFreq_12996kHz; + break; + case 3: + buf[2] |= cOscFreq_17328kHz; + break; + default: /* nothing */ + break; + } + + dprintk(PREFIX "writing: 0w=0x%02x 1w=0x%02x 2w=0x%02x\n", + buf[0], buf[1], buf[2]); + + rc = i2c_master_send(client, buf, 3); + if (rc != 3) + printk(PREFIX "i2c i/o error: rc == %d (should be 3)\n", rc); +} + +/* ---------------------------------------------------------------------- */ + +static long saa6588_command(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + struct saa6588 *s = to_saa6588(sd); + struct saa6588_command *a = arg; + + switch (cmd) { + /* --- close() for /dev/radio --- */ + case SAA6588_CMD_CLOSE: + s->data_available_for_read = 1; + wake_up_interruptible(&s->read_queue); + s->data_available_for_read = 0; + a->result = 0; + break; + /* --- read() for /dev/radio --- */ + case SAA6588_CMD_READ: + read_from_buf(s, a); + break; + /* --- poll() for /dev/radio --- */ + case SAA6588_CMD_POLL: + a->poll_mask = 0; + if (s->data_available_for_read) + a->poll_mask |= EPOLLIN | EPOLLRDNORM; + poll_wait(a->instance, &s->read_queue, a->event_list); + break; + + default: + /* nothing */ + return -ENOIOCTLCMD; + } + return 0; +} + +static int saa6588_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct saa6588 *s = to_saa6588(sd); + + vt->capability |= V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO; + if (s->sync) + vt->rxsubchans |= V4L2_TUNER_SUB_RDS; + return 0; +} + +static int saa6588_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + struct saa6588 *s = to_saa6588(sd); + + saa6588_configure(s); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops saa6588_core_ops = { + .command = saa6588_command, +}; + +static const struct v4l2_subdev_tuner_ops saa6588_tuner_ops = { + .g_tuner = saa6588_g_tuner, + .s_tuner = saa6588_s_tuner, +}; + +static const struct v4l2_subdev_ops saa6588_ops = { + .core = &saa6588_core_ops, + .tuner = &saa6588_tuner_ops, +}; + +/* ---------------------------------------------------------------------- */ + +static int saa6588_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct saa6588 *s; + struct v4l2_subdev *sd; + + v4l_info(client, "saa6588 found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + s = devm_kzalloc(&client->dev, sizeof(*s), GFP_KERNEL); + if (s == NULL) + return -ENOMEM; + + s->buf_size = bufblocks * 3; + + s->buffer = devm_kzalloc(&client->dev, s->buf_size, GFP_KERNEL); + if (s->buffer == NULL) + return -ENOMEM; + sd = &s->sd; + v4l2_i2c_subdev_init(sd, client, &saa6588_ops); + spin_lock_init(&s->lock); + s->block_count = 0; + s->wr_index = 0; + s->rd_index = 0; + s->last_blocknum = 0xff; + init_waitqueue_head(&s->read_queue); + s->data_available_for_read = 0; + + saa6588_configure(s); + + /* start polling via eventd */ + INIT_DELAYED_WORK(&s->work, saa6588_work); + schedule_delayed_work(&s->work, 0); + return 0; +} + +static int saa6588_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct saa6588 *s = to_saa6588(sd); + + v4l2_device_unregister_subdev(sd); + + cancel_delayed_work_sync(&s->work); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id saa6588_id[] = { + { "saa6588", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, saa6588_id); + +static struct i2c_driver saa6588_driver = { + .driver = { + .name = "saa6588", + }, + .probe = saa6588_probe, + .remove = saa6588_remove, + .id_table = saa6588_id, +}; + +module_i2c_driver(saa6588_driver); diff --git a/drivers/media/i2c/saa6752hs.c b/drivers/media/i2c/saa6752hs.c new file mode 100644 index 000000000..6171ced80 --- /dev/null +++ b/drivers/media/i2c/saa6752hs.c @@ -0,0 +1,791 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + /* + saa6752hs - i2c-driver for the saa6752hs by Philips + + Copyright (C) 2004 Andrew de Quincey + + AC-3 support: + + Copyright (C) 2008 Hans Verkuil <hverkuil@xs4all.nl> + + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev2.h> +#include <linux/init.h> +#include <linux/crc32.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-common.h> + +#define MPEG_VIDEO_TARGET_BITRATE_MAX 27000 +#define MPEG_VIDEO_MAX_BITRATE_MAX 27000 +#define MPEG_TOTAL_TARGET_BITRATE_MAX 27000 +#define MPEG_PID_MAX ((1 << 14) - 1) + + +MODULE_DESCRIPTION("device driver for saa6752hs MPEG2 encoder"); +MODULE_AUTHOR("Andrew de Quincey"); +MODULE_LICENSE("GPL"); + +enum saa6752hs_videoformat { + SAA6752HS_VF_D1 = 0, /* standard D1 video format: 720x576 */ + SAA6752HS_VF_2_3_D1 = 1,/* 2/3D1 video format: 480x576 */ + SAA6752HS_VF_1_2_D1 = 2,/* 1/2D1 video format: 352x576 */ + SAA6752HS_VF_SIF = 3, /* SIF video format: 352x288 */ + SAA6752HS_VF_UNKNOWN, +}; + +struct saa6752hs_mpeg_params { + /* transport streams */ + __u16 ts_pid_pmt; + __u16 ts_pid_audio; + __u16 ts_pid_video; + __u16 ts_pid_pcr; + + /* audio */ + enum v4l2_mpeg_audio_encoding au_encoding; + enum v4l2_mpeg_audio_l2_bitrate au_l2_bitrate; + enum v4l2_mpeg_audio_ac3_bitrate au_ac3_bitrate; + + /* video */ + enum v4l2_mpeg_video_aspect vi_aspect; + enum v4l2_mpeg_video_bitrate_mode vi_bitrate_mode; + __u32 vi_bitrate; + __u32 vi_bitrate_peak; +}; + +static const struct v4l2_format v4l2_format_table[] = +{ + [SAA6752HS_VF_D1] = + { .fmt = { .pix = { .width = 720, .height = 576 }}}, + [SAA6752HS_VF_2_3_D1] = + { .fmt = { .pix = { .width = 480, .height = 576 }}}, + [SAA6752HS_VF_1_2_D1] = + { .fmt = { .pix = { .width = 352, .height = 576 }}}, + [SAA6752HS_VF_SIF] = + { .fmt = { .pix = { .width = 352, .height = 288 }}}, + [SAA6752HS_VF_UNKNOWN] = + { .fmt = { .pix = { .width = 0, .height = 0}}}, +}; + +struct saa6752hs_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct { /* video bitrate mode control cluster */ + struct v4l2_ctrl *video_bitrate_mode; + struct v4l2_ctrl *video_bitrate; + struct v4l2_ctrl *video_bitrate_peak; + }; + u32 revision; + int has_ac3; + struct saa6752hs_mpeg_params params; + enum saa6752hs_videoformat video_format; + v4l2_std_id standard; +}; + +enum saa6752hs_command { + SAA6752HS_COMMAND_RESET = 0, + SAA6752HS_COMMAND_STOP = 1, + SAA6752HS_COMMAND_START = 2, + SAA6752HS_COMMAND_PAUSE = 3, + SAA6752HS_COMMAND_RECONFIGURE = 4, + SAA6752HS_COMMAND_SLEEP = 5, + SAA6752HS_COMMAND_RECONFIGURE_FORCE = 6, + + SAA6752HS_COMMAND_MAX +}; + +static inline struct saa6752hs_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct saa6752hs_state, sd); +} + +/* ---------------------------------------------------------------------- */ + +static const u8 PAT[] = { + 0xc2, /* i2c register */ + 0x00, /* table number for encoder */ + + 0x47, /* sync */ + 0x40, 0x00, /* transport_error_indicator(0), payload_unit_start(1), transport_priority(0), pid(0) */ + 0x10, /* transport_scrambling_control(00), adaptation_field_control(01), continuity_counter(0) */ + + 0x00, /* PSI pointer to start of table */ + + 0x00, /* tid(0) */ + 0xb0, 0x0d, /* section_syntax_indicator(1), section_length(13) */ + + 0x00, 0x01, /* transport_stream_id(1) */ + + 0xc1, /* version_number(0), current_next_indicator(1) */ + + 0x00, 0x00, /* section_number(0), last_section_number(0) */ + + 0x00, 0x01, /* program_number(1) */ + + 0xe0, 0x00, /* PMT PID */ + + 0x00, 0x00, 0x00, 0x00 /* CRC32 */ +}; + +static const u8 PMT[] = { + 0xc2, /* i2c register */ + 0x01, /* table number for encoder */ + + 0x47, /* sync */ + 0x40, 0x00, /* transport_error_indicator(0), payload_unit_start(1), transport_priority(0), pid */ + 0x10, /* transport_scrambling_control(00), adaptation_field_control(01), continuity_counter(0) */ + + 0x00, /* PSI pointer to start of table */ + + 0x02, /* tid(2) */ + 0xb0, 0x17, /* section_syntax_indicator(1), section_length(23) */ + + 0x00, 0x01, /* program_number(1) */ + + 0xc1, /* version_number(0), current_next_indicator(1) */ + + 0x00, 0x00, /* section_number(0), last_section_number(0) */ + + 0xe0, 0x00, /* PCR_PID */ + + 0xf0, 0x00, /* program_info_length(0) */ + + 0x02, 0xe0, 0x00, 0xf0, 0x00, /* video stream type(2), pid */ + 0x04, 0xe0, 0x00, 0xf0, 0x00, /* audio stream type(4), pid */ + + 0x00, 0x00, 0x00, 0x00 /* CRC32 */ +}; + +static const u8 PMT_AC3[] = { + 0xc2, /* i2c register */ + 0x01, /* table number for encoder(1) */ + 0x47, /* sync */ + + 0x40, /* transport_error_indicator(0), payload_unit_start(1), transport_priority(0) */ + 0x10, /* PMT PID (0x0010) */ + 0x10, /* transport_scrambling_control(00), adaptation_field_control(01), continuity_counter(0) */ + + 0x00, /* PSI pointer to start of table */ + + 0x02, /* TID (2) */ + 0xb0, 0x1a, /* section_syntax_indicator(1), section_length(26) */ + + 0x00, 0x01, /* program_number(1) */ + + 0xc1, /* version_number(0), current_next_indicator(1) */ + + 0x00, 0x00, /* section_number(0), last_section_number(0) */ + + 0xe1, 0x04, /* PCR_PID (0x0104) */ + + 0xf0, 0x00, /* program_info_length(0) */ + + 0x02, 0xe1, 0x00, 0xf0, 0x00, /* video stream type(2), pid */ + 0x06, 0xe1, 0x03, 0xf0, 0x03, /* audio stream type(6), pid */ + 0x6a, /* AC3 */ + 0x01, /* Descriptor_length(1) */ + 0x00, /* component_type_flag(0), bsid_flag(0), mainid_flag(0), asvc_flag(0), reserved flags(0) */ + + 0xED, 0xDE, 0x2D, 0xF3 /* CRC32 BE */ +}; + +static const struct saa6752hs_mpeg_params param_defaults = +{ + .ts_pid_pmt = 16, + .ts_pid_video = 260, + .ts_pid_audio = 256, + .ts_pid_pcr = 259, + + .vi_aspect = V4L2_MPEG_VIDEO_ASPECT_4x3, + .vi_bitrate = 4000, + .vi_bitrate_peak = 6000, + .vi_bitrate_mode = V4L2_MPEG_VIDEO_BITRATE_MODE_VBR, + + .au_encoding = V4L2_MPEG_AUDIO_ENCODING_LAYER_2, + .au_l2_bitrate = V4L2_MPEG_AUDIO_L2_BITRATE_256K, + .au_ac3_bitrate = V4L2_MPEG_AUDIO_AC3_BITRATE_256K, +}; + +/* ---------------------------------------------------------------------- */ + +static int saa6752hs_chip_command(struct i2c_client *client, + enum saa6752hs_command command) +{ + unsigned char buf[3]; + unsigned long timeout; + int status = 0; + + /* execute the command */ + switch(command) { + case SAA6752HS_COMMAND_RESET: + buf[0] = 0x00; + break; + + case SAA6752HS_COMMAND_STOP: + buf[0] = 0x03; + break; + + case SAA6752HS_COMMAND_START: + buf[0] = 0x02; + break; + + case SAA6752HS_COMMAND_PAUSE: + buf[0] = 0x04; + break; + + case SAA6752HS_COMMAND_RECONFIGURE: + buf[0] = 0x05; + break; + + case SAA6752HS_COMMAND_SLEEP: + buf[0] = 0x06; + break; + + case SAA6752HS_COMMAND_RECONFIGURE_FORCE: + buf[0] = 0x07; + break; + + default: + return -EINVAL; + } + + /* set it and wait for it to be so */ + i2c_master_send(client, buf, 1); + timeout = jiffies + HZ * 3; + for (;;) { + /* get the current status */ + buf[0] = 0x10; + i2c_master_send(client, buf, 1); + i2c_master_recv(client, buf, 1); + + if (!(buf[0] & 0x20)) + break; + if (time_after(jiffies,timeout)) { + status = -ETIMEDOUT; + break; + } + + msleep(10); + } + + /* delay a bit to let encoder settle */ + msleep(50); + + return status; +} + + +static inline void set_reg8(struct i2c_client *client, uint8_t reg, uint8_t val) +{ + u8 buf[2]; + + buf[0] = reg; + buf[1] = val; + i2c_master_send(client, buf, 2); +} + +static inline void set_reg16(struct i2c_client *client, uint8_t reg, uint16_t val) +{ + u8 buf[3]; + + buf[0] = reg; + buf[1] = val >> 8; + buf[2] = val & 0xff; + i2c_master_send(client, buf, 3); +} + +static int saa6752hs_set_bitrate(struct i2c_client *client, + struct saa6752hs_state *h) +{ + struct saa6752hs_mpeg_params *params = &h->params; + int tot_bitrate; + int is_384k; + + /* set the bitrate mode */ + set_reg8(client, 0x71, + params->vi_bitrate_mode != V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + + /* set the video bitrate */ + if (params->vi_bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR) { + /* set the target bitrate */ + set_reg16(client, 0x80, params->vi_bitrate); + + /* set the max bitrate */ + set_reg16(client, 0x81, params->vi_bitrate_peak); + tot_bitrate = params->vi_bitrate_peak; + } else { + /* set the target bitrate (no max bitrate for CBR) */ + set_reg16(client, 0x81, params->vi_bitrate); + tot_bitrate = params->vi_bitrate; + } + + /* set the audio encoding */ + set_reg8(client, 0x93, + params->au_encoding == V4L2_MPEG_AUDIO_ENCODING_AC3); + + /* set the audio bitrate */ + if (params->au_encoding == V4L2_MPEG_AUDIO_ENCODING_AC3) + is_384k = V4L2_MPEG_AUDIO_AC3_BITRATE_384K == params->au_ac3_bitrate; + else + is_384k = V4L2_MPEG_AUDIO_L2_BITRATE_384K == params->au_l2_bitrate; + set_reg8(client, 0x94, is_384k); + tot_bitrate += is_384k ? 384 : 256; + + /* Note: the total max bitrate is determined by adding the video and audio + bitrates together and also adding an extra 768kbit/s to stay on the + safe side. If more control should be required, then an extra MPEG control + should be added. */ + tot_bitrate += 768; + if (tot_bitrate > MPEG_TOTAL_TARGET_BITRATE_MAX) + tot_bitrate = MPEG_TOTAL_TARGET_BITRATE_MAX; + + /* set the total bitrate */ + set_reg16(client, 0xb1, tot_bitrate); + return 0; +} + +static int saa6752hs_try_ctrl(struct v4l2_ctrl *ctrl) +{ + struct saa6752hs_state *h = + container_of(ctrl->handler, struct saa6752hs_state, hdl); + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + /* peak bitrate shall be >= normal bitrate */ + if (ctrl->val == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR && + h->video_bitrate_peak->val < h->video_bitrate->val) + h->video_bitrate_peak->val = h->video_bitrate->val; + break; + } + return 0; +} + +static int saa6752hs_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct saa6752hs_state *h = + container_of(ctrl->handler, struct saa6752hs_state, hdl); + struct saa6752hs_mpeg_params *params = &h->params; + + switch (ctrl->id) { + case V4L2_CID_MPEG_STREAM_TYPE: + break; + case V4L2_CID_MPEG_STREAM_PID_PMT: + params->ts_pid_pmt = ctrl->val; + break; + case V4L2_CID_MPEG_STREAM_PID_AUDIO: + params->ts_pid_audio = ctrl->val; + break; + case V4L2_CID_MPEG_STREAM_PID_VIDEO: + params->ts_pid_video = ctrl->val; + break; + case V4L2_CID_MPEG_STREAM_PID_PCR: + params->ts_pid_pcr = ctrl->val; + break; + case V4L2_CID_MPEG_AUDIO_ENCODING: + params->au_encoding = ctrl->val; + break; + case V4L2_CID_MPEG_AUDIO_L2_BITRATE: + params->au_l2_bitrate = ctrl->val; + break; + case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: + params->au_ac3_bitrate = ctrl->val; + break; + case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: + break; + case V4L2_CID_MPEG_VIDEO_ENCODING: + break; + case V4L2_CID_MPEG_VIDEO_ASPECT: + params->vi_aspect = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + params->vi_bitrate_mode = ctrl->val; + params->vi_bitrate = h->video_bitrate->val / 1000; + params->vi_bitrate_peak = h->video_bitrate_peak->val / 1000; + v4l2_ctrl_activate(h->video_bitrate_peak, + ctrl->val == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + break; + default: + return -EINVAL; + } + return 0; +} + +static int saa6752hs_init(struct v4l2_subdev *sd, u32 leading_null_bytes) +{ + unsigned char buf[9], buf2[4]; + struct saa6752hs_state *h = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned size; + u32 crc; + unsigned char localPAT[256]; + unsigned char localPMT[256]; + + /* Set video format - must be done first as it resets other settings */ + set_reg8(client, 0x41, h->video_format); + + /* Set number of lines in input signal */ + set_reg8(client, 0x40, (h->standard & V4L2_STD_525_60) ? 1 : 0); + + /* set bitrate */ + saa6752hs_set_bitrate(client, h); + + /* Set GOP structure {3, 13} */ + set_reg16(client, 0x72, 0x030d); + + /* Set minimum Q-scale {4} */ + set_reg8(client, 0x82, 0x04); + + /* Set maximum Q-scale {12} */ + set_reg8(client, 0x83, 0x0c); + + /* Set Output Protocol */ + set_reg8(client, 0xd0, 0x81); + + /* Set video output stream format {TS} */ + set_reg8(client, 0xb0, 0x05); + + /* Set leading null byte for TS */ + set_reg16(client, 0xf6, leading_null_bytes); + + /* compute PAT */ + memcpy(localPAT, PAT, sizeof(PAT)); + localPAT[17] = 0xe0 | ((h->params.ts_pid_pmt >> 8) & 0x0f); + localPAT[18] = h->params.ts_pid_pmt & 0xff; + crc = crc32_be(~0, &localPAT[7], sizeof(PAT) - 7 - 4); + localPAT[sizeof(PAT) - 4] = (crc >> 24) & 0xFF; + localPAT[sizeof(PAT) - 3] = (crc >> 16) & 0xFF; + localPAT[sizeof(PAT) - 2] = (crc >> 8) & 0xFF; + localPAT[sizeof(PAT) - 1] = crc & 0xFF; + + /* compute PMT */ + if (h->params.au_encoding == V4L2_MPEG_AUDIO_ENCODING_AC3) { + size = sizeof(PMT_AC3); + memcpy(localPMT, PMT_AC3, size); + } else { + size = sizeof(PMT); + memcpy(localPMT, PMT, size); + } + localPMT[3] = 0x40 | ((h->params.ts_pid_pmt >> 8) & 0x0f); + localPMT[4] = h->params.ts_pid_pmt & 0xff; + localPMT[15] = 0xE0 | ((h->params.ts_pid_pcr >> 8) & 0x0F); + localPMT[16] = h->params.ts_pid_pcr & 0xFF; + localPMT[20] = 0xE0 | ((h->params.ts_pid_video >> 8) & 0x0F); + localPMT[21] = h->params.ts_pid_video & 0xFF; + localPMT[25] = 0xE0 | ((h->params.ts_pid_audio >> 8) & 0x0F); + localPMT[26] = h->params.ts_pid_audio & 0xFF; + crc = crc32_be(~0, &localPMT[7], size - 7 - 4); + localPMT[size - 4] = (crc >> 24) & 0xFF; + localPMT[size - 3] = (crc >> 16) & 0xFF; + localPMT[size - 2] = (crc >> 8) & 0xFF; + localPMT[size - 1] = crc & 0xFF; + + /* Set Audio PID */ + set_reg16(client, 0xc1, h->params.ts_pid_audio); + + /* Set Video PID */ + set_reg16(client, 0xc0, h->params.ts_pid_video); + + /* Set PCR PID */ + set_reg16(client, 0xc4, h->params.ts_pid_pcr); + + /* Send SI tables */ + i2c_master_send(client, localPAT, sizeof(PAT)); + i2c_master_send(client, localPMT, size); + + /* mute then unmute audio. This removes buzzing artefacts */ + set_reg8(client, 0xa4, 1); + set_reg8(client, 0xa4, 0); + + /* start it going */ + saa6752hs_chip_command(client, SAA6752HS_COMMAND_START); + + /* readout current state */ + buf[0] = 0xE1; + buf[1] = 0xA7; + buf[2] = 0xFE; + buf[3] = 0x82; + buf[4] = 0xB0; + i2c_master_send(client, buf, 5); + i2c_master_recv(client, buf2, 4); + + /* change aspect ratio */ + buf[0] = 0xE0; + buf[1] = 0xA7; + buf[2] = 0xFE; + buf[3] = 0x82; + buf[4] = 0xB0; + buf[5] = buf2[0]; + switch (h->params.vi_aspect) { + case V4L2_MPEG_VIDEO_ASPECT_16x9: + buf[6] = buf2[1] | 0x40; + break; + case V4L2_MPEG_VIDEO_ASPECT_4x3: + default: + buf[6] = buf2[1] & 0xBF; + break; + } + buf[7] = buf2[2]; + buf[8] = buf2[3]; + i2c_master_send(client, buf, 9); + + return 0; +} + +static int saa6752hs_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *f = &format->format; + struct saa6752hs_state *h = to_state(sd); + + if (format->pad) + return -EINVAL; + + if (h->video_format == SAA6752HS_VF_UNKNOWN) + h->video_format = SAA6752HS_VF_D1; + f->width = v4l2_format_table[h->video_format].fmt.pix.width; + f->height = v4l2_format_table[h->video_format].fmt.pix.height; + f->code = MEDIA_BUS_FMT_FIXED; + f->field = V4L2_FIELD_INTERLACED; + f->colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int saa6752hs_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *f = &format->format; + struct saa6752hs_state *h = to_state(sd); + int dist_352, dist_480, dist_720; + + if (format->pad) + return -EINVAL; + + f->code = MEDIA_BUS_FMT_FIXED; + + dist_352 = abs(f->width - 352); + dist_480 = abs(f->width - 480); + dist_720 = abs(f->width - 720); + if (dist_720 < dist_480) { + f->width = 720; + f->height = 576; + } else if (dist_480 < dist_352) { + f->width = 480; + f->height = 576; + } else { + f->width = 352; + if (abs(f->height - 576) < abs(f->height - 288)) + f->height = 576; + else + f->height = 288; + } + f->field = V4L2_FIELD_INTERLACED; + f->colorspace = V4L2_COLORSPACE_SMPTE170M; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + cfg->try_fmt = *f; + return 0; + } + + /* + FIXME: translate and round width/height into EMPRESS + subsample type: + + type | PAL | NTSC + --------------------------- + SIF | 352x288 | 352x240 + 1/2 D1 | 352x576 | 352x480 + 2/3 D1 | 480x576 | 480x480 + D1 | 720x576 | 720x480 + */ + + if (f->code != MEDIA_BUS_FMT_FIXED) + return -EINVAL; + + if (f->width == 720) + h->video_format = SAA6752HS_VF_D1; + else if (f->width == 480) + h->video_format = SAA6752HS_VF_2_3_D1; + else if (f->height == 576) + h->video_format = SAA6752HS_VF_1_2_D1; + else + h->video_format = SAA6752HS_VF_SIF; + return 0; +} + +static int saa6752hs_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa6752hs_state *h = to_state(sd); + + h->standard = std; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops saa6752hs_ctrl_ops = { + .try_ctrl = saa6752hs_try_ctrl, + .s_ctrl = saa6752hs_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops saa6752hs_core_ops = { + .init = saa6752hs_init, +}; + +static const struct v4l2_subdev_video_ops saa6752hs_video_ops = { + .s_std = saa6752hs_s_std, +}; + +static const struct v4l2_subdev_pad_ops saa6752hs_pad_ops = { + .get_fmt = saa6752hs_get_fmt, + .set_fmt = saa6752hs_set_fmt, +}; + +static const struct v4l2_subdev_ops saa6752hs_ops = { + .core = &saa6752hs_core_ops, + .video = &saa6752hs_video_ops, + .pad = &saa6752hs_pad_ops, +}; + +static int saa6752hs_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct saa6752hs_state *h; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + u8 addr = 0x13; + u8 data[12]; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + h = devm_kzalloc(&client->dev, sizeof(*h), GFP_KERNEL); + if (h == NULL) + return -ENOMEM; + sd = &h->sd; + v4l2_i2c_subdev_init(sd, client, &saa6752hs_ops); + + i2c_master_send(client, &addr, 1); + i2c_master_recv(client, data, sizeof(data)); + h->revision = (data[8] << 8) | data[9]; + h->has_ac3 = 0; + if (h->revision == 0x0206) { + h->has_ac3 = 1; + v4l_info(client, "supports AC-3\n"); + } + h->params = param_defaults; + + hdl = &h->hdl; + v4l2_ctrl_handler_init(hdl, 14); + v4l2_ctrl_new_std_menu(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_AUDIO_ENCODING, + h->has_ac3 ? V4L2_MPEG_AUDIO_ENCODING_AC3 : + V4L2_MPEG_AUDIO_ENCODING_LAYER_2, + 0x0d, V4L2_MPEG_AUDIO_ENCODING_LAYER_2); + + v4l2_ctrl_new_std_menu(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_AUDIO_L2_BITRATE, + V4L2_MPEG_AUDIO_L2_BITRATE_384K, + ~((1 << V4L2_MPEG_AUDIO_L2_BITRATE_256K) | + (1 << V4L2_MPEG_AUDIO_L2_BITRATE_384K)), + V4L2_MPEG_AUDIO_L2_BITRATE_256K); + + if (h->has_ac3) + v4l2_ctrl_new_std_menu(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_AUDIO_AC3_BITRATE, + V4L2_MPEG_AUDIO_AC3_BITRATE_384K, + ~((1 << V4L2_MPEG_AUDIO_AC3_BITRATE_256K) | + (1 << V4L2_MPEG_AUDIO_AC3_BITRATE_384K)), + V4L2_MPEG_AUDIO_AC3_BITRATE_256K); + + v4l2_ctrl_new_std_menu(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ, + V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000, + ~(1 << V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000), + V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000); + + v4l2_ctrl_new_std_menu(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_VIDEO_ENCODING, + V4L2_MPEG_VIDEO_ENCODING_MPEG_2, + ~(1 << V4L2_MPEG_VIDEO_ENCODING_MPEG_2), + V4L2_MPEG_VIDEO_ENCODING_MPEG_2); + + v4l2_ctrl_new_std_menu(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_VIDEO_ASPECT, + V4L2_MPEG_VIDEO_ASPECT_16x9, 0x01, + V4L2_MPEG_VIDEO_ASPECT_4x3); + + h->video_bitrate_peak = v4l2_ctrl_new_std(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, + 1000000, 27000000, 1000, 8000000); + + v4l2_ctrl_new_std_menu(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_STREAM_TYPE, + V4L2_MPEG_STREAM_TYPE_MPEG2_TS, + ~(1 << V4L2_MPEG_STREAM_TYPE_MPEG2_TS), + V4L2_MPEG_STREAM_TYPE_MPEG2_TS); + + h->video_bitrate_mode = v4l2_ctrl_new_std_menu(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0, + V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + h->video_bitrate = v4l2_ctrl_new_std(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, 1000000, 27000000, 1000, 6000000); + v4l2_ctrl_new_std(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_STREAM_PID_PMT, 0, (1 << 14) - 1, 1, 16); + v4l2_ctrl_new_std(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_STREAM_PID_AUDIO, 0, (1 << 14) - 1, 1, 260); + v4l2_ctrl_new_std(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_STREAM_PID_VIDEO, 0, (1 << 14) - 1, 1, 256); + v4l2_ctrl_new_std(hdl, &saa6752hs_ctrl_ops, + V4L2_CID_MPEG_STREAM_PID_PCR, 0, (1 << 14) - 1, 1, 259); + sd->ctrl_handler = hdl; + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return err; + } + v4l2_ctrl_cluster(3, &h->video_bitrate_mode); + v4l2_ctrl_handler_setup(hdl); + h->standard = 0; /* Assume 625 input lines */ + return 0; +} + +static int saa6752hs_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&to_state(sd)->hdl); + return 0; +} + +static const struct i2c_device_id saa6752hs_id[] = { + { "saa6752hs", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, saa6752hs_id); + +static struct i2c_driver saa6752hs_driver = { + .driver = { + .name = "saa6752hs", + }, + .probe = saa6752hs_probe, + .remove = saa6752hs_remove, + .id_table = saa6752hs_id, +}; + +module_i2c_driver(saa6752hs_driver); diff --git a/drivers/media/i2c/saa7110.c b/drivers/media/i2c/saa7110.c new file mode 100644 index 000000000..0c7a9ce0a --- /dev/null +++ b/drivers/media/i2c/saa7110.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * saa7110 - Philips SAA7110(A) video decoder driver + * + * Copyright (C) 1998 Pauline Middelink <middelin@polyware.nl> + * + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +MODULE_DESCRIPTION("Philips SAA7110 video decoder driver"); +MODULE_AUTHOR("Pauline Middelink"); +MODULE_LICENSE("GPL"); + + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define SAA7110_MAX_INPUT 9 /* 6 CVBS, 3 SVHS */ +#define SAA7110_MAX_OUTPUT 1 /* 1 YUV */ + +#define SAA7110_NR_REG 0x35 + +struct saa7110 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + u8 reg[SAA7110_NR_REG]; + + v4l2_std_id norm; + int input; + int enable; + + wait_queue_head_t wq; +}; + +static inline struct saa7110 *to_saa7110(struct v4l2_subdev *sd) +{ + return container_of(sd, struct saa7110, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct saa7110, hdl)->sd; +} + +/* ----------------------------------------------------------------------- */ +/* I2C support functions */ +/* ----------------------------------------------------------------------- */ + +static int saa7110_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct saa7110 *decoder = to_saa7110(sd); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int saa7110_write_block(struct v4l2_subdev *sd, const u8 *data, unsigned int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct saa7110 *decoder = to_saa7110(sd); + int ret = -1; + u8 reg = *data; /* first register to write to */ + + /* Sanity check */ + if (reg + (len - 1) > SAA7110_NR_REG) + return ret; + + /* the saa7110 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + ret = i2c_master_send(client, data, len); + + /* Cache the written data */ + memcpy(decoder->reg + reg, data + 1, len - 1); + } else { + for (++data, --len; len; len--) { + ret = saa7110_write(sd, reg++, *data++); + if (ret < 0) + break; + } + } + + return ret; +} + +static inline int saa7110_read(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte(client); +} + +/* ----------------------------------------------------------------------- */ +/* SAA7110 functions */ +/* ----------------------------------------------------------------------- */ + +#define FRESP_06H_COMPST 0x03 /*0x13*/ +#define FRESP_06H_SVIDEO 0x83 /*0xC0*/ + + +static int saa7110_selmux(struct v4l2_subdev *sd, int chan) +{ + static const unsigned char modes[9][8] = { + /* mode 0 */ + {FRESP_06H_COMPST, 0xD9, 0x17, 0x40, 0x03, + 0x44, 0x75, 0x16}, + /* mode 1 */ + {FRESP_06H_COMPST, 0xD8, 0x17, 0x40, 0x03, + 0x44, 0x75, 0x16}, + /* mode 2 */ + {FRESP_06H_COMPST, 0xBA, 0x07, 0x91, 0x03, + 0x60, 0xB5, 0x05}, + /* mode 3 */ + {FRESP_06H_COMPST, 0xB8, 0x07, 0x91, 0x03, + 0x60, 0xB5, 0x05}, + /* mode 4 */ + {FRESP_06H_COMPST, 0x7C, 0x07, 0xD2, 0x83, + 0x60, 0xB5, 0x03}, + /* mode 5 */ + {FRESP_06H_COMPST, 0x78, 0x07, 0xD2, 0x83, + 0x60, 0xB5, 0x03}, + /* mode 6 */ + {FRESP_06H_SVIDEO, 0x59, 0x17, 0x42, 0xA3, + 0x44, 0x75, 0x12}, + /* mode 7 */ + {FRESP_06H_SVIDEO, 0x9A, 0x17, 0xB1, 0x13, + 0x60, 0xB5, 0x14}, + /* mode 8 */ + {FRESP_06H_SVIDEO, 0x3C, 0x27, 0xC1, 0x23, + 0x44, 0x75, 0x21} + }; + struct saa7110 *decoder = to_saa7110(sd); + const unsigned char *ptr = modes[chan]; + + saa7110_write(sd, 0x06, ptr[0]); /* Luminance control */ + saa7110_write(sd, 0x20, ptr[1]); /* Analog Control #1 */ + saa7110_write(sd, 0x21, ptr[2]); /* Analog Control #2 */ + saa7110_write(sd, 0x22, ptr[3]); /* Mixer Control #1 */ + saa7110_write(sd, 0x2C, ptr[4]); /* Mixer Control #2 */ + saa7110_write(sd, 0x30, ptr[5]); /* ADCs gain control */ + saa7110_write(sd, 0x31, ptr[6]); /* Mixer Control #3 */ + saa7110_write(sd, 0x21, ptr[7]); /* Analog Control #2 */ + decoder->input = chan; + + return 0; +} + +static const unsigned char initseq[1 + SAA7110_NR_REG] = { + 0, 0x4C, 0x3C, 0x0D, 0xEF, 0xBD, 0xF2, 0x03, 0x00, + /* 0x08 */ 0xF8, 0xF8, 0x60, 0x60, 0x00, 0x86, 0x18, 0x90, + /* 0x10 */ 0x00, 0x59, 0x40, 0x46, 0x42, 0x1A, 0xFF, 0xDA, + /* 0x18 */ 0xF2, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x20 */ 0xD9, 0x16, 0x40, 0x41, 0x80, 0x41, 0x80, 0x4F, + /* 0x28 */ 0xFE, 0x01, 0xCF, 0x0F, 0x03, 0x01, 0x03, 0x0C, + /* 0x30 */ 0x44, 0x71, 0x02, 0x8C, 0x02 +}; + +static v4l2_std_id determine_norm(struct v4l2_subdev *sd) +{ + DEFINE_WAIT(wait); + struct saa7110 *decoder = to_saa7110(sd); + int status; + + /* mode changed, start automatic detection */ + saa7110_write_block(sd, initseq, sizeof(initseq)); + saa7110_selmux(sd, decoder->input); + prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(250)); + finish_wait(&decoder->wq, &wait); + status = saa7110_read(sd); + if (status & 0x40) { + v4l2_dbg(1, debug, sd, "status=0x%02x (no signal)\n", status); + return V4L2_STD_UNKNOWN; + } + if ((status & 3) == 0) { + saa7110_write(sd, 0x06, 0x83); + if (status & 0x20) { + v4l2_dbg(1, debug, sd, "status=0x%02x (NTSC/no color)\n", status); + /*saa7110_write(sd,0x2E,0x81);*/ + return V4L2_STD_NTSC; + } + v4l2_dbg(1, debug, sd, "status=0x%02x (PAL/no color)\n", status); + /*saa7110_write(sd,0x2E,0x9A);*/ + return V4L2_STD_PAL; + } + /*saa7110_write(sd,0x06,0x03);*/ + if (status & 0x20) { /* 60Hz */ + v4l2_dbg(1, debug, sd, "status=0x%02x (NTSC)\n", status); + saa7110_write(sd, 0x0D, 0x86); + saa7110_write(sd, 0x0F, 0x50); + saa7110_write(sd, 0x11, 0x2C); + /*saa7110_write(sd,0x2E,0x81);*/ + return V4L2_STD_NTSC; + } + + /* 50Hz -> PAL/SECAM */ + saa7110_write(sd, 0x0D, 0x86); + saa7110_write(sd, 0x0F, 0x10); + saa7110_write(sd, 0x11, 0x59); + /*saa7110_write(sd,0x2E,0x9A);*/ + + prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(250)); + finish_wait(&decoder->wq, &wait); + + status = saa7110_read(sd); + if ((status & 0x03) == 0x01) { + v4l2_dbg(1, debug, sd, "status=0x%02x (SECAM)\n", status); + saa7110_write(sd, 0x0D, 0x87); + return V4L2_STD_SECAM; + } + v4l2_dbg(1, debug, sd, "status=0x%02x (PAL)\n", status); + return V4L2_STD_PAL; +} + +static int saa7110_g_input_status(struct v4l2_subdev *sd, u32 *pstatus) +{ + struct saa7110 *decoder = to_saa7110(sd); + int res = V4L2_IN_ST_NO_SIGNAL; + int status = saa7110_read(sd); + + v4l2_dbg(1, debug, sd, "status=0x%02x norm=%llx\n", + status, (unsigned long long)decoder->norm); + if (!(status & 0x40)) + res = 0; + if (!(status & 0x03)) + res |= V4L2_IN_ST_NO_COLOR; + + *pstatus = res; + return 0; +} + +static int saa7110_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + *std &= determine_norm(sd); + return 0; +} + +static int saa7110_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa7110 *decoder = to_saa7110(sd); + + if (decoder->norm != std) { + decoder->norm = std; + /*saa7110_write(sd, 0x06, 0x03);*/ + if (std & V4L2_STD_NTSC) { + saa7110_write(sd, 0x0D, 0x86); + saa7110_write(sd, 0x0F, 0x50); + saa7110_write(sd, 0x11, 0x2C); + /*saa7110_write(sd, 0x2E, 0x81);*/ + v4l2_dbg(1, debug, sd, "switched to NTSC\n"); + } else if (std & V4L2_STD_PAL) { + saa7110_write(sd, 0x0D, 0x86); + saa7110_write(sd, 0x0F, 0x10); + saa7110_write(sd, 0x11, 0x59); + /*saa7110_write(sd, 0x2E, 0x9A);*/ + v4l2_dbg(1, debug, sd, "switched to PAL\n"); + } else if (std & V4L2_STD_SECAM) { + saa7110_write(sd, 0x0D, 0x87); + saa7110_write(sd, 0x0F, 0x10); + saa7110_write(sd, 0x11, 0x59); + /*saa7110_write(sd, 0x2E, 0x9A);*/ + v4l2_dbg(1, debug, sd, "switched to SECAM\n"); + } else { + return -EINVAL; + } + } + return 0; +} + +static int saa7110_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct saa7110 *decoder = to_saa7110(sd); + + if (input >= SAA7110_MAX_INPUT) { + v4l2_dbg(1, debug, sd, "input=%d not available\n", input); + return -EINVAL; + } + if (decoder->input != input) { + saa7110_selmux(sd, input); + v4l2_dbg(1, debug, sd, "switched to input=%d\n", input); + } + return 0; +} + +static int saa7110_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct saa7110 *decoder = to_saa7110(sd); + + if (decoder->enable != enable) { + decoder->enable = enable; + saa7110_write(sd, 0x0E, enable ? 0x18 : 0x80); + v4l2_dbg(1, debug, sd, "YUV %s\n", enable ? "on" : "off"); + } + return 0; +} + +static int saa7110_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + saa7110_write(sd, 0x19, ctrl->val); + break; + case V4L2_CID_CONTRAST: + saa7110_write(sd, 0x13, ctrl->val); + break; + case V4L2_CID_SATURATION: + saa7110_write(sd, 0x12, ctrl->val); + break; + case V4L2_CID_HUE: + saa7110_write(sd, 0x07, ctrl->val); + break; + default: + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops saa7110_ctrl_ops = { + .s_ctrl = saa7110_s_ctrl, +}; + +static const struct v4l2_subdev_video_ops saa7110_video_ops = { + .s_std = saa7110_s_std, + .s_routing = saa7110_s_routing, + .s_stream = saa7110_s_stream, + .querystd = saa7110_querystd, + .g_input_status = saa7110_g_input_status, +}; + +static const struct v4l2_subdev_ops saa7110_ops = { + .video = &saa7110_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int saa7110_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct saa7110 *decoder; + struct v4l2_subdev *sd; + int rv; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + decoder = devm_kzalloc(&client->dev, sizeof(*decoder), GFP_KERNEL); + if (!decoder) + return -ENOMEM; + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &saa7110_ops); + decoder->norm = V4L2_STD_PAL; + decoder->input = 0; + decoder->enable = 1; + v4l2_ctrl_handler_init(&decoder->hdl, 2); + v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + sd->ctrl_handler = &decoder->hdl; + if (decoder->hdl.error) { + int err = decoder->hdl.error; + + v4l2_ctrl_handler_free(&decoder->hdl); + return err; + } + v4l2_ctrl_handler_setup(&decoder->hdl); + + init_waitqueue_head(&decoder->wq); + + rv = saa7110_write_block(sd, initseq, sizeof(initseq)); + if (rv < 0) { + v4l2_dbg(1, debug, sd, "init status %d\n", rv); + } else { + int ver, status; + saa7110_write(sd, 0x21, 0x10); + saa7110_write(sd, 0x0e, 0x18); + saa7110_write(sd, 0x0D, 0x04); + ver = saa7110_read(sd); + saa7110_write(sd, 0x0D, 0x06); + /*mdelay(150);*/ + status = saa7110_read(sd); + v4l2_dbg(1, debug, sd, "version %x, status=0x%02x\n", + ver, status); + saa7110_write(sd, 0x0D, 0x86); + saa7110_write(sd, 0x0F, 0x10); + saa7110_write(sd, 0x11, 0x59); + /*saa7110_write(sd, 0x2E, 0x9A);*/ + } + + /*saa7110_selmux(sd,0);*/ + /*determine_norm(sd);*/ + /* setup and implicit mode 0 select has been performed */ + + return 0; +} + +static int saa7110_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct saa7110 *decoder = to_saa7110(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&decoder->hdl); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id saa7110_id[] = { + { "saa7110", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, saa7110_id); + +static struct i2c_driver saa7110_driver = { + .driver = { + .name = "saa7110", + }, + .probe = saa7110_probe, + .remove = saa7110_remove, + .id_table = saa7110_id, +}; + +module_i2c_driver(saa7110_driver); diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c new file mode 100644 index 000000000..88dc6baac --- /dev/null +++ b/drivers/media/i2c/saa7115.c @@ -0,0 +1,1960 @@ +// SPDX-License-Identifier: GPL-2.0+ +// saa711x - Philips SAA711x video decoder driver +// This driver can work with saa7111, saa7111a, saa7113, saa7114, +// saa7115 and saa7118. +// +// Based on saa7114 driver by Maxim Yevtyushkin, which is based on +// the saa7111 driver by Dave Perks. +// +// Copyright (C) 1998 Dave Perks <dperks@ibm.net> +// Copyright (C) 2002 Maxim Yevtyushkin <max@linuxmedialabs.com> +// +// Slight changes for video timing and attachment output by +// Wolfgang Scherr <scherr@net4you.net> +// +// Moved over to the linux >= 2.4.x i2c protocol (1/1/2003) +// by Ronald Bultje <rbultje@ronald.bitfreak.net> +// +// Added saa7115 support by Kevin Thayer <nufan_wfk at yahoo.com> +// (2/17/2003) +// +// VBI support (2004) and cleanups (2005) by Hans Verkuil <hverkuil@xs4all.nl> +// +// Copyright (c) 2005-2006 Mauro Carvalho Chehab <mchehab@kernel.org> +// SAA7111, SAA7113 and SAA7118 support + +#include "saa711x_regs.h" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-mc.h> +#include <media/i2c/saa7115.h> +#include <asm/div64.h> + +#define VRES_60HZ (480+16) + +MODULE_DESCRIPTION("Philips SAA7111/SAA7113/SAA7114/SAA7115/SAA7118 video decoder driver"); +MODULE_AUTHOR( "Maxim Yevtyushkin, Kevin Thayer, Chris Kennedy, " + "Hans Verkuil, Mauro Carvalho Chehab"); +MODULE_LICENSE("GPL"); + +static bool debug; +module_param(debug, bool, 0644); + +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +enum saa711x_model { + SAA7111A, + SAA7111, + SAA7113, + GM7113C, + SAA7114, + SAA7115, + SAA7118, +}; + +enum saa711x_pads { + SAA711X_PAD_IF_INPUT, + SAA711X_PAD_VID_OUT, + SAA711X_NUM_PADS +}; + +struct saa711x_state { + struct v4l2_subdev sd; +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_pad pads[SAA711X_NUM_PADS]; +#endif + struct v4l2_ctrl_handler hdl; + + struct { + /* chroma gain control cluster */ + struct v4l2_ctrl *agc; + struct v4l2_ctrl *gain; + }; + + v4l2_std_id std; + int input; + int output; + int enable; + int radio; + int width; + int height; + enum saa711x_model ident; + u32 audclk_freq; + u32 crystal_freq; + bool ucgc; + u8 cgcdiv; + bool apll; + bool double_asclk; +}; + +static inline struct saa711x_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct saa711x_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct saa711x_state, hdl)->sd; +} + +/* ----------------------------------------------------------------------- */ + +static inline int saa711x_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* Sanity routine to check if a register is present */ +static int saa711x_has_reg(const int id, const u8 reg) +{ + if (id == SAA7111) + return reg < 0x20 && reg != 0x01 && reg != 0x0f && + (reg < 0x13 || reg > 0x19) && reg != 0x1d && reg != 0x1e; + if (id == SAA7111A) + return reg < 0x20 && reg != 0x01 && reg != 0x0f && + reg != 0x14 && reg != 0x18 && reg != 0x19 && + reg != 0x1d && reg != 0x1e; + + /* common for saa7113/4/5/8 */ + if (unlikely((reg >= 0x3b && reg <= 0x3f) || reg == 0x5c || reg == 0x5f || + reg == 0xa3 || reg == 0xa7 || reg == 0xab || reg == 0xaf || (reg >= 0xb5 && reg <= 0xb7) || + reg == 0xd3 || reg == 0xd7 || reg == 0xdb || reg == 0xdf || (reg >= 0xe5 && reg <= 0xe7) || + reg == 0x82 || (reg >= 0x89 && reg <= 0x8e))) + return 0; + + switch (id) { + case GM7113C: + return reg != 0x14 && (reg < 0x18 || reg > 0x1e) && reg < 0x20; + case SAA7113: + return reg != 0x14 && (reg < 0x18 || reg > 0x1e) && (reg < 0x20 || reg > 0x3f) && + reg != 0x5d && reg < 0x63; + case SAA7114: + return (reg < 0x1a || reg > 0x1e) && (reg < 0x20 || reg > 0x2f) && + (reg < 0x63 || reg > 0x7f) && reg != 0x33 && reg != 0x37 && + reg != 0x81 && reg < 0xf0; + case SAA7115: + return (reg < 0x20 || reg > 0x2f) && reg != 0x65 && (reg < 0xfc || reg > 0xfe); + case SAA7118: + return (reg < 0x1a || reg > 0x1d) && (reg < 0x20 || reg > 0x22) && + (reg < 0x26 || reg > 0x28) && reg != 0x33 && reg != 0x37 && + (reg < 0x63 || reg > 0x7f) && reg != 0x81 && reg < 0xf0; + } + return 1; +} + +static int saa711x_writeregs(struct v4l2_subdev *sd, const unsigned char *regs) +{ + struct saa711x_state *state = to_state(sd); + unsigned char reg, data; + + while (*regs != 0x00) { + reg = *(regs++); + data = *(regs++); + + /* According with datasheets, reserved regs should be + filled with 0 - seems better not to touch on they */ + if (saa711x_has_reg(state->ident, reg)) { + if (saa711x_write(sd, reg, data) < 0) + return -1; + } else { + v4l2_dbg(1, debug, sd, "tried to access reserved reg 0x%02x\n", reg); + } + } + return 0; +} + +static inline int saa711x_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +/* SAA7111 initialization table */ +static const unsigned char saa7111_init[] = { + R_01_INC_DELAY, 0x00, /* reserved */ + + /*front end */ + R_02_INPUT_CNTL_1, 0xd0, /* FUSE=3, GUDL=2, MODE=0 */ + R_03_INPUT_CNTL_2, 0x23, /* HLNRS=0, VBSL=1, WPOFF=0, HOLDG=0, + * GAFIX=0, GAI1=256, GAI2=256 */ + R_04_INPUT_CNTL_3, 0x00, /* GAI1=256 */ + R_05_INPUT_CNTL_4, 0x00, /* GAI2=256 */ + + /* decoder */ + R_06_H_SYNC_START, 0xf3, /* HSB at 13(50Hz) / 17(60Hz) + * pixels after end of last line */ + R_07_H_SYNC_STOP, 0xe8, /* HSS seems to be needed to + * work with NTSC, too */ + R_08_SYNC_CNTL, 0xc8, /* AUFD=1, FSEL=1, EXFIL=0, + * VTRC=1, HPLL=0, VNOI=0 */ + R_09_LUMA_CNTL, 0x01, /* BYPS=0, PREF=0, BPSS=0, + * VBLB=0, UPTCV=0, APER=1 */ + R_0A_LUMA_BRIGHT_CNTL, 0x80, + R_0B_LUMA_CONTRAST_CNTL, 0x47, /* 0b - CONT=1.109 */ + R_0C_CHROMA_SAT_CNTL, 0x40, + R_0D_CHROMA_HUE_CNTL, 0x00, + R_0E_CHROMA_CNTL_1, 0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, + * FCTC=0, CHBW=1 */ + R_0F_CHROMA_GAIN_CNTL, 0x00, /* reserved */ + R_10_CHROMA_CNTL_2, 0x48, /* 10 - OFTS=1, HDEL=0, VRLN=1, YDEL=0 */ + R_11_MODE_DELAY_CNTL, 0x1c, /* 11 - GPSW=0, CM99=0, FECO=0, COMPO=1, + * OEYC=1, OEHV=1, VIPB=0, COLO=0 */ + R_12_RT_SIGNAL_CNTL, 0x00, /* 12 - output control 2 */ + R_13_RT_X_PORT_OUT_CNTL, 0x00, /* 13 - output control 3 */ + R_14_ANAL_ADC_COMPAT_CNTL, 0x00, + R_15_VGATE_START_FID_CHG, 0x00, + R_16_VGATE_STOP, 0x00, + R_17_MISC_VGATE_CONF_AND_MSB, 0x00, + + 0x00, 0x00 +}; + +/* + * This table has one illegal value, and some values that are not + * correct according to the datasheet initialization table. + * + * If you need a table with legal/default values tell the driver in + * i2c_board_info.platform_data, and you will get the gm7113c_init + * table instead. + */ + +/* SAA7113 Init codes */ +static const unsigned char saa7113_init[] = { + R_01_INC_DELAY, 0x08, + R_02_INPUT_CNTL_1, 0xc2, + R_03_INPUT_CNTL_2, 0x30, + R_04_INPUT_CNTL_3, 0x00, + R_05_INPUT_CNTL_4, 0x00, + R_06_H_SYNC_START, 0x89, /* Illegal value -119, + * min. value = -108 (0x94) */ + R_07_H_SYNC_STOP, 0x0d, + R_08_SYNC_CNTL, 0x88, /* Not datasheet default. + * HTC = VTR mode, should be 0x98 */ + R_09_LUMA_CNTL, 0x01, + R_0A_LUMA_BRIGHT_CNTL, 0x80, + R_0B_LUMA_CONTRAST_CNTL, 0x47, + R_0C_CHROMA_SAT_CNTL, 0x40, + R_0D_CHROMA_HUE_CNTL, 0x00, + R_0E_CHROMA_CNTL_1, 0x01, + R_0F_CHROMA_GAIN_CNTL, 0x2a, + R_10_CHROMA_CNTL_2, 0x08, /* Not datsheet default. + * VRLN enabled, should be 0x00 */ + R_11_MODE_DELAY_CNTL, 0x0c, + R_12_RT_SIGNAL_CNTL, 0x07, /* Not datasheet default, + * should be 0x01 */ + R_13_RT_X_PORT_OUT_CNTL, 0x00, + R_14_ANAL_ADC_COMPAT_CNTL, 0x00, + R_15_VGATE_START_FID_CHG, 0x00, + R_16_VGATE_STOP, 0x00, + R_17_MISC_VGATE_CONF_AND_MSB, 0x00, + + 0x00, 0x00 +}; + +/* + * GM7113C is a clone of the SAA7113 chip + * This init table is copied out of the saa7113 datasheet. + * In R_08 we enable "Automatic Field Detection" [AUFD], + * this is disabled when saa711x_set_v4lstd is called. + */ +static const unsigned char gm7113c_init[] = { + R_01_INC_DELAY, 0x08, + R_02_INPUT_CNTL_1, 0xc0, + R_03_INPUT_CNTL_2, 0x33, + R_04_INPUT_CNTL_3, 0x00, + R_05_INPUT_CNTL_4, 0x00, + R_06_H_SYNC_START, 0xe9, + R_07_H_SYNC_STOP, 0x0d, + R_08_SYNC_CNTL, 0x98, + R_09_LUMA_CNTL, 0x01, + R_0A_LUMA_BRIGHT_CNTL, 0x80, + R_0B_LUMA_CONTRAST_CNTL, 0x47, + R_0C_CHROMA_SAT_CNTL, 0x40, + R_0D_CHROMA_HUE_CNTL, 0x00, + R_0E_CHROMA_CNTL_1, 0x01, + R_0F_CHROMA_GAIN_CNTL, 0x2a, + R_10_CHROMA_CNTL_2, 0x00, + R_11_MODE_DELAY_CNTL, 0x0c, + R_12_RT_SIGNAL_CNTL, 0x01, + R_13_RT_X_PORT_OUT_CNTL, 0x00, + R_14_ANAL_ADC_COMPAT_CNTL, 0x00, + R_15_VGATE_START_FID_CHG, 0x00, + R_16_VGATE_STOP, 0x00, + R_17_MISC_VGATE_CONF_AND_MSB, 0x00, + + 0x00, 0x00 +}; + +/* If a value differs from the Hauppauge driver values, then the comment starts with + 'was 0xXX' to denote the Hauppauge value. Otherwise the value is identical to what the + Hauppauge driver sets. */ + +/* SAA7114 and SAA7115 initialization table */ +static const unsigned char saa7115_init_auto_input[] = { + /* Front-End Part */ + R_01_INC_DELAY, 0x48, /* white peak control disabled */ + R_03_INPUT_CNTL_2, 0x20, /* was 0x30. 0x20: long vertical blanking */ + R_04_INPUT_CNTL_3, 0x90, /* analog gain set to 0 */ + R_05_INPUT_CNTL_4, 0x90, /* analog gain set to 0 */ + /* Decoder Part */ + R_06_H_SYNC_START, 0xeb, /* horiz sync begin = -21 */ + R_07_H_SYNC_STOP, 0xe0, /* horiz sync stop = -17 */ + R_09_LUMA_CNTL, 0x53, /* 0x53, was 0x56 for 60hz. luminance control */ + R_0A_LUMA_BRIGHT_CNTL, 0x80, /* was 0x88. decoder brightness, 0x80 is itu standard */ + R_0B_LUMA_CONTRAST_CNTL, 0x44, /* was 0x48. decoder contrast, 0x44 is itu standard */ + R_0C_CHROMA_SAT_CNTL, 0x40, /* was 0x47. decoder saturation, 0x40 is itu standard */ + R_0D_CHROMA_HUE_CNTL, 0x00, + R_0F_CHROMA_GAIN_CNTL, 0x00, /* use automatic gain */ + R_10_CHROMA_CNTL_2, 0x06, /* chroma: active adaptive combfilter */ + R_11_MODE_DELAY_CNTL, 0x00, + R_12_RT_SIGNAL_CNTL, 0x9d, /* RTS0 output control: VGATE */ + R_13_RT_X_PORT_OUT_CNTL, 0x80, /* ITU656 standard mode, RTCO output enable RTCE */ + R_14_ANAL_ADC_COMPAT_CNTL, 0x00, + R_18_RAW_DATA_GAIN_CNTL, 0x40, /* gain 0x00 = nominal */ + R_19_RAW_DATA_OFF_CNTL, 0x80, + R_1A_COLOR_KILL_LVL_CNTL, 0x77, /* recommended value */ + R_1B_MISC_TVVCRDET, 0x42, /* recommended value */ + R_1C_ENHAN_COMB_CTRL1, 0xa9, /* recommended value */ + R_1D_ENHAN_COMB_CTRL2, 0x01, /* recommended value */ + + + R_80_GLOBAL_CNTL_1, 0x0, /* No tasks enabled at init */ + + /* Power Device Control */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset device */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xf0, /* set device programmed, all in operational mode */ + 0x00, 0x00 +}; + +/* Used to reset saa7113, saa7114 and saa7115 */ +static const unsigned char saa7115_cfg_reset_scaler[] = { + R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 0x00, /* disable I-port output */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xf0, /* activate scaler */ + R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 0x01, /* enable I-port output */ + 0x00, 0x00 +}; + +/* ============== SAA7715 VIDEO templates ============= */ + +static const unsigned char saa7115_cfg_60hz_video[] = { + R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ + + R_15_VGATE_START_FID_CHG, 0x03, + R_16_VGATE_STOP, 0x11, + R_17_MISC_VGATE_CONF_AND_MSB, 0x9c, + + R_08_SYNC_CNTL, 0x68, /* 0xBO: auto detection, 0x68 = NTSC */ + R_0E_CHROMA_CNTL_1, 0x07, /* video autodetection is on */ + + R_5A_V_OFF_FOR_SLICER, 0x06, /* standard 60hz value for ITU656 line counting */ + + /* Task A */ + R_90_A_TASK_HANDLING_CNTL, 0x80, + R_91_A_X_PORT_FORMATS_AND_CONF, 0x48, + R_92_A_X_PORT_INPUT_REFERENCE_SIGNAL, 0x40, + R_93_A_I_PORT_OUTPUT_FORMATS_AND_CONF, 0x84, + + /* hoffset low (input), 0x0002 is minimum */ + R_94_A_HORIZ_INPUT_WINDOW_START, 0x01, + R_95_A_HORIZ_INPUT_WINDOW_START_MSB, 0x00, + + /* hsize low (input), 0x02d0 = 720 */ + R_96_A_HORIZ_INPUT_WINDOW_LENGTH, 0xd0, + R_97_A_HORIZ_INPUT_WINDOW_LENGTH_MSB, 0x02, + + R_98_A_VERT_INPUT_WINDOW_START, 0x05, + R_99_A_VERT_INPUT_WINDOW_START_MSB, 0x00, + + R_9A_A_VERT_INPUT_WINDOW_LENGTH, 0x0c, + R_9B_A_VERT_INPUT_WINDOW_LENGTH_MSB, 0x00, + + R_9C_A_HORIZ_OUTPUT_WINDOW_LENGTH, 0xa0, + R_9D_A_HORIZ_OUTPUT_WINDOW_LENGTH_MSB, 0x05, + + R_9E_A_VERT_OUTPUT_WINDOW_LENGTH, 0x0c, + R_9F_A_VERT_OUTPUT_WINDOW_LENGTH_MSB, 0x00, + + /* Task B */ + R_C0_B_TASK_HANDLING_CNTL, 0x00, + R_C1_B_X_PORT_FORMATS_AND_CONF, 0x08, + R_C2_B_INPUT_REFERENCE_SIGNAL_DEFINITION, 0x00, + R_C3_B_I_PORT_FORMATS_AND_CONF, 0x80, + + /* 0x0002 is minimum */ + R_C4_B_HORIZ_INPUT_WINDOW_START, 0x02, + R_C5_B_HORIZ_INPUT_WINDOW_START_MSB, 0x00, + + /* 0x02d0 = 720 */ + R_C6_B_HORIZ_INPUT_WINDOW_LENGTH, 0xd0, + R_C7_B_HORIZ_INPUT_WINDOW_LENGTH_MSB, 0x02, + + /* vwindow start 0x12 = 18 */ + R_C8_B_VERT_INPUT_WINDOW_START, 0x12, + R_C9_B_VERT_INPUT_WINDOW_START_MSB, 0x00, + + /* vwindow length 0xf8 = 248 */ + R_CA_B_VERT_INPUT_WINDOW_LENGTH, VRES_60HZ>>1, + R_CB_B_VERT_INPUT_WINDOW_LENGTH_MSB, VRES_60HZ>>9, + + /* hwindow 0x02d0 = 720 */ + R_CC_B_HORIZ_OUTPUT_WINDOW_LENGTH, 0xd0, + R_CD_B_HORIZ_OUTPUT_WINDOW_LENGTH_MSB, 0x02, + + R_F0_LFCO_PER_LINE, 0xad, /* Set PLL Register. 60hz 525 lines per frame, 27 MHz */ + R_F1_P_I_PARAM_SELECT, 0x05, /* low bit with 0xF0 */ + R_F5_PULSGEN_LINE_LENGTH, 0xad, + R_F6_PULSE_A_POS_LSB_AND_PULSEGEN_CONFIG, 0x01, + + 0x00, 0x00 +}; + +static const unsigned char saa7115_cfg_50hz_video[] = { + R_80_GLOBAL_CNTL_1, 0x00, + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ + + R_15_VGATE_START_FID_CHG, 0x37, /* VGATE start */ + R_16_VGATE_STOP, 0x16, + R_17_MISC_VGATE_CONF_AND_MSB, 0x99, + + R_08_SYNC_CNTL, 0x28, /* 0x28 = PAL */ + R_0E_CHROMA_CNTL_1, 0x07, + + R_5A_V_OFF_FOR_SLICER, 0x03, /* standard 50hz value */ + + /* Task A */ + R_90_A_TASK_HANDLING_CNTL, 0x81, + R_91_A_X_PORT_FORMATS_AND_CONF, 0x48, + R_92_A_X_PORT_INPUT_REFERENCE_SIGNAL, 0x40, + R_93_A_I_PORT_OUTPUT_FORMATS_AND_CONF, 0x84, + + /* This is weird: the datasheet says that you should use 2 as the minimum value, */ + /* but Hauppauge uses 0, and changing that to 2 causes indeed problems (for 50hz) */ + /* hoffset low (input), 0x0002 is minimum */ + R_94_A_HORIZ_INPUT_WINDOW_START, 0x00, + R_95_A_HORIZ_INPUT_WINDOW_START_MSB, 0x00, + + /* hsize low (input), 0x02d0 = 720 */ + R_96_A_HORIZ_INPUT_WINDOW_LENGTH, 0xd0, + R_97_A_HORIZ_INPUT_WINDOW_LENGTH_MSB, 0x02, + + R_98_A_VERT_INPUT_WINDOW_START, 0x03, + R_99_A_VERT_INPUT_WINDOW_START_MSB, 0x00, + + /* vsize 0x12 = 18 */ + R_9A_A_VERT_INPUT_WINDOW_LENGTH, 0x12, + R_9B_A_VERT_INPUT_WINDOW_LENGTH_MSB, 0x00, + + /* hsize 0x05a0 = 1440 */ + R_9C_A_HORIZ_OUTPUT_WINDOW_LENGTH, 0xa0, + R_9D_A_HORIZ_OUTPUT_WINDOW_LENGTH_MSB, 0x05, /* hsize hi (output) */ + R_9E_A_VERT_OUTPUT_WINDOW_LENGTH, 0x12, /* vsize low (output), 0x12 = 18 */ + R_9F_A_VERT_OUTPUT_WINDOW_LENGTH_MSB, 0x00, /* vsize hi (output) */ + + /* Task B */ + R_C0_B_TASK_HANDLING_CNTL, 0x00, + R_C1_B_X_PORT_FORMATS_AND_CONF, 0x08, + R_C2_B_INPUT_REFERENCE_SIGNAL_DEFINITION, 0x00, + R_C3_B_I_PORT_FORMATS_AND_CONF, 0x80, + + /* This is weird: the datasheet says that you should use 2 as the minimum value, */ + /* but Hauppauge uses 0, and changing that to 2 causes indeed problems (for 50hz) */ + /* hoffset low (input), 0x0002 is minimum. See comment above. */ + R_C4_B_HORIZ_INPUT_WINDOW_START, 0x00, + R_C5_B_HORIZ_INPUT_WINDOW_START_MSB, 0x00, + + /* hsize 0x02d0 = 720 */ + R_C6_B_HORIZ_INPUT_WINDOW_LENGTH, 0xd0, + R_C7_B_HORIZ_INPUT_WINDOW_LENGTH_MSB, 0x02, + + /* voffset 0x16 = 22 */ + R_C8_B_VERT_INPUT_WINDOW_START, 0x16, + R_C9_B_VERT_INPUT_WINDOW_START_MSB, 0x00, + + /* vsize 0x0120 = 288 */ + R_CA_B_VERT_INPUT_WINDOW_LENGTH, 0x20, + R_CB_B_VERT_INPUT_WINDOW_LENGTH_MSB, 0x01, + + /* hsize 0x02d0 = 720 */ + R_CC_B_HORIZ_OUTPUT_WINDOW_LENGTH, 0xd0, + R_CD_B_HORIZ_OUTPUT_WINDOW_LENGTH_MSB, 0x02, + + R_F0_LFCO_PER_LINE, 0xb0, /* Set PLL Register. 50hz 625 lines per frame, 27 MHz */ + R_F1_P_I_PARAM_SELECT, 0x05, /* low bit with 0xF0, (was 0x05) */ + R_F5_PULSGEN_LINE_LENGTH, 0xb0, + R_F6_PULSE_A_POS_LSB_AND_PULSEGEN_CONFIG, 0x01, + + 0x00, 0x00 +}; + +/* ============== SAA7715 VIDEO templates (end) ======= */ + +static const unsigned char saa7115_cfg_vbi_on[] = { + R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ + R_80_GLOBAL_CNTL_1, 0x30, /* Activate both tasks */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xf0, /* activate scaler */ + R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 0x01, /* Enable I-port output */ + + 0x00, 0x00 +}; + +static const unsigned char saa7115_cfg_vbi_off[] = { + R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ + R_80_GLOBAL_CNTL_1, 0x20, /* Activate only task "B" */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xf0, /* activate scaler */ + R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 0x01, /* Enable I-port output */ + + 0x00, 0x00 +}; + + +static const unsigned char saa7115_init_misc[] = { + R_81_V_SYNC_FLD_ID_SRC_SEL_AND_RETIMED_V_F, 0x01, + R_83_X_PORT_I_O_ENA_AND_OUT_CLK, 0x01, + R_84_I_PORT_SIGNAL_DEF, 0x20, + R_85_I_PORT_SIGNAL_POLAR, 0x21, + R_86_I_PORT_FIFO_FLAG_CNTL_AND_ARBIT, 0xc5, + R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 0x01, + + /* Task A */ + R_A0_A_HORIZ_PRESCALING, 0x01, + R_A1_A_ACCUMULATION_LENGTH, 0x00, + R_A2_A_PRESCALER_DC_GAIN_AND_FIR_PREFILTER, 0x00, + + /* Configure controls at nominal value*/ + R_A4_A_LUMA_BRIGHTNESS_CNTL, 0x80, + R_A5_A_LUMA_CONTRAST_CNTL, 0x40, + R_A6_A_CHROMA_SATURATION_CNTL, 0x40, + + /* note: 2 x zoom ensures that VBI lines have same length as video lines. */ + R_A8_A_HORIZ_LUMA_SCALING_INC, 0x00, + R_A9_A_HORIZ_LUMA_SCALING_INC_MSB, 0x02, + + R_AA_A_HORIZ_LUMA_PHASE_OFF, 0x00, + + /* must be horiz lum scaling / 2 */ + R_AC_A_HORIZ_CHROMA_SCALING_INC, 0x00, + R_AD_A_HORIZ_CHROMA_SCALING_INC_MSB, 0x01, + + /* must be offset luma / 2 */ + R_AE_A_HORIZ_CHROMA_PHASE_OFF, 0x00, + + R_B0_A_VERT_LUMA_SCALING_INC, 0x00, + R_B1_A_VERT_LUMA_SCALING_INC_MSB, 0x04, + + R_B2_A_VERT_CHROMA_SCALING_INC, 0x00, + R_B3_A_VERT_CHROMA_SCALING_INC_MSB, 0x04, + + R_B4_A_VERT_SCALING_MODE_CNTL, 0x01, + + R_B8_A_VERT_CHROMA_PHASE_OFF_00, 0x00, + R_B9_A_VERT_CHROMA_PHASE_OFF_01, 0x00, + R_BA_A_VERT_CHROMA_PHASE_OFF_10, 0x00, + R_BB_A_VERT_CHROMA_PHASE_OFF_11, 0x00, + + R_BC_A_VERT_LUMA_PHASE_OFF_00, 0x00, + R_BD_A_VERT_LUMA_PHASE_OFF_01, 0x00, + R_BE_A_VERT_LUMA_PHASE_OFF_10, 0x00, + R_BF_A_VERT_LUMA_PHASE_OFF_11, 0x00, + + /* Task B */ + R_D0_B_HORIZ_PRESCALING, 0x01, + R_D1_B_ACCUMULATION_LENGTH, 0x00, + R_D2_B_PRESCALER_DC_GAIN_AND_FIR_PREFILTER, 0x00, + + /* Configure controls at nominal value*/ + R_D4_B_LUMA_BRIGHTNESS_CNTL, 0x80, + R_D5_B_LUMA_CONTRAST_CNTL, 0x40, + R_D6_B_CHROMA_SATURATION_CNTL, 0x40, + + /* hor lum scaling 0x0400 = 1 */ + R_D8_B_HORIZ_LUMA_SCALING_INC, 0x00, + R_D9_B_HORIZ_LUMA_SCALING_INC_MSB, 0x04, + + R_DA_B_HORIZ_LUMA_PHASE_OFF, 0x00, + + /* must be hor lum scaling / 2 */ + R_DC_B_HORIZ_CHROMA_SCALING, 0x00, + R_DD_B_HORIZ_CHROMA_SCALING_MSB, 0x02, + + /* must be offset luma / 2 */ + R_DE_B_HORIZ_PHASE_OFFSET_CRHOMA, 0x00, + + R_E0_B_VERT_LUMA_SCALING_INC, 0x00, + R_E1_B_VERT_LUMA_SCALING_INC_MSB, 0x04, + + R_E2_B_VERT_CHROMA_SCALING_INC, 0x00, + R_E3_B_VERT_CHROMA_SCALING_INC_MSB, 0x04, + + R_E4_B_VERT_SCALING_MODE_CNTL, 0x01, + + R_E8_B_VERT_CHROMA_PHASE_OFF_00, 0x00, + R_E9_B_VERT_CHROMA_PHASE_OFF_01, 0x00, + R_EA_B_VERT_CHROMA_PHASE_OFF_10, 0x00, + R_EB_B_VERT_CHROMA_PHASE_OFF_11, 0x00, + + R_EC_B_VERT_LUMA_PHASE_OFF_00, 0x00, + R_ED_B_VERT_LUMA_PHASE_OFF_01, 0x00, + R_EE_B_VERT_LUMA_PHASE_OFF_10, 0x00, + R_EF_B_VERT_LUMA_PHASE_OFF_11, 0x00, + + R_F2_NOMINAL_PLL2_DTO, 0x50, /* crystal clock = 24.576 MHz, target = 27MHz */ + R_F3_PLL_INCREMENT, 0x46, + R_F4_PLL2_STATUS, 0x00, + R_F7_PULSE_A_POS_MSB, 0x4b, /* not the recommended settings! */ + R_F8_PULSE_B_POS, 0x00, + R_F9_PULSE_B_POS_MSB, 0x4b, + R_FA_PULSE_C_POS, 0x00, + R_FB_PULSE_C_POS_MSB, 0x4b, + + /* PLL2 lock detection settings: 71 lines 50% phase error */ + R_FF_S_PLL_MAX_PHASE_ERR_THRESH_NUM_LINES, 0x88, + + /* Turn off VBI */ + R_40_SLICER_CNTL_1, 0x20, /* No framing code errors allowed. */ + R_41_LCR_BASE, 0xff, + R_41_LCR_BASE+1, 0xff, + R_41_LCR_BASE+2, 0xff, + R_41_LCR_BASE+3, 0xff, + R_41_LCR_BASE+4, 0xff, + R_41_LCR_BASE+5, 0xff, + R_41_LCR_BASE+6, 0xff, + R_41_LCR_BASE+7, 0xff, + R_41_LCR_BASE+8, 0xff, + R_41_LCR_BASE+9, 0xff, + R_41_LCR_BASE+10, 0xff, + R_41_LCR_BASE+11, 0xff, + R_41_LCR_BASE+12, 0xff, + R_41_LCR_BASE+13, 0xff, + R_41_LCR_BASE+14, 0xff, + R_41_LCR_BASE+15, 0xff, + R_41_LCR_BASE+16, 0xff, + R_41_LCR_BASE+17, 0xff, + R_41_LCR_BASE+18, 0xff, + R_41_LCR_BASE+19, 0xff, + R_41_LCR_BASE+20, 0xff, + R_41_LCR_BASE+21, 0xff, + R_41_LCR_BASE+22, 0xff, + R_58_PROGRAM_FRAMING_CODE, 0x40, + R_59_H_OFF_FOR_SLICER, 0x47, + R_5B_FLD_OFF_AND_MSB_FOR_H_AND_V_OFF, 0x83, + R_5D_DID, 0xbd, + R_5E_SDID, 0x35, + + R_02_INPUT_CNTL_1, 0xc4, /* input tuner -> input 4, amplifier active */ + + R_80_GLOBAL_CNTL_1, 0x20, /* enable task B */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xf0, + 0x00, 0x00 +}; + +static int saa711x_odd_parity(u8 c) +{ + c ^= (c >> 4); + c ^= (c >> 2); + c ^= (c >> 1); + + return c & 1; +} + +static int saa711x_decode_vps(u8 *dst, u8 *p) +{ + static const u8 biphase_tbl[] = { + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87, + 0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3, + 0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85, + 0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86, + 0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2, + 0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84, + 0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + }; + int i; + u8 c, err = 0; + + for (i = 0; i < 2 * 13; i += 2) { + err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]]; + c = (biphase_tbl[p[i + 1]] & 0xf) | ((biphase_tbl[p[i]] & 0xf) << 4); + dst[i / 2] = c; + } + return err & 0xf0; +} + +static int saa711x_decode_wss(u8 *p) +{ + static const int wss_bits[8] = { + 0, 0, 0, 1, 0, 1, 1, 1 + }; + unsigned char parity; + int wss = 0; + int i; + + for (i = 0; i < 16; i++) { + int b1 = wss_bits[p[i] & 7]; + int b2 = wss_bits[(p[i] >> 3) & 7]; + + if (b1 == b2) + return -1; + wss |= b2 << i; + } + parity = wss & 15; + parity ^= parity >> 2; + parity ^= parity >> 1; + + if (!(parity & 1)) + return -1; + + return wss; +} + +static int saa711x_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + struct saa711x_state *state = to_state(sd); + u32 acpf; + u32 acni; + u32 hz; + u64 f; + u8 acc = 0; /* reg 0x3a, audio clock control */ + + /* Checks for chips that don't have audio clock (saa7111, saa7113) */ + if (!saa711x_has_reg(state->ident, R_30_AUD_MAST_CLK_CYCLES_PER_FIELD)) + return 0; + + v4l2_dbg(1, debug, sd, "set audio clock freq: %d\n", freq); + + /* sanity check */ + if (freq < 32000 || freq > 48000) + return -EINVAL; + + /* hz is the refresh rate times 100 */ + hz = (state->std & V4L2_STD_525_60) ? 5994 : 5000; + /* acpf = (256 * freq) / field_frequency == (256 * 100 * freq) / hz */ + acpf = (25600 * freq) / hz; + /* acni = (256 * freq * 2^23) / crystal_frequency = + (freq * 2^(8+23)) / crystal_frequency = + (freq << 31) / crystal_frequency */ + f = freq; + f = f << 31; + do_div(f, state->crystal_freq); + acni = f; + if (state->ucgc) { + acpf = acpf * state->cgcdiv / 16; + acni = acni * state->cgcdiv / 16; + acc = 0x80; + if (state->cgcdiv == 3) + acc |= 0x40; + } + if (state->apll) + acc |= 0x08; + + if (state->double_asclk) { + acpf <<= 1; + acni <<= 1; + } + saa711x_write(sd, R_38_CLK_RATIO_AMXCLK_TO_ASCLK, 0x03); + saa711x_write(sd, R_39_CLK_RATIO_ASCLK_TO_ALRCLK, 0x10 << state->double_asclk); + saa711x_write(sd, R_3A_AUD_CLK_GEN_BASIC_SETUP, acc); + + saa711x_write(sd, R_30_AUD_MAST_CLK_CYCLES_PER_FIELD, acpf & 0xff); + saa711x_write(sd, R_30_AUD_MAST_CLK_CYCLES_PER_FIELD+1, + (acpf >> 8) & 0xff); + saa711x_write(sd, R_30_AUD_MAST_CLK_CYCLES_PER_FIELD+2, + (acpf >> 16) & 0x03); + + saa711x_write(sd, R_34_AUD_MAST_CLK_NOMINAL_INC, acni & 0xff); + saa711x_write(sd, R_34_AUD_MAST_CLK_NOMINAL_INC+1, (acni >> 8) & 0xff); + saa711x_write(sd, R_34_AUD_MAST_CLK_NOMINAL_INC+2, (acni >> 16) & 0x3f); + state->audclk_freq = freq; + return 0; +} + +static int saa711x_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct saa711x_state *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_CHROMA_AGC: + /* chroma gain cluster */ + if (state->agc->val) + state->gain->val = + saa711x_read(sd, R_0F_CHROMA_GAIN_CNTL) & 0x7f; + break; + } + return 0; +} + +static int saa711x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct saa711x_state *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + saa711x_write(sd, R_0A_LUMA_BRIGHT_CNTL, ctrl->val); + break; + + case V4L2_CID_CONTRAST: + saa711x_write(sd, R_0B_LUMA_CONTRAST_CNTL, ctrl->val); + break; + + case V4L2_CID_SATURATION: + saa711x_write(sd, R_0C_CHROMA_SAT_CNTL, ctrl->val); + break; + + case V4L2_CID_HUE: + saa711x_write(sd, R_0D_CHROMA_HUE_CNTL, ctrl->val); + break; + + case V4L2_CID_CHROMA_AGC: + /* chroma gain cluster */ + if (state->agc->val) + saa711x_write(sd, R_0F_CHROMA_GAIN_CNTL, state->gain->val); + else + saa711x_write(sd, R_0F_CHROMA_GAIN_CNTL, state->gain->val | 0x80); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int saa711x_set_size(struct v4l2_subdev *sd, int width, int height) +{ + struct saa711x_state *state = to_state(sd); + int HPSC, HFSC; + int VSCY; + int res; + int is_50hz = state->std & V4L2_STD_625_50; + int Vsrc = is_50hz ? 576 : 480; + + v4l2_dbg(1, debug, sd, "decoder set size to %ix%i\n", width, height); + + /* FIXME need better bounds checking here */ + if ((width < 1) || (width > 1440)) + return -EINVAL; + if ((height < 1) || (height > Vsrc)) + return -EINVAL; + + if (!saa711x_has_reg(state->ident, R_D0_B_HORIZ_PRESCALING)) { + /* Decoder only supports 720 columns and 480 or 576 lines */ + if (width != 720) + return -EINVAL; + if (height != Vsrc) + return -EINVAL; + } + + state->width = width; + state->height = height; + + if (!saa711x_has_reg(state->ident, R_CC_B_HORIZ_OUTPUT_WINDOW_LENGTH)) + return 0; + + /* probably have a valid size, let's set it */ + /* Set output width/height */ + /* width */ + + saa711x_write(sd, R_CC_B_HORIZ_OUTPUT_WINDOW_LENGTH, + (u8) (width & 0xff)); + saa711x_write(sd, R_CD_B_HORIZ_OUTPUT_WINDOW_LENGTH_MSB, + (u8) ((width >> 8) & 0xff)); + + /* Vertical Scaling uses height/2 */ + res = height / 2; + + /* On 60Hz, it is using a higher Vertical Output Size */ + if (!is_50hz) + res += (VRES_60HZ - 480) >> 1; + + /* height */ + saa711x_write(sd, R_CE_B_VERT_OUTPUT_WINDOW_LENGTH, + (u8) (res & 0xff)); + saa711x_write(sd, R_CF_B_VERT_OUTPUT_WINDOW_LENGTH_MSB, + (u8) ((res >> 8) & 0xff)); + + /* Scaling settings */ + /* Hprescaler is floor(inres/outres) */ + HPSC = (int)(720 / width); + /* 0 is not allowed (div. by zero) */ + HPSC = HPSC ? HPSC : 1; + HFSC = (int)((1024 * 720) / (HPSC * width)); + /* FIXME hardcodes to "Task B" + * write H prescaler integer */ + saa711x_write(sd, R_D0_B_HORIZ_PRESCALING, + (u8) (HPSC & 0x3f)); + + v4l2_dbg(1, debug, sd, "Hpsc: 0x%05x, Hfsc: 0x%05x\n", HPSC, HFSC); + /* write H fine-scaling (luminance) */ + saa711x_write(sd, R_D8_B_HORIZ_LUMA_SCALING_INC, + (u8) (HFSC & 0xff)); + saa711x_write(sd, R_D9_B_HORIZ_LUMA_SCALING_INC_MSB, + (u8) ((HFSC >> 8) & 0xff)); + /* write H fine-scaling (chrominance) + * must be lum/2, so i'll just bitshift :) */ + saa711x_write(sd, R_DC_B_HORIZ_CHROMA_SCALING, + (u8) ((HFSC >> 1) & 0xff)); + saa711x_write(sd, R_DD_B_HORIZ_CHROMA_SCALING_MSB, + (u8) ((HFSC >> 9) & 0xff)); + + VSCY = (int)((1024 * Vsrc) / height); + v4l2_dbg(1, debug, sd, "Vsrc: %d, Vscy: 0x%05x\n", Vsrc, VSCY); + + /* Correct Contrast and Luminance */ + saa711x_write(sd, R_D5_B_LUMA_CONTRAST_CNTL, + (u8) (64 * 1024 / VSCY)); + saa711x_write(sd, R_D6_B_CHROMA_SATURATION_CNTL, + (u8) (64 * 1024 / VSCY)); + + /* write V fine-scaling (luminance) */ + saa711x_write(sd, R_E0_B_VERT_LUMA_SCALING_INC, + (u8) (VSCY & 0xff)); + saa711x_write(sd, R_E1_B_VERT_LUMA_SCALING_INC_MSB, + (u8) ((VSCY >> 8) & 0xff)); + /* write V fine-scaling (chrominance) */ + saa711x_write(sd, R_E2_B_VERT_CHROMA_SCALING_INC, + (u8) (VSCY & 0xff)); + saa711x_write(sd, R_E3_B_VERT_CHROMA_SCALING_INC_MSB, + (u8) ((VSCY >> 8) & 0xff)); + + saa711x_writeregs(sd, saa7115_cfg_reset_scaler); + + /* Activates task "B" */ + saa711x_write(sd, R_80_GLOBAL_CNTL_1, + saa711x_read(sd, R_80_GLOBAL_CNTL_1) | 0x20); + + return 0; +} + +static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa711x_state *state = to_state(sd); + + /* Prevent unnecessary standard changes. During a standard + change the I-Port is temporarily disabled. Any devices + reading from that port can get confused. + Note that s_std is also used to switch from + radio to TV mode, so if a s_std is broadcast to + all I2C devices then you do not want to have an unwanted + side-effect here. */ + if (std == state->std) + return; + + state->std = std; + + // This works for NTSC-M, SECAM-L and the 50Hz PAL variants. + if (std & V4L2_STD_525_60) { + v4l2_dbg(1, debug, sd, "decoder set standard 60 Hz\n"); + if (state->ident == GM7113C) { + u8 reg = saa711x_read(sd, R_08_SYNC_CNTL); + reg &= ~(SAA7113_R_08_FSEL | SAA7113_R_08_AUFD); + reg |= SAA7113_R_08_FSEL; + saa711x_write(sd, R_08_SYNC_CNTL, reg); + } else { + saa711x_writeregs(sd, saa7115_cfg_60hz_video); + } + saa711x_set_size(sd, 720, 480); + } else { + v4l2_dbg(1, debug, sd, "decoder set standard 50 Hz\n"); + if (state->ident == GM7113C) { + u8 reg = saa711x_read(sd, R_08_SYNC_CNTL); + reg &= ~(SAA7113_R_08_FSEL | SAA7113_R_08_AUFD); + saa711x_write(sd, R_08_SYNC_CNTL, reg); + } else { + saa711x_writeregs(sd, saa7115_cfg_50hz_video); + } + saa711x_set_size(sd, 720, 576); + } + + /* Register 0E - Bits D6-D4 on NO-AUTO mode + (SAA7111 and SAA7113 doesn't have auto mode) + 50 Hz / 625 lines 60 Hz / 525 lines + 000 PAL BGDHI (4.43Mhz) NTSC M (3.58MHz) + 001 NTSC 4.43 (50 Hz) PAL 4.43 (60 Hz) + 010 Combination-PAL N (3.58MHz) NTSC 4.43 (60 Hz) + 011 NTSC N (3.58MHz) PAL M (3.58MHz) + 100 reserved NTSC-Japan (3.58MHz) + */ + if (state->ident <= SAA7113 || + state->ident == GM7113C) { + u8 reg = saa711x_read(sd, R_0E_CHROMA_CNTL_1) & 0x8f; + + if (std == V4L2_STD_PAL_M) { + reg |= 0x30; + } else if (std == V4L2_STD_PAL_Nc) { + reg |= 0x20; + } else if (std == V4L2_STD_PAL_60) { + reg |= 0x10; + } else if (std == V4L2_STD_NTSC_M_JP) { + reg |= 0x40; + } else if (std & V4L2_STD_SECAM) { + reg |= 0x50; + } + saa711x_write(sd, R_0E_CHROMA_CNTL_1, reg); + } else { + /* restart task B if needed */ + int taskb = saa711x_read(sd, R_80_GLOBAL_CNTL_1) & 0x10; + + if (taskb && state->ident == SAA7114) + saa711x_writeregs(sd, saa7115_cfg_vbi_on); + + /* switch audio mode too! */ + saa711x_s_clock_freq(sd, state->audclk_freq); + } +} + +/* setup the sliced VBI lcr registers according to the sliced VBI format */ +static void saa711x_set_lcr(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *fmt) +{ + struct saa711x_state *state = to_state(sd); + int is_50hz = (state->std & V4L2_STD_625_50); + u8 lcr[24]; + int i, x; + +#if 1 + /* saa7113/7114/7118 VBI support are experimental */ + if (!saa711x_has_reg(state->ident, R_41_LCR_BASE)) + return; + +#else + /* SAA7113 and SAA7118 also should support VBI - Need testing */ + if (state->ident != SAA7115) + return; +#endif + + for (i = 0; i <= 23; i++) + lcr[i] = 0xff; + + if (fmt == NULL) { + /* raw VBI */ + if (is_50hz) + for (i = 6; i <= 23; i++) + lcr[i] = 0xdd; + else + for (i = 10; i <= 21; i++) + lcr[i] = 0xdd; + } else { + /* sliced VBI */ + /* first clear lines that cannot be captured */ + if (is_50hz) { + for (i = 0; i <= 5; i++) + fmt->service_lines[0][i] = + fmt->service_lines[1][i] = 0; + } + else { + for (i = 0; i <= 9; i++) + fmt->service_lines[0][i] = + fmt->service_lines[1][i] = 0; + for (i = 22; i <= 23; i++) + fmt->service_lines[0][i] = + fmt->service_lines[1][i] = 0; + } + + /* Now set the lcr values according to the specified service */ + for (i = 6; i <= 23; i++) { + lcr[i] = 0; + for (x = 0; x <= 1; x++) { + switch (fmt->service_lines[1-x][i]) { + case 0: + lcr[i] |= 0xf << (4 * x); + break; + case V4L2_SLICED_TELETEXT_B: + lcr[i] |= 1 << (4 * x); + break; + case V4L2_SLICED_CAPTION_525: + lcr[i] |= 4 << (4 * x); + break; + case V4L2_SLICED_WSS_625: + lcr[i] |= 5 << (4 * x); + break; + case V4L2_SLICED_VPS: + lcr[i] |= 7 << (4 * x); + break; + } + } + } + } + + /* write the lcr registers */ + for (i = 2; i <= 23; i++) { + saa711x_write(sd, i - 2 + R_41_LCR_BASE, lcr[i]); + } + + /* enable/disable raw VBI capturing */ + saa711x_writeregs(sd, fmt == NULL ? + saa7115_cfg_vbi_on : + saa7115_cfg_vbi_off); +} + +static int saa711x_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *sliced) +{ + static u16 lcr2vbi[] = { + 0, V4L2_SLICED_TELETEXT_B, 0, /* 1 */ + 0, V4L2_SLICED_CAPTION_525, /* 4 */ + V4L2_SLICED_WSS_625, 0, /* 5 */ + V4L2_SLICED_VPS, 0, 0, 0, 0, /* 7 */ + 0, 0, 0, 0 + }; + int i; + + memset(sliced->service_lines, 0, sizeof(sliced->service_lines)); + sliced->service_set = 0; + /* done if using raw VBI */ + if (saa711x_read(sd, R_80_GLOBAL_CNTL_1) & 0x10) + return 0; + for (i = 2; i <= 23; i++) { + u8 v = saa711x_read(sd, i - 2 + R_41_LCR_BASE); + + sliced->service_lines[0][i] = lcr2vbi[v >> 4]; + sliced->service_lines[1][i] = lcr2vbi[v & 0xf]; + sliced->service_set |= + sliced->service_lines[0][i] | sliced->service_lines[1][i]; + } + return 0; +} + +static int saa711x_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt) +{ + saa711x_set_lcr(sd, NULL); + return 0; +} + +static int saa711x_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *fmt) +{ + saa711x_set_lcr(sd, fmt); + return 0; +} + +static int saa711x_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + + if (format->pad || fmt->code != MEDIA_BUS_FMT_FIXED) + return -EINVAL; + fmt->field = V4L2_FIELD_INTERLACED; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + return saa711x_set_size(sd, fmt->width, fmt->height); +} + +/* Decode the sliced VBI data stream as created by the saa7115. + The format is described in the saa7115 datasheet in Tables 25 and 26 + and in Figure 33. + The current implementation uses SAV/EAV codes and not the ancillary data + headers. The vbi->p pointer points to the R_5E_SDID byte right after the SAV + code. */ +static int saa711x_decode_vbi_line(struct v4l2_subdev *sd, struct v4l2_decode_vbi_line *vbi) +{ + struct saa711x_state *state = to_state(sd); + static const char vbi_no_data_pattern[] = { + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0 + }; + u8 *p = vbi->p; + u32 wss; + int id1, id2; /* the ID1 and ID2 bytes from the internal header */ + + vbi->type = 0; /* mark result as a failure */ + id1 = p[2]; + id2 = p[3]; + /* Note: the field bit is inverted for 60 Hz video */ + if (state->std & V4L2_STD_525_60) + id1 ^= 0x40; + + /* Skip internal header, p now points to the start of the payload */ + p += 4; + vbi->p = p; + + /* calculate field and line number of the VBI packet (1-23) */ + vbi->is_second_field = ((id1 & 0x40) != 0); + vbi->line = (id1 & 0x3f) << 3; + vbi->line |= (id2 & 0x70) >> 4; + + /* Obtain data type */ + id2 &= 0xf; + + /* If the VBI slicer does not detect any signal it will fill up + the payload buffer with 0xa0 bytes. */ + if (!memcmp(p, vbi_no_data_pattern, sizeof(vbi_no_data_pattern))) + return 0; + + /* decode payloads */ + switch (id2) { + case 1: + vbi->type = V4L2_SLICED_TELETEXT_B; + break; + case 4: + if (!saa711x_odd_parity(p[0]) || !saa711x_odd_parity(p[1])) + return 0; + vbi->type = V4L2_SLICED_CAPTION_525; + break; + case 5: + wss = saa711x_decode_wss(p); + if (wss == -1) + return 0; + p[0] = wss & 0xff; + p[1] = wss >> 8; + vbi->type = V4L2_SLICED_WSS_625; + break; + case 7: + if (saa711x_decode_vps(p, p) != 0) + return 0; + vbi->type = V4L2_SLICED_VPS; + break; + default: + break; + } + return 0; +} + +/* ============ SAA7115 AUDIO settings (end) ============= */ + +static int saa711x_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct saa711x_state *state = to_state(sd); + int status; + + if (state->radio) + return 0; + status = saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC); + + v4l2_dbg(1, debug, sd, "status: 0x%02x\n", status); + vt->signal = ((status & (1 << 6)) == 0) ? 0xffff : 0x0; + return 0; +} + +static int saa711x_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa711x_state *state = to_state(sd); + + state->radio = 0; + saa711x_set_v4lstd(sd, std); + return 0; +} + +static int saa711x_s_radio(struct v4l2_subdev *sd) +{ + struct saa711x_state *state = to_state(sd); + + state->radio = 1; + return 0; +} + +static int saa711x_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct saa711x_state *state = to_state(sd); + u8 mask = (state->ident <= SAA7111A) ? 0xf8 : 0xf0; + + v4l2_dbg(1, debug, sd, "decoder set input %d output %d\n", + input, output); + + /* saa7111/3 does not have these inputs */ + if ((state->ident <= SAA7113 || + state->ident == GM7113C) && + (input == SAA7115_COMPOSITE4 || + input == SAA7115_COMPOSITE5)) { + return -EINVAL; + } + if (input > SAA7115_SVIDEO3) + return -EINVAL; + if (state->input == input && state->output == output) + return 0; + v4l2_dbg(1, debug, sd, "now setting %s input %s output\n", + (input >= SAA7115_SVIDEO0) ? "S-Video" : "Composite", + (output == SAA7115_IPORT_ON) ? "iport on" : "iport off"); + state->input = input; + + /* saa7111 has slightly different input numbering */ + if (state->ident <= SAA7111A) { + if (input >= SAA7115_COMPOSITE4) + input -= 2; + /* saa7111 specific */ + saa711x_write(sd, R_10_CHROMA_CNTL_2, + (saa711x_read(sd, R_10_CHROMA_CNTL_2) & 0x3f) | + ((output & 0xc0) ^ 0x40)); + saa711x_write(sd, R_13_RT_X_PORT_OUT_CNTL, + (saa711x_read(sd, R_13_RT_X_PORT_OUT_CNTL) & 0xf0) | + ((output & 2) ? 0x0a : 0)); + } + + /* select mode */ + saa711x_write(sd, R_02_INPUT_CNTL_1, + (saa711x_read(sd, R_02_INPUT_CNTL_1) & mask) | + input); + + /* bypass chrominance trap for S-Video modes */ + saa711x_write(sd, R_09_LUMA_CNTL, + (saa711x_read(sd, R_09_LUMA_CNTL) & 0x7f) | + (state->input >= SAA7115_SVIDEO0 ? 0x80 : 0x0)); + + state->output = output; + if (state->ident == SAA7114 || + state->ident == SAA7115) { + saa711x_write(sd, R_83_X_PORT_I_O_ENA_AND_OUT_CLK, + (saa711x_read(sd, R_83_X_PORT_I_O_ENA_AND_OUT_CLK) & 0xfe) | + (state->output & 0x01)); + } + if (state->ident > SAA7111A) { + if (config & SAA7115_IDQ_IS_DEFAULT) + saa711x_write(sd, R_85_I_PORT_SIGNAL_POLAR, 0x20); + else + saa711x_write(sd, R_85_I_PORT_SIGNAL_POLAR, 0x21); + } + return 0; +} + +static int saa711x_s_gpio(struct v4l2_subdev *sd, u32 val) +{ + struct saa711x_state *state = to_state(sd); + + if (state->ident > SAA7111A) + return -EINVAL; + saa711x_write(sd, 0x11, (saa711x_read(sd, 0x11) & 0x7f) | + (val ? 0x80 : 0)); + return 0; +} + +static int saa711x_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct saa711x_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s output\n", + enable ? "enable" : "disable"); + + if (state->enable == enable) + return 0; + state->enable = enable; + if (!saa711x_has_reg(state->ident, R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED)) + return 0; + saa711x_write(sd, R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, state->enable); + return 0; +} + +static int saa711x_s_crystal_freq(struct v4l2_subdev *sd, u32 freq, u32 flags) +{ + struct saa711x_state *state = to_state(sd); + + if (freq != SAA7115_FREQ_32_11_MHZ && freq != SAA7115_FREQ_24_576_MHZ) + return -EINVAL; + state->crystal_freq = freq; + state->double_asclk = flags & SAA7115_FREQ_FL_DOUBLE_ASCLK; + state->cgcdiv = (flags & SAA7115_FREQ_FL_CGCDIV) ? 3 : 4; + state->ucgc = flags & SAA7115_FREQ_FL_UCGC; + state->apll = flags & SAA7115_FREQ_FL_APLL; + saa711x_s_clock_freq(sd, state->audclk_freq); + return 0; +} + +static int saa711x_reset(struct v4l2_subdev *sd, u32 val) +{ + v4l2_dbg(1, debug, sd, "decoder RESET\n"); + saa711x_writeregs(sd, saa7115_cfg_reset_scaler); + return 0; +} + +static int saa711x_g_vbi_data(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_data *data) +{ + /* Note: the internal field ID is inverted for NTSC, + so data->field 0 maps to the saa7115 even field, + whereas for PAL it maps to the saa7115 odd field. */ + switch (data->id) { + case V4L2_SLICED_WSS_625: + if (saa711x_read(sd, 0x6b) & 0xc0) + return -EIO; + data->data[0] = saa711x_read(sd, 0x6c); + data->data[1] = saa711x_read(sd, 0x6d); + return 0; + case V4L2_SLICED_CAPTION_525: + if (data->field == 0) { + /* CC */ + if (saa711x_read(sd, 0x66) & 0x30) + return -EIO; + data->data[0] = saa711x_read(sd, 0x69); + data->data[1] = saa711x_read(sd, 0x6a); + return 0; + } + /* XDS */ + if (saa711x_read(sd, 0x66) & 0xc0) + return -EIO; + data->data[0] = saa711x_read(sd, 0x67); + data->data[1] = saa711x_read(sd, 0x68); + return 0; + default: + return -EINVAL; + } +} + +static int saa711x_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct saa711x_state *state = to_state(sd); + int reg1f, reg1e; + + /* + * The V4L2 core already initializes std with all supported + * Standards. All driver needs to do is to mask it, to remove + * standards that don't apply from the mask + */ + + reg1f = saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC); + + if (state->ident == SAA7115) { + reg1e = saa711x_read(sd, R_1E_STATUS_BYTE_1_VD_DEC); + + v4l2_dbg(1, debug, sd, "Status byte 1 (0x1e)=0x%02x\n", reg1e); + + switch (reg1e & 0x03) { + case 1: + *std &= V4L2_STD_NTSC; + break; + case 2: + /* + * V4L2_STD_PAL just cover the european PAL standards. + * This is wrong, as the device could also be using an + * other PAL standard. + */ + *std &= V4L2_STD_PAL | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | + V4L2_STD_PAL_M | V4L2_STD_PAL_60; + break; + case 3: + *std &= V4L2_STD_SECAM; + break; + default: + *std = V4L2_STD_UNKNOWN; + /* Can't detect anything */ + break; + } + } + + v4l2_dbg(1, debug, sd, "Status byte 2 (0x1f)=0x%02x\n", reg1f); + + /* horizontal/vertical not locked */ + if (reg1f & 0x40) { + *std = V4L2_STD_UNKNOWN; + goto ret; + } + + if (reg1f & 0x20) + *std &= V4L2_STD_525_60; + else + *std &= V4L2_STD_625_50; + +ret: + v4l2_dbg(1, debug, sd, "detected std mask = %08Lx\n", *std); + + return 0; +} + +static int saa711x_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct saa711x_state *state = to_state(sd); + int reg1e = 0x80; + int reg1f; + + *status = V4L2_IN_ST_NO_SIGNAL; + if (state->ident == SAA7115) + reg1e = saa711x_read(sd, R_1E_STATUS_BYTE_1_VD_DEC); + reg1f = saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC); + if ((reg1f & 0xc1) == 0x81 && (reg1e & 0xc0) == 0x80) + *status = 0; + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int saa711x_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->val = saa711x_read(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int saa711x_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + saa711x_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static int saa711x_log_status(struct v4l2_subdev *sd) +{ + struct saa711x_state *state = to_state(sd); + int reg1e, reg1f; + int signalOk; + int vcr; + + v4l2_info(sd, "Audio frequency: %d Hz\n", state->audclk_freq); + if (state->ident != SAA7115) { + /* status for the saa7114 */ + reg1f = saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC); + signalOk = (reg1f & 0xc1) == 0x81; + v4l2_info(sd, "Video signal: %s\n", signalOk ? "ok" : "bad"); + v4l2_info(sd, "Frequency: %s\n", (reg1f & 0x20) ? "60 Hz" : "50 Hz"); + return 0; + } + + /* status for the saa7115 */ + reg1e = saa711x_read(sd, R_1E_STATUS_BYTE_1_VD_DEC); + reg1f = saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC); + + signalOk = (reg1f & 0xc1) == 0x81 && (reg1e & 0xc0) == 0x80; + vcr = !(reg1f & 0x10); + + if (state->input >= 6) + v4l2_info(sd, "Input: S-Video %d\n", state->input - 6); + else + v4l2_info(sd, "Input: Composite %d\n", state->input); + v4l2_info(sd, "Video signal: %s\n", signalOk ? (vcr ? "VCR" : "broadcast/DVD") : "bad"); + v4l2_info(sd, "Frequency: %s\n", (reg1f & 0x20) ? "60 Hz" : "50 Hz"); + + switch (reg1e & 0x03) { + case 1: + v4l2_info(sd, "Detected format: NTSC\n"); + break; + case 2: + v4l2_info(sd, "Detected format: PAL\n"); + break; + case 3: + v4l2_info(sd, "Detected format: SECAM\n"); + break; + default: + v4l2_info(sd, "Detected format: BW/No color\n"); + break; + } + v4l2_info(sd, "Width, Height: %d, %d\n", state->width, state->height); + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops saa711x_ctrl_ops = { + .s_ctrl = saa711x_s_ctrl, + .g_volatile_ctrl = saa711x_g_volatile_ctrl, +}; + +static const struct v4l2_subdev_core_ops saa711x_core_ops = { + .log_status = saa711x_log_status, + .reset = saa711x_reset, + .s_gpio = saa711x_s_gpio, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = saa711x_g_register, + .s_register = saa711x_s_register, +#endif +}; + +static const struct v4l2_subdev_tuner_ops saa711x_tuner_ops = { + .s_radio = saa711x_s_radio, + .g_tuner = saa711x_g_tuner, +}; + +static const struct v4l2_subdev_audio_ops saa711x_audio_ops = { + .s_clock_freq = saa711x_s_clock_freq, +}; + +static const struct v4l2_subdev_video_ops saa711x_video_ops = { + .s_std = saa711x_s_std, + .s_routing = saa711x_s_routing, + .s_crystal_freq = saa711x_s_crystal_freq, + .s_stream = saa711x_s_stream, + .querystd = saa711x_querystd, + .g_input_status = saa711x_g_input_status, +}; + +static const struct v4l2_subdev_vbi_ops saa711x_vbi_ops = { + .g_vbi_data = saa711x_g_vbi_data, + .decode_vbi_line = saa711x_decode_vbi_line, + .g_sliced_fmt = saa711x_g_sliced_fmt, + .s_sliced_fmt = saa711x_s_sliced_fmt, + .s_raw_fmt = saa711x_s_raw_fmt, +}; + +static const struct v4l2_subdev_pad_ops saa711x_pad_ops = { + .set_fmt = saa711x_set_fmt, +}; + +static const struct v4l2_subdev_ops saa711x_ops = { + .core = &saa711x_core_ops, + .tuner = &saa711x_tuner_ops, + .audio = &saa711x_audio_ops, + .video = &saa711x_video_ops, + .vbi = &saa711x_vbi_ops, + .pad = &saa711x_pad_ops, +}; + +#define CHIP_VER_SIZE 16 + +/* ----------------------------------------------------------------------- */ + +static void saa711x_write_platform_data(struct saa711x_state *state, + struct saa7115_platform_data *data) +{ + struct v4l2_subdev *sd = &state->sd; + u8 work; + + if (state->ident != GM7113C && + state->ident != SAA7113) + return; + + if (data->saa7113_r08_htc) { + work = saa711x_read(sd, R_08_SYNC_CNTL); + work &= ~SAA7113_R_08_HTC_MASK; + work |= ((*data->saa7113_r08_htc) << SAA7113_R_08_HTC_OFFSET); + saa711x_write(sd, R_08_SYNC_CNTL, work); + } + + if (data->saa7113_r10_vrln) { + work = saa711x_read(sd, R_10_CHROMA_CNTL_2); + work &= ~SAA7113_R_10_VRLN_MASK; + if (*data->saa7113_r10_vrln) + work |= (1 << SAA7113_R_10_VRLN_OFFSET); + saa711x_write(sd, R_10_CHROMA_CNTL_2, work); + } + + if (data->saa7113_r10_ofts) { + work = saa711x_read(sd, R_10_CHROMA_CNTL_2); + work &= ~SAA7113_R_10_OFTS_MASK; + work |= (*data->saa7113_r10_ofts << SAA7113_R_10_OFTS_OFFSET); + saa711x_write(sd, R_10_CHROMA_CNTL_2, work); + } + + if (data->saa7113_r12_rts0) { + work = saa711x_read(sd, R_12_RT_SIGNAL_CNTL); + work &= ~SAA7113_R_12_RTS0_MASK; + work |= (*data->saa7113_r12_rts0 << SAA7113_R_12_RTS0_OFFSET); + + /* According to the datasheet, + * SAA7113_RTS_DOT_IN should only be used on RTS1 */ + WARN_ON(*data->saa7113_r12_rts0 == SAA7113_RTS_DOT_IN); + saa711x_write(sd, R_12_RT_SIGNAL_CNTL, work); + } + + if (data->saa7113_r12_rts1) { + work = saa711x_read(sd, R_12_RT_SIGNAL_CNTL); + work &= ~SAA7113_R_12_RTS1_MASK; + work |= (*data->saa7113_r12_rts1 << SAA7113_R_12_RTS1_OFFSET); + saa711x_write(sd, R_12_RT_SIGNAL_CNTL, work); + } + + if (data->saa7113_r13_adlsb) { + work = saa711x_read(sd, R_13_RT_X_PORT_OUT_CNTL); + work &= ~SAA7113_R_13_ADLSB_MASK; + if (*data->saa7113_r13_adlsb) + work |= (1 << SAA7113_R_13_ADLSB_OFFSET); + saa711x_write(sd, R_13_RT_X_PORT_OUT_CNTL, work); + } +} + +/** + * saa711x_detect_chip - Detects the saa711x (or clone) variant + * @client: I2C client structure. + * @id: I2C device ID structure. + * @name: Name of the device to be filled. + * + * Detects the Philips/NXP saa711x chip, or some clone of it. + * if 'id' is NULL or id->driver_data is equal to 1, it auto-probes + * the analog demod. + * If the tuner is not found, it returns -ENODEV. + * If auto-detection is disabled and the tuner doesn't match what it was + * required, it returns -EINVAL and fills 'name'. + * If the chip is found, it returns the chip ID and fills 'name'. + */ +static int saa711x_detect_chip(struct i2c_client *client, + const struct i2c_device_id *id, + char *name) +{ + char chip_ver[CHIP_VER_SIZE]; + char chip_id; + int i; + int autodetect; + + autodetect = !id || id->driver_data == 1; + + /* Read the chip version register */ + for (i = 0; i < CHIP_VER_SIZE; i++) { + i2c_smbus_write_byte_data(client, 0, i); + chip_ver[i] = i2c_smbus_read_byte_data(client, 0); + name[i] = (chip_ver[i] & 0x0f) + '0'; + if (name[i] > '9') + name[i] += 'a' - '9' - 1; + } + name[i] = '\0'; + + /* Check if it is a Philips/NXP chip */ + if (!memcmp(name + 1, "f711", 4)) { + chip_id = name[5]; + snprintf(name, CHIP_VER_SIZE, "saa711%c", chip_id); + + if (!autodetect && strcmp(name, id->name)) + return -EINVAL; + + switch (chip_id) { + case '1': + if (chip_ver[0] & 0xf0) { + snprintf(name, CHIP_VER_SIZE, "saa711%ca", chip_id); + v4l_info(client, "saa7111a variant found\n"); + return SAA7111A; + } + return SAA7111; + case '3': + return SAA7113; + case '4': + return SAA7114; + case '5': + return SAA7115; + case '8': + return SAA7118; + default: + v4l2_info(client, + "WARNING: Philips/NXP chip unknown - Falling back to saa7111\n"); + return SAA7111; + } + } + + /* Check if it is a gm7113c */ + if (!memcmp(name, "0000", 4)) { + chip_id = 0; + for (i = 0; i < 4; i++) { + chip_id = chip_id << 1; + chip_id |= (chip_ver[i] & 0x80) ? 1 : 0; + } + + /* + * Note: From the datasheet, only versions 1 and 2 + * exists. However, tests on a device labeled as: + * "GM7113C 1145" returned "10" on all 16 chip + * version (reg 0x00) reads. So, we need to also + * accept at least version 0. For now, let's just + * assume that a device that returns "0000" for + * the lower nibble is a gm7113c. + */ + + strscpy(name, "gm7113c", CHIP_VER_SIZE); + + if (!autodetect && strcmp(name, id->name)) + return -EINVAL; + + v4l_dbg(1, debug, client, + "It seems to be a %s chip (%*ph) @ 0x%x.\n", + name, 16, chip_ver, client->addr << 1); + + return GM7113C; + } + + /* Check if it is a CJC7113 */ + if (!memcmp(name, "1111111111111111", CHIP_VER_SIZE)) { + strscpy(name, "cjc7113", CHIP_VER_SIZE); + + if (!autodetect && strcmp(name, id->name)) + return -EINVAL; + + v4l_dbg(1, debug, client, + "It seems to be a %s chip (%*ph) @ 0x%x.\n", + name, 16, chip_ver, client->addr << 1); + + /* CJC7113 seems to be SAA7113-compatible */ + return SAA7113; + } + + /* Chip was not discovered. Return its ID and don't bind */ + v4l_dbg(1, debug, client, "chip %*ph @ 0x%x is unknown.\n", + 16, chip_ver, client->addr << 1); + return -ENODEV; +} + +static int saa711x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct saa711x_state *state; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + struct saa7115_platform_data *pdata; + int ident; + char name[CHIP_VER_SIZE + 1]; +#if defined(CONFIG_MEDIA_CONTROLLER) + int ret; +#endif + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + ident = saa711x_detect_chip(client, id, name); + if (ident == -EINVAL) { + /* Chip exists, but doesn't match */ + v4l_warn(client, "found %s while %s was expected\n", + name, id->name); + return -ENODEV; + } + if (ident < 0) + return ident; + + strscpy(client->name, name, sizeof(client->name)); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &saa711x_ops); + +#if defined(CONFIG_MEDIA_CONTROLLER) + state->pads[SAA711X_PAD_IF_INPUT].flags = MEDIA_PAD_FL_SINK; + state->pads[SAA711X_PAD_IF_INPUT].sig_type = PAD_SIGNAL_ANALOG; + state->pads[SAA711X_PAD_VID_OUT].flags = MEDIA_PAD_FL_SOURCE; + state->pads[SAA711X_PAD_VID_OUT].sig_type = PAD_SIGNAL_DV; + + sd->entity.function = MEDIA_ENT_F_ATV_DECODER; + + ret = media_entity_pads_init(&sd->entity, SAA711X_NUM_PADS, + state->pads); + if (ret < 0) + return ret; +#endif + + v4l_info(client, "%s found @ 0x%x (%s)\n", name, + client->addr << 1, client->adapter->name); + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 6); + /* add in ascending ID order */ + v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + state->agc = v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_CHROMA_AGC, 0, 1, 1, 1); + state->gain = v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_CHROMA_GAIN, 0, 127, 1, 40); + sd->ctrl_handler = hdl; + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return err; + } + v4l2_ctrl_auto_cluster(2, &state->agc, 0, true); + + state->input = -1; + state->output = SAA7115_IPORT_ON; + state->enable = 1; + state->radio = 0; + state->ident = ident; + + state->audclk_freq = 48000; + + v4l2_dbg(1, debug, sd, "writing init values\n"); + + /* init to 60hz/48khz */ + state->crystal_freq = SAA7115_FREQ_24_576_MHZ; + pdata = client->dev.platform_data; + switch (state->ident) { + case SAA7111: + case SAA7111A: + saa711x_writeregs(sd, saa7111_init); + break; + case GM7113C: + saa711x_writeregs(sd, gm7113c_init); + break; + case SAA7113: + if (pdata && pdata->saa7113_force_gm7113c_init) + saa711x_writeregs(sd, gm7113c_init); + else + saa711x_writeregs(sd, saa7113_init); + break; + default: + state->crystal_freq = SAA7115_FREQ_32_11_MHZ; + saa711x_writeregs(sd, saa7115_init_auto_input); + } + if (state->ident > SAA7111A && state->ident != GM7113C) + saa711x_writeregs(sd, saa7115_init_misc); + + if (pdata) + saa711x_write_platform_data(state, pdata); + + saa711x_set_v4lstd(sd, V4L2_STD_NTSC); + v4l2_ctrl_handler_setup(hdl); + + v4l2_dbg(1, debug, sd, "status: (1E) 0x%02x, (1F) 0x%02x\n", + saa711x_read(sd, R_1E_STATUS_BYTE_1_VD_DEC), + saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC)); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa711x_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return 0; +} + +static const struct i2c_device_id saa711x_id[] = { + { "saa7115_auto", 1 }, /* autodetect */ + { "saa7111", 0 }, + { "saa7113", 0 }, + { "saa7114", 0 }, + { "saa7115", 0 }, + { "saa7118", 0 }, + { "gm7113c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, saa711x_id); + +static struct i2c_driver saa711x_driver = { + .driver = { + .name = "saa7115", + }, + .probe = saa711x_probe, + .remove = saa711x_remove, + .id_table = saa711x_id, +}; + +module_i2c_driver(saa711x_driver); diff --git a/drivers/media/i2c/saa711x_regs.h b/drivers/media/i2c/saa711x_regs.h new file mode 100644 index 000000000..4b5f69857 --- /dev/null +++ b/drivers/media/i2c/saa711x_regs.h @@ -0,0 +1,560 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * saa711x - Philips SAA711x video decoder register specifications + * + * Copyright (c) 2006 Mauro Carvalho Chehab <mchehab@kernel.org> + */ + +#define R_00_CHIP_VERSION 0x00 +/* Video Decoder */ + /* Video Decoder - Frontend part */ +#define R_01_INC_DELAY 0x01 +#define R_02_INPUT_CNTL_1 0x02 +#define R_03_INPUT_CNTL_2 0x03 +#define R_04_INPUT_CNTL_3 0x04 +#define R_05_INPUT_CNTL_4 0x05 + /* Video Decoder - Decoder part */ +#define R_06_H_SYNC_START 0x06 +#define R_07_H_SYNC_STOP 0x07 +#define R_08_SYNC_CNTL 0x08 +#define R_09_LUMA_CNTL 0x09 +#define R_0A_LUMA_BRIGHT_CNTL 0x0a +#define R_0B_LUMA_CONTRAST_CNTL 0x0b +#define R_0C_CHROMA_SAT_CNTL 0x0c +#define R_0D_CHROMA_HUE_CNTL 0x0d +#define R_0E_CHROMA_CNTL_1 0x0e +#define R_0F_CHROMA_GAIN_CNTL 0x0f +#define R_10_CHROMA_CNTL_2 0x10 +#define R_11_MODE_DELAY_CNTL 0x11 +#define R_12_RT_SIGNAL_CNTL 0x12 +#define R_13_RT_X_PORT_OUT_CNTL 0x13 +#define R_14_ANAL_ADC_COMPAT_CNTL 0x14 +#define R_15_VGATE_START_FID_CHG 0x15 +#define R_16_VGATE_STOP 0x16 +#define R_17_MISC_VGATE_CONF_AND_MSB 0x17 +#define R_18_RAW_DATA_GAIN_CNTL 0x18 +#define R_19_RAW_DATA_OFF_CNTL 0x19 +#define R_1A_COLOR_KILL_LVL_CNTL 0x1a +#define R_1B_MISC_TVVCRDET 0x1b +#define R_1C_ENHAN_COMB_CTRL1 0x1c +#define R_1D_ENHAN_COMB_CTRL2 0x1d +#define R_1E_STATUS_BYTE_1_VD_DEC 0x1e +#define R_1F_STATUS_BYTE_2_VD_DEC 0x1f + +/* Component processing and interrupt masking part */ +#define R_23_INPUT_CNTL_5 0x23 +#define R_24_INPUT_CNTL_6 0x24 +#define R_25_INPUT_CNTL_7 0x25 +#define R_29_COMP_DELAY 0x29 +#define R_2A_COMP_BRIGHT_CNTL 0x2a +#define R_2B_COMP_CONTRAST_CNTL 0x2b +#define R_2C_COMP_SAT_CNTL 0x2c +#define R_2D_INTERRUPT_MASK_1 0x2d +#define R_2E_INTERRUPT_MASK_2 0x2e +#define R_2F_INTERRUPT_MASK_3 0x2f + +/* Audio clock generator part */ +#define R_30_AUD_MAST_CLK_CYCLES_PER_FIELD 0x30 +#define R_34_AUD_MAST_CLK_NOMINAL_INC 0x34 +#define R_38_CLK_RATIO_AMXCLK_TO_ASCLK 0x38 +#define R_39_CLK_RATIO_ASCLK_TO_ALRCLK 0x39 +#define R_3A_AUD_CLK_GEN_BASIC_SETUP 0x3a + +/* General purpose VBI data slicer part */ +#define R_40_SLICER_CNTL_1 0x40 +#define R_41_LCR_BASE 0x41 +#define R_58_PROGRAM_FRAMING_CODE 0x58 +#define R_59_H_OFF_FOR_SLICER 0x59 +#define R_5A_V_OFF_FOR_SLICER 0x5a +#define R_5B_FLD_OFF_AND_MSB_FOR_H_AND_V_OFF 0x5b +#define R_5D_DID 0x5d +#define R_5E_SDID 0x5e +#define R_60_SLICER_STATUS_BYTE_0 0x60 +#define R_61_SLICER_STATUS_BYTE_1 0x61 +#define R_62_SLICER_STATUS_BYTE_2 0x62 + +/* X port, I port and the scaler part */ + /* Task independent global settings */ +#define R_80_GLOBAL_CNTL_1 0x80 +#define R_81_V_SYNC_FLD_ID_SRC_SEL_AND_RETIMED_V_F 0x81 +#define R_83_X_PORT_I_O_ENA_AND_OUT_CLK 0x83 +#define R_84_I_PORT_SIGNAL_DEF 0x84 +#define R_85_I_PORT_SIGNAL_POLAR 0x85 +#define R_86_I_PORT_FIFO_FLAG_CNTL_AND_ARBIT 0x86 +#define R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED 0x87 +#define R_88_POWER_SAVE_ADC_PORT_CNTL 0x88 +#define R_8F_STATUS_INFO_SCALER 0x8f + /* Task A definition */ + /* Basic settings and acquisition window definition */ +#define R_90_A_TASK_HANDLING_CNTL 0x90 +#define R_91_A_X_PORT_FORMATS_AND_CONF 0x91 +#define R_92_A_X_PORT_INPUT_REFERENCE_SIGNAL 0x92 +#define R_93_A_I_PORT_OUTPUT_FORMATS_AND_CONF 0x93 +#define R_94_A_HORIZ_INPUT_WINDOW_START 0x94 +#define R_95_A_HORIZ_INPUT_WINDOW_START_MSB 0x95 +#define R_96_A_HORIZ_INPUT_WINDOW_LENGTH 0x96 +#define R_97_A_HORIZ_INPUT_WINDOW_LENGTH_MSB 0x97 +#define R_98_A_VERT_INPUT_WINDOW_START 0x98 +#define R_99_A_VERT_INPUT_WINDOW_START_MSB 0x99 +#define R_9A_A_VERT_INPUT_WINDOW_LENGTH 0x9a +#define R_9B_A_VERT_INPUT_WINDOW_LENGTH_MSB 0x9b +#define R_9C_A_HORIZ_OUTPUT_WINDOW_LENGTH 0x9c +#define R_9D_A_HORIZ_OUTPUT_WINDOW_LENGTH_MSB 0x9d +#define R_9E_A_VERT_OUTPUT_WINDOW_LENGTH 0x9e +#define R_9F_A_VERT_OUTPUT_WINDOW_LENGTH_MSB 0x9f + /* FIR filtering and prescaling */ +#define R_A0_A_HORIZ_PRESCALING 0xa0 +#define R_A1_A_ACCUMULATION_LENGTH 0xa1 +#define R_A2_A_PRESCALER_DC_GAIN_AND_FIR_PREFILTER 0xa2 +#define R_A4_A_LUMA_BRIGHTNESS_CNTL 0xa4 +#define R_A5_A_LUMA_CONTRAST_CNTL 0xa5 +#define R_A6_A_CHROMA_SATURATION_CNTL 0xa6 + /* Horizontal phase scaling */ +#define R_A8_A_HORIZ_LUMA_SCALING_INC 0xa8 +#define R_A9_A_HORIZ_LUMA_SCALING_INC_MSB 0xa9 +#define R_AA_A_HORIZ_LUMA_PHASE_OFF 0xaa +#define R_AC_A_HORIZ_CHROMA_SCALING_INC 0xac +#define R_AD_A_HORIZ_CHROMA_SCALING_INC_MSB 0xad +#define R_AE_A_HORIZ_CHROMA_PHASE_OFF 0xae +#define R_AF_A_HORIZ_CHROMA_PHASE_OFF_MSB 0xaf + /* Vertical scaling */ +#define R_B0_A_VERT_LUMA_SCALING_INC 0xb0 +#define R_B1_A_VERT_LUMA_SCALING_INC_MSB 0xb1 +#define R_B2_A_VERT_CHROMA_SCALING_INC 0xb2 +#define R_B3_A_VERT_CHROMA_SCALING_INC_MSB 0xb3 +#define R_B4_A_VERT_SCALING_MODE_CNTL 0xb4 +#define R_B8_A_VERT_CHROMA_PHASE_OFF_00 0xb8 +#define R_B9_A_VERT_CHROMA_PHASE_OFF_01 0xb9 +#define R_BA_A_VERT_CHROMA_PHASE_OFF_10 0xba +#define R_BB_A_VERT_CHROMA_PHASE_OFF_11 0xbb +#define R_BC_A_VERT_LUMA_PHASE_OFF_00 0xbc +#define R_BD_A_VERT_LUMA_PHASE_OFF_01 0xbd +#define R_BE_A_VERT_LUMA_PHASE_OFF_10 0xbe +#define R_BF_A_VERT_LUMA_PHASE_OFF_11 0xbf + /* Task B definition */ + /* Basic settings and acquisition window definition */ +#define R_C0_B_TASK_HANDLING_CNTL 0xc0 +#define R_C1_B_X_PORT_FORMATS_AND_CONF 0xc1 +#define R_C2_B_INPUT_REFERENCE_SIGNAL_DEFINITION 0xc2 +#define R_C3_B_I_PORT_FORMATS_AND_CONF 0xc3 +#define R_C4_B_HORIZ_INPUT_WINDOW_START 0xc4 +#define R_C5_B_HORIZ_INPUT_WINDOW_START_MSB 0xc5 +#define R_C6_B_HORIZ_INPUT_WINDOW_LENGTH 0xc6 +#define R_C7_B_HORIZ_INPUT_WINDOW_LENGTH_MSB 0xc7 +#define R_C8_B_VERT_INPUT_WINDOW_START 0xc8 +#define R_C9_B_VERT_INPUT_WINDOW_START_MSB 0xc9 +#define R_CA_B_VERT_INPUT_WINDOW_LENGTH 0xca +#define R_CB_B_VERT_INPUT_WINDOW_LENGTH_MSB 0xcb +#define R_CC_B_HORIZ_OUTPUT_WINDOW_LENGTH 0xcc +#define R_CD_B_HORIZ_OUTPUT_WINDOW_LENGTH_MSB 0xcd +#define R_CE_B_VERT_OUTPUT_WINDOW_LENGTH 0xce +#define R_CF_B_VERT_OUTPUT_WINDOW_LENGTH_MSB 0xcf + /* FIR filtering and prescaling */ +#define R_D0_B_HORIZ_PRESCALING 0xd0 +#define R_D1_B_ACCUMULATION_LENGTH 0xd1 +#define R_D2_B_PRESCALER_DC_GAIN_AND_FIR_PREFILTER 0xd2 +#define R_D4_B_LUMA_BRIGHTNESS_CNTL 0xd4 +#define R_D5_B_LUMA_CONTRAST_CNTL 0xd5 +#define R_D6_B_CHROMA_SATURATION_CNTL 0xd6 + /* Horizontal phase scaling */ +#define R_D8_B_HORIZ_LUMA_SCALING_INC 0xd8 +#define R_D9_B_HORIZ_LUMA_SCALING_INC_MSB 0xd9 +#define R_DA_B_HORIZ_LUMA_PHASE_OFF 0xda +#define R_DC_B_HORIZ_CHROMA_SCALING 0xdc +#define R_DD_B_HORIZ_CHROMA_SCALING_MSB 0xdd +#define R_DE_B_HORIZ_PHASE_OFFSET_CRHOMA 0xde + /* Vertical scaling */ +#define R_E0_B_VERT_LUMA_SCALING_INC 0xe0 +#define R_E1_B_VERT_LUMA_SCALING_INC_MSB 0xe1 +#define R_E2_B_VERT_CHROMA_SCALING_INC 0xe2 +#define R_E3_B_VERT_CHROMA_SCALING_INC_MSB 0xe3 +#define R_E4_B_VERT_SCALING_MODE_CNTL 0xe4 +#define R_E8_B_VERT_CHROMA_PHASE_OFF_00 0xe8 +#define R_E9_B_VERT_CHROMA_PHASE_OFF_01 0xe9 +#define R_EA_B_VERT_CHROMA_PHASE_OFF_10 0xea +#define R_EB_B_VERT_CHROMA_PHASE_OFF_11 0xeb +#define R_EC_B_VERT_LUMA_PHASE_OFF_00 0xec +#define R_ED_B_VERT_LUMA_PHASE_OFF_01 0xed +#define R_EE_B_VERT_LUMA_PHASE_OFF_10 0xee +#define R_EF_B_VERT_LUMA_PHASE_OFF_11 0xef + +/* second PLL (PLL2) and Pulsegenerator Programming */ +#define R_F0_LFCO_PER_LINE 0xf0 +#define R_F1_P_I_PARAM_SELECT 0xf1 +#define R_F2_NOMINAL_PLL2_DTO 0xf2 +#define R_F3_PLL_INCREMENT 0xf3 +#define R_F4_PLL2_STATUS 0xf4 +#define R_F5_PULSGEN_LINE_LENGTH 0xf5 +#define R_F6_PULSE_A_POS_LSB_AND_PULSEGEN_CONFIG 0xf6 +#define R_F7_PULSE_A_POS_MSB 0xf7 +#define R_F8_PULSE_B_POS 0xf8 +#define R_F9_PULSE_B_POS_MSB 0xf9 +#define R_FA_PULSE_C_POS 0xfa +#define R_FB_PULSE_C_POS_MSB 0xfb +#define R_FF_S_PLL_MAX_PHASE_ERR_THRESH_NUM_LINES 0xff + +/* SAA7113 bit-masks */ +#define SAA7113_R_08_HTC_OFFSET 3 +#define SAA7113_R_08_HTC_MASK (0x3 << SAA7113_R_08_HTC_OFFSET) +#define SAA7113_R_08_FSEL 0x40 +#define SAA7113_R_08_AUFD 0x80 + +#define SAA7113_R_10_VRLN_OFFSET 3 +#define SAA7113_R_10_VRLN_MASK (0x1 << SAA7113_R_10_VRLN_OFFSET) +#define SAA7113_R_10_OFTS_OFFSET 6 +#define SAA7113_R_10_OFTS_MASK (0x3 << SAA7113_R_10_OFTS_OFFSET) + +#define SAA7113_R_12_RTS0_OFFSET 0 +#define SAA7113_R_12_RTS0_MASK (0xf << SAA7113_R_12_RTS0_OFFSET) +#define SAA7113_R_12_RTS1_OFFSET 4 +#define SAA7113_R_12_RTS1_MASK (0xf << SAA7113_R_12_RTS1_OFFSET) + +#define SAA7113_R_13_ADLSB_OFFSET 7 +#define SAA7113_R_13_ADLSB_MASK (0x1 << SAA7113_R_13_ADLSB_OFFSET) + +#if 0 +/* Those structs will be used in the future for debug purposes */ +struct saa711x_reg_descr { + u8 reg; + int count; + char *name; +}; + +struct saa711x_reg_descr saa711x_regs[] = { + /* REG COUNT NAME */ + {R_00_CHIP_VERSION,1, + "Chip version"}, + + /* Video Decoder: R_01_INC_DELAY to R_1F_STATUS_BYTE_2_VD_DEC */ + + /* Video Decoder - Frontend part: R_01_INC_DELAY to R_05_INPUT_CNTL_4 */ + {R_01_INC_DELAY,1, + "Increment delay"}, + {R_02_INPUT_CNTL_1,1, + "Analog input control 1"}, + {R_03_INPUT_CNTL_2,1, + "Analog input control 2"}, + {R_04_INPUT_CNTL_3,1, + "Analog input control 3"}, + {R_05_INPUT_CNTL_4,1, + "Analog input control 4"}, + + /* Video Decoder - Decoder part: R_06_H_SYNC_START to R_1F_STATUS_BYTE_2_VD_DEC */ + {R_06_H_SYNC_START,1, + "Horizontal sync start"}, + {R_07_H_SYNC_STOP,1, + "Horizontal sync stop"}, + {R_08_SYNC_CNTL,1, + "Sync control"}, + {R_09_LUMA_CNTL,1, + "Luminance control"}, + {R_0A_LUMA_BRIGHT_CNTL,1, + "Luminance brightness control"}, + {R_0B_LUMA_CONTRAST_CNTL,1, + "Luminance contrast control"}, + {R_0C_CHROMA_SAT_CNTL,1, + "Chrominance saturation control"}, + {R_0D_CHROMA_HUE_CNTL,1, + "Chrominance hue control"}, + {R_0E_CHROMA_CNTL_1,1, + "Chrominance control 1"}, + {R_0F_CHROMA_GAIN_CNTL,1, + "Chrominance gain control"}, + {R_10_CHROMA_CNTL_2,1, + "Chrominance control 2"}, + {R_11_MODE_DELAY_CNTL,1, + "Mode/delay control"}, + {R_12_RT_SIGNAL_CNTL,1, + "RT signal control"}, + {R_13_RT_X_PORT_OUT_CNTL,1, + "RT/X port output control"}, + {R_14_ANAL_ADC_COMPAT_CNTL,1, + "Analog/ADC/compatibility control"}, + {R_15_VGATE_START_FID_CHG, 1, + "VGATE start FID change"}, + {R_16_VGATE_STOP,1, + "VGATE stop"}, + {R_17_MISC_VGATE_CONF_AND_MSB, 1, + "Miscellaneous VGATE configuration and MSBs"}, + {R_18_RAW_DATA_GAIN_CNTL,1, + "Raw data gain control",}, + {R_19_RAW_DATA_OFF_CNTL,1, + "Raw data offset control",}, + {R_1A_COLOR_KILL_LVL_CNTL,1, + "Color Killer Level Control"}, + { R_1B_MISC_TVVCRDET, 1, + "MISC /TVVCRDET"}, + { R_1C_ENHAN_COMB_CTRL1, 1, + "Enhanced comb ctrl1"}, + { R_1D_ENHAN_COMB_CTRL2, 1, + "Enhanced comb ctrl1"}, + {R_1E_STATUS_BYTE_1_VD_DEC,1, + "Status byte 1 video decoder"}, + {R_1F_STATUS_BYTE_2_VD_DEC,1, + "Status byte 2 video decoder"}, + + /* Component processing and interrupt masking part: 0x20h to R_2F_INTERRUPT_MASK_3 */ + /* 0x20 to 0x22 - Reserved */ + {R_23_INPUT_CNTL_5,1, + "Analog input control 5"}, + {R_24_INPUT_CNTL_6,1, + "Analog input control 6"}, + {R_25_INPUT_CNTL_7,1, + "Analog input control 7"}, + /* 0x26 to 0x28 - Reserved */ + {R_29_COMP_DELAY,1, + "Component delay"}, + {R_2A_COMP_BRIGHT_CNTL,1, + "Component brightness control"}, + {R_2B_COMP_CONTRAST_CNTL,1, + "Component contrast control"}, + {R_2C_COMP_SAT_CNTL,1, + "Component saturation control"}, + {R_2D_INTERRUPT_MASK_1,1, + "Interrupt mask 1"}, + {R_2E_INTERRUPT_MASK_2,1, + "Interrupt mask 2"}, + {R_2F_INTERRUPT_MASK_3,1, + "Interrupt mask 3"}, + + /* Audio clock generator part: R_30_AUD_MAST_CLK_CYCLES_PER_FIELD to 0x3f */ + {R_30_AUD_MAST_CLK_CYCLES_PER_FIELD,3, + "Audio master clock cycles per field"}, + /* 0x33 - Reserved */ + {R_34_AUD_MAST_CLK_NOMINAL_INC,3, + "Audio master clock nominal increment"}, + /* 0x37 - Reserved */ + {R_38_CLK_RATIO_AMXCLK_TO_ASCLK,1, + "Clock ratio AMXCLK to ASCLK"}, + {R_39_CLK_RATIO_ASCLK_TO_ALRCLK,1, + "Clock ratio ASCLK to ALRCLK"}, + {R_3A_AUD_CLK_GEN_BASIC_SETUP,1, + "Audio clock generator basic setup"}, + /* 0x3b-0x3f - Reserved */ + + /* General purpose VBI data slicer part: R_40_SLICER_CNTL_1 to 0x7f */ + {R_40_SLICER_CNTL_1,1, + "Slicer control 1"}, + {R_41_LCR,23, + "R_41_LCR"}, + {R_58_PROGRAM_FRAMING_CODE,1, + "Programmable framing code"}, + {R_59_H_OFF_FOR_SLICER,1, + "Horizontal offset for slicer"}, + {R_5A_V_OFF_FOR_SLICER,1, + "Vertical offset for slicer"}, + {R_5B_FLD_OFF_AND_MSB_FOR_H_AND_V_OFF,1, + "Field offset and MSBs for horizontal and vertical offset"}, + {R_5D_DID,1, + "Header and data identification (R_5D_DID)"}, + {R_5E_SDID,1, + "Sliced data identification (R_5E_SDID) code"}, + {R_60_SLICER_STATUS_BYTE_0,1, + "Slicer status byte 0"}, + {R_61_SLICER_STATUS_BYTE_1,1, + "Slicer status byte 1"}, + {R_62_SLICER_STATUS_BYTE_2,1, + "Slicer status byte 2"}, + /* 0x63-0x7f - Reserved */ + + /* X port, I port and the scaler part: R_80_GLOBAL_CNTL_1 to R_EF_B_VERT_LUMA_PHASE_OFF_11 */ + /* Task independent global settings: R_80_GLOBAL_CNTL_1 to R_8F_STATUS_INFO_SCALER */ + {R_80_GLOBAL_CNTL_1,1, + "Global control 1"}, + {R_81_V_SYNC_FLD_ID_SRC_SEL_AND_RETIMED_V_F,1, + "Vertical sync and Field ID source selection, retimed V and F signals"}, + /* 0x82 - Reserved */ + {R_83_X_PORT_I_O_ENA_AND_OUT_CLK,1, + "X port I/O enable and output clock"}, + {R_84_I_PORT_SIGNAL_DEF,1, + "I port signal definitions"}, + {R_85_I_PORT_SIGNAL_POLAR,1, + "I port signal polarities"}, + {R_86_I_PORT_FIFO_FLAG_CNTL_AND_ARBIT,1, + "I port FIFO flag control and arbitration"}, + {R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 1, + "I port I/O enable output clock and gated"}, + {R_88_POWER_SAVE_ADC_PORT_CNTL,1, + "Power save/ADC port control"}, + /* 089-0x8e - Reserved */ + {R_8F_STATUS_INFO_SCALER,1, + "Status information scaler part"}, + + /* Task A definition: R_90_A_TASK_HANDLING_CNTL to R_BF_A_VERT_LUMA_PHASE_OFF_11 */ + /* Task A: Basic settings and acquisition window definition */ + {R_90_A_TASK_HANDLING_CNTL,1, + "Task A: Task handling control"}, + {R_91_A_X_PORT_FORMATS_AND_CONF,1, + "Task A: X port formats and configuration"}, + {R_92_A_X_PORT_INPUT_REFERENCE_SIGNAL,1, + "Task A: X port input reference signal definition"}, + {R_93_A_I_PORT_OUTPUT_FORMATS_AND_CONF,1, + "Task A: I port output formats and configuration"}, + {R_94_A_HORIZ_INPUT_WINDOW_START,2, + "Task A: Horizontal input window start"}, + {R_96_A_HORIZ_INPUT_WINDOW_LENGTH,2, + "Task A: Horizontal input window length"}, + {R_98_A_VERT_INPUT_WINDOW_START,2, + "Task A: Vertical input window start"}, + {R_9A_A_VERT_INPUT_WINDOW_LENGTH,2, + "Task A: Vertical input window length"}, + {R_9C_A_HORIZ_OUTPUT_WINDOW_LENGTH,2, + "Task A: Horizontal output window length"}, + {R_9E_A_VERT_OUTPUT_WINDOW_LENGTH,2, + "Task A: Vertical output window length"}, + + /* Task A: FIR filtering and prescaling */ + {R_A0_A_HORIZ_PRESCALING,1, + "Task A: Horizontal prescaling"}, + {R_A1_A_ACCUMULATION_LENGTH,1, + "Task A: Accumulation length"}, + {R_A2_A_PRESCALER_DC_GAIN_AND_FIR_PREFILTER,1, + "Task A: Prescaler DC gain and FIR prefilter"}, + /* 0xa3 - Reserved */ + {R_A4_A_LUMA_BRIGHTNESS_CNTL,1, + "Task A: Luminance brightness control"}, + {R_A5_A_LUMA_CONTRAST_CNTL,1, + "Task A: Luminance contrast control"}, + {R_A6_A_CHROMA_SATURATION_CNTL,1, + "Task A: Chrominance saturation control"}, + /* 0xa7 - Reserved */ + + /* Task A: Horizontal phase scaling */ + {R_A8_A_HORIZ_LUMA_SCALING_INC,2, + "Task A: Horizontal luminance scaling increment"}, + {R_AA_A_HORIZ_LUMA_PHASE_OFF,1, + "Task A: Horizontal luminance phase offset"}, + /* 0xab - Reserved */ + {R_AC_A_HORIZ_CHROMA_SCALING_INC,2, + "Task A: Horizontal chrominance scaling increment"}, + {R_AE_A_HORIZ_CHROMA_PHASE_OFF,1, + "Task A: Horizontal chrominance phase offset"}, + /* 0xaf - Reserved */ + + /* Task A: Vertical scaling */ + {R_B0_A_VERT_LUMA_SCALING_INC,2, + "Task A: Vertical luminance scaling increment"}, + {R_B2_A_VERT_CHROMA_SCALING_INC,2, + "Task A: Vertical chrominance scaling increment"}, + {R_B4_A_VERT_SCALING_MODE_CNTL,1, + "Task A: Vertical scaling mode control"}, + /* 0xb5-0xb7 - Reserved */ + {R_B8_A_VERT_CHROMA_PHASE_OFF_00,1, + "Task A: Vertical chrominance phase offset '00'"}, + {R_B9_A_VERT_CHROMA_PHASE_OFF_01,1, + "Task A: Vertical chrominance phase offset '01'"}, + {R_BA_A_VERT_CHROMA_PHASE_OFF_10,1, + "Task A: Vertical chrominance phase offset '10'"}, + {R_BB_A_VERT_CHROMA_PHASE_OFF_11,1, + "Task A: Vertical chrominance phase offset '11'"}, + {R_BC_A_VERT_LUMA_PHASE_OFF_00,1, + "Task A: Vertical luminance phase offset '00'"}, + {R_BD_A_VERT_LUMA_PHASE_OFF_01,1, + "Task A: Vertical luminance phase offset '01'"}, + {R_BE_A_VERT_LUMA_PHASE_OFF_10,1, + "Task A: Vertical luminance phase offset '10'"}, + {R_BF_A_VERT_LUMA_PHASE_OFF_11,1, + "Task A: Vertical luminance phase offset '11'"}, + + /* Task B definition: R_C0_B_TASK_HANDLING_CNTL to R_EF_B_VERT_LUMA_PHASE_OFF_11 */ + /* Task B: Basic settings and acquisition window definition */ + {R_C0_B_TASK_HANDLING_CNTL,1, + "Task B: Task handling control"}, + {R_C1_B_X_PORT_FORMATS_AND_CONF,1, + "Task B: X port formats and configuration"}, + {R_C2_B_INPUT_REFERENCE_SIGNAL_DEFINITION,1, + "Task B: Input reference signal definition"}, + {R_C3_B_I_PORT_FORMATS_AND_CONF,1, + "Task B: I port formats and configuration"}, + {R_C4_B_HORIZ_INPUT_WINDOW_START,2, + "Task B: Horizontal input window start"}, + {R_C6_B_HORIZ_INPUT_WINDOW_LENGTH,2, + "Task B: Horizontal input window length"}, + {R_C8_B_VERT_INPUT_WINDOW_START,2, + "Task B: Vertical input window start"}, + {R_CA_B_VERT_INPUT_WINDOW_LENGTH,2, + "Task B: Vertical input window length"}, + {R_CC_B_HORIZ_OUTPUT_WINDOW_LENGTH,2, + "Task B: Horizontal output window length"}, + {R_CE_B_VERT_OUTPUT_WINDOW_LENGTH,2, + "Task B: Vertical output window length"}, + + /* Task B: FIR filtering and prescaling */ + {R_D0_B_HORIZ_PRESCALING,1, + "Task B: Horizontal prescaling"}, + {R_D1_B_ACCUMULATION_LENGTH,1, + "Task B: Accumulation length"}, + {R_D2_B_PRESCALER_DC_GAIN_AND_FIR_PREFILTER,1, + "Task B: Prescaler DC gain and FIR prefilter"}, + /* 0xd3 - Reserved */ + {R_D4_B_LUMA_BRIGHTNESS_CNTL,1, + "Task B: Luminance brightness control"}, + {R_D5_B_LUMA_CONTRAST_CNTL,1, + "Task B: Luminance contrast control"}, + {R_D6_B_CHROMA_SATURATION_CNTL,1, + "Task B: Chrominance saturation control"}, + /* 0xd7 - Reserved */ + + /* Task B: Horizontal phase scaling */ + {R_D8_B_HORIZ_LUMA_SCALING_INC,2, + "Task B: Horizontal luminance scaling increment"}, + {R_DA_B_HORIZ_LUMA_PHASE_OFF,1, + "Task B: Horizontal luminance phase offset"}, + /* 0xdb - Reserved */ + {R_DC_B_HORIZ_CHROMA_SCALING,2, + "Task B: Horizontal chrominance scaling"}, + {R_DE_B_HORIZ_PHASE_OFFSET_CRHOMA,1, + "Task B: Horizontal Phase Offset Chroma"}, + /* 0xdf - Reserved */ + + /* Task B: Vertical scaling */ + {R_E0_B_VERT_LUMA_SCALING_INC,2, + "Task B: Vertical luminance scaling increment"}, + {R_E2_B_VERT_CHROMA_SCALING_INC,2, + "Task B: Vertical chrominance scaling increment"}, + {R_E4_B_VERT_SCALING_MODE_CNTL,1, + "Task B: Vertical scaling mode control"}, + /* 0xe5-0xe7 - Reserved */ + {R_E8_B_VERT_CHROMA_PHASE_OFF_00,1, + "Task B: Vertical chrominance phase offset '00'"}, + {R_E9_B_VERT_CHROMA_PHASE_OFF_01,1, + "Task B: Vertical chrominance phase offset '01'"}, + {R_EA_B_VERT_CHROMA_PHASE_OFF_10,1, + "Task B: Vertical chrominance phase offset '10'"}, + {R_EB_B_VERT_CHROMA_PHASE_OFF_11,1, + "Task B: Vertical chrominance phase offset '11'"}, + {R_EC_B_VERT_LUMA_PHASE_OFF_00,1, + "Task B: Vertical luminance phase offset '00'"}, + {R_ED_B_VERT_LUMA_PHASE_OFF_01,1, + "Task B: Vertical luminance phase offset '01'"}, + {R_EE_B_VERT_LUMA_PHASE_OFF_10,1, + "Task B: Vertical luminance phase offset '10'"}, + {R_EF_B_VERT_LUMA_PHASE_OFF_11,1, + "Task B: Vertical luminance phase offset '11'"}, + + /* second PLL (PLL2) and Pulsegenerator Programming */ + { R_F0_LFCO_PER_LINE, 1, + "LFCO's per line"}, + { R_F1_P_I_PARAM_SELECT,1, + "P-/I- Param. Select., PLL Mode, PLL H-Src., LFCO's per line"}, + { R_F2_NOMINAL_PLL2_DTO,1, + "Nominal PLL2 DTO"}, + {R_F3_PLL_INCREMENT,1, + "PLL2 Increment"}, + {R_F4_PLL2_STATUS,1, + "PLL2 Status"}, + {R_F5_PULSGEN_LINE_LENGTH,1, + "Pulsgen. line length"}, + {R_F6_PULSE_A_POS_LSB_AND_PULSEGEN_CONFIG,1, + "Pulse A Position, Pulsgen Resync., Pulsgen. H-Src., Pulsgen. line length"}, + {R_F7_PULSE_A_POS_MSB,1, + "Pulse A Position"}, + {R_F8_PULSE_B_POS,2, + "Pulse B Position"}, + {R_FA_PULSE_C_POS,2, + "Pulse C Position"}, + /* 0xfc to 0xfe - Reserved */ + {R_FF_S_PLL_MAX_PHASE_ERR_THRESH_NUM_LINES,1, + "S_PLL max. phase, error threshold, PLL2 no. of lines, threshold"}, +}; +#endif diff --git a/drivers/media/i2c/saa7127.c b/drivers/media/i2c/saa7127.c new file mode 100644 index 000000000..891192f64 --- /dev/null +++ b/drivers/media/i2c/saa7127.c @@ -0,0 +1,819 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * saa7127 - Philips SAA7127/SAA7129 video encoder driver + * + * Copyright (C) 2003 Roy Bulter <rbulter@hetnet.nl> + * + * Based on SAA7126 video encoder driver by Gillem & Andreas Oberritter + * + * Copyright (C) 2000-2001 Gillem <htoa@gmx.net> + * Copyright (C) 2002 Andreas Oberritter <obi@saftware.de> + * + * Based on Stadis 4:2:2 MPEG-2 Decoder Driver by Nathan Laredo + * + * Copyright (C) 1999 Nathan Laredo <laredo@gnu.org> + * + * This driver is designed for the Hauppauge 250/350 Linux driver + * from the ivtv Project + * + * Copyright (C) 2003 Kevin Thayer <nufan_wfk@yahoo.com> + * + * Dual output support: + * Copyright (C) 2004 Eric Varsanyi + * + * NTSC Tuning and 7.5 IRE Setup + * Copyright (C) 2004 Chris Kennedy <c@groovy.org> + * + * VBI additions & cleanup: + * Copyright (C) 2004, 2005 Hans Verkuil <hverkuil@xs4all.nl> + * + * Note: the saa7126 is identical to the saa7127, and the saa7128 is + * identical to the saa7129, except that the saa7126 and saa7128 have + * macrovision anti-taping support. This driver will almost certainly + * work fine for those chips, except of course for the missing anti-taping + * support. + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/i2c/saa7127.h> + +static int debug; +static int test_image; + +MODULE_DESCRIPTION("Philips SAA7127/9 video encoder driver"); +MODULE_AUTHOR("Kevin Thayer, Chris Kennedy, Hans Verkuil"); +MODULE_LICENSE("GPL"); +module_param(debug, int, 0644); +module_param(test_image, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); +MODULE_PARM_DESC(test_image, "test_image (0-1)"); + + +/* + * SAA7127 registers + */ + +#define SAA7127_REG_STATUS 0x00 +#define SAA7127_REG_WIDESCREEN_CONFIG 0x26 +#define SAA7127_REG_WIDESCREEN_ENABLE 0x27 +#define SAA7127_REG_BURST_START 0x28 +#define SAA7127_REG_BURST_END 0x29 +#define SAA7127_REG_COPYGEN_0 0x2a +#define SAA7127_REG_COPYGEN_1 0x2b +#define SAA7127_REG_COPYGEN_2 0x2c +#define SAA7127_REG_OUTPUT_PORT_CONTROL 0x2d +#define SAA7127_REG_GAIN_LUMINANCE_RGB 0x38 +#define SAA7127_REG_GAIN_COLORDIFF_RGB 0x39 +#define SAA7127_REG_INPUT_PORT_CONTROL_1 0x3a +#define SAA7129_REG_FADE_KEY_COL2 0x4f +#define SAA7127_REG_CHROMA_PHASE 0x5a +#define SAA7127_REG_GAINU 0x5b +#define SAA7127_REG_GAINV 0x5c +#define SAA7127_REG_BLACK_LEVEL 0x5d +#define SAA7127_REG_BLANKING_LEVEL 0x5e +#define SAA7127_REG_VBI_BLANKING 0x5f +#define SAA7127_REG_DAC_CONTROL 0x61 +#define SAA7127_REG_BURST_AMP 0x62 +#define SAA7127_REG_SUBC3 0x63 +#define SAA7127_REG_SUBC2 0x64 +#define SAA7127_REG_SUBC1 0x65 +#define SAA7127_REG_SUBC0 0x66 +#define SAA7127_REG_LINE_21_ODD_0 0x67 +#define SAA7127_REG_LINE_21_ODD_1 0x68 +#define SAA7127_REG_LINE_21_EVEN_0 0x69 +#define SAA7127_REG_LINE_21_EVEN_1 0x6a +#define SAA7127_REG_RCV_PORT_CONTROL 0x6b +#define SAA7127_REG_VTRIG 0x6c +#define SAA7127_REG_HTRIG_HI 0x6d +#define SAA7127_REG_MULTI 0x6e +#define SAA7127_REG_CLOSED_CAPTION 0x6f +#define SAA7127_REG_RCV2_OUTPUT_START 0x70 +#define SAA7127_REG_RCV2_OUTPUT_END 0x71 +#define SAA7127_REG_RCV2_OUTPUT_MSBS 0x72 +#define SAA7127_REG_TTX_REQUEST_H_START 0x73 +#define SAA7127_REG_TTX_REQUEST_H_DELAY_LENGTH 0x74 +#define SAA7127_REG_CSYNC_ADVANCE_VSYNC_SHIFT 0x75 +#define SAA7127_REG_TTX_ODD_REQ_VERT_START 0x76 +#define SAA7127_REG_TTX_ODD_REQ_VERT_END 0x77 +#define SAA7127_REG_TTX_EVEN_REQ_VERT_START 0x78 +#define SAA7127_REG_TTX_EVEN_REQ_VERT_END 0x79 +#define SAA7127_REG_FIRST_ACTIVE 0x7a +#define SAA7127_REG_LAST_ACTIVE 0x7b +#define SAA7127_REG_MSB_VERTICAL 0x7c +#define SAA7127_REG_DISABLE_TTX_LINE_LO_0 0x7e +#define SAA7127_REG_DISABLE_TTX_LINE_LO_1 0x7f + +/* + ********************************************************************** + * + * Arrays with configuration parameters for the SAA7127 + * + ********************************************************************** + */ + +struct i2c_reg_value { + unsigned char reg; + unsigned char value; +}; + +static const struct i2c_reg_value saa7129_init_config_extra[] = { + { SAA7127_REG_OUTPUT_PORT_CONTROL, 0x38 }, + { SAA7127_REG_VTRIG, 0xfa }, + { 0, 0 } +}; + +static const struct i2c_reg_value saa7127_init_config_common[] = { + { SAA7127_REG_WIDESCREEN_CONFIG, 0x0d }, + { SAA7127_REG_WIDESCREEN_ENABLE, 0x00 }, + { SAA7127_REG_COPYGEN_0, 0x77 }, + { SAA7127_REG_COPYGEN_1, 0x41 }, + { SAA7127_REG_COPYGEN_2, 0x00 }, /* Macrovision enable/disable */ + { SAA7127_REG_OUTPUT_PORT_CONTROL, 0xbf }, + { SAA7127_REG_GAIN_LUMINANCE_RGB, 0x00 }, + { SAA7127_REG_GAIN_COLORDIFF_RGB, 0x00 }, + { SAA7127_REG_INPUT_PORT_CONTROL_1, 0x80 }, /* for color bars */ + { SAA7127_REG_LINE_21_ODD_0, 0x77 }, + { SAA7127_REG_LINE_21_ODD_1, 0x41 }, + { SAA7127_REG_LINE_21_EVEN_0, 0x88 }, + { SAA7127_REG_LINE_21_EVEN_1, 0x41 }, + { SAA7127_REG_RCV_PORT_CONTROL, 0x12 }, + { SAA7127_REG_VTRIG, 0xf9 }, + { SAA7127_REG_HTRIG_HI, 0x00 }, + { SAA7127_REG_RCV2_OUTPUT_START, 0x41 }, + { SAA7127_REG_RCV2_OUTPUT_END, 0xc3 }, + { SAA7127_REG_RCV2_OUTPUT_MSBS, 0x00 }, + { SAA7127_REG_TTX_REQUEST_H_START, 0x3e }, + { SAA7127_REG_TTX_REQUEST_H_DELAY_LENGTH, 0xb8 }, + { SAA7127_REG_CSYNC_ADVANCE_VSYNC_SHIFT, 0x03 }, + { SAA7127_REG_TTX_ODD_REQ_VERT_START, 0x15 }, + { SAA7127_REG_TTX_ODD_REQ_VERT_END, 0x16 }, + { SAA7127_REG_TTX_EVEN_REQ_VERT_START, 0x15 }, + { SAA7127_REG_TTX_EVEN_REQ_VERT_END, 0x16 }, + { SAA7127_REG_FIRST_ACTIVE, 0x1a }, + { SAA7127_REG_LAST_ACTIVE, 0x01 }, + { SAA7127_REG_MSB_VERTICAL, 0xc0 }, + { SAA7127_REG_DISABLE_TTX_LINE_LO_0, 0x00 }, + { SAA7127_REG_DISABLE_TTX_LINE_LO_1, 0x00 }, + { 0, 0 } +}; + +#define SAA7127_60HZ_DAC_CONTROL 0x15 +static const struct i2c_reg_value saa7127_init_config_60hz[] = { + { SAA7127_REG_BURST_START, 0x19 }, + /* BURST_END is also used as a chip ID in saa7127_probe */ + { SAA7127_REG_BURST_END, 0x1d }, + { SAA7127_REG_CHROMA_PHASE, 0xa3 }, + { SAA7127_REG_GAINU, 0x98 }, + { SAA7127_REG_GAINV, 0xd3 }, + { SAA7127_REG_BLACK_LEVEL, 0x39 }, + { SAA7127_REG_BLANKING_LEVEL, 0x2e }, + { SAA7127_REG_VBI_BLANKING, 0x2e }, + { SAA7127_REG_DAC_CONTROL, 0x15 }, + { SAA7127_REG_BURST_AMP, 0x4d }, + { SAA7127_REG_SUBC3, 0x1f }, + { SAA7127_REG_SUBC2, 0x7c }, + { SAA7127_REG_SUBC1, 0xf0 }, + { SAA7127_REG_SUBC0, 0x21 }, + { SAA7127_REG_MULTI, 0x90 }, + { SAA7127_REG_CLOSED_CAPTION, 0x11 }, + { 0, 0 } +}; + +#define SAA7127_50HZ_PAL_DAC_CONTROL 0x02 +static struct i2c_reg_value saa7127_init_config_50hz_pal[] = { + { SAA7127_REG_BURST_START, 0x21 }, + /* BURST_END is also used as a chip ID in saa7127_probe */ + { SAA7127_REG_BURST_END, 0x1d }, + { SAA7127_REG_CHROMA_PHASE, 0x3f }, + { SAA7127_REG_GAINU, 0x7d }, + { SAA7127_REG_GAINV, 0xaf }, + { SAA7127_REG_BLACK_LEVEL, 0x33 }, + { SAA7127_REG_BLANKING_LEVEL, 0x35 }, + { SAA7127_REG_VBI_BLANKING, 0x35 }, + { SAA7127_REG_DAC_CONTROL, 0x02 }, + { SAA7127_REG_BURST_AMP, 0x2f }, + { SAA7127_REG_SUBC3, 0xcb }, + { SAA7127_REG_SUBC2, 0x8a }, + { SAA7127_REG_SUBC1, 0x09 }, + { SAA7127_REG_SUBC0, 0x2a }, + { SAA7127_REG_MULTI, 0xa0 }, + { SAA7127_REG_CLOSED_CAPTION, 0x00 }, + { 0, 0 } +}; + +#define SAA7127_50HZ_SECAM_DAC_CONTROL 0x08 +static struct i2c_reg_value saa7127_init_config_50hz_secam[] = { + { SAA7127_REG_BURST_START, 0x21 }, + /* BURST_END is also used as a chip ID in saa7127_probe */ + { SAA7127_REG_BURST_END, 0x1d }, + { SAA7127_REG_CHROMA_PHASE, 0x3f }, + { SAA7127_REG_GAINU, 0x6a }, + { SAA7127_REG_GAINV, 0x81 }, + { SAA7127_REG_BLACK_LEVEL, 0x33 }, + { SAA7127_REG_BLANKING_LEVEL, 0x35 }, + { SAA7127_REG_VBI_BLANKING, 0x35 }, + { SAA7127_REG_DAC_CONTROL, 0x08 }, + { SAA7127_REG_BURST_AMP, 0x2f }, + { SAA7127_REG_SUBC3, 0xb2 }, + { SAA7127_REG_SUBC2, 0x3b }, + { SAA7127_REG_SUBC1, 0xa3 }, + { SAA7127_REG_SUBC0, 0x28 }, + { SAA7127_REG_MULTI, 0x90 }, + { SAA7127_REG_CLOSED_CAPTION, 0x00 }, + { 0, 0 } +}; + +/* + ********************************************************************** + * + * Encoder Struct, holds the configuration state of the encoder + * + ********************************************************************** + */ + +enum saa712x_model { + SAA7127, + SAA7129, +}; + +struct saa7127_state { + struct v4l2_subdev sd; + v4l2_std_id std; + enum saa712x_model ident; + enum saa7127_input_type input_type; + enum saa7127_output_type output_type; + int video_enable; + int wss_enable; + u16 wss_mode; + int cc_enable; + u16 cc_data; + int xds_enable; + u16 xds_data; + int vps_enable; + u8 vps_data[5]; + u8 reg_2d; + u8 reg_3a; + u8 reg_3a_cb; /* colorbar bit */ + u8 reg_61; +}; + +static inline struct saa7127_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct saa7127_state, sd); +} + +static const char * const output_strs[] = +{ + "S-Video + Composite", + "Composite", + "S-Video", + "RGB", + "YUV C", + "YUV V" +}; + +static const char * const wss_strs[] = { + "invalid", + "letterbox 14:9 center", + "letterbox 14:9 top", + "invalid", + "letterbox 16:9 top", + "invalid", + "invalid", + "16:9 full format anamorphic", + "4:3 full format", + "invalid", + "invalid", + "letterbox 16:9 center", + "invalid", + "letterbox >16:9 center", + "14:9 full format center", + "invalid", +}; + +/* ----------------------------------------------------------------------- */ + +static int saa7127_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int i; + + for (i = 0; i < 3; i++) { + if (i2c_smbus_write_byte_data(client, reg, val) == 0) + return 0; + } + v4l2_err(sd, "I2C Write Problem\n"); + return -1; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_write_inittab(struct v4l2_subdev *sd, + const struct i2c_reg_value *regs) +{ + while (regs->reg != 0) { + saa7127_write(sd, regs->reg, regs->value); + regs++; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_vps(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = to_state(sd); + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 16)) + return -EINVAL; + if (state->vps_enable != enable) { + v4l2_dbg(1, debug, sd, "Turn VPS Signal %s\n", enable ? "on" : "off"); + saa7127_write(sd, 0x54, enable << 7); + state->vps_enable = enable; + } + if (!enable) + return 0; + + state->vps_data[0] = data->data[2]; + state->vps_data[1] = data->data[8]; + state->vps_data[2] = data->data[9]; + state->vps_data[3] = data->data[10]; + state->vps_data[4] = data->data[11]; + v4l2_dbg(1, debug, sd, "Set VPS data %*ph\n", 5, state->vps_data); + saa7127_write(sd, 0x55, state->vps_data[0]); + saa7127_write(sd, 0x56, state->vps_data[1]); + saa7127_write(sd, 0x57, state->vps_data[2]); + saa7127_write(sd, 0x58, state->vps_data[3]); + saa7127_write(sd, 0x59, state->vps_data[4]); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_cc(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = to_state(sd); + u16 cc = data->data[1] << 8 | data->data[0]; + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 21)) + return -EINVAL; + if (state->cc_enable != enable) { + v4l2_dbg(1, debug, sd, + "Turn CC %s\n", enable ? "on" : "off"); + saa7127_write(sd, SAA7127_REG_CLOSED_CAPTION, + (state->xds_enable << 7) | (enable << 6) | 0x11); + state->cc_enable = enable; + } + if (!enable) + return 0; + + v4l2_dbg(2, debug, sd, "CC data: %04x\n", cc); + saa7127_write(sd, SAA7127_REG_LINE_21_ODD_0, cc & 0xff); + saa7127_write(sd, SAA7127_REG_LINE_21_ODD_1, cc >> 8); + state->cc_data = cc; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_xds(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = to_state(sd); + u16 xds = data->data[1] << 8 | data->data[0]; + int enable = (data->line != 0); + + if (enable && (data->field != 1 || data->line != 21)) + return -EINVAL; + if (state->xds_enable != enable) { + v4l2_dbg(1, debug, sd, "Turn XDS %s\n", enable ? "on" : "off"); + saa7127_write(sd, SAA7127_REG_CLOSED_CAPTION, + (enable << 7) | (state->cc_enable << 6) | 0x11); + state->xds_enable = enable; + } + if (!enable) + return 0; + + v4l2_dbg(2, debug, sd, "XDS data: %04x\n", xds); + saa7127_write(sd, SAA7127_REG_LINE_21_EVEN_0, xds & 0xff); + saa7127_write(sd, SAA7127_REG_LINE_21_EVEN_1, xds >> 8); + state->xds_data = xds; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_wss(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = to_state(sd); + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 23)) + return -EINVAL; + if (state->wss_enable != enable) { + v4l2_dbg(1, debug, sd, "Turn WSS %s\n", enable ? "on" : "off"); + saa7127_write(sd, 0x27, enable << 7); + state->wss_enable = enable; + } + if (!enable) + return 0; + + saa7127_write(sd, 0x26, data->data[0]); + saa7127_write(sd, 0x27, 0x80 | (data->data[1] & 0x3f)); + v4l2_dbg(1, debug, sd, + "WSS mode: %s\n", wss_strs[data->data[0] & 0xf]); + state->wss_mode = (data->data[1] & 0x3f) << 8 | data->data[0]; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_video_enable(struct v4l2_subdev *sd, int enable) +{ + struct saa7127_state *state = to_state(sd); + + if (enable) { + v4l2_dbg(1, debug, sd, "Enable Video Output\n"); + saa7127_write(sd, 0x2d, state->reg_2d); + saa7127_write(sd, 0x61, state->reg_61); + } else { + v4l2_dbg(1, debug, sd, "Disable Video Output\n"); + saa7127_write(sd, 0x2d, (state->reg_2d & 0xf0)); + saa7127_write(sd, 0x61, (state->reg_61 | 0xc0)); + } + state->video_enable = enable; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa7127_state *state = to_state(sd); + const struct i2c_reg_value *inittab; + + if (std & V4L2_STD_525_60) { + v4l2_dbg(1, debug, sd, "Selecting 60 Hz video Standard\n"); + inittab = saa7127_init_config_60hz; + state->reg_61 = SAA7127_60HZ_DAC_CONTROL; + + } else if (state->ident == SAA7129 && + (std & V4L2_STD_SECAM) && + !(std & (V4L2_STD_625_50 & ~V4L2_STD_SECAM))) { + + /* If and only if SECAM, with a SAA712[89] */ + v4l2_dbg(1, debug, sd, + "Selecting 50 Hz SECAM video Standard\n"); + inittab = saa7127_init_config_50hz_secam; + state->reg_61 = SAA7127_50HZ_SECAM_DAC_CONTROL; + + } else { + v4l2_dbg(1, debug, sd, "Selecting 50 Hz PAL video Standard\n"); + inittab = saa7127_init_config_50hz_pal; + state->reg_61 = SAA7127_50HZ_PAL_DAC_CONTROL; + } + + /* Write Table */ + saa7127_write_inittab(sd, inittab); + state->std = std; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_output_type(struct v4l2_subdev *sd, int output) +{ + struct saa7127_state *state = to_state(sd); + + switch (output) { + case SAA7127_OUTPUT_TYPE_RGB: + state->reg_2d = 0x0f; /* RGB + CVBS (for sync) */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_COMPOSITE: + if (state->ident == SAA7129) + state->reg_2d = 0x20; /* CVBS only */ + else + state->reg_2d = 0x08; /* 00001000 CVBS only, RGB DAC's off (high impedance mode) */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_SVIDEO: + if (state->ident == SAA7129) + state->reg_2d = 0x18; /* Y + C */ + else + state->reg_2d = 0xff; /*11111111 croma -> R, luma -> CVBS + G + B */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_YUV_V: + state->reg_2d = 0x4f; /* reg 2D = 01001111, all DAC's on, RGB + VBS */ + state->reg_3a = 0x0b; /* reg 3A = 00001011, bypass RGB-matrix */ + break; + + case SAA7127_OUTPUT_TYPE_YUV_C: + state->reg_2d = 0x0f; /* reg 2D = 00001111, all DAC's on, RGB + CVBS */ + state->reg_3a = 0x0b; /* reg 3A = 00001011, bypass RGB-matrix */ + break; + + case SAA7127_OUTPUT_TYPE_BOTH: + if (state->ident == SAA7129) + state->reg_2d = 0x38; + else + state->reg_2d = 0xbf; + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + default: + return -EINVAL; + } + v4l2_dbg(1, debug, sd, + "Selecting %s output type\n", output_strs[output]); + + /* Configure Encoder */ + saa7127_write(sd, 0x2d, state->reg_2d); + saa7127_write(sd, 0x3a, state->reg_3a | state->reg_3a_cb); + state->output_type = output; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_input_type(struct v4l2_subdev *sd, int input) +{ + struct saa7127_state *state = to_state(sd); + + switch (input) { + case SAA7127_INPUT_TYPE_NORMAL: /* avia */ + v4l2_dbg(1, debug, sd, "Selecting Normal Encoder Input\n"); + state->reg_3a_cb = 0; + break; + + case SAA7127_INPUT_TYPE_TEST_IMAGE: /* color bar */ + v4l2_dbg(1, debug, sd, "Selecting Color Bar generator\n"); + state->reg_3a_cb = 0x80; + break; + + default: + return -EINVAL; + } + saa7127_write(sd, 0x3a, state->reg_3a | state->reg_3a_cb); + state->input_type = input; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa7127_state *state = to_state(sd); + + if (state->std == std) + return 0; + return saa7127_set_std(sd, std); +} + +static int saa7127_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct saa7127_state *state = to_state(sd); + int rc = 0; + + if (state->input_type != input) + rc = saa7127_set_input_type(sd, input); + if (rc == 0 && state->output_type != output) + rc = saa7127_set_output_type(sd, output); + return rc; +} + +static int saa7127_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct saa7127_state *state = to_state(sd); + + if (state->video_enable == enable) + return 0; + return saa7127_set_video_enable(sd, enable); +} + +static int saa7127_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *fmt) +{ + struct saa7127_state *state = to_state(sd); + + memset(fmt->service_lines, 0, sizeof(fmt->service_lines)); + if (state->vps_enable) + fmt->service_lines[0][16] = V4L2_SLICED_VPS; + if (state->wss_enable) + fmt->service_lines[0][23] = V4L2_SLICED_WSS_625; + if (state->cc_enable) { + fmt->service_lines[0][21] = V4L2_SLICED_CAPTION_525; + fmt->service_lines[1][21] = V4L2_SLICED_CAPTION_525; + } + fmt->service_set = + (state->vps_enable ? V4L2_SLICED_VPS : 0) | + (state->wss_enable ? V4L2_SLICED_WSS_625 : 0) | + (state->cc_enable ? V4L2_SLICED_CAPTION_525 : 0); + return 0; +} + +static int saa7127_s_vbi_data(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *data) +{ + switch (data->id) { + case V4L2_SLICED_WSS_625: + return saa7127_set_wss(sd, data); + case V4L2_SLICED_VPS: + return saa7127_set_vps(sd, data); + case V4L2_SLICED_CAPTION_525: + if (data->field == 0) + return saa7127_set_cc(sd, data); + return saa7127_set_xds(sd, data); + default: + return -EINVAL; + } + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int saa7127_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->val = saa7127_read(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int saa7127_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + saa7127_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static int saa7127_log_status(struct v4l2_subdev *sd) +{ + struct saa7127_state *state = to_state(sd); + + v4l2_info(sd, "Standard: %s\n", (state->std & V4L2_STD_525_60) ? "60 Hz" : "50 Hz"); + v4l2_info(sd, "Input: %s\n", state->input_type ? "color bars" : "normal"); + v4l2_info(sd, "Output: %s\n", state->video_enable ? + output_strs[state->output_type] : "disabled"); + v4l2_info(sd, "WSS: %s\n", state->wss_enable ? + wss_strs[state->wss_mode] : "disabled"); + v4l2_info(sd, "VPS: %s\n", state->vps_enable ? "enabled" : "disabled"); + v4l2_info(sd, "CC: %s\n", state->cc_enable ? "enabled" : "disabled"); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops saa7127_core_ops = { + .log_status = saa7127_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = saa7127_g_register, + .s_register = saa7127_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops saa7127_video_ops = { + .s_std_output = saa7127_s_std_output, + .s_routing = saa7127_s_routing, + .s_stream = saa7127_s_stream, +}; + +static const struct v4l2_subdev_vbi_ops saa7127_vbi_ops = { + .s_vbi_data = saa7127_s_vbi_data, + .g_sliced_fmt = saa7127_g_sliced_fmt, +}; + +static const struct v4l2_subdev_ops saa7127_ops = { + .core = &saa7127_core_ops, + .video = &saa7127_video_ops, + .vbi = &saa7127_vbi_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int saa7127_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct saa7127_state *state; + struct v4l2_subdev *sd; + struct v4l2_sliced_vbi_data vbi = { 0, 0, 0, 0 }; /* set to disabled */ + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_dbg(1, debug, client, "detecting saa7127 client on address 0x%x\n", + client->addr << 1); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &saa7127_ops); + + /* First test register 0: Bits 5-7 are a version ID (should be 0), + and bit 2 should also be 0. + This is rather general, so the second test is more specific and + looks at the 'ending point of burst in clock cycles' which is + 0x1d after a reset and not expected to ever change. */ + if ((saa7127_read(sd, 0) & 0xe4) != 0 || + (saa7127_read(sd, 0x29) & 0x3f) != 0x1d) { + v4l2_dbg(1, debug, sd, "saa7127 not found\n"); + return -ENODEV; + } + + if (id->driver_data) { /* Chip type is already known */ + state->ident = id->driver_data; + } else { /* Needs detection */ + int read_result; + + /* Detect if it's an saa7129 */ + read_result = saa7127_read(sd, SAA7129_REG_FADE_KEY_COL2); + saa7127_write(sd, SAA7129_REG_FADE_KEY_COL2, 0xaa); + if (saa7127_read(sd, SAA7129_REG_FADE_KEY_COL2) == 0xaa) { + saa7127_write(sd, SAA7129_REG_FADE_KEY_COL2, + read_result); + state->ident = SAA7129; + strscpy(client->name, "saa7129", I2C_NAME_SIZE); + } else { + state->ident = SAA7127; + strscpy(client->name, "saa7127", I2C_NAME_SIZE); + } + } + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + v4l2_dbg(1, debug, sd, "Configuring encoder\n"); + saa7127_write_inittab(sd, saa7127_init_config_common); + saa7127_set_std(sd, V4L2_STD_NTSC); + saa7127_set_output_type(sd, SAA7127_OUTPUT_TYPE_BOTH); + saa7127_set_vps(sd, &vbi); + saa7127_set_wss(sd, &vbi); + saa7127_set_cc(sd, &vbi); + saa7127_set_xds(sd, &vbi); + if (test_image == 1) + /* The Encoder has an internal Colorbar generator */ + /* This can be used for debugging */ + saa7127_set_input_type(sd, SAA7127_INPUT_TYPE_TEST_IMAGE); + else + saa7127_set_input_type(sd, SAA7127_INPUT_TYPE_NORMAL); + saa7127_set_video_enable(sd, 1); + + if (state->ident == SAA7129) + saa7127_write_inittab(sd, saa7129_init_config_extra); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + /* Turn off TV output */ + saa7127_set_video_enable(sd, 0); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id saa7127_id[] = { + { "saa7127_auto", 0 }, /* auto-detection */ + { "saa7126", SAA7127 }, + { "saa7127", SAA7127 }, + { "saa7128", SAA7129 }, + { "saa7129", SAA7129 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, saa7127_id); + +static struct i2c_driver saa7127_driver = { + .driver = { + .name = "saa7127", + }, + .probe = saa7127_probe, + .remove = saa7127_remove, + .id_table = saa7127_id, +}; + +module_i2c_driver(saa7127_driver); diff --git a/drivers/media/i2c/saa717x.c b/drivers/media/i2c/saa717x.c new file mode 100644 index 000000000..ba103a6a1 --- /dev/null +++ b/drivers/media/i2c/saa717x.c @@ -0,0 +1,1353 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * saa717x - Philips SAA717xHL video decoder driver + * + * Based on the saa7115 driver + * + * Changes by Ohta Kyuma <alpha292@bremen.or.jp> + * - Apply to SAA717x,NEC uPD64031,uPD64083. (1/31/2004) + * + * Changes by T.Adachi (tadachi@tadachi-net.com) + * - support audio, video scaler etc, and checked the initialize sequence. + * + * Cleaned up by Hans Verkuil <hverkuil@xs4all.nl> + * + * Note: this is a reversed engineered driver based on captures from + * the I2C bus under Windows. This chip is very similar to the saa7134, + * though. Unfortunately, this driver is currently only working for NTSC. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sched.h> + +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +MODULE_DESCRIPTION("Philips SAA717x audio/video decoder driver"); +MODULE_AUTHOR("K. Ohta, T. Adachi, Hans Verkuil"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ + +struct saa717x_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + v4l2_std_id std; + int input; + int enable; + int radio; + int playback; + int audio; + int tuner_audio_mode; + int audio_main_mute; + int audio_main_vol_r; + int audio_main_vol_l; + u16 audio_main_bass; + u16 audio_main_treble; + u16 audio_main_volume; + u16 audio_main_balance; + int audio_input; +}; + +static inline struct saa717x_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct saa717x_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct saa717x_state, hdl)->sd; +} + +/* ----------------------------------------------------------------------- */ + +/* for audio mode */ +#define TUNER_AUDIO_MONO 0 /* LL */ +#define TUNER_AUDIO_STEREO 1 /* LR */ +#define TUNER_AUDIO_LANG1 2 /* LL */ +#define TUNER_AUDIO_LANG2 3 /* RR */ + +#define SAA717X_NTSC_WIDTH (704) +#define SAA717X_NTSC_HEIGHT (480) + +/* ----------------------------------------------------------------------- */ + +static int saa717x_write(struct v4l2_subdev *sd, u32 reg, u32 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct i2c_adapter *adap = client->adapter; + int fw_addr = reg == 0x454 || (reg >= 0x464 && reg <= 0x478) || reg == 0x480 || reg == 0x488; + unsigned char mm1[6]; + struct i2c_msg msg; + + msg.flags = 0; + msg.addr = client->addr; + mm1[0] = (reg >> 8) & 0xff; + mm1[1] = reg & 0xff; + + if (fw_addr) { + mm1[4] = (value >> 16) & 0xff; + mm1[3] = (value >> 8) & 0xff; + mm1[2] = value & 0xff; + } else { + mm1[2] = value & 0xff; + } + msg.len = fw_addr ? 5 : 3; /* Long Registers have *only* three bytes! */ + msg.buf = mm1; + v4l2_dbg(2, debug, sd, "wrote: reg 0x%03x=%08x\n", reg, value); + return i2c_transfer(adap, &msg, 1) == 1; +} + +static void saa717x_write_regs(struct v4l2_subdev *sd, u32 *data) +{ + while (data[0] || data[1]) { + saa717x_write(sd, data[0], data[1]); + data += 2; + } +} + +static u32 saa717x_read(struct v4l2_subdev *sd, u32 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct i2c_adapter *adap = client->adapter; + int fw_addr = (reg >= 0x404 && reg <= 0x4b8) || reg == 0x528; + unsigned char mm1[2]; + unsigned char mm2[4] = { 0, 0, 0, 0 }; + struct i2c_msg msgs[2]; + u32 value; + + msgs[0].flags = 0; + msgs[1].flags = I2C_M_RD; + msgs[0].addr = msgs[1].addr = client->addr; + mm1[0] = (reg >> 8) & 0xff; + mm1[1] = reg & 0xff; + msgs[0].len = 2; + msgs[0].buf = mm1; + msgs[1].len = fw_addr ? 3 : 1; /* Multibyte Registers contains *only* 3 bytes */ + msgs[1].buf = mm2; + i2c_transfer(adap, msgs, 2); + + if (fw_addr) + value = (mm2[2] << 16) | (mm2[1] << 8) | mm2[0]; + else + value = mm2[0]; + + v4l2_dbg(2, debug, sd, "read: reg 0x%03x=0x%08x\n", reg, value); + return value; +} + +/* ----------------------------------------------------------------------- */ + +static u32 reg_init_initialize[] = +{ + /* from linux driver */ + 0x101, 0x008, /* Increment delay */ + + 0x103, 0x000, /* Analog input control 2 */ + 0x104, 0x090, /* Analog input control 3 */ + 0x105, 0x090, /* Analog input control 4 */ + 0x106, 0x0eb, /* Horizontal sync start */ + 0x107, 0x0e0, /* Horizontal sync stop */ + 0x109, 0x055, /* Luminance control */ + + 0x10f, 0x02a, /* Chroma gain control */ + 0x110, 0x000, /* Chroma control 2 */ + + 0x114, 0x045, /* analog/ADC */ + + 0x118, 0x040, /* RAW data gain */ + 0x119, 0x080, /* RAW data offset */ + + 0x044, 0x000, /* VBI horizontal input window start (L) TASK A */ + 0x045, 0x000, /* VBI horizontal input window start (H) TASK A */ + 0x046, 0x0cf, /* VBI horizontal input window stop (L) TASK A */ + 0x047, 0x002, /* VBI horizontal input window stop (H) TASK A */ + + 0x049, 0x000, /* VBI vertical input window start (H) TASK A */ + + 0x04c, 0x0d0, /* VBI horizontal output length (L) TASK A */ + 0x04d, 0x002, /* VBI horizontal output length (H) TASK A */ + + 0x064, 0x080, /* Lumina brightness TASK A */ + 0x065, 0x040, /* Luminance contrast TASK A */ + 0x066, 0x040, /* Chroma saturation TASK A */ + /* 067H: Reserved */ + 0x068, 0x000, /* VBI horizontal scaling increment (L) TASK A */ + 0x069, 0x004, /* VBI horizontal scaling increment (H) TASK A */ + 0x06a, 0x000, /* VBI phase offset TASK A */ + + 0x06e, 0x000, /* Horizontal phase offset Luma TASK A */ + 0x06f, 0x000, /* Horizontal phase offset Chroma TASK A */ + + 0x072, 0x000, /* Vertical filter mode TASK A */ + + 0x084, 0x000, /* VBI horizontal input window start (L) TAKS B */ + 0x085, 0x000, /* VBI horizontal input window start (H) TAKS B */ + 0x086, 0x0cf, /* VBI horizontal input window stop (L) TAKS B */ + 0x087, 0x002, /* VBI horizontal input window stop (H) TAKS B */ + + 0x089, 0x000, /* VBI vertical input window start (H) TAKS B */ + + 0x08c, 0x0d0, /* VBI horizontal output length (L) TASK B */ + 0x08d, 0x002, /* VBI horizontal output length (H) TASK B */ + + 0x0a4, 0x080, /* Lumina brightness TASK B */ + 0x0a5, 0x040, /* Luminance contrast TASK B */ + 0x0a6, 0x040, /* Chroma saturation TASK B */ + /* 0A7H reserved */ + 0x0a8, 0x000, /* VBI horizontal scaling increment (L) TASK B */ + 0x0a9, 0x004, /* VBI horizontal scaling increment (H) TASK B */ + 0x0aa, 0x000, /* VBI phase offset TASK B */ + + 0x0ae, 0x000, /* Horizontal phase offset Luma TASK B */ + 0x0af, 0x000, /*Horizontal phase offset Chroma TASK B */ + + 0x0b2, 0x000, /* Vertical filter mode TASK B */ + + 0x00c, 0x000, /* Start point GREEN path */ + 0x00d, 0x000, /* Start point BLUE path */ + 0x00e, 0x000, /* Start point RED path */ + + 0x010, 0x010, /* GREEN path gamma curve --- */ + 0x011, 0x020, + 0x012, 0x030, + 0x013, 0x040, + 0x014, 0x050, + 0x015, 0x060, + 0x016, 0x070, + 0x017, 0x080, + 0x018, 0x090, + 0x019, 0x0a0, + 0x01a, 0x0b0, + 0x01b, 0x0c0, + 0x01c, 0x0d0, + 0x01d, 0x0e0, + 0x01e, 0x0f0, + 0x01f, 0x0ff, /* --- GREEN path gamma curve */ + + 0x020, 0x010, /* BLUE path gamma curve --- */ + 0x021, 0x020, + 0x022, 0x030, + 0x023, 0x040, + 0x024, 0x050, + 0x025, 0x060, + 0x026, 0x070, + 0x027, 0x080, + 0x028, 0x090, + 0x029, 0x0a0, + 0x02a, 0x0b0, + 0x02b, 0x0c0, + 0x02c, 0x0d0, + 0x02d, 0x0e0, + 0x02e, 0x0f0, + 0x02f, 0x0ff, /* --- BLUE path gamma curve */ + + 0x030, 0x010, /* RED path gamma curve --- */ + 0x031, 0x020, + 0x032, 0x030, + 0x033, 0x040, + 0x034, 0x050, + 0x035, 0x060, + 0x036, 0x070, + 0x037, 0x080, + 0x038, 0x090, + 0x039, 0x0a0, + 0x03a, 0x0b0, + 0x03b, 0x0c0, + 0x03c, 0x0d0, + 0x03d, 0x0e0, + 0x03e, 0x0f0, + 0x03f, 0x0ff, /* --- RED path gamma curve */ + + 0x109, 0x085, /* Luminance control */ + + /**** from app start ****/ + 0x584, 0x000, /* AGC gain control */ + 0x585, 0x000, /* Program count */ + 0x586, 0x003, /* Status reset */ + 0x588, 0x0ff, /* Number of audio samples (L) */ + 0x589, 0x00f, /* Number of audio samples (M) */ + 0x58a, 0x000, /* Number of audio samples (H) */ + 0x58b, 0x000, /* Audio select */ + 0x58c, 0x010, /* Audio channel assign1 */ + 0x58d, 0x032, /* Audio channel assign2 */ + 0x58e, 0x054, /* Audio channel assign3 */ + 0x58f, 0x023, /* Audio format */ + 0x590, 0x000, /* SIF control */ + + 0x595, 0x000, /* ?? */ + 0x596, 0x000, /* ?? */ + 0x597, 0x000, /* ?? */ + + 0x464, 0x00, /* Digital input crossbar1 */ + + 0x46c, 0xbbbb10, /* Digital output selection1-3 */ + 0x470, 0x101010, /* Digital output selection4-6 */ + + 0x478, 0x00, /* Sound feature control */ + + 0x474, 0x18, /* Softmute control */ + + 0x454, 0x0425b9, /* Sound Easy programming(reset) */ + 0x454, 0x042539, /* Sound Easy programming(reset) */ + + + /**** common setting( of DVD play, including scaler commands) ****/ + 0x042, 0x003, /* Data path configuration for VBI (TASK A) */ + + 0x082, 0x003, /* Data path configuration for VBI (TASK B) */ + + 0x108, 0x0f8, /* Sync control */ + 0x2a9, 0x0fd, /* ??? */ + 0x102, 0x089, /* select video input "mode 9" */ + 0x111, 0x000, /* Mode/delay control */ + + 0x10e, 0x00a, /* Chroma control 1 */ + + 0x594, 0x002, /* SIF, analog I/O select */ + + 0x454, 0x0425b9, /* Sound */ + 0x454, 0x042539, + + 0x111, 0x000, + 0x10e, 0x00a, + 0x464, 0x000, + 0x300, 0x000, + 0x301, 0x006, + 0x302, 0x000, + 0x303, 0x006, + 0x308, 0x040, + 0x309, 0x000, + 0x30a, 0x000, + 0x30b, 0x000, + 0x000, 0x002, + 0x001, 0x000, + 0x002, 0x000, + 0x003, 0x000, + 0x004, 0x033, + 0x040, 0x01d, + 0x041, 0x001, + 0x042, 0x004, + 0x043, 0x000, + 0x080, 0x01e, + 0x081, 0x001, + 0x082, 0x004, + 0x083, 0x000, + 0x190, 0x018, + 0x115, 0x000, + 0x116, 0x012, + 0x117, 0x018, + 0x04a, 0x011, + 0x08a, 0x011, + 0x04b, 0x000, + 0x08b, 0x000, + 0x048, 0x000, + 0x088, 0x000, + 0x04e, 0x012, + 0x08e, 0x012, + 0x058, 0x012, + 0x098, 0x012, + 0x059, 0x000, + 0x099, 0x000, + 0x05a, 0x003, + 0x09a, 0x003, + 0x05b, 0x001, + 0x09b, 0x001, + 0x054, 0x008, + 0x094, 0x008, + 0x055, 0x000, + 0x095, 0x000, + 0x056, 0x0c7, + 0x096, 0x0c7, + 0x057, 0x002, + 0x097, 0x002, + 0x0ff, 0x0ff, + 0x060, 0x001, + 0x0a0, 0x001, + 0x061, 0x000, + 0x0a1, 0x000, + 0x062, 0x000, + 0x0a2, 0x000, + 0x063, 0x000, + 0x0a3, 0x000, + 0x070, 0x000, + 0x0b0, 0x000, + 0x071, 0x004, + 0x0b1, 0x004, + 0x06c, 0x0e9, + 0x0ac, 0x0e9, + 0x06d, 0x003, + 0x0ad, 0x003, + 0x05c, 0x0d0, + 0x09c, 0x0d0, + 0x05d, 0x002, + 0x09d, 0x002, + 0x05e, 0x0f2, + 0x09e, 0x0f2, + 0x05f, 0x000, + 0x09f, 0x000, + 0x074, 0x000, + 0x0b4, 0x000, + 0x075, 0x000, + 0x0b5, 0x000, + 0x076, 0x000, + 0x0b6, 0x000, + 0x077, 0x000, + 0x0b7, 0x000, + 0x195, 0x008, + 0x0ff, 0x0ff, + 0x108, 0x0f8, + 0x111, 0x000, + 0x10e, 0x00a, + 0x2a9, 0x0fd, + 0x464, 0x001, + 0x454, 0x042135, + 0x598, 0x0e7, + 0x599, 0x07d, + 0x59a, 0x018, + 0x59c, 0x066, + 0x59d, 0x090, + 0x59e, 0x001, + 0x584, 0x000, + 0x585, 0x000, + 0x586, 0x003, + 0x588, 0x0ff, + 0x589, 0x00f, + 0x58a, 0x000, + 0x58b, 0x000, + 0x58c, 0x010, + 0x58d, 0x032, + 0x58e, 0x054, + 0x58f, 0x023, + 0x590, 0x000, + 0x595, 0x000, + 0x596, 0x000, + 0x597, 0x000, + 0x464, 0x000, + 0x46c, 0xbbbb10, + 0x470, 0x101010, + + + 0x478, 0x000, + 0x474, 0x018, + 0x454, 0x042135, + 0x598, 0x0e7, + 0x599, 0x07d, + 0x59a, 0x018, + 0x59c, 0x066, + 0x59d, 0x090, + 0x59e, 0x001, + 0x584, 0x000, + 0x585, 0x000, + 0x586, 0x003, + 0x588, 0x0ff, + 0x589, 0x00f, + 0x58a, 0x000, + 0x58b, 0x000, + 0x58c, 0x010, + 0x58d, 0x032, + 0x58e, 0x054, + 0x58f, 0x023, + 0x590, 0x000, + 0x595, 0x000, + 0x596, 0x000, + 0x597, 0x000, + 0x464, 0x000, + 0x46c, 0xbbbb10, + 0x470, 0x101010, + + 0x478, 0x000, + 0x474, 0x018, + 0x454, 0x042135, + 0x598, 0x0e7, + 0x599, 0x07d, + 0x59a, 0x018, + 0x59c, 0x066, + 0x59d, 0x090, + 0x59e, 0x001, + 0x584, 0x000, + 0x585, 0x000, + 0x586, 0x003, + 0x588, 0x0ff, + 0x589, 0x00f, + 0x58a, 0x000, + 0x58b, 0x000, + 0x58c, 0x010, + 0x58d, 0x032, + 0x58e, 0x054, + 0x58f, 0x023, + 0x590, 0x000, + 0x595, 0x000, + 0x596, 0x000, + 0x597, 0x000, + 0x464, 0x000, + 0x46c, 0xbbbb10, + 0x470, 0x101010, + 0x478, 0x000, + 0x474, 0x018, + 0x454, 0x042135, + 0x193, 0x000, + 0x300, 0x000, + 0x301, 0x006, + 0x302, 0x000, + 0x303, 0x006, + 0x308, 0x040, + 0x309, 0x000, + 0x30a, 0x000, + 0x30b, 0x000, + 0x000, 0x002, + 0x001, 0x000, + 0x002, 0x000, + 0x003, 0x000, + 0x004, 0x033, + 0x040, 0x01d, + 0x041, 0x001, + 0x042, 0x004, + 0x043, 0x000, + 0x080, 0x01e, + 0x081, 0x001, + 0x082, 0x004, + 0x083, 0x000, + 0x190, 0x018, + 0x115, 0x000, + 0x116, 0x012, + 0x117, 0x018, + 0x04a, 0x011, + 0x08a, 0x011, + 0x04b, 0x000, + 0x08b, 0x000, + 0x048, 0x000, + 0x088, 0x000, + 0x04e, 0x012, + 0x08e, 0x012, + 0x058, 0x012, + 0x098, 0x012, + 0x059, 0x000, + 0x099, 0x000, + 0x05a, 0x003, + 0x09a, 0x003, + 0x05b, 0x001, + 0x09b, 0x001, + 0x054, 0x008, + 0x094, 0x008, + 0x055, 0x000, + 0x095, 0x000, + 0x056, 0x0c7, + 0x096, 0x0c7, + 0x057, 0x002, + 0x097, 0x002, + 0x060, 0x001, + 0x0a0, 0x001, + 0x061, 0x000, + 0x0a1, 0x000, + 0x062, 0x000, + 0x0a2, 0x000, + 0x063, 0x000, + 0x0a3, 0x000, + 0x070, 0x000, + 0x0b0, 0x000, + 0x071, 0x004, + 0x0b1, 0x004, + 0x06c, 0x0e9, + 0x0ac, 0x0e9, + 0x06d, 0x003, + 0x0ad, 0x003, + 0x05c, 0x0d0, + 0x09c, 0x0d0, + 0x05d, 0x002, + 0x09d, 0x002, + 0x05e, 0x0f2, + 0x09e, 0x0f2, + 0x05f, 0x000, + 0x09f, 0x000, + 0x074, 0x000, + 0x0b4, 0x000, + 0x075, 0x000, + 0x0b5, 0x000, + 0x076, 0x000, + 0x0b6, 0x000, + 0x077, 0x000, + 0x0b7, 0x000, + 0x195, 0x008, + 0x598, 0x0e7, + 0x599, 0x07d, + 0x59a, 0x018, + 0x59c, 0x066, + 0x59d, 0x090, + 0x59e, 0x001, + 0x584, 0x000, + 0x585, 0x000, + 0x586, 0x003, + 0x588, 0x0ff, + 0x589, 0x00f, + 0x58a, 0x000, + 0x58b, 0x000, + 0x58c, 0x010, + 0x58d, 0x032, + 0x58e, 0x054, + 0x58f, 0x023, + 0x590, 0x000, + 0x595, 0x000, + 0x596, 0x000, + 0x597, 0x000, + 0x464, 0x000, + 0x46c, 0xbbbb10, + 0x470, 0x101010, + 0x478, 0x000, + 0x474, 0x018, + 0x454, 0x042135, + 0x193, 0x0a6, + 0x108, 0x0f8, + 0x042, 0x003, + 0x082, 0x003, + 0x454, 0x0425b9, + 0x454, 0x042539, + 0x193, 0x000, + 0x193, 0x0a6, + 0x464, 0x000, + + 0, 0 +}; + +/* Tuner */ +static u32 reg_init_tuner_input[] = { + 0x108, 0x0f8, /* Sync control */ + 0x111, 0x000, /* Mode/delay control */ + 0x10e, 0x00a, /* Chroma control 1 */ + 0, 0 +}; + +/* Composite */ +static u32 reg_init_composite_input[] = { + 0x108, 0x0e8, /* Sync control */ + 0x111, 0x000, /* Mode/delay control */ + 0x10e, 0x04a, /* Chroma control 1 */ + 0, 0 +}; + +/* S-Video */ +static u32 reg_init_svideo_input[] = { + 0x108, 0x0e8, /* Sync control */ + 0x111, 0x000, /* Mode/delay control */ + 0x10e, 0x04a, /* Chroma control 1 */ + 0, 0 +}; + +static u32 reg_set_audio_template[4][2] = +{ + { /* for MONO + tadachi 6/29 DMA audio output select? + Register 0x46c + 7-4: DMA2, 3-0: DMA1 ch. DMA4, DMA3 DMA2, DMA1 + 0: MAIN left, 1: MAIN right + 2: AUX1 left, 3: AUX1 right + 4: AUX2 left, 5: AUX2 right + 6: DPL left, 7: DPL right + 8: DPL center, 9: DPL surround + A: monitor output, B: digital sense */ + 0xbbbb00, + + /* tadachi 6/29 DAC and I2S output select? + Register 0x470 + 7-4:DAC right ch. 3-0:DAC left ch. + I2S1 right,left I2S2 right,left */ + 0x00, + }, + { /* for STEREO */ + 0xbbbb10, 0x101010, + }, + { /* for LANG1 */ + 0xbbbb00, 0x00, + }, + { /* for LANG2/SAP */ + 0xbbbb11, 0x111111, + } +}; + + +/* Get detected audio flags (from saa7134 driver) */ +static void get_inf_dev_status(struct v4l2_subdev *sd, + int *dual_flag, int *stereo_flag) +{ + u32 reg_data3; + + static char *stdres[0x20] = { + [0x00] = "no standard detected", + [0x01] = "B/G (in progress)", + [0x02] = "D/K (in progress)", + [0x03] = "M (in progress)", + + [0x04] = "B/G A2", + [0x05] = "B/G NICAM", + [0x06] = "D/K A2 (1)", + [0x07] = "D/K A2 (2)", + [0x08] = "D/K A2 (3)", + [0x09] = "D/K NICAM", + [0x0a] = "L NICAM", + [0x0b] = "I NICAM", + + [0x0c] = "M Korea", + [0x0d] = "M BTSC ", + [0x0e] = "M EIAJ", + + [0x0f] = "FM radio / IF 10.7 / 50 deemp", + [0x10] = "FM radio / IF 10.7 / 75 deemp", + [0x11] = "FM radio / IF sel / 50 deemp", + [0x12] = "FM radio / IF sel / 75 deemp", + + [0x13 ... 0x1e] = "unknown", + [0x1f] = "??? [in progress]", + }; + + + *dual_flag = *stereo_flag = 0; + + /* (demdec status: 0x528) */ + + /* read current status */ + reg_data3 = saa717x_read(sd, 0x0528); + + v4l2_dbg(1, debug, sd, "tvaudio thread status: 0x%x [%s%s%s]\n", + reg_data3, stdres[reg_data3 & 0x1f], + (reg_data3 & 0x000020) ? ",stereo" : "", + (reg_data3 & 0x000040) ? ",dual" : ""); + v4l2_dbg(1, debug, sd, "detailed status: " + "%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s\n", + (reg_data3 & 0x000080) ? " A2/EIAJ pilot tone " : "", + (reg_data3 & 0x000100) ? " A2/EIAJ dual " : "", + (reg_data3 & 0x000200) ? " A2/EIAJ stereo " : "", + (reg_data3 & 0x000400) ? " A2/EIAJ noise mute " : "", + + (reg_data3 & 0x000800) ? " BTSC/FM radio pilot " : "", + (reg_data3 & 0x001000) ? " SAP carrier " : "", + (reg_data3 & 0x002000) ? " BTSC stereo noise mute " : "", + (reg_data3 & 0x004000) ? " SAP noise mute " : "", + (reg_data3 & 0x008000) ? " VDSP " : "", + + (reg_data3 & 0x010000) ? " NICST " : "", + (reg_data3 & 0x020000) ? " NICDU " : "", + (reg_data3 & 0x040000) ? " NICAM muted " : "", + (reg_data3 & 0x080000) ? " NICAM reserve sound " : "", + + (reg_data3 & 0x100000) ? " init done " : ""); + + if (reg_data3 & 0x000220) { + v4l2_dbg(1, debug, sd, "ST!!!\n"); + *stereo_flag = 1; + } + + if (reg_data3 & 0x000140) { + v4l2_dbg(1, debug, sd, "DUAL!!!\n"); + *dual_flag = 1; + } +} + +/* regs write to set audio mode */ +static void set_audio_mode(struct v4l2_subdev *sd, int audio_mode) +{ + v4l2_dbg(1, debug, sd, "writing registers to set audio mode by set %d\n", + audio_mode); + + saa717x_write(sd, 0x46c, reg_set_audio_template[audio_mode][0]); + saa717x_write(sd, 0x470, reg_set_audio_template[audio_mode][1]); +} + +/* write regs to set audio volume, bass and treble */ +static int set_audio_regs(struct v4l2_subdev *sd, + struct saa717x_state *decoder) +{ + u8 mute = 0xac; /* -84 dB */ + u32 val; + unsigned int work_l, work_r; + + /* set SIF analog I/O select */ + saa717x_write(sd, 0x0594, decoder->audio_input); + v4l2_dbg(1, debug, sd, "set audio input %d\n", + decoder->audio_input); + + /* normalize ( 65535 to 0 -> 24 to -40 (not -84)) */ + work_l = (min(65536 - decoder->audio_main_balance, 32768) * decoder->audio_main_volume) / 32768; + work_r = (min(decoder->audio_main_balance, (u16)32768) * decoder->audio_main_volume) / 32768; + decoder->audio_main_vol_l = (long)work_l * (24 - (-40)) / 65535 - 40; + decoder->audio_main_vol_r = (long)work_r * (24 - (-40)) / 65535 - 40; + + /* set main volume */ + /* main volume L[7-0],R[7-0],0x00 24=24dB,-83dB, -84(mute) */ + /* def:0dB->6dB(MPG600GR) */ + /* if mute is on, set mute */ + if (decoder->audio_main_mute) { + val = mute | (mute << 8); + } else { + val = (u8)decoder->audio_main_vol_l | + ((u8)decoder->audio_main_vol_r << 8); + } + + saa717x_write(sd, 0x480, val); + + /* set bass and treble */ + val = decoder->audio_main_bass & 0x1f; + val |= (decoder->audio_main_treble & 0x1f) << 5; + saa717x_write(sd, 0x488, val); + return 0; +} + +/********** scaling staff ***********/ +static void set_h_prescale(struct v4l2_subdev *sd, + int task, int prescale) +{ + static const struct { + int xpsc; + int xacl; + int xc2_1; + int xdcg; + int vpfy; + } vals[] = { + /* XPSC XACL XC2_1 XDCG VPFY */ + { 1, 0, 0, 0, 0 }, + { 2, 2, 1, 2, 2 }, + { 3, 4, 1, 3, 2 }, + { 4, 8, 1, 4, 2 }, + { 5, 8, 1, 4, 2 }, + { 6, 8, 1, 4, 3 }, + { 7, 8, 1, 4, 3 }, + { 8, 15, 0, 4, 3 }, + { 9, 15, 0, 4, 3 }, + { 10, 16, 1, 5, 3 }, + }; + static const int count = ARRAY_SIZE(vals); + int i, task_shift; + + task_shift = task * 0x40; + for (i = 0; i < count; i++) + if (vals[i].xpsc == prescale) + break; + if (i == count) + return; + + /* horizontal prescaling */ + saa717x_write(sd, 0x60 + task_shift, vals[i].xpsc); + /* accumulation length */ + saa717x_write(sd, 0x61 + task_shift, vals[i].xacl); + /* level control */ + saa717x_write(sd, 0x62 + task_shift, + (vals[i].xc2_1 << 3) | vals[i].xdcg); + /*FIR prefilter control */ + saa717x_write(sd, 0x63 + task_shift, + (vals[i].vpfy << 2) | vals[i].vpfy); +} + +/********** scaling staff ***********/ +static void set_v_scale(struct v4l2_subdev *sd, int task, int yscale) +{ + int task_shift; + + task_shift = task * 0x40; + /* Vertical scaling ratio (LOW) */ + saa717x_write(sd, 0x70 + task_shift, yscale & 0xff); + /* Vertical scaling ratio (HI) */ + saa717x_write(sd, 0x71 + task_shift, yscale >> 8); +} + +static int saa717x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct saa717x_state *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + saa717x_write(sd, 0x10a, ctrl->val); + return 0; + + case V4L2_CID_CONTRAST: + saa717x_write(sd, 0x10b, ctrl->val); + return 0; + + case V4L2_CID_SATURATION: + saa717x_write(sd, 0x10c, ctrl->val); + return 0; + + case V4L2_CID_HUE: + saa717x_write(sd, 0x10d, ctrl->val); + return 0; + + case V4L2_CID_AUDIO_MUTE: + state->audio_main_mute = ctrl->val; + break; + + case V4L2_CID_AUDIO_VOLUME: + state->audio_main_volume = ctrl->val; + break; + + case V4L2_CID_AUDIO_BALANCE: + state->audio_main_balance = ctrl->val; + break; + + case V4L2_CID_AUDIO_TREBLE: + state->audio_main_treble = ctrl->val; + break; + + case V4L2_CID_AUDIO_BASS: + state->audio_main_bass = ctrl->val; + break; + + default: + return 0; + } + set_audio_regs(sd, state); + return 0; +} + +static int saa717x_s_video_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct saa717x_state *decoder = to_state(sd); + int is_tuner = input & 0x80; /* tuner input flag */ + + input &= 0x7f; + + v4l2_dbg(1, debug, sd, "decoder set input (%d)\n", input); + /* inputs from 0-9 are available*/ + /* saa717x have mode0-mode9 but mode5 is reserved. */ + if (input > 9 || input == 5) + return -EINVAL; + + if (decoder->input != input) { + int input_line = input; + + decoder->input = input_line; + v4l2_dbg(1, debug, sd, "now setting %s input %d\n", + input_line >= 6 ? "S-Video" : "Composite", + input_line); + + /* select mode */ + saa717x_write(sd, 0x102, + (saa717x_read(sd, 0x102) & 0xf0) | + input_line); + + /* bypass chrominance trap for modes 6..9 */ + saa717x_write(sd, 0x109, + (saa717x_read(sd, 0x109) & 0x7f) | + (input_line < 6 ? 0x0 : 0x80)); + + /* change audio_mode */ + if (is_tuner) { + /* tuner */ + set_audio_mode(sd, decoder->tuner_audio_mode); + } else { + /* Force to STEREO mode if Composite or + * S-Video were chosen */ + set_audio_mode(sd, TUNER_AUDIO_STEREO); + } + /* change initialize procedure (Composite/S-Video) */ + if (is_tuner) + saa717x_write_regs(sd, reg_init_tuner_input); + else if (input_line >= 6) + saa717x_write_regs(sd, reg_init_svideo_input); + else + saa717x_write_regs(sd, reg_init_composite_input); + } + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int saa717x_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->val = saa717x_read(sd, reg->reg); + reg->size = 1; + return 0; +} + +static int saa717x_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + u16 addr = reg->reg & 0xffff; + u8 val = reg->val & 0xff; + + saa717x_write(sd, addr, val); + return 0; +} +#endif + +static int saa717x_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + int prescale, h_scale, v_scale; + + v4l2_dbg(1, debug, sd, "decoder set size\n"); + + if (format->pad || fmt->code != MEDIA_BUS_FMT_FIXED) + return -EINVAL; + + /* FIXME need better bounds checking here */ + if (fmt->width < 1 || fmt->width > 1440) + return -EINVAL; + if (fmt->height < 1 || fmt->height > 960) + return -EINVAL; + + fmt->field = V4L2_FIELD_INTERLACED; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + /* scaling setting */ + /* NTSC and interlace only */ + prescale = SAA717X_NTSC_WIDTH / fmt->width; + if (prescale == 0) + prescale = 1; + h_scale = 1024 * SAA717X_NTSC_WIDTH / prescale / fmt->width; + /* interlace */ + v_scale = 512 * 2 * SAA717X_NTSC_HEIGHT / fmt->height; + + /* Horizontal prescaling etc */ + set_h_prescale(sd, 0, prescale); + set_h_prescale(sd, 1, prescale); + + /* Horizontal scaling increment */ + /* TASK A */ + saa717x_write(sd, 0x6C, (u8)(h_scale & 0xFF)); + saa717x_write(sd, 0x6D, (u8)((h_scale >> 8) & 0xFF)); + /* TASK B */ + saa717x_write(sd, 0xAC, (u8)(h_scale & 0xFF)); + saa717x_write(sd, 0xAD, (u8)((h_scale >> 8) & 0xFF)); + + /* Vertical prescaling etc */ + set_v_scale(sd, 0, v_scale); + set_v_scale(sd, 1, v_scale); + + /* set video output size */ + /* video number of pixels at output */ + /* TASK A */ + saa717x_write(sd, 0x5C, (u8)(fmt->width & 0xFF)); + saa717x_write(sd, 0x5D, (u8)((fmt->width >> 8) & 0xFF)); + /* TASK B */ + saa717x_write(sd, 0x9C, (u8)(fmt->width & 0xFF)); + saa717x_write(sd, 0x9D, (u8)((fmt->width >> 8) & 0xFF)); + + /* video number of lines at output */ + /* TASK A */ + saa717x_write(sd, 0x5E, (u8)(fmt->height & 0xFF)); + saa717x_write(sd, 0x5F, (u8)((fmt->height >> 8) & 0xFF)); + /* TASK B */ + saa717x_write(sd, 0x9E, (u8)(fmt->height & 0xFF)); + saa717x_write(sd, 0x9F, (u8)((fmt->height >> 8) & 0xFF)); + return 0; +} + +static int saa717x_s_radio(struct v4l2_subdev *sd) +{ + struct saa717x_state *decoder = to_state(sd); + + decoder->radio = 1; + return 0; +} + +static int saa717x_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa717x_state *decoder = to_state(sd); + + v4l2_dbg(1, debug, sd, "decoder set norm "); + v4l2_dbg(1, debug, sd, "(not yet implemented)\n"); + + decoder->radio = 0; + decoder->std = std; + return 0; +} + +static int saa717x_s_audio_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct saa717x_state *decoder = to_state(sd); + + if (input < 3) { /* FIXME! --tadachi */ + decoder->audio_input = input; + v4l2_dbg(1, debug, sd, + "set decoder audio input to %d\n", + decoder->audio_input); + set_audio_regs(sd, decoder); + return 0; + } + return -ERANGE; +} + +static int saa717x_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct saa717x_state *decoder = to_state(sd); + + v4l2_dbg(1, debug, sd, "decoder %s output\n", + enable ? "enable" : "disable"); + decoder->enable = enable; + saa717x_write(sd, 0x193, enable ? 0xa6 : 0x26); + return 0; +} + +/* change audio mode */ +static int saa717x_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + struct saa717x_state *decoder = to_state(sd); + int audio_mode; + char *mes[4] = { + "MONO", "STEREO", "LANG1", "LANG2/SAP" + }; + + audio_mode = TUNER_AUDIO_STEREO; + + switch (vt->audmode) { + case V4L2_TUNER_MODE_MONO: + audio_mode = TUNER_AUDIO_MONO; + break; + case V4L2_TUNER_MODE_STEREO: + audio_mode = TUNER_AUDIO_STEREO; + break; + case V4L2_TUNER_MODE_LANG2: + audio_mode = TUNER_AUDIO_LANG2; + break; + case V4L2_TUNER_MODE_LANG1: + audio_mode = TUNER_AUDIO_LANG1; + break; + } + + v4l2_dbg(1, debug, sd, "change audio mode to %s\n", + mes[audio_mode]); + decoder->tuner_audio_mode = audio_mode; + /* The registers are not changed here. */ + /* See DECODER_ENABLE_OUTPUT section. */ + set_audio_mode(sd, decoder->tuner_audio_mode); + return 0; +} + +static int saa717x_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct saa717x_state *decoder = to_state(sd); + int dual_f, stereo_f; + + if (decoder->radio) + return 0; + get_inf_dev_status(sd, &dual_f, &stereo_f); + + v4l2_dbg(1, debug, sd, "DETECT==st:%d dual:%d\n", + stereo_f, dual_f); + + /* mono */ + if ((dual_f == 0) && (stereo_f == 0)) { + vt->rxsubchans = V4L2_TUNER_SUB_MONO; + v4l2_dbg(1, debug, sd, "DETECT==MONO\n"); + } + + /* stereo */ + if (stereo_f == 1) { + if (vt->audmode == V4L2_TUNER_MODE_STEREO || + vt->audmode == V4L2_TUNER_MODE_LANG1) { + vt->rxsubchans = V4L2_TUNER_SUB_STEREO; + v4l2_dbg(1, debug, sd, "DETECT==ST(ST)\n"); + } else { + vt->rxsubchans = V4L2_TUNER_SUB_MONO; + v4l2_dbg(1, debug, sd, "DETECT==ST(MONO)\n"); + } + } + + /* dual */ + if (dual_f == 1) { + if (vt->audmode == V4L2_TUNER_MODE_LANG2) { + vt->rxsubchans = V4L2_TUNER_SUB_LANG2 | V4L2_TUNER_SUB_MONO; + v4l2_dbg(1, debug, sd, "DETECT==DUAL1\n"); + } else { + vt->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_MONO; + v4l2_dbg(1, debug, sd, "DETECT==DUAL2\n"); + } + } + return 0; +} + +static int saa717x_log_status(struct v4l2_subdev *sd) +{ + struct saa717x_state *state = to_state(sd); + + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops saa717x_ctrl_ops = { + .s_ctrl = saa717x_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops saa717x_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = saa717x_g_register, + .s_register = saa717x_s_register, +#endif + .log_status = saa717x_log_status, +}; + +static const struct v4l2_subdev_tuner_ops saa717x_tuner_ops = { + .g_tuner = saa717x_g_tuner, + .s_tuner = saa717x_s_tuner, + .s_radio = saa717x_s_radio, +}; + +static const struct v4l2_subdev_video_ops saa717x_video_ops = { + .s_std = saa717x_s_std, + .s_routing = saa717x_s_video_routing, + .s_stream = saa717x_s_stream, +}; + +static const struct v4l2_subdev_audio_ops saa717x_audio_ops = { + .s_routing = saa717x_s_audio_routing, +}; + +static const struct v4l2_subdev_pad_ops saa717x_pad_ops = { + .set_fmt = saa717x_set_fmt, +}; + +static const struct v4l2_subdev_ops saa717x_ops = { + .core = &saa717x_core_ops, + .tuner = &saa717x_tuner_ops, + .audio = &saa717x_audio_ops, + .video = &saa717x_video_ops, + .pad = &saa717x_pad_ops, +}; + +/* ----------------------------------------------------------------------- */ + + +/* i2c implementation */ + +/* ----------------------------------------------------------------------- */ +static int saa717x_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct saa717x_state *decoder; + struct v4l2_ctrl_handler *hdl; + struct v4l2_subdev *sd; + u8 id = 0; + char *p = ""; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + decoder = devm_kzalloc(&client->dev, sizeof(*decoder), GFP_KERNEL); + if (decoder == NULL) + return -ENOMEM; + + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &saa717x_ops); + + if (saa717x_write(sd, 0x5a4, 0xfe) && + saa717x_write(sd, 0x5a5, 0x0f) && + saa717x_write(sd, 0x5a6, 0x00) && + saa717x_write(sd, 0x5a7, 0x01)) + id = saa717x_read(sd, 0x5a0); + if (id != 0xc2 && id != 0x32 && id != 0xf2 && id != 0x6c) { + v4l2_dbg(1, debug, sd, "saa717x not found (id=%02x)\n", id); + return -ENODEV; + } + if (id == 0xc2) + p = "saa7173"; + else if (id == 0x32) + p = "saa7174A"; + else if (id == 0x6c) + p = "saa7174HL"; + else + p = "saa7171"; + v4l2_info(sd, "%s found @ 0x%x (%s)\n", p, + client->addr << 1, client->adapter->name); + + hdl = &decoder->hdl; + v4l2_ctrl_handler_init(hdl, 9); + /* add in ascending ID order */ + v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 68); + v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 64); + v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, 65535, 65535 / 100, 42000); + v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops, + V4L2_CID_AUDIO_BALANCE, 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops, + V4L2_CID_AUDIO_BASS, -16, 15, 1, 0); + v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops, + V4L2_CID_AUDIO_TREBLE, -16, 15, 1, 0); + v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + sd->ctrl_handler = hdl; + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return err; + } + + decoder->std = V4L2_STD_NTSC; + decoder->input = -1; + decoder->enable = 1; + + /* FIXME!! */ + decoder->playback = 0; /* initially capture mode used */ + decoder->audio = 1; /* DECODER_AUDIO_48_KHZ */ + + decoder->audio_input = 2; /* FIXME!! */ + + decoder->tuner_audio_mode = TUNER_AUDIO_STEREO; + /* set volume, bass and treble */ + decoder->audio_main_vol_l = 6; + decoder->audio_main_vol_r = 6; + + v4l2_dbg(1, debug, sd, "writing init values\n"); + + /* FIXME!! */ + saa717x_write_regs(sd, reg_init_initialize); + + v4l2_ctrl_handler_setup(hdl); + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(2*HZ); + return 0; +} + +static int saa717x_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id saa717x_id[] = { + { "saa717x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, saa717x_id); + +static struct i2c_driver saa717x_driver = { + .driver = { + .name = "saa717x", + }, + .probe = saa717x_probe, + .remove = saa717x_remove, + .id_table = saa717x_id, +}; + +module_i2c_driver(saa717x_driver); diff --git a/drivers/media/i2c/saa7185.c b/drivers/media/i2c/saa7185.c new file mode 100644 index 000000000..7a04422df --- /dev/null +++ b/drivers/media/i2c/saa7185.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * saa7185 - Philips SAA7185B video encoder driver version 0.0.3 + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Slight changes for video timing and attachment output by + * Wolfgang Scherr <scherr@net4you.net> + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> + +MODULE_DESCRIPTION("Philips SAA7185 video encoder driver"); +MODULE_AUTHOR("Dave Perks"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* ----------------------------------------------------------------------- */ + +struct saa7185 { + struct v4l2_subdev sd; + unsigned char reg[128]; + + v4l2_std_id norm; +}; + +static inline struct saa7185 *to_saa7185(struct v4l2_subdev *sd) +{ + return container_of(sd, struct saa7185, sd); +} + +/* ----------------------------------------------------------------------- */ + +static inline int saa7185_read(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte(client); +} + +static int saa7185_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct saa7185 *encoder = to_saa7185(sd); + + v4l2_dbg(1, debug, sd, "%02x set to %02x\n", reg, value); + encoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int saa7185_write_block(struct v4l2_subdev *sd, + const u8 *data, unsigned int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct saa7185 *encoder = to_saa7185(sd); + int ret = -1; + u8 reg; + + /* the adv7175 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = + encoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && block_len < 32); + ret = i2c_master_send(client, block_data, block_len); + if (ret < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + ret = saa7185_write(sd, reg, *data++); + if (ret < 0) + break; + len -= 2; + } + } + + return ret; +} + +/* ----------------------------------------------------------------------- */ + +static const unsigned char init_common[] = { + 0x3a, 0x0f, /* CBENB=0, V656=0, VY2C=1, + * YUV2C=1, MY2C=1, MUV2C=1 */ + + 0x42, 0x6b, /* OVLY0=107 */ + 0x43, 0x00, /* OVLU0=0 white */ + 0x44, 0x00, /* OVLV0=0 */ + 0x45, 0x22, /* OVLY1=34 */ + 0x46, 0xac, /* OVLU1=172 yellow */ + 0x47, 0x0e, /* OVLV1=14 */ + 0x48, 0x03, /* OVLY2=3 */ + 0x49, 0x1d, /* OVLU2=29 cyan */ + 0x4a, 0xac, /* OVLV2=172 */ + 0x4b, 0xf0, /* OVLY3=240 */ + 0x4c, 0xc8, /* OVLU3=200 green */ + 0x4d, 0xb9, /* OVLV3=185 */ + 0x4e, 0xd4, /* OVLY4=212 */ + 0x4f, 0x38, /* OVLU4=56 magenta */ + 0x50, 0x47, /* OVLV4=71 */ + 0x51, 0xc1, /* OVLY5=193 */ + 0x52, 0xe3, /* OVLU5=227 red */ + 0x53, 0x54, /* OVLV5=84 */ + 0x54, 0xa3, /* OVLY6=163 */ + 0x55, 0x54, /* OVLU6=84 blue */ + 0x56, 0xf2, /* OVLV6=242 */ + 0x57, 0x90, /* OVLY7=144 */ + 0x58, 0x00, /* OVLU7=0 black */ + 0x59, 0x00, /* OVLV7=0 */ + + 0x5a, 0x00, /* CHPS=0 */ + 0x5b, 0x76, /* GAINU=118 */ + 0x5c, 0xa5, /* GAINV=165 */ + 0x5d, 0x3c, /* BLCKL=60 */ + 0x5e, 0x3a, /* BLNNL=58 */ + 0x5f, 0x3a, /* CCRS=0, BLNVB=58 */ + 0x60, 0x00, /* NULL */ + + /* 0x61 - 0x66 set according to norm */ + + 0x67, 0x00, /* 0 : caption 1st byte odd field */ + 0x68, 0x00, /* 0 : caption 2nd byte odd field */ + 0x69, 0x00, /* 0 : caption 1st byte even field */ + 0x6a, 0x00, /* 0 : caption 2nd byte even field */ + + 0x6b, 0x91, /* MODIN=2, PCREF=0, SCCLN=17 */ + 0x6c, 0x20, /* SRCV1=0, TRCV2=1, ORCV1=0, PRCV1=0, + * CBLF=0, ORCV2=0, PRCV2=0 */ + 0x6d, 0x00, /* SRCM1=0, CCEN=0 */ + + 0x6e, 0x0e, /* HTRIG=0x005, approx. centered, at + * least for PAL */ + 0x6f, 0x00, /* HTRIG upper bits */ + 0x70, 0x20, /* PHRES=0, SBLN=1, VTRIG=0 */ + + /* The following should not be needed */ + + 0x71, 0x15, /* BMRQ=0x115 */ + 0x72, 0x90, /* EMRQ=0x690 */ + 0x73, 0x61, /* EMRQ=0x690, BMRQ=0x115 */ + 0x74, 0x00, /* NULL */ + 0x75, 0x00, /* NULL */ + 0x76, 0x00, /* NULL */ + 0x77, 0x15, /* BRCV=0x115 */ + 0x78, 0x90, /* ERCV=0x690 */ + 0x79, 0x61, /* ERCV=0x690, BRCV=0x115 */ + + /* Field length controls */ + + 0x7a, 0x70, /* FLC=0 */ + + /* The following should not be needed if SBLN = 1 */ + + 0x7b, 0x16, /* FAL=22 */ + 0x7c, 0x35, /* LAL=244 */ + 0x7d, 0x20, /* LAL=244, FAL=22 */ +}; + +static const unsigned char init_pal[] = { + 0x61, 0x1e, /* FISE=0, PAL=1, SCBW=1, RTCE=1, + * YGS=1, INPI=0, DOWN=0 */ + 0x62, 0xc8, /* DECTYP=1, BSTA=72 */ + 0x63, 0xcb, /* FSC0 */ + 0x64, 0x8a, /* FSC1 */ + 0x65, 0x09, /* FSC2 */ + 0x66, 0x2a, /* FSC3 */ +}; + +static const unsigned char init_ntsc[] = { + 0x61, 0x1d, /* FISE=1, PAL=0, SCBW=1, RTCE=1, + * YGS=1, INPI=0, DOWN=0 */ + 0x62, 0xe6, /* DECTYP=1, BSTA=102 */ + 0x63, 0x1f, /* FSC0 */ + 0x64, 0x7c, /* FSC1 */ + 0x65, 0xf0, /* FSC2 */ + 0x66, 0x21, /* FSC3 */ +}; + + +static int saa7185_init(struct v4l2_subdev *sd, u32 val) +{ + struct saa7185 *encoder = to_saa7185(sd); + + saa7185_write_block(sd, init_common, sizeof(init_common)); + if (encoder->norm & V4L2_STD_NTSC) + saa7185_write_block(sd, init_ntsc, sizeof(init_ntsc)); + else + saa7185_write_block(sd, init_pal, sizeof(init_pal)); + return 0; +} + +static int saa7185_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa7185 *encoder = to_saa7185(sd); + + if (std & V4L2_STD_NTSC) + saa7185_write_block(sd, init_ntsc, sizeof(init_ntsc)); + else if (std & V4L2_STD_PAL) + saa7185_write_block(sd, init_pal, sizeof(init_pal)); + else + return -EINVAL; + encoder->norm = std; + return 0; +} + +static int saa7185_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct saa7185 *encoder = to_saa7185(sd); + + /* RJ: input = 0: input is from SA7111 + input = 1: input is from ZR36060 */ + + switch (input) { + case 0: + /* turn off colorbar */ + saa7185_write(sd, 0x3a, 0x0f); + /* Switch RTCE to 1 */ + saa7185_write(sd, 0x61, (encoder->reg[0x61] & 0xf7) | 0x08); + saa7185_write(sd, 0x6e, 0x01); + break; + + case 1: + /* turn off colorbar */ + saa7185_write(sd, 0x3a, 0x0f); + /* Switch RTCE to 0 */ + saa7185_write(sd, 0x61, (encoder->reg[0x61] & 0xf7) | 0x00); + /* SW: a slight sync problem... */ + saa7185_write(sd, 0x6e, 0x00); + break; + + case 2: + /* turn on colorbar */ + saa7185_write(sd, 0x3a, 0x8f); + /* Switch RTCE to 0 */ + saa7185_write(sd, 0x61, (encoder->reg[0x61] & 0xf7) | 0x08); + /* SW: a slight sync problem... */ + saa7185_write(sd, 0x6e, 0x01); + break; + + default: + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops saa7185_core_ops = { + .init = saa7185_init, +}; + +static const struct v4l2_subdev_video_ops saa7185_video_ops = { + .s_std_output = saa7185_s_std_output, + .s_routing = saa7185_s_routing, +}; + +static const struct v4l2_subdev_ops saa7185_ops = { + .core = &saa7185_core_ops, + .video = &saa7185_video_ops, +}; + + +/* ----------------------------------------------------------------------- */ + +static int saa7185_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i; + struct saa7185 *encoder; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + encoder = devm_kzalloc(&client->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + encoder->norm = V4L2_STD_NTSC; + sd = &encoder->sd; + v4l2_i2c_subdev_init(sd, client, &saa7185_ops); + + i = saa7185_write_block(sd, init_common, sizeof(init_common)); + if (i >= 0) + i = saa7185_write_block(sd, init_ntsc, sizeof(init_ntsc)); + if (i < 0) + v4l2_dbg(1, debug, sd, "init error %d\n", i); + else + v4l2_dbg(1, debug, sd, "revision 0x%x\n", + saa7185_read(sd) >> 5); + return 0; +} + +static int saa7185_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct saa7185 *encoder = to_saa7185(sd); + + v4l2_device_unregister_subdev(sd); + /* SW: output off is active */ + saa7185_write(sd, 0x61, (encoder->reg[0x61]) | 0x40); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id saa7185_id[] = { + { "saa7185", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, saa7185_id); + +static struct i2c_driver saa7185_driver = { + .driver = { + .name = "saa7185", + }, + .probe = saa7185_probe, + .remove = saa7185_remove, + .id_table = saa7185_id, +}; + +module_i2c_driver(saa7185_driver); diff --git a/drivers/media/i2c/smiapp-pll.c b/drivers/media/i2c/smiapp-pll.c new file mode 100644 index 000000000..690abe8cb --- /dev/null +++ b/drivers/media/i2c/smiapp-pll.c @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/smiapp-pll.c + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ + +#include <linux/device.h> +#include <linux/gcd.h> +#include <linux/lcm.h> +#include <linux/module.h> + +#include "smiapp-pll.h" + +/* Return an even number or one. */ +static inline uint32_t clk_div_even(uint32_t a) +{ + return max_t(uint32_t, 1, a & ~1); +} + +/* Return an even number or one. */ +static inline uint32_t clk_div_even_up(uint32_t a) +{ + if (a == 1) + return 1; + return (a + 1) & ~1; +} + +static inline uint32_t is_one_or_even(uint32_t a) +{ + if (a == 1) + return 1; + if (a & 1) + return 0; + + return 1; +} + +static int bounds_check(struct device *dev, uint32_t val, + uint32_t min, uint32_t max, char *str) +{ + if (val >= min && val <= max) + return 0; + + dev_dbg(dev, "%s out of bounds: %d (%d--%d)\n", str, val, min, max); + + return -EINVAL; +} + +static void print_pll(struct device *dev, struct smiapp_pll *pll) +{ + dev_dbg(dev, "pre_pll_clk_div\t%u\n", pll->pre_pll_clk_div); + dev_dbg(dev, "pll_multiplier \t%u\n", pll->pll_multiplier); + if (!(pll->flags & SMIAPP_PLL_FLAG_NO_OP_CLOCKS)) { + dev_dbg(dev, "op_sys_clk_div \t%u\n", pll->op.sys_clk_div); + dev_dbg(dev, "op_pix_clk_div \t%u\n", pll->op.pix_clk_div); + } + dev_dbg(dev, "vt_sys_clk_div \t%u\n", pll->vt.sys_clk_div); + dev_dbg(dev, "vt_pix_clk_div \t%u\n", pll->vt.pix_clk_div); + + dev_dbg(dev, "ext_clk_freq_hz \t%u\n", pll->ext_clk_freq_hz); + dev_dbg(dev, "pll_ip_clk_freq_hz \t%u\n", pll->pll_ip_clk_freq_hz); + dev_dbg(dev, "pll_op_clk_freq_hz \t%u\n", pll->pll_op_clk_freq_hz); + if (!(pll->flags & SMIAPP_PLL_FLAG_NO_OP_CLOCKS)) { + dev_dbg(dev, "op_sys_clk_freq_hz \t%u\n", + pll->op.sys_clk_freq_hz); + dev_dbg(dev, "op_pix_clk_freq_hz \t%u\n", + pll->op.pix_clk_freq_hz); + } + dev_dbg(dev, "vt_sys_clk_freq_hz \t%u\n", pll->vt.sys_clk_freq_hz); + dev_dbg(dev, "vt_pix_clk_freq_hz \t%u\n", pll->vt.pix_clk_freq_hz); +} + +static int check_all_bounds(struct device *dev, + const struct smiapp_pll_limits *limits, + const struct smiapp_pll_branch_limits *op_limits, + struct smiapp_pll *pll, + struct smiapp_pll_branch *op_pll) +{ + int rval; + + rval = bounds_check(dev, pll->pll_ip_clk_freq_hz, + limits->min_pll_ip_freq_hz, + limits->max_pll_ip_freq_hz, + "pll_ip_clk_freq_hz"); + if (!rval) + rval = bounds_check( + dev, pll->pll_multiplier, + limits->min_pll_multiplier, limits->max_pll_multiplier, + "pll_multiplier"); + if (!rval) + rval = bounds_check( + dev, pll->pll_op_clk_freq_hz, + limits->min_pll_op_freq_hz, limits->max_pll_op_freq_hz, + "pll_op_clk_freq_hz"); + if (!rval) + rval = bounds_check( + dev, op_pll->sys_clk_div, + op_limits->min_sys_clk_div, op_limits->max_sys_clk_div, + "op_sys_clk_div"); + if (!rval) + rval = bounds_check( + dev, op_pll->sys_clk_freq_hz, + op_limits->min_sys_clk_freq_hz, + op_limits->max_sys_clk_freq_hz, + "op_sys_clk_freq_hz"); + if (!rval) + rval = bounds_check( + dev, op_pll->pix_clk_freq_hz, + op_limits->min_pix_clk_freq_hz, + op_limits->max_pix_clk_freq_hz, + "op_pix_clk_freq_hz"); + + /* + * If there are no OP clocks, the VT clocks are contained in + * the OP clock struct. + */ + if (pll->flags & SMIAPP_PLL_FLAG_NO_OP_CLOCKS) + return rval; + + if (!rval) + rval = bounds_check( + dev, pll->vt.sys_clk_freq_hz, + limits->vt.min_sys_clk_freq_hz, + limits->vt.max_sys_clk_freq_hz, + "vt_sys_clk_freq_hz"); + if (!rval) + rval = bounds_check( + dev, pll->vt.pix_clk_freq_hz, + limits->vt.min_pix_clk_freq_hz, + limits->vt.max_pix_clk_freq_hz, + "vt_pix_clk_freq_hz"); + + return rval; +} + +/* + * Heuristically guess the PLL tree for a given common multiplier and + * divisor. Begin with the operational timing and continue to video + * timing once operational timing has been verified. + * + * @mul is the PLL multiplier and @div is the common divisor + * (pre_pll_clk_div and op_sys_clk_div combined). The final PLL + * multiplier will be a multiple of @mul. + * + * @return Zero on success, error code on error. + */ +static int __smiapp_pll_calculate( + struct device *dev, const struct smiapp_pll_limits *limits, + const struct smiapp_pll_branch_limits *op_limits, + struct smiapp_pll *pll, struct smiapp_pll_branch *op_pll, uint32_t mul, + uint32_t div, uint32_t lane_op_clock_ratio) +{ + uint32_t sys_div; + uint32_t best_pix_div = INT_MAX >> 1; + uint32_t vt_op_binning_div; + /* + * Higher multipliers (and divisors) are often required than + * necessitated by the external clock and the output clocks. + * There are limits for all values in the clock tree. These + * are the minimum and maximum multiplier for mul. + */ + uint32_t more_mul_min, more_mul_max; + uint32_t more_mul_factor; + uint32_t min_vt_div, max_vt_div, vt_div; + uint32_t min_sys_div, max_sys_div; + unsigned int i; + + /* + * Get pre_pll_clk_div so that our pll_op_clk_freq_hz won't be + * too high. + */ + dev_dbg(dev, "pre_pll_clk_div %u\n", pll->pre_pll_clk_div); + + /* Don't go above max pll multiplier. */ + more_mul_max = limits->max_pll_multiplier / mul; + dev_dbg(dev, "more_mul_max: max_pll_multiplier check: %u\n", + more_mul_max); + /* Don't go above max pll op frequency. */ + more_mul_max = + min_t(uint32_t, + more_mul_max, + limits->max_pll_op_freq_hz + / (pll->ext_clk_freq_hz / pll->pre_pll_clk_div * mul)); + dev_dbg(dev, "more_mul_max: max_pll_op_freq_hz check: %u\n", + more_mul_max); + /* Don't go above the division capability of op sys clock divider. */ + more_mul_max = min(more_mul_max, + op_limits->max_sys_clk_div * pll->pre_pll_clk_div + / div); + dev_dbg(dev, "more_mul_max: max_op_sys_clk_div check: %u\n", + more_mul_max); + /* Ensure we won't go above min_pll_multiplier. */ + more_mul_max = min(more_mul_max, + DIV_ROUND_UP(limits->max_pll_multiplier, mul)); + dev_dbg(dev, "more_mul_max: min_pll_multiplier check: %u\n", + more_mul_max); + + /* Ensure we won't go below min_pll_op_freq_hz. */ + more_mul_min = DIV_ROUND_UP(limits->min_pll_op_freq_hz, + pll->ext_clk_freq_hz / pll->pre_pll_clk_div + * mul); + dev_dbg(dev, "more_mul_min: min_pll_op_freq_hz check: %u\n", + more_mul_min); + /* Ensure we won't go below min_pll_multiplier. */ + more_mul_min = max(more_mul_min, + DIV_ROUND_UP(limits->min_pll_multiplier, mul)); + dev_dbg(dev, "more_mul_min: min_pll_multiplier check: %u\n", + more_mul_min); + + if (more_mul_min > more_mul_max) { + dev_dbg(dev, + "unable to compute more_mul_min and more_mul_max\n"); + return -EINVAL; + } + + more_mul_factor = lcm(div, pll->pre_pll_clk_div) / div; + dev_dbg(dev, "more_mul_factor: %u\n", more_mul_factor); + more_mul_factor = lcm(more_mul_factor, op_limits->min_sys_clk_div); + dev_dbg(dev, "more_mul_factor: min_op_sys_clk_div: %d\n", + more_mul_factor); + i = roundup(more_mul_min, more_mul_factor); + if (!is_one_or_even(i)) + i <<= 1; + + dev_dbg(dev, "final more_mul: %u\n", i); + if (i > more_mul_max) { + dev_dbg(dev, "final more_mul is bad, max %u\n", more_mul_max); + return -EINVAL; + } + + pll->pll_multiplier = mul * i; + op_pll->sys_clk_div = div * i / pll->pre_pll_clk_div; + dev_dbg(dev, "op_sys_clk_div: %u\n", op_pll->sys_clk_div); + + pll->pll_ip_clk_freq_hz = pll->ext_clk_freq_hz + / pll->pre_pll_clk_div; + + pll->pll_op_clk_freq_hz = pll->pll_ip_clk_freq_hz + * pll->pll_multiplier; + + /* Derive pll_op_clk_freq_hz. */ + op_pll->sys_clk_freq_hz = + pll->pll_op_clk_freq_hz / op_pll->sys_clk_div; + + op_pll->pix_clk_div = pll->bits_per_pixel; + dev_dbg(dev, "op_pix_clk_div: %u\n", op_pll->pix_clk_div); + + op_pll->pix_clk_freq_hz = + op_pll->sys_clk_freq_hz / op_pll->pix_clk_div; + + if (pll->flags & SMIAPP_PLL_FLAG_NO_OP_CLOCKS) { + /* No OP clocks --- VT clocks are used instead. */ + goto out_skip_vt_calc; + } + + /* + * Some sensors perform analogue binning and some do this + * digitally. The ones doing this digitally can be roughly be + * found out using this formula. The ones doing this digitally + * should run at higher clock rate, so smaller divisor is used + * on video timing side. + */ + if (limits->min_line_length_pck_bin > limits->min_line_length_pck + / pll->binning_horizontal) + vt_op_binning_div = pll->binning_horizontal; + else + vt_op_binning_div = 1; + dev_dbg(dev, "vt_op_binning_div: %u\n", vt_op_binning_div); + + /* + * Profile 2 supports vt_pix_clk_div E [4, 10] + * + * Horizontal binning can be used as a base for difference in + * divisors. One must make sure that horizontal blanking is + * enough to accommodate the CSI-2 sync codes. + * + * Take scaling factor into account as well. + * + * Find absolute limits for the factor of vt divider. + */ + dev_dbg(dev, "scale_m: %u\n", pll->scale_m); + min_vt_div = DIV_ROUND_UP(op_pll->pix_clk_div * op_pll->sys_clk_div + * pll->scale_n, + lane_op_clock_ratio * vt_op_binning_div + * pll->scale_m); + + /* Find smallest and biggest allowed vt divisor. */ + dev_dbg(dev, "min_vt_div: %u\n", min_vt_div); + min_vt_div = max(min_vt_div, + DIV_ROUND_UP(pll->pll_op_clk_freq_hz, + limits->vt.max_pix_clk_freq_hz)); + dev_dbg(dev, "min_vt_div: max_vt_pix_clk_freq_hz: %u\n", + min_vt_div); + min_vt_div = max_t(uint32_t, min_vt_div, + limits->vt.min_pix_clk_div + * limits->vt.min_sys_clk_div); + dev_dbg(dev, "min_vt_div: min_vt_clk_div: %u\n", min_vt_div); + + max_vt_div = limits->vt.max_sys_clk_div * limits->vt.max_pix_clk_div; + dev_dbg(dev, "max_vt_div: %u\n", max_vt_div); + max_vt_div = min(max_vt_div, + DIV_ROUND_UP(pll->pll_op_clk_freq_hz, + limits->vt.min_pix_clk_freq_hz)); + dev_dbg(dev, "max_vt_div: min_vt_pix_clk_freq_hz: %u\n", + max_vt_div); + + /* + * Find limitsits for sys_clk_div. Not all values are possible + * with all values of pix_clk_div. + */ + min_sys_div = limits->vt.min_sys_clk_div; + dev_dbg(dev, "min_sys_div: %u\n", min_sys_div); + min_sys_div = max(min_sys_div, + DIV_ROUND_UP(min_vt_div, + limits->vt.max_pix_clk_div)); + dev_dbg(dev, "min_sys_div: max_vt_pix_clk_div: %u\n", min_sys_div); + min_sys_div = max(min_sys_div, + pll->pll_op_clk_freq_hz + / limits->vt.max_sys_clk_freq_hz); + dev_dbg(dev, "min_sys_div: max_pll_op_clk_freq_hz: %u\n", min_sys_div); + min_sys_div = clk_div_even_up(min_sys_div); + dev_dbg(dev, "min_sys_div: one or even: %u\n", min_sys_div); + + max_sys_div = limits->vt.max_sys_clk_div; + dev_dbg(dev, "max_sys_div: %u\n", max_sys_div); + max_sys_div = min(max_sys_div, + DIV_ROUND_UP(max_vt_div, + limits->vt.min_pix_clk_div)); + dev_dbg(dev, "max_sys_div: min_vt_pix_clk_div: %u\n", max_sys_div); + max_sys_div = min(max_sys_div, + DIV_ROUND_UP(pll->pll_op_clk_freq_hz, + limits->vt.min_pix_clk_freq_hz)); + dev_dbg(dev, "max_sys_div: min_vt_pix_clk_freq_hz: %u\n", max_sys_div); + + /* + * Find pix_div such that a legal pix_div * sys_div results + * into a value which is not smaller than div, the desired + * divisor. + */ + for (vt_div = min_vt_div; vt_div <= max_vt_div; + vt_div += 2 - (vt_div & 1)) { + for (sys_div = min_sys_div; + sys_div <= max_sys_div; + sys_div += 2 - (sys_div & 1)) { + uint16_t pix_div = DIV_ROUND_UP(vt_div, sys_div); + + if (pix_div < limits->vt.min_pix_clk_div + || pix_div > limits->vt.max_pix_clk_div) { + dev_dbg(dev, + "pix_div %u too small or too big (%u--%u)\n", + pix_div, + limits->vt.min_pix_clk_div, + limits->vt.max_pix_clk_div); + continue; + } + + /* Check if this one is better. */ + if (pix_div * sys_div + <= roundup(min_vt_div, best_pix_div)) + best_pix_div = pix_div; + } + if (best_pix_div < INT_MAX >> 1) + break; + } + + pll->vt.sys_clk_div = DIV_ROUND_UP(min_vt_div, best_pix_div); + pll->vt.pix_clk_div = best_pix_div; + + pll->vt.sys_clk_freq_hz = + pll->pll_op_clk_freq_hz / pll->vt.sys_clk_div; + pll->vt.pix_clk_freq_hz = + pll->vt.sys_clk_freq_hz / pll->vt.pix_clk_div; + +out_skip_vt_calc: + pll->pixel_rate_csi = + op_pll->pix_clk_freq_hz * lane_op_clock_ratio; + pll->pixel_rate_pixel_array = pll->vt.pix_clk_freq_hz; + + return check_all_bounds(dev, limits, op_limits, pll, op_pll); +} + +int smiapp_pll_calculate(struct device *dev, + const struct smiapp_pll_limits *limits, + struct smiapp_pll *pll) +{ + const struct smiapp_pll_branch_limits *op_limits = &limits->op; + struct smiapp_pll_branch *op_pll = &pll->op; + uint16_t min_pre_pll_clk_div; + uint16_t max_pre_pll_clk_div; + uint32_t lane_op_clock_ratio; + uint32_t mul, div; + unsigned int i; + int rval = -EINVAL; + + if (pll->flags & SMIAPP_PLL_FLAG_NO_OP_CLOCKS) { + /* + * If there's no OP PLL at all, use the VT values + * instead. The OP values are ignored for the rest of + * the PLL calculation. + */ + op_limits = &limits->vt; + op_pll = &pll->vt; + } + + if (pll->flags & SMIAPP_PLL_FLAG_OP_PIX_CLOCK_PER_LANE) + lane_op_clock_ratio = pll->csi2.lanes; + else + lane_op_clock_ratio = 1; + dev_dbg(dev, "lane_op_clock_ratio: %u\n", lane_op_clock_ratio); + + dev_dbg(dev, "binning: %ux%u\n", pll->binning_horizontal, + pll->binning_vertical); + + switch (pll->bus_type) { + case SMIAPP_PLL_BUS_TYPE_CSI2: + /* CSI transfers 2 bits per clock per lane; thus times 2 */ + pll->pll_op_clk_freq_hz = pll->link_freq * 2 + * (pll->csi2.lanes / lane_op_clock_ratio); + break; + case SMIAPP_PLL_BUS_TYPE_PARALLEL: + pll->pll_op_clk_freq_hz = pll->link_freq * pll->bits_per_pixel + / DIV_ROUND_UP(pll->bits_per_pixel, + pll->parallel.bus_width); + break; + default: + return -EINVAL; + } + + /* Figure out limits for pre-pll divider based on extclk */ + dev_dbg(dev, "min / max pre_pll_clk_div: %u / %u\n", + limits->min_pre_pll_clk_div, limits->max_pre_pll_clk_div); + max_pre_pll_clk_div = + min_t(uint16_t, limits->max_pre_pll_clk_div, + clk_div_even(pll->ext_clk_freq_hz / + limits->min_pll_ip_freq_hz)); + min_pre_pll_clk_div = + max_t(uint16_t, limits->min_pre_pll_clk_div, + clk_div_even_up( + DIV_ROUND_UP(pll->ext_clk_freq_hz, + limits->max_pll_ip_freq_hz))); + dev_dbg(dev, "pre-pll check: min / max pre_pll_clk_div: %u / %u\n", + min_pre_pll_clk_div, max_pre_pll_clk_div); + + i = gcd(pll->pll_op_clk_freq_hz, pll->ext_clk_freq_hz); + mul = div_u64(pll->pll_op_clk_freq_hz, i); + div = pll->ext_clk_freq_hz / i; + dev_dbg(dev, "mul %u / div %u\n", mul, div); + + min_pre_pll_clk_div = + max_t(uint16_t, min_pre_pll_clk_div, + clk_div_even_up( + DIV_ROUND_UP(mul * pll->ext_clk_freq_hz, + limits->max_pll_op_freq_hz))); + dev_dbg(dev, "pll_op check: min / max pre_pll_clk_div: %u / %u\n", + min_pre_pll_clk_div, max_pre_pll_clk_div); + + for (pll->pre_pll_clk_div = min_pre_pll_clk_div; + pll->pre_pll_clk_div <= max_pre_pll_clk_div; + pll->pre_pll_clk_div += 2 - (pll->pre_pll_clk_div & 1)) { + rval = __smiapp_pll_calculate(dev, limits, op_limits, pll, + op_pll, mul, div, + lane_op_clock_ratio); + if (rval) + continue; + + print_pll(dev, pll); + return 0; + } + + dev_dbg(dev, "unable to compute pre_pll divisor\n"); + + return rval; +} +EXPORT_SYMBOL_GPL(smiapp_pll_calculate); + +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>"); +MODULE_DESCRIPTION("Generic SMIA/SMIA++ PLL calculator"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/smiapp-pll.h b/drivers/media/i2c/smiapp-pll.h new file mode 100644 index 000000000..bd6902f54 --- /dev/null +++ b/drivers/media/i2c/smiapp-pll.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/smiapp-pll.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ + +#ifndef SMIAPP_PLL_H +#define SMIAPP_PLL_H + +/* CSI-2 or CCP-2 */ +#define SMIAPP_PLL_BUS_TYPE_CSI2 0x00 +#define SMIAPP_PLL_BUS_TYPE_PARALLEL 0x01 + +/* op pix clock is for all lanes in total normally */ +#define SMIAPP_PLL_FLAG_OP_PIX_CLOCK_PER_LANE (1 << 0) +#define SMIAPP_PLL_FLAG_NO_OP_CLOCKS (1 << 1) + +struct smiapp_pll_branch { + uint16_t sys_clk_div; + uint16_t pix_clk_div; + uint32_t sys_clk_freq_hz; + uint32_t pix_clk_freq_hz; +}; + +struct smiapp_pll { + /* input values */ + uint8_t bus_type; + union { + struct { + uint8_t lanes; + } csi2; + struct { + uint8_t bus_width; + } parallel; + }; + unsigned long flags; + uint8_t binning_horizontal; + uint8_t binning_vertical; + uint8_t scale_m; + uint8_t scale_n; + uint8_t bits_per_pixel; + uint32_t link_freq; + uint32_t ext_clk_freq_hz; + + /* output values */ + uint16_t pre_pll_clk_div; + uint16_t pll_multiplier; + uint32_t pll_ip_clk_freq_hz; + uint32_t pll_op_clk_freq_hz; + struct smiapp_pll_branch vt; + struct smiapp_pll_branch op; + + uint32_t pixel_rate_csi; + uint32_t pixel_rate_pixel_array; +}; + +struct smiapp_pll_branch_limits { + uint16_t min_sys_clk_div; + uint16_t max_sys_clk_div; + uint32_t min_sys_clk_freq_hz; + uint32_t max_sys_clk_freq_hz; + uint16_t min_pix_clk_div; + uint16_t max_pix_clk_div; + uint32_t min_pix_clk_freq_hz; + uint32_t max_pix_clk_freq_hz; +}; + +struct smiapp_pll_limits { + /* Strict PLL limits */ + uint32_t min_ext_clk_freq_hz; + uint32_t max_ext_clk_freq_hz; + uint16_t min_pre_pll_clk_div; + uint16_t max_pre_pll_clk_div; + uint32_t min_pll_ip_freq_hz; + uint32_t max_pll_ip_freq_hz; + uint16_t min_pll_multiplier; + uint16_t max_pll_multiplier; + uint32_t min_pll_op_freq_hz; + uint32_t max_pll_op_freq_hz; + + struct smiapp_pll_branch_limits vt; + struct smiapp_pll_branch_limits op; + + /* Other relevant limits */ + uint32_t min_line_length_pck_bin; + uint32_t min_line_length_pck; +}; + +struct device; + +int smiapp_pll_calculate(struct device *dev, + const struct smiapp_pll_limits *limits, + struct smiapp_pll *pll); + +#endif /* SMIAPP_PLL_H */ diff --git a/drivers/media/i2c/smiapp/Kconfig b/drivers/media/i2c/smiapp/Kconfig new file mode 100644 index 000000000..6893b5328 --- /dev/null +++ b/drivers/media/i2c/smiapp/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SMIAPP + tristate "SMIA++/SMIA sensor support" + depends on I2C && VIDEO_V4L2 && HAVE_CLK + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEO_SMIAPP_PLL + select V4L2_FWNODE + help + This is a generic driver for SMIA++/SMIA camera modules. diff --git a/drivers/media/i2c/smiapp/Makefile b/drivers/media/i2c/smiapp/Makefile new file mode 100644 index 000000000..86f57a43f --- /dev/null +++ b/drivers/media/i2c/smiapp/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +smiapp-objs += smiapp-core.o smiapp-regs.o \ + smiapp-quirk.o smiapp-limits.o +obj-$(CONFIG_VIDEO_SMIAPP) += smiapp.o + +ccflags-y += -I $(srctree)/drivers/media/i2c diff --git a/drivers/media/i2c/smiapp/smiapp-core.c b/drivers/media/i2c/smiapp/smiapp-core.c new file mode 100644 index 000000000..3bf96b047 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-core.c @@ -0,0 +1,3175 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/smiapp/smiapp-core.c + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2010--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + * + * Based on smiapp driver by Vimarsh Zutshi + * Based on jt8ev1.c by Vimarsh Zutshi + * Based on smia-sensor.c by Tuukka Toivonen <tuukkat76@gmail.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/smiapp.h> +#include <linux/v4l2-mediabus.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-device.h> + +#include "smiapp.h" + +#define SMIAPP_ALIGN_DIM(dim, flags) \ + ((flags) & V4L2_SEL_FLAG_GE \ + ? ALIGN((dim), 2) \ + : (dim) & ~1) + +/* + * smiapp_module_idents - supported camera modules + */ +static const struct smiapp_module_ident smiapp_module_idents[] = { + SMIAPP_IDENT_L(0x01, 0x022b, -1, "vs6555"), + SMIAPP_IDENT_L(0x01, 0x022e, -1, "vw6558"), + SMIAPP_IDENT_L(0x07, 0x7698, -1, "ovm7698"), + SMIAPP_IDENT_L(0x0b, 0x4242, -1, "smiapp-003"), + SMIAPP_IDENT_L(0x0c, 0x208a, -1, "tcm8330md"), + SMIAPP_IDENT_LQ(0x0c, 0x2134, -1, "tcm8500md", &smiapp_tcm8500md_quirk), + SMIAPP_IDENT_L(0x0c, 0x213e, -1, "et8en2"), + SMIAPP_IDENT_L(0x0c, 0x2184, -1, "tcm8580md"), + SMIAPP_IDENT_LQ(0x0c, 0x560f, -1, "jt8ew9", &smiapp_jt8ew9_quirk), + SMIAPP_IDENT_LQ(0x10, 0x4141, -1, "jt8ev1", &smiapp_jt8ev1_quirk), + SMIAPP_IDENT_LQ(0x10, 0x4241, -1, "imx125es", &smiapp_imx125es_quirk), +}; + +/* + * + * Dynamic Capability Identification + * + */ + +static u32 smiapp_get_limit(struct smiapp_sensor *sensor, + unsigned int limit) +{ + if (WARN_ON(limit >= SMIAPP_LIMIT_LAST)) + return 1; + + return sensor->limits[limit]; +} + +#define SMIA_LIM(sensor, limit) \ + smiapp_get_limit(sensor, SMIAPP_LIMIT_##limit) + +static int smiapp_read_all_smia_limits(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int i; + int rval; + + for (i = 0; i < SMIAPP_LIMIT_LAST; i++) { + u32 val; + + rval = smiapp_read( + sensor, smiapp_reg_limits[i].addr, &val); + if (rval) + return rval; + + sensor->limits[i] = val; + + dev_dbg(&client->dev, "0x%8.8x \"%s\" = %u, 0x%x\n", + smiapp_reg_limits[i].addr, + smiapp_reg_limits[i].what, val, val); + } + + if (SMIA_LIM(sensor, SCALER_N_MIN) == 0) + smiapp_replace_limit(sensor, SMIAPP_LIMIT_SCALER_N_MIN, 16); + + return 0; +} + +static int smiapp_read_frame_fmt(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + u32 fmt_model_type, fmt_model_subtype, ncol_desc, nrow_desc; + unsigned int i; + int pixel_count = 0; + int line_count = 0; + int rval; + + rval = smiapp_read(sensor, SMIAPP_REG_U8_FRAME_FORMAT_MODEL_TYPE, + &fmt_model_type); + if (rval) + return rval; + + rval = smiapp_read(sensor, SMIAPP_REG_U8_FRAME_FORMAT_MODEL_SUBTYPE, + &fmt_model_subtype); + if (rval) + return rval; + + ncol_desc = (fmt_model_subtype + & SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NCOLS_MASK) + >> SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NCOLS_SHIFT; + nrow_desc = fmt_model_subtype + & SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NROWS_MASK; + + dev_dbg(&client->dev, "format_model_type %s\n", + fmt_model_type == SMIAPP_FRAME_FORMAT_MODEL_TYPE_2BYTE + ? "2 byte" : + fmt_model_type == SMIAPP_FRAME_FORMAT_MODEL_TYPE_4BYTE + ? "4 byte" : "is simply bad"); + + for (i = 0; i < ncol_desc + nrow_desc; i++) { + u32 desc; + u32 pixelcode; + u32 pixels; + char *which; + char *what; + u32 reg; + + if (fmt_model_type == SMIAPP_FRAME_FORMAT_MODEL_TYPE_2BYTE) { + reg = SMIAPP_REG_U16_FRAME_FORMAT_DESCRIPTOR_2(i); + rval = smiapp_read(sensor, reg, &desc); + if (rval) + return rval; + + pixelcode = + (desc + & SMIAPP_FRAME_FORMAT_DESC_2_PIXELCODE_MASK) + >> SMIAPP_FRAME_FORMAT_DESC_2_PIXELCODE_SHIFT; + pixels = desc & SMIAPP_FRAME_FORMAT_DESC_2_PIXELS_MASK; + } else if (fmt_model_type + == SMIAPP_FRAME_FORMAT_MODEL_TYPE_4BYTE) { + reg = SMIAPP_REG_U32_FRAME_FORMAT_DESCRIPTOR_4(i); + rval = smiapp_read(sensor, reg, &desc); + if (rval) + return rval; + + pixelcode = + (desc + & SMIAPP_FRAME_FORMAT_DESC_4_PIXELCODE_MASK) + >> SMIAPP_FRAME_FORMAT_DESC_4_PIXELCODE_SHIFT; + pixels = desc & SMIAPP_FRAME_FORMAT_DESC_4_PIXELS_MASK; + } else { + dev_dbg(&client->dev, + "invalid frame format model type %d\n", + fmt_model_type); + return -EINVAL; + } + + if (i < ncol_desc) + which = "columns"; + else + which = "rows"; + + switch (pixelcode) { + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_EMBEDDED: + what = "embedded"; + break; + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_DUMMY: + what = "dummy"; + break; + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_BLACK: + what = "black"; + break; + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_DARK: + what = "dark"; + break; + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_VISIBLE: + what = "visible"; + break; + default: + what = "invalid"; + break; + } + + dev_dbg(&client->dev, + "0x%8.8x %s pixels: %d %s (pixelcode %u)\n", reg, + what, pixels, which, pixelcode); + + if (i < ncol_desc) { + if (pixelcode == + SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_VISIBLE) + sensor->visible_pixel_start = pixel_count; + pixel_count += pixels; + continue; + } + + /* Handle row descriptors */ + switch (pixelcode) { + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_EMBEDDED: + if (sensor->embedded_end) + break; + sensor->embedded_start = line_count; + sensor->embedded_end = line_count + pixels; + break; + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_VISIBLE: + sensor->image_start = line_count; + break; + } + line_count += pixels; + } + + if (sensor->embedded_end > sensor->image_start) { + dev_dbg(&client->dev, + "adjusting image start line to %u (was %u)\n", + sensor->embedded_end, sensor->image_start); + sensor->image_start = sensor->embedded_end; + } + + dev_dbg(&client->dev, "embedded data from lines %d to %d\n", + sensor->embedded_start, sensor->embedded_end); + dev_dbg(&client->dev, "image data starts at line %d\n", + sensor->image_start); + + return 0; +} + +static int smiapp_pll_configure(struct smiapp_sensor *sensor) +{ + struct smiapp_pll *pll = &sensor->pll; + int rval; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_VT_PIX_CLK_DIV, pll->vt.pix_clk_div); + if (rval < 0) + return rval; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_VT_SYS_CLK_DIV, pll->vt.sys_clk_div); + if (rval < 0) + return rval; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_PRE_PLL_CLK_DIV, pll->pre_pll_clk_div); + if (rval < 0) + return rval; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_PLL_MULTIPLIER, pll->pll_multiplier); + if (rval < 0) + return rval; + + /* Lane op clock ratio does not apply here. */ + rval = smiapp_write( + sensor, SMIAPP_REG_U32_REQUESTED_LINK_BIT_RATE_MBPS, + DIV_ROUND_UP(pll->op.sys_clk_freq_hz, 1000000 / 256 / 256)); + if (rval < 0 || sensor->minfo.smiapp_profile == SMIAPP_PROFILE_0) + return rval; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_OP_PIX_CLK_DIV, pll->op.pix_clk_div); + if (rval < 0) + return rval; + + return smiapp_write( + sensor, SMIAPP_REG_U16_OP_SYS_CLK_DIV, pll->op.sys_clk_div); +} + +static int smiapp_pll_try(struct smiapp_sensor *sensor, + struct smiapp_pll *pll) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct smiapp_pll_limits lim = { + .min_pre_pll_clk_div = SMIA_LIM(sensor, MIN_PRE_PLL_CLK_DIV), + .max_pre_pll_clk_div = SMIA_LIM(sensor, MAX_PRE_PLL_CLK_DIV), + .min_pll_ip_freq_hz = SMIA_LIM(sensor, MIN_PLL_IP_FREQ_HZ), + .max_pll_ip_freq_hz = SMIA_LIM(sensor, MAX_PLL_IP_FREQ_HZ), + .min_pll_multiplier = SMIA_LIM(sensor, MIN_PLL_MULTIPLIER), + .max_pll_multiplier = SMIA_LIM(sensor, MAX_PLL_MULTIPLIER), + .min_pll_op_freq_hz = SMIA_LIM(sensor, MIN_PLL_OP_FREQ_HZ), + .max_pll_op_freq_hz = SMIA_LIM(sensor, MAX_PLL_OP_FREQ_HZ), + + .op.min_sys_clk_div = SMIA_LIM(sensor, MIN_OP_SYS_CLK_DIV), + .op.max_sys_clk_div = SMIA_LIM(sensor, MAX_OP_SYS_CLK_DIV), + .op.min_pix_clk_div = SMIA_LIM(sensor, MIN_OP_PIX_CLK_DIV), + .op.max_pix_clk_div = SMIA_LIM(sensor, MAX_OP_PIX_CLK_DIV), + .op.min_sys_clk_freq_hz = SMIA_LIM(sensor, MIN_OP_SYS_CLK_FREQ_HZ), + .op.max_sys_clk_freq_hz = SMIA_LIM(sensor, MAX_OP_SYS_CLK_FREQ_HZ), + .op.min_pix_clk_freq_hz = SMIA_LIM(sensor, MIN_OP_PIX_CLK_FREQ_HZ), + .op.max_pix_clk_freq_hz = SMIA_LIM(sensor, MAX_OP_PIX_CLK_FREQ_HZ), + + .vt.min_sys_clk_div = SMIA_LIM(sensor, MIN_VT_SYS_CLK_DIV), + .vt.max_sys_clk_div = SMIA_LIM(sensor, MAX_VT_SYS_CLK_DIV), + .vt.min_pix_clk_div = SMIA_LIM(sensor, MIN_VT_PIX_CLK_DIV), + .vt.max_pix_clk_div = SMIA_LIM(sensor, MAX_VT_PIX_CLK_DIV), + .vt.min_sys_clk_freq_hz = SMIA_LIM(sensor, MIN_VT_SYS_CLK_FREQ_HZ), + .vt.max_sys_clk_freq_hz = SMIA_LIM(sensor, MAX_VT_SYS_CLK_FREQ_HZ), + .vt.min_pix_clk_freq_hz = SMIA_LIM(sensor, MIN_VT_PIX_CLK_FREQ_HZ), + .vt.max_pix_clk_freq_hz = SMIA_LIM(sensor, MAX_VT_PIX_CLK_FREQ_HZ), + + .min_line_length_pck_bin = SMIA_LIM(sensor, MIN_LINE_LENGTH_PCK_BIN), + .min_line_length_pck = SMIA_LIM(sensor, MIN_LINE_LENGTH_PCK), + }; + + return smiapp_pll_calculate(&client->dev, &lim, pll); +} + +static int smiapp_pll_update(struct smiapp_sensor *sensor) +{ + struct smiapp_pll *pll = &sensor->pll; + int rval; + + pll->binning_horizontal = sensor->binning_horizontal; + pll->binning_vertical = sensor->binning_vertical; + pll->link_freq = + sensor->link_freq->qmenu_int[sensor->link_freq->val]; + pll->scale_m = sensor->scale_m; + pll->bits_per_pixel = sensor->csi_format->compressed; + + rval = smiapp_pll_try(sensor, pll); + if (rval < 0) + return rval; + + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_parray, + pll->pixel_rate_pixel_array); + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_csi, pll->pixel_rate_csi); + + return 0; +} + + +/* + * + * V4L2 Controls handling + * + */ + +static void __smiapp_update_exposure_limits(struct smiapp_sensor *sensor) +{ + struct v4l2_ctrl *ctrl = sensor->exposure; + int max; + + max = sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height + + sensor->vblank->val + - SMIA_LIM(sensor, COARSE_INTEGRATION_TIME_MAX_MARGIN); + + __v4l2_ctrl_modify_range(ctrl, ctrl->minimum, max, ctrl->step, max); +} + +/* + * Order matters. + * + * 1. Bits-per-pixel, descending. + * 2. Bits-per-pixel compressed, descending. + * 3. Pixel order, same as in pixel_order_str. Formats for all four pixel + * orders must be defined. + */ +static const struct smiapp_csi_data_format smiapp_csi_data_formats[] = { + { MEDIA_BUS_FMT_SGRBG16_1X16, 16, 16, SMIAPP_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB16_1X16, 16, 16, SMIAPP_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR16_1X16, 16, 16, SMIAPP_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG16_1X16, 16, 16, SMIAPP_PIXEL_ORDER_GBRG, }, + { MEDIA_BUS_FMT_SGRBG14_1X14, 14, 14, SMIAPP_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB14_1X14, 14, 14, SMIAPP_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR14_1X14, 14, 14, SMIAPP_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG14_1X14, 14, 14, SMIAPP_PIXEL_ORDER_GBRG, }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_GBRG, }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_GBRG, }, + { MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_GBRG, }, + { MEDIA_BUS_FMT_SGRBG8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_GBRG, }, +}; + +static const char *pixel_order_str[] = { "GRBG", "RGGB", "BGGR", "GBRG" }; + +#define to_csi_format_idx(fmt) (((unsigned long)(fmt) \ + - (unsigned long)smiapp_csi_data_formats) \ + / sizeof(*smiapp_csi_data_formats)) + +static u32 smiapp_pixel_order(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int flip = 0; + + if (sensor->hflip) { + if (sensor->hflip->val) + flip |= SMIAPP_IMAGE_ORIENTATION_HFLIP; + + if (sensor->vflip->val) + flip |= SMIAPP_IMAGE_ORIENTATION_VFLIP; + } + + flip ^= sensor->hvflip_inv_mask; + + dev_dbg(&client->dev, "flip %d\n", flip); + return sensor->default_pixel_order ^ flip; +} + +static void smiapp_update_mbus_formats(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int csi_format_idx = + to_csi_format_idx(sensor->csi_format) & ~3; + unsigned int internal_csi_format_idx = + to_csi_format_idx(sensor->internal_csi_format) & ~3; + unsigned int pixel_order = smiapp_pixel_order(sensor); + + sensor->mbus_frame_fmts = + sensor->default_mbus_frame_fmts << pixel_order; + sensor->csi_format = + &smiapp_csi_data_formats[csi_format_idx + pixel_order]; + sensor->internal_csi_format = + &smiapp_csi_data_formats[internal_csi_format_idx + + pixel_order]; + + BUG_ON(max(internal_csi_format_idx, csi_format_idx) + pixel_order + >= ARRAY_SIZE(smiapp_csi_data_formats)); + + dev_dbg(&client->dev, "new pixel order %s\n", + pixel_order_str[pixel_order]); +} + +static const char * const smiapp_test_patterns[] = { + "Disabled", + "Solid Colour", + "Eight Vertical Colour Bars", + "Colour Bars With Fade to Grey", + "Pseudorandom Sequence (PN9)", +}; + +static int smiapp_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct smiapp_sensor *sensor = + container_of(ctrl->handler, struct smiapp_subdev, ctrl_handler) + ->sensor; + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int pm_status; + u32 orient = 0; + unsigned int i; + int exposure; + int rval; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + if (sensor->streaming) + return -EBUSY; + + if (sensor->hflip->val) + orient |= SMIAPP_IMAGE_ORIENTATION_HFLIP; + + if (sensor->vflip->val) + orient |= SMIAPP_IMAGE_ORIENTATION_VFLIP; + + orient ^= sensor->hvflip_inv_mask; + + smiapp_update_mbus_formats(sensor); + + break; + case V4L2_CID_VBLANK: + exposure = sensor->exposure->val; + + __smiapp_update_exposure_limits(sensor); + + if (exposure > sensor->exposure->maximum) { + sensor->exposure->val = sensor->exposure->maximum; + rval = smiapp_set_ctrl(sensor->exposure); + if (rval < 0) + return rval; + } + + break; + case V4L2_CID_LINK_FREQ: + if (sensor->streaming) + return -EBUSY; + + rval = smiapp_pll_update(sensor); + if (rval) + return rval; + + return 0; + case V4L2_CID_TEST_PATTERN: + for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++) + v4l2_ctrl_activate( + sensor->test_data[i], + ctrl->val == + V4L2_SMIAPP_TEST_PATTERN_MODE_SOLID_COLOUR); + + break; + } + + pm_status = pm_runtime_get_if_active(&client->dev, true); + if (!pm_status) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + rval = smiapp_write( + sensor, + SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GLOBAL, ctrl->val); + + break; + case V4L2_CID_EXPOSURE: + rval = smiapp_write( + sensor, + SMIAPP_REG_U16_COARSE_INTEGRATION_TIME, ctrl->val); + + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + rval = smiapp_write(sensor, SMIAPP_REG_U8_IMAGE_ORIENTATION, + orient); + + break; + case V4L2_CID_VBLANK: + rval = smiapp_write( + sensor, SMIAPP_REG_U16_FRAME_LENGTH_LINES, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height + + ctrl->val); + + break; + case V4L2_CID_HBLANK: + rval = smiapp_write( + sensor, SMIAPP_REG_U16_LINE_LENGTH_PCK, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width + + ctrl->val); + + break; + case V4L2_CID_TEST_PATTERN: + rval = smiapp_write( + sensor, SMIAPP_REG_U16_TEST_PATTERN_MODE, ctrl->val); + + break; + case V4L2_CID_TEST_PATTERN_RED: + rval = smiapp_write( + sensor, SMIAPP_REG_U16_TEST_DATA_RED, ctrl->val); + + break; + case V4L2_CID_TEST_PATTERN_GREENR: + rval = smiapp_write( + sensor, SMIAPP_REG_U16_TEST_DATA_GREENR, ctrl->val); + + break; + case V4L2_CID_TEST_PATTERN_BLUE: + rval = smiapp_write( + sensor, SMIAPP_REG_U16_TEST_DATA_BLUE, ctrl->val); + + break; + case V4L2_CID_TEST_PATTERN_GREENB: + rval = smiapp_write( + sensor, SMIAPP_REG_U16_TEST_DATA_GREENB, ctrl->val); + + break; + case V4L2_CID_PIXEL_RATE: + /* For v4l2_ctrl_s_ctrl_int64() used internally. */ + rval = 0; + + break; + default: + rval = -EINVAL; + } + + if (pm_status > 0) { + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + } + + return rval; +} + +static const struct v4l2_ctrl_ops smiapp_ctrl_ops = { + .s_ctrl = smiapp_set_ctrl, +}; + +static int smiapp_init_controls(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + rval = v4l2_ctrl_handler_init(&sensor->pixel_array->ctrl_handler, 12); + if (rval) + return rval; + + sensor->pixel_array->ctrl_handler.lock = &sensor->mutex; + + sensor->analog_gain = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, + SMIA_LIM(sensor, ANALOGUE_GAIN_CODE_MIN), + SMIA_LIM(sensor, ANALOGUE_GAIN_CODE_MAX), + max(SMIA_LIM(sensor, ANALOGUE_GAIN_CODE_STEP), 1U), + SMIA_LIM(sensor, ANALOGUE_GAIN_CODE_MIN)); + + /* Exposure limits will be updated soon, use just something here. */ + sensor->exposure = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 0, 1, 0); + + sensor->hflip = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + sensor->vflip = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + sensor->vblank = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_VBLANK, 0, 1, 1, 0); + + if (sensor->vblank) + sensor->vblank->flags |= V4L2_CTRL_FLAG_UPDATE; + + sensor->hblank = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_HBLANK, 0, 1, 1, 0); + + if (sensor->hblank) + sensor->hblank->flags |= V4L2_CTRL_FLAG_UPDATE; + + sensor->pixel_rate_parray = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1); + + v4l2_ctrl_new_std_menu_items(&sensor->pixel_array->ctrl_handler, + &smiapp_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(smiapp_test_patterns) - 1, + 0, 0, smiapp_test_patterns); + + if (sensor->pixel_array->ctrl_handler.error) { + dev_err(&client->dev, + "pixel array controls initialization failed (%d)\n", + sensor->pixel_array->ctrl_handler.error); + return sensor->pixel_array->ctrl_handler.error; + } + + sensor->pixel_array->sd.ctrl_handler = + &sensor->pixel_array->ctrl_handler; + + v4l2_ctrl_cluster(2, &sensor->hflip); + + rval = v4l2_ctrl_handler_init(&sensor->src->ctrl_handler, 0); + if (rval) + return rval; + + sensor->src->ctrl_handler.lock = &sensor->mutex; + + sensor->pixel_rate_csi = v4l2_ctrl_new_std( + &sensor->src->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1); + + if (sensor->src->ctrl_handler.error) { + dev_err(&client->dev, + "src controls initialization failed (%d)\n", + sensor->src->ctrl_handler.error); + return sensor->src->ctrl_handler.error; + } + + sensor->src->sd.ctrl_handler = &sensor->src->ctrl_handler; + + return 0; +} + +/* + * For controls that require information on available media bus codes + * and linke frequencies. + */ +static int smiapp_init_late_controls(struct smiapp_sensor *sensor) +{ + unsigned long *valid_link_freqs = &sensor->valid_link_freqs[ + sensor->csi_format->compressed - sensor->compressed_min_bpp]; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++) { + int max_value = (1 << sensor->csi_format->width) - 1; + + sensor->test_data[i] = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, + &smiapp_ctrl_ops, V4L2_CID_TEST_PATTERN_RED + i, + 0, max_value, 1, max_value); + } + + sensor->link_freq = v4l2_ctrl_new_int_menu( + &sensor->src->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_LINK_FREQ, __fls(*valid_link_freqs), + __ffs(*valid_link_freqs), sensor->hwcfg->op_sys_clock); + + return sensor->src->ctrl_handler.error; +} + +static void smiapp_free_controls(struct smiapp_sensor *sensor) +{ + unsigned int i; + + for (i = 0; i < sensor->ssds_used; i++) + v4l2_ctrl_handler_free(&sensor->ssds[i].ctrl_handler); +} + +static int smiapp_get_mbus_formats(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct smiapp_pll *pll = &sensor->pll; + u8 compressed_max_bpp = 0; + unsigned int type, n; + unsigned int i, pixel_order; + int rval; + + rval = smiapp_read( + sensor, SMIAPP_REG_U8_DATA_FORMAT_MODEL_TYPE, &type); + if (rval) + return rval; + + dev_dbg(&client->dev, "data_format_model_type %d\n", type); + + rval = smiapp_read(sensor, SMIAPP_REG_U8_PIXEL_ORDER, + &pixel_order); + if (rval) + return rval; + + if (pixel_order >= ARRAY_SIZE(pixel_order_str)) { + dev_dbg(&client->dev, "bad pixel order %d\n", pixel_order); + return -EINVAL; + } + + dev_dbg(&client->dev, "pixel order %d (%s)\n", pixel_order, + pixel_order_str[pixel_order]); + + switch (type) { + case SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL: + n = SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL_N; + break; + case SMIAPP_DATA_FORMAT_MODEL_TYPE_EXTENDED: + n = SMIAPP_DATA_FORMAT_MODEL_TYPE_EXTENDED_N; + break; + default: + return -EINVAL; + } + + sensor->default_pixel_order = pixel_order; + sensor->mbus_frame_fmts = 0; + + for (i = 0; i < n; i++) { + unsigned int fmt, j; + + rval = smiapp_read( + sensor, + SMIAPP_REG_U16_DATA_FORMAT_DESCRIPTOR(i), &fmt); + if (rval) + return rval; + + dev_dbg(&client->dev, "%u: bpp %u, compressed %u\n", + i, fmt >> 8, (u8)fmt); + + for (j = 0; j < ARRAY_SIZE(smiapp_csi_data_formats); j++) { + const struct smiapp_csi_data_format *f = + &smiapp_csi_data_formats[j]; + + if (f->pixel_order != SMIAPP_PIXEL_ORDER_GRBG) + continue; + + if (f->width != fmt >> 8 || f->compressed != (u8)fmt) + continue; + + dev_dbg(&client->dev, "jolly good! %d\n", j); + + sensor->default_mbus_frame_fmts |= 1 << j; + } + } + + /* Figure out which BPP values can be used with which formats. */ + pll->binning_horizontal = 1; + pll->binning_vertical = 1; + pll->scale_m = sensor->scale_m; + + for (i = 0; i < ARRAY_SIZE(smiapp_csi_data_formats); i++) { + sensor->compressed_min_bpp = + min(smiapp_csi_data_formats[i].compressed, + sensor->compressed_min_bpp); + compressed_max_bpp = + max(smiapp_csi_data_formats[i].compressed, + compressed_max_bpp); + } + + sensor->valid_link_freqs = devm_kcalloc( + &client->dev, + compressed_max_bpp - sensor->compressed_min_bpp + 1, + sizeof(*sensor->valid_link_freqs), GFP_KERNEL); + if (!sensor->valid_link_freqs) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(smiapp_csi_data_formats); i++) { + const struct smiapp_csi_data_format *f = + &smiapp_csi_data_formats[i]; + unsigned long *valid_link_freqs = + &sensor->valid_link_freqs[ + f->compressed - sensor->compressed_min_bpp]; + unsigned int j; + + if (!(sensor->default_mbus_frame_fmts & 1 << i)) + continue; + + pll->bits_per_pixel = f->compressed; + + for (j = 0; sensor->hwcfg->op_sys_clock[j]; j++) { + pll->link_freq = sensor->hwcfg->op_sys_clock[j]; + + rval = smiapp_pll_try(sensor, pll); + dev_dbg(&client->dev, "link freq %u Hz, bpp %u %s\n", + pll->link_freq, pll->bits_per_pixel, + rval ? "not ok" : "ok"); + if (rval) + continue; + + set_bit(j, valid_link_freqs); + } + + if (!*valid_link_freqs) { + dev_info(&client->dev, + "no valid link frequencies for %u bpp\n", + f->compressed); + sensor->default_mbus_frame_fmts &= ~BIT(i); + continue; + } + + if (!sensor->csi_format + || f->width > sensor->csi_format->width + || (f->width == sensor->csi_format->width + && f->compressed > sensor->csi_format->compressed)) { + sensor->csi_format = f; + sensor->internal_csi_format = f; + } + } + + if (!sensor->csi_format) { + dev_err(&client->dev, "no supported mbus code found\n"); + return -EINVAL; + } + + smiapp_update_mbus_formats(sensor); + + return 0; +} + +static void smiapp_update_blanking(struct smiapp_sensor *sensor) +{ + struct v4l2_ctrl *vblank = sensor->vblank; + struct v4l2_ctrl *hblank = sensor->hblank; + uint16_t min_fll, max_fll, min_llp, max_llp, min_lbp; + int min, max; + + if (sensor->binning_vertical > 1 || sensor->binning_horizontal > 1) { + min_fll = SMIA_LIM(sensor, MIN_FRAME_LENGTH_LINES_BIN); + max_fll = SMIA_LIM(sensor, MAX_FRAME_LENGTH_LINES_BIN); + min_llp = SMIA_LIM(sensor, MIN_LINE_LENGTH_PCK_BIN); + max_llp = SMIA_LIM(sensor, MAX_LINE_LENGTH_PCK_BIN); + min_lbp = SMIA_LIM(sensor, MIN_LINE_BLANKING_PCK_BIN); + } else { + min_fll = SMIA_LIM(sensor, MIN_FRAME_LENGTH_LINES); + max_fll = SMIA_LIM(sensor, MAX_FRAME_LENGTH_LINES); + min_llp = SMIA_LIM(sensor, MIN_LINE_LENGTH_PCK); + max_llp = SMIA_LIM(sensor, MAX_LINE_LENGTH_PCK); + min_lbp = SMIA_LIM(sensor, MIN_LINE_BLANKING_PCK); + } + + min = max_t(int, + SMIA_LIM(sensor, MIN_FRAME_BLANKING_LINES), + min_fll - + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height); + max = max_fll - sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height; + + __v4l2_ctrl_modify_range(vblank, min, max, vblank->step, min); + + min = max_t(int, + min_llp - + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width, + min_lbp); + max = max_llp - sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width; + + __v4l2_ctrl_modify_range(hblank, min, max, hblank->step, min); + + __smiapp_update_exposure_limits(sensor); +} + +static int smiapp_pll_blanking_update(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + rval = smiapp_pll_update(sensor); + if (rval < 0) + return rval; + + /* Output from pixel array, including blanking */ + smiapp_update_blanking(sensor); + + dev_dbg(&client->dev, "vblank\t\t%d\n", sensor->vblank->val); + dev_dbg(&client->dev, "hblank\t\t%d\n", sensor->hblank->val); + + dev_dbg(&client->dev, "real timeperframe\t100/%d\n", + sensor->pll.pixel_rate_pixel_array / + ((sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width + + sensor->hblank->val) * + (sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height + + sensor->vblank->val) / 100)); + + return 0; +} + +/* + * + * SMIA++ NVM handling + * + */ + +static int smiapp_read_nvm_page(struct smiapp_sensor *sensor, u32 p, u8 *nvm, + u8 *status) +{ + unsigned int i; + int rval; + u32 s; + + *status = 0; + + rval = smiapp_write(sensor, + SMIAPP_REG_U8_DATA_TRANSFER_IF_1_PAGE_SELECT, p); + if (rval) + return rval; + + rval = smiapp_write(sensor, SMIAPP_REG_U8_DATA_TRANSFER_IF_1_CTRL, + SMIAPP_DATA_TRANSFER_IF_1_CTRL_EN); + if (rval) + return rval; + + rval = smiapp_read(sensor, SMIAPP_REG_U8_DATA_TRANSFER_IF_1_STATUS, + &s); + if (rval) + return rval; + + if (s & SMIAPP_DATA_TRANSFER_IF_1_STATUS_EUSAGE) { + *status = s; + return -ENODATA; + } + + if (SMIA_LIM(sensor, DATA_TRANSFER_IF_CAPABILITY) & + SMIAPP_DATA_TRANSFER_IF_CAPABILITY_POLL) { + for (i = 1000; i > 0; i--) { + if (s & SMIAPP_DATA_TRANSFER_IF_1_STATUS_RD_READY) + break; + + rval = smiapp_read( + sensor, + SMIAPP_REG_U8_DATA_TRANSFER_IF_1_STATUS, + &s); + + if (rval) + return rval; + } + + if (!i) + return -ETIMEDOUT; + } + + for (i = 0; i < SMIAPP_NVM_PAGE_SIZE; i++) { + u32 v; + + rval = smiapp_read(sensor, + SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_0 + i, + &v); + if (rval) + return rval; + + *nvm++ = v; + } + + return 0; +} + +static int smiapp_read_nvm(struct smiapp_sensor *sensor, unsigned char *nvm, + size_t nvm_size) +{ + u8 status = 0; + u32 p; + int rval = 0, rval2; + + for (p = 0; p < nvm_size / SMIAPP_NVM_PAGE_SIZE && !rval; p++) { + rval = smiapp_read_nvm_page(sensor, p, nvm, &status); + nvm += SMIAPP_NVM_PAGE_SIZE; + } + + if (rval == -ENODATA && + status & SMIAPP_DATA_TRANSFER_IF_1_STATUS_EUSAGE) + rval = 0; + + rval2 = smiapp_write(sensor, SMIAPP_REG_U8_DATA_TRANSFER_IF_1_CTRL, 0); + if (rval < 0) + return rval; + else + return rval2 ?: p * SMIAPP_NVM_PAGE_SIZE; +} + +/* + * + * SMIA++ CCI address control + * + */ +static int smiapp_change_cci_addr(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + u32 val; + + client->addr = sensor->hwcfg->i2c_addr_dfl; + + rval = smiapp_write(sensor, + SMIAPP_REG_U8_CCI_ADDRESS_CONTROL, + sensor->hwcfg->i2c_addr_alt << 1); + if (rval) + return rval; + + client->addr = sensor->hwcfg->i2c_addr_alt; + + /* verify addr change went ok */ + rval = smiapp_read(sensor, SMIAPP_REG_U8_CCI_ADDRESS_CONTROL, &val); + if (rval) + return rval; + + if (val != sensor->hwcfg->i2c_addr_alt << 1) + return -ENODEV; + + return 0; +} + +/* + * + * SMIA++ Mode Control + * + */ +static int smiapp_setup_flash_strobe(struct smiapp_sensor *sensor) +{ + struct smiapp_flash_strobe_parms *strobe_setup; + unsigned int ext_freq = sensor->hwcfg->ext_clk; + u32 tmp; + u32 strobe_adjustment; + u32 strobe_width_high_rs; + int rval; + + strobe_setup = sensor->hwcfg->strobe_setup; + + /* + * How to calculate registers related to strobe length. Please + * do not change, or if you do at least know what you're + * doing. :-) + * + * Sakari Ailus <sakari.ailus@iki.fi> 2010-10-25 + * + * flash_strobe_length [us] / 10^6 = (tFlash_strobe_width_ctrl + * / EXTCLK freq [Hz]) * flash_strobe_adjustment + * + * tFlash_strobe_width_ctrl E N, [1 - 0xffff] + * flash_strobe_adjustment E N, [1 - 0xff] + * + * The formula above is written as below to keep it on one + * line: + * + * l / 10^6 = w / e * a + * + * Let's mark w * a by x: + * + * x = w * a + * + * Thus, we get: + * + * x = l * e / 10^6 + * + * The strobe width must be at least as long as requested, + * thus rounding upwards is needed. + * + * x = (l * e + 10^6 - 1) / 10^6 + * ----------------------------- + * + * Maximum possible accuracy is wanted at all times. Thus keep + * a as small as possible. + * + * Calculate a, assuming maximum w, with rounding upwards: + * + * a = (x + (2^16 - 1) - 1) / (2^16 - 1) + * ------------------------------------- + * + * Thus, we also get w, with that a, with rounding upwards: + * + * w = (x + a - 1) / a + * ------------------- + * + * To get limits: + * + * x E [1, (2^16 - 1) * (2^8 - 1)] + * + * Substituting maximum x to the original formula (with rounding), + * the maximum l is thus + * + * (2^16 - 1) * (2^8 - 1) * 10^6 = l * e + 10^6 - 1 + * + * l = (10^6 * (2^16 - 1) * (2^8 - 1) - 10^6 + 1) / e + * -------------------------------------------------- + * + * flash_strobe_length must be clamped between 1 and + * (10^6 * (2^16 - 1) * (2^8 - 1) - 10^6 + 1) / EXTCLK freq. + * + * Then, + * + * flash_strobe_adjustment = ((flash_strobe_length * + * EXTCLK freq + 10^6 - 1) / 10^6 + (2^16 - 1) - 1) / (2^16 - 1) + * + * tFlash_strobe_width_ctrl = ((flash_strobe_length * + * EXTCLK freq + 10^6 - 1) / 10^6 + + * flash_strobe_adjustment - 1) / flash_strobe_adjustment + */ + tmp = div_u64(1000000ULL * ((1 << 16) - 1) * ((1 << 8) - 1) - + 1000000 + 1, ext_freq); + strobe_setup->strobe_width_high_us = + clamp_t(u32, strobe_setup->strobe_width_high_us, 1, tmp); + + tmp = div_u64(((u64)strobe_setup->strobe_width_high_us * (u64)ext_freq + + 1000000 - 1), 1000000ULL); + strobe_adjustment = (tmp + (1 << 16) - 1 - 1) / ((1 << 16) - 1); + strobe_width_high_rs = (tmp + strobe_adjustment - 1) / + strobe_adjustment; + + rval = smiapp_write(sensor, SMIAPP_REG_U8_FLASH_MODE_RS, + strobe_setup->mode); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U8_FLASH_STROBE_ADJUSTMENT, + strobe_adjustment); + if (rval < 0) + goto out; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_HIGH_RS_CTRL, + strobe_width_high_rs); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U16_TFLASH_STROBE_DELAY_RS_CTRL, + strobe_setup->strobe_delay); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U16_FLASH_STROBE_START_POINT, + strobe_setup->stobe_start_point); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U8_FLASH_TRIGGER_RS, + strobe_setup->trigger); + +out: + sensor->hwcfg->strobe_setup->trigger = 0; + + return rval; +} + +/* ----------------------------------------------------------------------------- + * Power management + */ + +static int smiapp_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + /* + * The sub-device related to the I2C device is always the + * source one, i.e. ssds[0]. + */ + struct smiapp_sensor *sensor = + container_of(ssd, struct smiapp_sensor, ssds[0]); + unsigned int sleep; + int rval; + + rval = regulator_enable(sensor->vana); + if (rval) { + dev_err(&client->dev, "failed to enable vana regulator\n"); + return rval; + } + usleep_range(1000, 1000); + + rval = clk_prepare_enable(sensor->ext_clk); + if (rval < 0) { + dev_dbg(&client->dev, "failed to enable xclk\n"); + goto out_xclk_fail; + } + usleep_range(1000, 1000); + + gpiod_set_value(sensor->xshutdown, 1); + + sleep = SMIAPP_RESET_DELAY(sensor->hwcfg->ext_clk); + usleep_range(sleep, sleep); + + /* + * Failures to respond to the address change command have been noticed. + * Those failures seem to be caused by the sensor requiring a longer + * boot time than advertised. An additional 10ms delay seems to work + * around the issue, but the SMIA++ I2C write retry hack makes the delay + * unnecessary. The failures need to be investigated to find a proper + * fix, and a delay will likely need to be added here if the I2C write + * retry hack is reverted before the root cause of the boot time issue + * is found. + */ + + if (sensor->hwcfg->i2c_addr_alt) { + rval = smiapp_change_cci_addr(sensor); + if (rval) { + dev_err(&client->dev, "cci address change error\n"); + goto out_cci_addr_fail; + } + } + + rval = smiapp_write(sensor, SMIAPP_REG_U8_SOFTWARE_RESET, + SMIAPP_SOFTWARE_RESET); + if (rval < 0) { + dev_err(&client->dev, "software reset failed\n"); + goto out_cci_addr_fail; + } + + if (sensor->hwcfg->i2c_addr_alt) { + rval = smiapp_change_cci_addr(sensor); + if (rval) { + dev_err(&client->dev, "cci address change error\n"); + goto out_cci_addr_fail; + } + } + + rval = smiapp_write(sensor, SMIAPP_REG_U16_COMPRESSION_MODE, + SMIAPP_COMPRESSION_MODE_SIMPLE_PREDICTOR); + if (rval) { + dev_err(&client->dev, "compression mode set failed\n"); + goto out_cci_addr_fail; + } + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_EXTCLK_FREQUENCY_MHZ, + sensor->hwcfg->ext_clk / (1000000 / (1 << 8))); + if (rval) { + dev_err(&client->dev, "extclk frequency set failed\n"); + goto out_cci_addr_fail; + } + + rval = smiapp_write(sensor, SMIAPP_REG_U8_CSI_LANE_MODE, + sensor->hwcfg->lanes - 1); + if (rval) { + dev_err(&client->dev, "csi lane mode set failed\n"); + goto out_cci_addr_fail; + } + + rval = smiapp_write(sensor, SMIAPP_REG_U8_FAST_STANDBY_CTRL, + SMIAPP_FAST_STANDBY_CTRL_IMMEDIATE); + if (rval) { + dev_err(&client->dev, "fast standby set failed\n"); + goto out_cci_addr_fail; + } + + rval = smiapp_write(sensor, SMIAPP_REG_U8_CSI_SIGNALLING_MODE, + sensor->hwcfg->csi_signalling_mode); + if (rval) { + dev_err(&client->dev, "csi signalling mode set failed\n"); + goto out_cci_addr_fail; + } + + /* DPHY control done by sensor based on requested link rate */ + rval = smiapp_write(sensor, SMIAPP_REG_U8_DPHY_CTRL, + SMIAPP_DPHY_CTRL_UI); + if (rval < 0) + goto out_cci_addr_fail; + + rval = smiapp_call_quirk(sensor, post_poweron); + if (rval) { + dev_err(&client->dev, "post_poweron quirks failed\n"); + goto out_cci_addr_fail; + } + + return 0; + +out_cci_addr_fail: + gpiod_set_value(sensor->xshutdown, 0); + clk_disable_unprepare(sensor->ext_clk); + +out_xclk_fail: + regulator_disable(sensor->vana); + + return rval; +} + +static int smiapp_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct smiapp_sensor *sensor = + container_of(ssd, struct smiapp_sensor, ssds[0]); + + /* + * Currently power/clock to lens are enable/disabled separately + * but they are essentially the same signals. So if the sensor is + * powered off while the lens is powered on the sensor does not + * really see a power off and next time the cci address change + * will fail. So do a soft reset explicitly here. + */ + if (sensor->hwcfg->i2c_addr_alt) + smiapp_write(sensor, + SMIAPP_REG_U8_SOFTWARE_RESET, + SMIAPP_SOFTWARE_RESET); + + gpiod_set_value(sensor->xshutdown, 0); + clk_disable_unprepare(sensor->ext_clk); + usleep_range(5000, 5000); + regulator_disable(sensor->vana); + sensor->streaming = false; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Video stream management + */ + +static int smiapp_start_streaming(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int binning_mode; + int rval; + + mutex_lock(&sensor->mutex); + + rval = smiapp_write(sensor, SMIAPP_REG_U16_CSI_DATA_FORMAT, + (sensor->csi_format->width << 8) | + sensor->csi_format->compressed); + if (rval) + goto out; + + /* Binning configuration */ + if (sensor->binning_horizontal == 1 && + sensor->binning_vertical == 1) { + binning_mode = 0; + } else { + u8 binning_type = + (sensor->binning_horizontal << 4) + | sensor->binning_vertical; + + rval = smiapp_write( + sensor, SMIAPP_REG_U8_BINNING_TYPE, binning_type); + if (rval < 0) + goto out; + + binning_mode = 1; + } + rval = smiapp_write(sensor, SMIAPP_REG_U8_BINNING_MODE, binning_mode); + if (rval < 0) + goto out; + + /* Set up PLL */ + rval = smiapp_pll_configure(sensor); + if (rval) + goto out; + + /* Analog crop start coordinates */ + rval = smiapp_write(sensor, SMIAPP_REG_U16_X_ADDR_START, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].left); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U16_Y_ADDR_START, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].top); + if (rval < 0) + goto out; + + /* Analog crop end coordinates */ + rval = smiapp_write( + sensor, SMIAPP_REG_U16_X_ADDR_END, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].left + + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width - 1); + if (rval < 0) + goto out; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_Y_ADDR_END, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].top + + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height - 1); + if (rval < 0) + goto out; + + /* + * Output from pixel array, including blanking, is set using + * controls below. No need to set here. + */ + + /* Digital crop */ + if (SMIA_LIM(sensor, DIGITAL_CROP_CAPABILITY) + == SMIAPP_DIGITAL_CROP_CAPABILITY_INPUT_CROP) { + rval = smiapp_write( + sensor, SMIAPP_REG_U16_DIGITAL_CROP_X_OFFSET, + sensor->scaler->crop[SMIAPP_PAD_SINK].left); + if (rval < 0) + goto out; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_DIGITAL_CROP_Y_OFFSET, + sensor->scaler->crop[SMIAPP_PAD_SINK].top); + if (rval < 0) + goto out; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_DIGITAL_CROP_IMAGE_WIDTH, + sensor->scaler->crop[SMIAPP_PAD_SINK].width); + if (rval < 0) + goto out; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_DIGITAL_CROP_IMAGE_HEIGHT, + sensor->scaler->crop[SMIAPP_PAD_SINK].height); + if (rval < 0) + goto out; + } + + /* Scaling */ + if (SMIA_LIM(sensor, SCALING_CAPABILITY) + != SMIAPP_SCALING_CAPABILITY_NONE) { + rval = smiapp_write(sensor, SMIAPP_REG_U16_SCALING_MODE, + sensor->scaling_mode); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U16_SCALE_M, + sensor->scale_m); + if (rval < 0) + goto out; + } + + /* Output size from sensor */ + rval = smiapp_write(sensor, SMIAPP_REG_U16_X_OUTPUT_SIZE, + sensor->src->crop[SMIAPP_PAD_SRC].width); + if (rval < 0) + goto out; + rval = smiapp_write(sensor, SMIAPP_REG_U16_Y_OUTPUT_SIZE, + sensor->src->crop[SMIAPP_PAD_SRC].height); + if (rval < 0) + goto out; + + if ((SMIA_LIM(sensor, FLASH_MODE_CAPABILITY) & + (SMIAPP_FLASH_MODE_CAPABILITY_SINGLE_STROBE | + SMIAPP_FLASH_MODE_CAPABILITY_MULTIPLE_STROBE)) && + sensor->hwcfg->strobe_setup != NULL && + sensor->hwcfg->strobe_setup->trigger != 0) { + rval = smiapp_setup_flash_strobe(sensor); + if (rval) + goto out; + } + + rval = smiapp_call_quirk(sensor, pre_streamon); + if (rval) { + dev_err(&client->dev, "pre_streamon quirks failed\n"); + goto out; + } + + rval = smiapp_write(sensor, SMIAPP_REG_U8_MODE_SELECT, + SMIAPP_MODE_SELECT_STREAMING); + +out: + mutex_unlock(&sensor->mutex); + + return rval; +} + +static int smiapp_stop_streaming(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + mutex_lock(&sensor->mutex); + rval = smiapp_write(sensor, SMIAPP_REG_U8_MODE_SELECT, + SMIAPP_MODE_SELECT_SOFTWARE_STANDBY); + if (rval) + goto out; + + rval = smiapp_call_quirk(sensor, post_streamoff); + if (rval) + dev_err(&client->dev, "post_streamoff quirks failed\n"); + +out: + mutex_unlock(&sensor->mutex); + return rval; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev video operations + */ + +static int smiapp_pm_get_init(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + rval = pm_runtime_get_sync(&client->dev); + if (rval < 0) { + if (rval != -EBUSY && rval != -EAGAIN) + pm_runtime_set_active(&client->dev); + pm_runtime_put_noidle(&client->dev); + + return rval; + } else if (!rval) { + rval = v4l2_ctrl_handler_setup(&sensor->pixel_array-> + ctrl_handler); + if (rval) + return rval; + + return v4l2_ctrl_handler_setup(&sensor->src->ctrl_handler); + } + + return 0; +} + +static int smiapp_set_stream(struct v4l2_subdev *subdev, int enable) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + if (sensor->streaming == enable) + return 0; + + if (!enable) { + smiapp_stop_streaming(sensor); + sensor->streaming = false; + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return 0; + } + + rval = smiapp_pm_get_init(sensor); + if (rval) + return rval; + + sensor->streaming = true; + + rval = smiapp_start_streaming(sensor); + if (rval < 0) { + sensor->streaming = false; + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + } + + return rval; +} + +static int smiapp_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + unsigned int i; + int idx = -1; + int rval = -EINVAL; + + mutex_lock(&sensor->mutex); + + dev_err(&client->dev, "subdev %s, pad %d, index %d\n", + subdev->name, code->pad, code->index); + + if (subdev != &sensor->src->sd || code->pad != SMIAPP_PAD_SRC) { + if (code->index) + goto out; + + code->code = sensor->internal_csi_format->code; + rval = 0; + goto out; + } + + for (i = 0; i < ARRAY_SIZE(smiapp_csi_data_formats); i++) { + if (sensor->mbus_frame_fmts & (1 << i)) + idx++; + + if (idx == code->index) { + code->code = smiapp_csi_data_formats[i].code; + dev_err(&client->dev, "found index %d, i %d, code %x\n", + code->index, i, code->code); + rval = 0; + break; + } + } + +out: + mutex_unlock(&sensor->mutex); + + return rval; +} + +static u32 __smiapp_get_mbus_code(struct v4l2_subdev *subdev, + unsigned int pad) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + + if (subdev == &sensor->src->sd && pad == SMIAPP_PAD_SRC) + return sensor->csi_format->code; + else + return sensor->internal_csi_format->code; +} + +static int __smiapp_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + fmt->format = *v4l2_subdev_get_try_format(subdev, cfg, + fmt->pad); + } else { + struct v4l2_rect *r; + + if (fmt->pad == ssd->source_pad) + r = &ssd->crop[ssd->source_pad]; + else + r = &ssd->sink_fmt; + + fmt->format.code = __smiapp_get_mbus_code(subdev, fmt->pad); + fmt->format.width = r->width; + fmt->format.height = r->height; + fmt->format.field = V4L2_FIELD_NONE; + } + + return 0; +} + +static int smiapp_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int rval; + + mutex_lock(&sensor->mutex); + rval = __smiapp_get_format(subdev, cfg, fmt); + mutex_unlock(&sensor->mutex); + + return rval; +} + +static void smiapp_get_crop_compose(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_rect **crops, + struct v4l2_rect **comps, int which) +{ + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + unsigned int i; + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) { + if (crops) + for (i = 0; i < subdev->entity.num_pads; i++) + crops[i] = &ssd->crop[i]; + if (comps) + *comps = &ssd->compose; + } else { + if (crops) { + for (i = 0; i < subdev->entity.num_pads; i++) { + crops[i] = v4l2_subdev_get_try_crop(subdev, cfg, i); + BUG_ON(!crops[i]); + } + } + if (comps) { + *comps = v4l2_subdev_get_try_compose(subdev, cfg, + SMIAPP_PAD_SINK); + BUG_ON(!*comps); + } + } +} + +/* Changes require propagation only on sink pad. */ +static void smiapp_propagate(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, int which, + int target) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct v4l2_rect *comp, *crops[SMIAPP_PADS]; + + smiapp_get_crop_compose(subdev, cfg, crops, &comp, which); + + switch (target) { + case V4L2_SEL_TGT_CROP: + comp->width = crops[SMIAPP_PAD_SINK]->width; + comp->height = crops[SMIAPP_PAD_SINK]->height; + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) { + if (ssd == sensor->scaler) { + sensor->scale_m = + SMIA_LIM(sensor, SCALER_N_MIN); + sensor->scaling_mode = + SMIAPP_SCALING_MODE_NONE; + } else if (ssd == sensor->binner) { + sensor->binning_horizontal = 1; + sensor->binning_vertical = 1; + } + } + fallthrough; + case V4L2_SEL_TGT_COMPOSE: + *crops[SMIAPP_PAD_SRC] = *comp; + break; + default: + BUG(); + } +} + +static const struct smiapp_csi_data_format +*smiapp_validate_csi_data_format(struct smiapp_sensor *sensor, u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(smiapp_csi_data_formats); i++) { + if (sensor->mbus_frame_fmts & (1 << i) + && smiapp_csi_data_formats[i].code == code) + return &smiapp_csi_data_formats[i]; + } + + return sensor->csi_format; +} + +static int smiapp_set_format_source(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + const struct smiapp_csi_data_format *csi_format, + *old_csi_format = sensor->csi_format; + unsigned long *valid_link_freqs; + u32 code = fmt->format.code; + unsigned int i; + int rval; + + rval = __smiapp_get_format(subdev, cfg, fmt); + if (rval) + return rval; + + /* + * Media bus code is changeable on src subdev's source pad. On + * other source pads we just get format here. + */ + if (subdev != &sensor->src->sd) + return 0; + + csi_format = smiapp_validate_csi_data_format(sensor, code); + + fmt->format.code = csi_format->code; + + if (fmt->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return 0; + + sensor->csi_format = csi_format; + + if (csi_format->width != old_csi_format->width) + for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++) + __v4l2_ctrl_modify_range( + sensor->test_data[i], 0, + (1 << csi_format->width) - 1, 1, 0); + + if (csi_format->compressed == old_csi_format->compressed) + return 0; + + valid_link_freqs = + &sensor->valid_link_freqs[sensor->csi_format->compressed + - sensor->compressed_min_bpp]; + + __v4l2_ctrl_modify_range( + sensor->link_freq, 0, + __fls(*valid_link_freqs), ~*valid_link_freqs, + __ffs(*valid_link_freqs)); + + return smiapp_pll_update(sensor); +} + +static int smiapp_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct v4l2_rect *crops[SMIAPP_PADS]; + + mutex_lock(&sensor->mutex); + + if (fmt->pad == ssd->source_pad) { + int rval; + + rval = smiapp_set_format_source(subdev, cfg, fmt); + + mutex_unlock(&sensor->mutex); + + return rval; + } + + /* Sink pad. Width and height are changeable here. */ + fmt->format.code = __smiapp_get_mbus_code(subdev, fmt->pad); + fmt->format.width &= ~1; + fmt->format.height &= ~1; + fmt->format.field = V4L2_FIELD_NONE; + + fmt->format.width = + clamp(fmt->format.width, + SMIA_LIM(sensor, MIN_X_OUTPUT_SIZE), + SMIA_LIM(sensor, MAX_X_OUTPUT_SIZE)); + fmt->format.height = + clamp(fmt->format.height, + SMIA_LIM(sensor, MIN_Y_OUTPUT_SIZE), + SMIA_LIM(sensor, MAX_Y_OUTPUT_SIZE)); + + smiapp_get_crop_compose(subdev, cfg, crops, NULL, fmt->which); + + crops[ssd->sink_pad]->left = 0; + crops[ssd->sink_pad]->top = 0; + crops[ssd->sink_pad]->width = fmt->format.width; + crops[ssd->sink_pad]->height = fmt->format.height; + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + ssd->sink_fmt = *crops[ssd->sink_pad]; + smiapp_propagate(subdev, cfg, fmt->which, + V4L2_SEL_TGT_CROP); + + mutex_unlock(&sensor->mutex); + + return 0; +} + +/* + * Calculate goodness of scaled image size compared to expected image + * size and flags provided. + */ +#define SCALING_GOODNESS 100000 +#define SCALING_GOODNESS_EXTREME 100000000 +static int scaling_goodness(struct v4l2_subdev *subdev, int w, int ask_w, + int h, int ask_h, u32 flags) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int val = 0; + + w &= ~1; + ask_w &= ~1; + h &= ~1; + ask_h &= ~1; + + if (flags & V4L2_SEL_FLAG_GE) { + if (w < ask_w) + val -= SCALING_GOODNESS; + if (h < ask_h) + val -= SCALING_GOODNESS; + } + + if (flags & V4L2_SEL_FLAG_LE) { + if (w > ask_w) + val -= SCALING_GOODNESS; + if (h > ask_h) + val -= SCALING_GOODNESS; + } + + val -= abs(w - ask_w); + val -= abs(h - ask_h); + + if (w < SMIA_LIM(sensor, MIN_X_OUTPUT_SIZE)) + val -= SCALING_GOODNESS_EXTREME; + + dev_dbg(&client->dev, "w %d ask_w %d h %d ask_h %d goodness %d\n", + w, ask_w, h, ask_h, val); + + return val; +} + +static void smiapp_set_compose_binner(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel, + struct v4l2_rect **crops, + struct v4l2_rect *comp) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + unsigned int i; + unsigned int binh = 1, binv = 1; + int best = scaling_goodness( + subdev, + crops[SMIAPP_PAD_SINK]->width, sel->r.width, + crops[SMIAPP_PAD_SINK]->height, sel->r.height, sel->flags); + + for (i = 0; i < sensor->nbinning_subtypes; i++) { + int this = scaling_goodness( + subdev, + crops[SMIAPP_PAD_SINK]->width + / sensor->binning_subtypes[i].horizontal, + sel->r.width, + crops[SMIAPP_PAD_SINK]->height + / sensor->binning_subtypes[i].vertical, + sel->r.height, sel->flags); + + if (this > best) { + binh = sensor->binning_subtypes[i].horizontal; + binv = sensor->binning_subtypes[i].vertical; + best = this; + } + } + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + sensor->binning_vertical = binv; + sensor->binning_horizontal = binh; + } + + sel->r.width = (crops[SMIAPP_PAD_SINK]->width / binh) & ~1; + sel->r.height = (crops[SMIAPP_PAD_SINK]->height / binv) & ~1; +} + +/* + * Calculate best scaling ratio and mode for given output resolution. + * + * Try all of these: horizontal ratio, vertical ratio and smallest + * size possible (horizontally). + * + * Also try whether horizontal scaler or full scaler gives a better + * result. + */ +static void smiapp_set_compose_scaler(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel, + struct v4l2_rect **crops, + struct v4l2_rect *comp) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + u32 min, max, a, b, max_m; + u32 scale_m = SMIA_LIM(sensor, SCALER_N_MIN); + int mode = SMIAPP_SCALING_MODE_HORIZONTAL; + u32 try[4]; + u32 ntry = 0; + unsigned int i; + int best = INT_MIN; + + sel->r.width = min_t(unsigned int, sel->r.width, + crops[SMIAPP_PAD_SINK]->width); + sel->r.height = min_t(unsigned int, sel->r.height, + crops[SMIAPP_PAD_SINK]->height); + + a = crops[SMIAPP_PAD_SINK]->width + * SMIA_LIM(sensor, SCALER_N_MIN) / sel->r.width; + b = crops[SMIAPP_PAD_SINK]->height + * SMIA_LIM(sensor, SCALER_N_MIN) / sel->r.height; + max_m = crops[SMIAPP_PAD_SINK]->width + * SMIA_LIM(sensor, SCALER_N_MIN) + / SMIA_LIM(sensor, MIN_X_OUTPUT_SIZE); + + a = clamp(a, SMIA_LIM(sensor, SCALER_M_MIN), + SMIA_LIM(sensor, SCALER_M_MAX)); + b = clamp(b, SMIA_LIM(sensor, SCALER_M_MIN), + SMIA_LIM(sensor, SCALER_M_MAX)); + max_m = clamp(max_m, SMIA_LIM(sensor, SCALER_M_MIN), + SMIA_LIM(sensor, SCALER_M_MAX)); + + dev_dbg(&client->dev, "scaling: a %d b %d max_m %d\n", a, b, max_m); + + min = min(max_m, min(a, b)); + max = min(max_m, max(a, b)); + + try[ntry] = min; + ntry++; + if (min != max) { + try[ntry] = max; + ntry++; + } + if (max != max_m) { + try[ntry] = min + 1; + ntry++; + if (min != max) { + try[ntry] = max + 1; + ntry++; + } + } + + for (i = 0; i < ntry; i++) { + int this = scaling_goodness( + subdev, + crops[SMIAPP_PAD_SINK]->width + / try[i] + * SMIA_LIM(sensor, SCALER_N_MIN), + sel->r.width, + crops[SMIAPP_PAD_SINK]->height, + sel->r.height, + sel->flags); + + dev_dbg(&client->dev, "trying factor %d (%d)\n", try[i], i); + + if (this > best) { + scale_m = try[i]; + mode = SMIAPP_SCALING_MODE_HORIZONTAL; + best = this; + } + + if (SMIA_LIM(sensor, SCALING_CAPABILITY) + == SMIAPP_SCALING_CAPABILITY_HORIZONTAL) + continue; + + this = scaling_goodness( + subdev, crops[SMIAPP_PAD_SINK]->width + / try[i] + * SMIA_LIM(sensor, SCALER_N_MIN), + sel->r.width, + crops[SMIAPP_PAD_SINK]->height + / try[i] + * SMIA_LIM(sensor, SCALER_N_MIN), + sel->r.height, + sel->flags); + + if (this > best) { + scale_m = try[i]; + mode = SMIAPP_SCALING_MODE_BOTH; + best = this; + } + } + + sel->r.width = + (crops[SMIAPP_PAD_SINK]->width + / scale_m + * SMIA_LIM(sensor, SCALER_N_MIN)) & ~1; + if (mode == SMIAPP_SCALING_MODE_BOTH) + sel->r.height = + (crops[SMIAPP_PAD_SINK]->height + / scale_m + * SMIA_LIM(sensor, SCALER_N_MIN)) + & ~1; + else + sel->r.height = crops[SMIAPP_PAD_SINK]->height; + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + sensor->scale_m = scale_m; + sensor->scaling_mode = mode; + } +} +/* We're only called on source pads. This function sets scaling. */ +static int smiapp_set_compose(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct v4l2_rect *comp, *crops[SMIAPP_PADS]; + + smiapp_get_crop_compose(subdev, cfg, crops, &comp, sel->which); + + sel->r.top = 0; + sel->r.left = 0; + + if (ssd == sensor->binner) + smiapp_set_compose_binner(subdev, cfg, sel, crops, comp); + else + smiapp_set_compose_scaler(subdev, cfg, sel, crops, comp); + + *comp = sel->r; + smiapp_propagate(subdev, cfg, sel->which, V4L2_SEL_TGT_COMPOSE); + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return smiapp_pll_blanking_update(sensor); + + return 0; +} + +static int __smiapp_sel_supported(struct v4l2_subdev *subdev, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + + /* We only implement crop in three places. */ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_BOUNDS: + if (ssd == sensor->pixel_array + && sel->pad == SMIAPP_PA_PAD_SRC) + return 0; + if (ssd == sensor->src + && sel->pad == SMIAPP_PAD_SRC) + return 0; + if (ssd == sensor->scaler + && sel->pad == SMIAPP_PAD_SINK + && SMIA_LIM(sensor, DIGITAL_CROP_CAPABILITY) + == SMIAPP_DIGITAL_CROP_CAPABILITY_INPUT_CROP) + return 0; + return -EINVAL; + case V4L2_SEL_TGT_NATIVE_SIZE: + if (ssd == sensor->pixel_array + && sel->pad == SMIAPP_PA_PAD_SRC) + return 0; + return -EINVAL; + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + if (sel->pad == ssd->source_pad) + return -EINVAL; + if (ssd == sensor->binner) + return 0; + if (ssd == sensor->scaler + && SMIA_LIM(sensor, SCALING_CAPABILITY) + != SMIAPP_SCALING_CAPABILITY_NONE) + return 0; + fallthrough; + default: + return -EINVAL; + } +} + +static int smiapp_set_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct v4l2_rect *src_size, *crops[SMIAPP_PADS]; + struct v4l2_rect _r; + + smiapp_get_crop_compose(subdev, cfg, crops, NULL, sel->which); + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + if (sel->pad == ssd->sink_pad) + src_size = &ssd->sink_fmt; + else + src_size = &ssd->compose; + } else { + if (sel->pad == ssd->sink_pad) { + _r.left = 0; + _r.top = 0; + _r.width = v4l2_subdev_get_try_format(subdev, cfg, sel->pad) + ->width; + _r.height = v4l2_subdev_get_try_format(subdev, cfg, sel->pad) + ->height; + src_size = &_r; + } else { + src_size = v4l2_subdev_get_try_compose( + subdev, cfg, ssd->sink_pad); + } + } + + if (ssd == sensor->src && sel->pad == SMIAPP_PAD_SRC) { + sel->r.left = 0; + sel->r.top = 0; + } + + sel->r.width = min(sel->r.width, src_size->width); + sel->r.height = min(sel->r.height, src_size->height); + + sel->r.left = min_t(int, sel->r.left, src_size->width - sel->r.width); + sel->r.top = min_t(int, sel->r.top, src_size->height - sel->r.height); + + *crops[sel->pad] = sel->r; + + if (ssd != sensor->pixel_array && sel->pad == SMIAPP_PAD_SINK) + smiapp_propagate(subdev, cfg, sel->which, + V4L2_SEL_TGT_CROP); + + return 0; +} + +static void smiapp_get_native_size(struct smiapp_subdev *ssd, + struct v4l2_rect *r) +{ + r->top = 0; + r->left = 0; + r->width = SMIA_LIM(ssd->sensor, X_ADDR_MAX) + 1; + r->height = SMIA_LIM(ssd->sensor, Y_ADDR_MAX) + 1; +} + +static int __smiapp_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct v4l2_rect *comp, *crops[SMIAPP_PADS]; + struct v4l2_rect sink_fmt; + int ret; + + ret = __smiapp_sel_supported(subdev, sel); + if (ret) + return ret; + + smiapp_get_crop_compose(subdev, cfg, crops, &comp, sel->which); + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + sink_fmt = ssd->sink_fmt; + } else { + struct v4l2_mbus_framefmt *fmt = + v4l2_subdev_get_try_format(subdev, cfg, ssd->sink_pad); + + sink_fmt.left = 0; + sink_fmt.top = 0; + sink_fmt.width = fmt->width; + sink_fmt.height = fmt->height; + } + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + if (ssd == sensor->pixel_array) + smiapp_get_native_size(ssd, &sel->r); + else if (sel->pad == ssd->sink_pad) + sel->r = sink_fmt; + else + sel->r = *comp; + break; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + sel->r = *crops[sel->pad]; + break; + case V4L2_SEL_TGT_COMPOSE: + sel->r = *comp; + break; + } + + return 0; +} + +static int smiapp_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int rval; + + mutex_lock(&sensor->mutex); + rval = __smiapp_get_selection(subdev, cfg, sel); + mutex_unlock(&sensor->mutex); + + return rval; +} +static int smiapp_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int ret; + + ret = __smiapp_sel_supported(subdev, sel); + if (ret) + return ret; + + mutex_lock(&sensor->mutex); + + sel->r.left = max(0, sel->r.left & ~1); + sel->r.top = max(0, sel->r.top & ~1); + sel->r.width = SMIAPP_ALIGN_DIM(sel->r.width, sel->flags); + sel->r.height = SMIAPP_ALIGN_DIM(sel->r.height, sel->flags); + + sel->r.width = max_t(unsigned int, + SMIA_LIM(sensor, MIN_X_OUTPUT_SIZE), + sel->r.width); + sel->r.height = max_t(unsigned int, + SMIA_LIM(sensor, MIN_Y_OUTPUT_SIZE), + sel->r.height); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + ret = smiapp_set_crop(subdev, cfg, sel); + break; + case V4L2_SEL_TGT_COMPOSE: + ret = smiapp_set_compose(subdev, cfg, sel); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&sensor->mutex); + return ret; +} + +static int smiapp_get_skip_frames(struct v4l2_subdev *subdev, u32 *frames) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + + *frames = sensor->frame_skip; + return 0; +} + +static int smiapp_get_skip_top_lines(struct v4l2_subdev *subdev, u32 *lines) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + + *lines = sensor->image_start; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * sysfs attributes + */ + +static ssize_t +smiapp_sysfs_nvm_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev)); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int rval; + + if (!sensor->dev_init_done) + return -EBUSY; + + rval = smiapp_pm_get_init(sensor); + if (rval < 0) + return -ENODEV; + + rval = smiapp_read_nvm(sensor, buf, PAGE_SIZE); + if (rval < 0) { + pm_runtime_put(&client->dev); + dev_err(&client->dev, "nvm read failed\n"); + return -ENODEV; + } + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + /* + * NVM is still way below a PAGE_SIZE, so we can safely + * assume this for now. + */ + return rval; +} +static DEVICE_ATTR(nvm, S_IRUGO, smiapp_sysfs_nvm_read, NULL); + +static ssize_t +smiapp_sysfs_ident_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev)); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_module_info *minfo = &sensor->minfo; + + return snprintf(buf, PAGE_SIZE, "%2.2x%4.4x%2.2x\n", + minfo->manufacturer_id, minfo->model_id, + minfo->revision_number_major) + 1; +} + +static DEVICE_ATTR(ident, S_IRUGO, smiapp_sysfs_ident_read, NULL); + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +static int smiapp_identify_module(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct smiapp_module_info *minfo = &sensor->minfo; + unsigned int i; + int rval = 0; + + minfo->name = SMIAPP_NAME; + + /* Module info */ + rval = smiapp_read_8only(sensor, SMIAPP_REG_U8_MANUFACTURER_ID, + &minfo->manufacturer_id); + if (!rval) + rval = smiapp_read_8only(sensor, SMIAPP_REG_U16_MODEL_ID, + &minfo->model_id); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_REVISION_NUMBER_MAJOR, + &minfo->revision_number_major); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_REVISION_NUMBER_MINOR, + &minfo->revision_number_minor); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_MODULE_DATE_YEAR, + &minfo->module_year); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_MODULE_DATE_MONTH, + &minfo->module_month); + if (!rval) + rval = smiapp_read_8only(sensor, SMIAPP_REG_U8_MODULE_DATE_DAY, + &minfo->module_day); + + /* Sensor info */ + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_SENSOR_MANUFACTURER_ID, + &minfo->sensor_manufacturer_id); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U16_SENSOR_MODEL_ID, + &minfo->sensor_model_id); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_SENSOR_REVISION_NUMBER, + &minfo->sensor_revision_number); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_SENSOR_FIRMWARE_VERSION, + &minfo->sensor_firmware_version); + + /* SMIA */ + if (!rval) + rval = smiapp_read_8only(sensor, SMIAPP_REG_U8_SMIA_VERSION, + &minfo->smia_version); + if (!rval) + rval = smiapp_read_8only(sensor, SMIAPP_REG_U8_SMIAPP_VERSION, + &minfo->smiapp_version); + + if (rval) { + dev_err(&client->dev, "sensor detection failed\n"); + return -ENODEV; + } + + dev_dbg(&client->dev, "module 0x%2.2x-0x%4.4x\n", + minfo->manufacturer_id, minfo->model_id); + + dev_dbg(&client->dev, + "module revision 0x%2.2x-0x%2.2x date %2.2d-%2.2d-%2.2d\n", + minfo->revision_number_major, minfo->revision_number_minor, + minfo->module_year, minfo->module_month, minfo->module_day); + + dev_dbg(&client->dev, "sensor 0x%2.2x-0x%4.4x\n", + minfo->sensor_manufacturer_id, minfo->sensor_model_id); + + dev_dbg(&client->dev, + "sensor revision 0x%2.2x firmware version 0x%2.2x\n", + minfo->sensor_revision_number, minfo->sensor_firmware_version); + + dev_dbg(&client->dev, "smia version %2.2d smiapp version %2.2d\n", + minfo->smia_version, minfo->smiapp_version); + + /* + * Some modules have bad data in the lvalues below. Hope the + * rvalues have better stuff. The lvalues are module + * parameters whereas the rvalues are sensor parameters. + */ + if (!minfo->manufacturer_id && !minfo->model_id) { + minfo->manufacturer_id = minfo->sensor_manufacturer_id; + minfo->model_id = minfo->sensor_model_id; + minfo->revision_number_major = minfo->sensor_revision_number; + } + + for (i = 0; i < ARRAY_SIZE(smiapp_module_idents); i++) { + if (smiapp_module_idents[i].manufacturer_id + != minfo->manufacturer_id) + continue; + if (smiapp_module_idents[i].model_id != minfo->model_id) + continue; + if (smiapp_module_idents[i].flags + & SMIAPP_MODULE_IDENT_FLAG_REV_LE) { + if (smiapp_module_idents[i].revision_number_major + < minfo->revision_number_major) + continue; + } else { + if (smiapp_module_idents[i].revision_number_major + != minfo->revision_number_major) + continue; + } + + minfo->name = smiapp_module_idents[i].name; + minfo->quirk = smiapp_module_idents[i].quirk; + break; + } + + if (i >= ARRAY_SIZE(smiapp_module_idents)) + dev_warn(&client->dev, + "no quirks for this module; let's hope it's fully compliant\n"); + + dev_dbg(&client->dev, "the sensor is called %s, ident %2.2x%4.4x%2.2x\n", + minfo->name, minfo->manufacturer_id, minfo->model_id, + minfo->revision_number_major); + + return 0; +} + +static const struct v4l2_subdev_ops smiapp_ops; +static const struct v4l2_subdev_internal_ops smiapp_internal_ops; +static const struct media_entity_operations smiapp_entity_ops; + +static int smiapp_register_subdev(struct smiapp_sensor *sensor, + struct smiapp_subdev *ssd, + struct smiapp_subdev *sink_ssd, + u16 source_pad, u16 sink_pad, u32 link_flags) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + if (!sink_ssd) + return 0; + + rval = media_entity_pads_init(&ssd->sd.entity, + ssd->npads, ssd->pads); + if (rval) { + dev_err(&client->dev, + "media_entity_pads_init failed\n"); + return rval; + } + + rval = v4l2_device_register_subdev(sensor->src->sd.v4l2_dev, + &ssd->sd); + if (rval) { + dev_err(&client->dev, + "v4l2_device_register_subdev failed\n"); + return rval; + } + + rval = media_create_pad_link(&ssd->sd.entity, source_pad, + &sink_ssd->sd.entity, sink_pad, + link_flags); + if (rval) { + dev_err(&client->dev, + "media_create_pad_link failed\n"); + v4l2_device_unregister_subdev(&ssd->sd); + return rval; + } + + return 0; +} + +static void smiapp_unregistered(struct v4l2_subdev *subdev) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + unsigned int i; + + for (i = 1; i < sensor->ssds_used; i++) + v4l2_device_unregister_subdev(&sensor->ssds[i].sd); +} + +static int smiapp_registered(struct v4l2_subdev *subdev) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int rval; + + if (sensor->scaler) { + rval = smiapp_register_subdev( + sensor, sensor->binner, sensor->scaler, + SMIAPP_PAD_SRC, SMIAPP_PAD_SINK, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); + if (rval < 0) + return rval; + } + + rval = smiapp_register_subdev( + sensor, sensor->pixel_array, sensor->binner, + SMIAPP_PA_PAD_SRC, SMIAPP_PAD_SINK, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); + if (rval) + goto out_err; + + return 0; + +out_err: + smiapp_unregistered(subdev); + + return rval; +} + +static void smiapp_cleanup(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + + device_remove_file(&client->dev, &dev_attr_nvm); + device_remove_file(&client->dev, &dev_attr_ident); + + smiapp_free_controls(sensor); +} + +static void smiapp_create_subdev(struct smiapp_sensor *sensor, + struct smiapp_subdev *ssd, const char *name, + unsigned short num_pads) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + + if (!ssd) + return; + + if (ssd != sensor->src) + v4l2_subdev_init(&ssd->sd, &smiapp_ops); + + ssd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ssd->sensor = sensor; + + ssd->npads = num_pads; + ssd->source_pad = num_pads - 1; + + v4l2_i2c_subdev_set_name(&ssd->sd, client, sensor->minfo.name, name); + + smiapp_get_native_size(ssd, &ssd->sink_fmt); + + ssd->compose.width = ssd->sink_fmt.width; + ssd->compose.height = ssd->sink_fmt.height; + ssd->crop[ssd->source_pad] = ssd->compose; + ssd->pads[ssd->source_pad].flags = MEDIA_PAD_FL_SOURCE; + if (ssd != sensor->pixel_array) { + ssd->crop[ssd->sink_pad] = ssd->compose; + ssd->pads[ssd->sink_pad].flags = MEDIA_PAD_FL_SINK; + } + + ssd->sd.entity.ops = &smiapp_entity_ops; + + if (ssd == sensor->src) + return; + + ssd->sd.internal_ops = &smiapp_internal_ops; + ssd->sd.owner = THIS_MODULE; + ssd->sd.dev = &client->dev; + v4l2_set_subdevdata(&ssd->sd, client); +} + +static int smiapp_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct smiapp_subdev *ssd = to_smiapp_subdev(sd); + struct smiapp_sensor *sensor = ssd->sensor; + unsigned int i; + + mutex_lock(&sensor->mutex); + + for (i = 0; i < ssd->npads; i++) { + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->pad, i); + struct v4l2_rect *try_crop = + v4l2_subdev_get_try_crop(sd, fh->pad, i); + struct v4l2_rect *try_comp; + + smiapp_get_native_size(ssd, try_crop); + + try_fmt->width = try_crop->width; + try_fmt->height = try_crop->height; + try_fmt->code = sensor->internal_csi_format->code; + try_fmt->field = V4L2_FIELD_NONE; + + if (ssd == sensor->pixel_array) + continue; + + try_comp = v4l2_subdev_get_try_compose(sd, fh->pad, i); + *try_comp = *try_crop; + } + + mutex_unlock(&sensor->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops smiapp_video_ops = { + .s_stream = smiapp_set_stream, +}; + +static const struct v4l2_subdev_pad_ops smiapp_pad_ops = { + .enum_mbus_code = smiapp_enum_mbus_code, + .get_fmt = smiapp_get_format, + .set_fmt = smiapp_set_format, + .get_selection = smiapp_get_selection, + .set_selection = smiapp_set_selection, +}; + +static const struct v4l2_subdev_sensor_ops smiapp_sensor_ops = { + .g_skip_frames = smiapp_get_skip_frames, + .g_skip_top_lines = smiapp_get_skip_top_lines, +}; + +static const struct v4l2_subdev_ops smiapp_ops = { + .video = &smiapp_video_ops, + .pad = &smiapp_pad_ops, + .sensor = &smiapp_sensor_ops, +}; + +static const struct media_entity_operations smiapp_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops smiapp_internal_src_ops = { + .registered = smiapp_registered, + .unregistered = smiapp_unregistered, + .open = smiapp_open, +}; + +static const struct v4l2_subdev_internal_ops smiapp_internal_ops = { + .open = smiapp_open, +}; + +/* ----------------------------------------------------------------------------- + * I2C Driver + */ + +static int __maybe_unused smiapp_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + bool streaming = sensor->streaming; + int rval; + + rval = pm_runtime_get_sync(dev); + if (rval < 0) { + if (rval != -EBUSY && rval != -EAGAIN) + pm_runtime_set_active(&client->dev); + pm_runtime_put(dev); + return -EAGAIN; + } + + if (sensor->streaming) + smiapp_stop_streaming(sensor); + + /* save state for resume */ + sensor->streaming = streaming; + + return 0; +} + +static int __maybe_unused smiapp_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int rval = 0; + + pm_runtime_put(dev); + + if (sensor->streaming) + rval = smiapp_start_streaming(sensor); + + return rval; +} + +static struct smiapp_hwconfig *smiapp_get_hwconfig(struct device *dev) +{ + struct smiapp_hwconfig *hwcfg; + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + u32 rotation; + int i; + int rval; + + if (!fwnode) + return dev->platform_data; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return NULL; + + bus_cfg.bus_type = V4L2_MBUS_CSI2_DPHY; + rval = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + if (rval == -ENXIO) { + bus_cfg = (struct v4l2_fwnode_endpoint) + { .bus_type = V4L2_MBUS_CCP2 }; + rval = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + } + if (rval) + goto out_err; + + hwcfg = devm_kzalloc(dev, sizeof(*hwcfg), GFP_KERNEL); + if (!hwcfg) + goto out_err; + + switch (bus_cfg.bus_type) { + case V4L2_MBUS_CSI2_DPHY: + hwcfg->csi_signalling_mode = SMIAPP_CSI_SIGNALLING_MODE_CSI2; + hwcfg->lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; + break; + case V4L2_MBUS_CCP2: + hwcfg->csi_signalling_mode = (bus_cfg.bus.mipi_csi1.strobe) ? + SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE : + SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK; + hwcfg->lanes = 1; + break; + default: + dev_err(dev, "unsupported bus %u\n", bus_cfg.bus_type); + goto out_err; + } + + dev_dbg(dev, "lanes %u\n", hwcfg->lanes); + + rval = fwnode_property_read_u32(fwnode, "rotation", &rotation); + if (!rval) { + switch (rotation) { + case 180: + hwcfg->module_board_orient = + SMIAPP_MODULE_BOARD_ORIENT_180; + fallthrough; + case 0: + break; + default: + dev_err(dev, "invalid rotation %u\n", rotation); + goto out_err; + } + } + + rval = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &hwcfg->ext_clk); + if (rval) + dev_info(dev, "can't get clock-frequency\n"); + + dev_dbg(dev, "clk %d, mode %d\n", hwcfg->ext_clk, + hwcfg->csi_signalling_mode); + + if (!bus_cfg.nr_of_link_frequencies) { + dev_warn(dev, "no link frequencies defined\n"); + goto out_err; + } + + hwcfg->op_sys_clock = devm_kcalloc( + dev, bus_cfg.nr_of_link_frequencies + 1 /* guardian */, + sizeof(*hwcfg->op_sys_clock), GFP_KERNEL); + if (!hwcfg->op_sys_clock) + goto out_err; + + for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) { + hwcfg->op_sys_clock[i] = bus_cfg.link_frequencies[i]; + dev_dbg(dev, "freq %d: %lld\n", i, hwcfg->op_sys_clock[i]); + } + + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(ep); + return hwcfg; + +out_err: + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(ep); + return NULL; +} + +static int smiapp_probe(struct i2c_client *client) +{ + struct smiapp_sensor *sensor; + struct smiapp_hwconfig *hwcfg = smiapp_get_hwconfig(&client->dev); + unsigned int i; + int rval; + + if (hwcfg == NULL) + return -ENODEV; + + sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); + if (sensor == NULL) + return -ENOMEM; + + sensor->hwcfg = hwcfg; + sensor->src = &sensor->ssds[sensor->ssds_used]; + + v4l2_i2c_subdev_init(&sensor->src->sd, client, &smiapp_ops); + sensor->src->sd.internal_ops = &smiapp_internal_src_ops; + + sensor->vana = devm_regulator_get(&client->dev, "vana"); + if (IS_ERR(sensor->vana)) { + dev_err(&client->dev, "could not get regulator for vana\n"); + return PTR_ERR(sensor->vana); + } + + sensor->ext_clk = devm_clk_get(&client->dev, NULL); + if (PTR_ERR(sensor->ext_clk) == -ENOENT) { + dev_info(&client->dev, "no clock defined, continuing...\n"); + sensor->ext_clk = NULL; + } else if (IS_ERR(sensor->ext_clk)) { + dev_err(&client->dev, "could not get clock (%ld)\n", + PTR_ERR(sensor->ext_clk)); + return -EPROBE_DEFER; + } + + if (sensor->ext_clk) { + if (sensor->hwcfg->ext_clk) { + unsigned long rate; + + rval = clk_set_rate(sensor->ext_clk, + sensor->hwcfg->ext_clk); + if (rval < 0) { + dev_err(&client->dev, + "unable to set clock freq to %u\n", + sensor->hwcfg->ext_clk); + return rval; + } + + rate = clk_get_rate(sensor->ext_clk); + if (rate != sensor->hwcfg->ext_clk) { + dev_err(&client->dev, + "can't set clock freq, asked for %u but got %lu\n", + sensor->hwcfg->ext_clk, rate); + return rval; + } + } else { + sensor->hwcfg->ext_clk = clk_get_rate(sensor->ext_clk); + dev_dbg(&client->dev, "obtained clock freq %u\n", + sensor->hwcfg->ext_clk); + } + } else if (sensor->hwcfg->ext_clk) { + dev_dbg(&client->dev, "assuming clock freq %u\n", + sensor->hwcfg->ext_clk); + } else { + dev_err(&client->dev, "unable to obtain clock freq\n"); + return -EINVAL; + } + + sensor->xshutdown = devm_gpiod_get_optional(&client->dev, "xshutdown", + GPIOD_OUT_LOW); + if (IS_ERR(sensor->xshutdown)) + return PTR_ERR(sensor->xshutdown); + + rval = smiapp_power_on(&client->dev); + if (rval < 0) + return rval; + + mutex_init(&sensor->mutex); + + rval = smiapp_identify_module(sensor); + if (rval) { + rval = -ENODEV; + goto out_power_off; + } + + rval = smiapp_read_all_smia_limits(sensor); + if (rval) { + rval = -ENODEV; + goto out_power_off; + } + + rval = smiapp_read_frame_fmt(sensor); + if (rval) { + rval = -ENODEV; + goto out_power_off; + } + + /* + * Handle Sensor Module orientation on the board. + * + * The application of H-FLIP and V-FLIP on the sensor is modified by + * the sensor orientation on the board. + * + * For SMIAPP_BOARD_SENSOR_ORIENT_180 the default behaviour is to set + * both H-FLIP and V-FLIP for normal operation which also implies + * that a set/unset operation for user space HFLIP and VFLIP v4l2 + * controls will need to be internally inverted. + * + * Rotation also changes the bayer pattern. + */ + if (sensor->hwcfg->module_board_orient == + SMIAPP_MODULE_BOARD_ORIENT_180) + sensor->hvflip_inv_mask = SMIAPP_IMAGE_ORIENTATION_HFLIP | + SMIAPP_IMAGE_ORIENTATION_VFLIP; + + rval = smiapp_call_quirk(sensor, limits); + if (rval) { + dev_err(&client->dev, "limits quirks failed\n"); + goto out_power_off; + } + + if (SMIA_LIM(sensor, BINNING_CAPABILITY)) { + u32 val; + + rval = smiapp_read(sensor, + SMIAPP_REG_U8_BINNING_SUBTYPES, &val); + if (rval < 0) { + rval = -ENODEV; + goto out_power_off; + } + sensor->nbinning_subtypes = min_t(u8, val, + SMIAPP_BINNING_SUBTYPES); + + for (i = 0; i < sensor->nbinning_subtypes; i++) { + rval = smiapp_read( + sensor, SMIAPP_REG_U8_BINNING_TYPE_n(i), &val); + if (rval < 0) { + rval = -ENODEV; + goto out_power_off; + } + sensor->binning_subtypes[i] = + *(struct smiapp_binning_subtype *)&val; + + dev_dbg(&client->dev, "binning %xx%x\n", + sensor->binning_subtypes[i].horizontal, + sensor->binning_subtypes[i].vertical); + } + } + sensor->binning_horizontal = 1; + sensor->binning_vertical = 1; + + if (device_create_file(&client->dev, &dev_attr_ident) != 0) { + dev_err(&client->dev, "sysfs ident entry creation failed\n"); + rval = -ENOENT; + goto out_power_off; + } + + if (sensor->minfo.smiapp_version && + SMIA_LIM(sensor, DATA_TRANSFER_IF_CAPABILITY) & + SMIAPP_DATA_TRANSFER_IF_CAPABILITY_SUPPORTED) { + if (device_create_file(&client->dev, &dev_attr_nvm) != 0) { + dev_err(&client->dev, "sysfs nvm entry failed\n"); + rval = -EBUSY; + goto out_cleanup; + } + } + + /* We consider this as profile 0 sensor if any of these are zero. */ + if (!SMIA_LIM(sensor, MIN_OP_SYS_CLK_DIV) || + !SMIA_LIM(sensor, MAX_OP_SYS_CLK_DIV) || + !SMIA_LIM(sensor, MIN_OP_PIX_CLK_DIV) || + !SMIA_LIM(sensor, MAX_OP_PIX_CLK_DIV)) { + sensor->minfo.smiapp_profile = SMIAPP_PROFILE_0; + } else if (SMIA_LIM(sensor, SCALING_CAPABILITY) + != SMIAPP_SCALING_CAPABILITY_NONE) { + if (SMIA_LIM(sensor, SCALING_CAPABILITY) + == SMIAPP_SCALING_CAPABILITY_HORIZONTAL) + sensor->minfo.smiapp_profile = SMIAPP_PROFILE_1; + else + sensor->minfo.smiapp_profile = SMIAPP_PROFILE_2; + sensor->scaler = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + } else if (SMIA_LIM(sensor, DIGITAL_CROP_CAPABILITY) + == SMIAPP_DIGITAL_CROP_CAPABILITY_INPUT_CROP) { + sensor->scaler = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + } + sensor->binner = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + sensor->pixel_array = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + + sensor->scale_m = SMIA_LIM(sensor, SCALER_N_MIN); + + /* prepare PLL configuration input values */ + sensor->pll.bus_type = SMIAPP_PLL_BUS_TYPE_CSI2; + sensor->pll.csi2.lanes = sensor->hwcfg->lanes; + sensor->pll.ext_clk_freq_hz = sensor->hwcfg->ext_clk; + sensor->pll.scale_n = SMIA_LIM(sensor, SCALER_N_MIN); + /* Profile 0 sensors have no separate OP clock branch. */ + if (sensor->minfo.smiapp_profile == SMIAPP_PROFILE_0) + sensor->pll.flags |= SMIAPP_PLL_FLAG_NO_OP_CLOCKS; + + smiapp_create_subdev(sensor, sensor->scaler, " scaler", 2); + smiapp_create_subdev(sensor, sensor->binner, " binner", 2); + smiapp_create_subdev(sensor, sensor->pixel_array, " pixel_array", 1); + + dev_dbg(&client->dev, "profile %d\n", sensor->minfo.smiapp_profile); + + sensor->pixel_array->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + rval = smiapp_init_controls(sensor); + if (rval < 0) + goto out_cleanup; + + rval = smiapp_call_quirk(sensor, init); + if (rval) + goto out_cleanup; + + rval = smiapp_get_mbus_formats(sensor); + if (rval) { + rval = -ENODEV; + goto out_cleanup; + } + + rval = smiapp_init_late_controls(sensor); + if (rval) { + rval = -ENODEV; + goto out_cleanup; + } + + mutex_lock(&sensor->mutex); + rval = smiapp_pll_blanking_update(sensor); + mutex_unlock(&sensor->mutex); + if (rval) { + dev_err(&client->dev, "update mode failed\n"); + goto out_cleanup; + } + + sensor->streaming = false; + sensor->dev_init_done = true; + + rval = media_entity_pads_init(&sensor->src->sd.entity, 2, + sensor->src->pads); + if (rval < 0) + goto out_media_entity_cleanup; + + pm_runtime_set_active(&client->dev); + pm_runtime_get_noresume(&client->dev); + pm_runtime_enable(&client->dev); + + rval = v4l2_async_register_subdev_sensor_common(&sensor->src->sd); + if (rval < 0) + goto out_disable_runtime_pm; + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return 0; + +out_disable_runtime_pm: + pm_runtime_put_noidle(&client->dev); + pm_runtime_disable(&client->dev); + +out_media_entity_cleanup: + media_entity_cleanup(&sensor->src->sd.entity); + +out_cleanup: + smiapp_cleanup(sensor); + +out_power_off: + smiapp_power_off(&client->dev); + mutex_destroy(&sensor->mutex); + + return rval; +} + +static int smiapp_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + unsigned int i; + + v4l2_async_unregister_subdev(subdev); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + smiapp_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + + for (i = 0; i < sensor->ssds_used; i++) { + v4l2_device_unregister_subdev(&sensor->ssds[i].sd); + media_entity_cleanup(&sensor->ssds[i].sd.entity); + } + smiapp_cleanup(sensor); + mutex_destroy(&sensor->mutex); + + return 0; +} + +static const struct of_device_id smiapp_of_table[] = { + { .compatible = "nokia,smia" }, + { }, +}; +MODULE_DEVICE_TABLE(of, smiapp_of_table); + +static const struct i2c_device_id smiapp_id_table[] = { + { SMIAPP_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, smiapp_id_table); + +static const struct dev_pm_ops smiapp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(smiapp_suspend, smiapp_resume) + SET_RUNTIME_PM_OPS(smiapp_power_off, smiapp_power_on, NULL) +}; + +static struct i2c_driver smiapp_i2c_driver = { + .driver = { + .of_match_table = smiapp_of_table, + .name = SMIAPP_NAME, + .pm = &smiapp_pm_ops, + }, + .probe_new = smiapp_probe, + .remove = smiapp_remove, + .id_table = smiapp_id_table, +}; + +module_i2c_driver(smiapp_i2c_driver); + +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>"); +MODULE_DESCRIPTION("Generic SMIA/SMIA++ camera module driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/smiapp/smiapp-limits.c b/drivers/media/i2c/smiapp/smiapp-limits.c new file mode 100644 index 000000000..de5ee5296 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-limits.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/smiapp/smiapp-limits.c + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ + +#include "smiapp.h" + +struct smiapp_reg_limits smiapp_reg_limits[] = { + { SMIAPP_REG_U16_ANALOGUE_GAIN_CAPABILITY, "analogue_gain_capability" }, /* 0 */ + { SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_MIN, "analogue_gain_code_min" }, + { SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_MAX, "analogue_gain_code_max" }, + { SMIAPP_REG_U8_THS_ZERO_MIN, "ths_zero_min" }, + { SMIAPP_REG_U8_TCLK_TRAIL_MIN, "tclk_trail_min" }, + { SMIAPP_REG_U16_INTEGRATION_TIME_CAPABILITY, "integration_time_capability" }, /* 5 */ + { SMIAPP_REG_U16_COARSE_INTEGRATION_TIME_MIN, "coarse_integration_time_min" }, + { SMIAPP_REG_U16_COARSE_INTEGRATION_TIME_MAX_MARGIN, "coarse_integration_time_max_margin" }, + { SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MIN, "fine_integration_time_min" }, + { SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MAX_MARGIN, "fine_integration_time_max_margin" }, + { SMIAPP_REG_U16_DIGITAL_GAIN_CAPABILITY, "digital_gain_capability" }, /* 10 */ + { SMIAPP_REG_U16_DIGITAL_GAIN_MIN, "digital_gain_min" }, + { SMIAPP_REG_U16_DIGITAL_GAIN_MAX, "digital_gain_max" }, + { SMIAPP_REG_F32_MIN_EXT_CLK_FREQ_HZ, "min_ext_clk_freq_hz" }, + { SMIAPP_REG_F32_MAX_EXT_CLK_FREQ_HZ, "max_ext_clk_freq_hz" }, + { SMIAPP_REG_U16_MIN_PRE_PLL_CLK_DIV, "min_pre_pll_clk_div" }, /* 15 */ + { SMIAPP_REG_U16_MAX_PRE_PLL_CLK_DIV, "max_pre_pll_clk_div" }, + { SMIAPP_REG_F32_MIN_PLL_IP_FREQ_HZ, "min_pll_ip_freq_hz" }, + { SMIAPP_REG_F32_MAX_PLL_IP_FREQ_HZ, "max_pll_ip_freq_hz" }, + { SMIAPP_REG_U16_MIN_PLL_MULTIPLIER, "min_pll_multiplier" }, + { SMIAPP_REG_U16_MAX_PLL_MULTIPLIER, "max_pll_multiplier" }, /* 20 */ + { SMIAPP_REG_F32_MIN_PLL_OP_FREQ_HZ, "min_pll_op_freq_hz" }, + { SMIAPP_REG_F32_MAX_PLL_OP_FREQ_HZ, "max_pll_op_freq_hz" }, + { SMIAPP_REG_U16_MIN_VT_SYS_CLK_DIV, "min_vt_sys_clk_div" }, + { SMIAPP_REG_U16_MAX_VT_SYS_CLK_DIV, "max_vt_sys_clk_div" }, + { SMIAPP_REG_F32_MIN_VT_SYS_CLK_FREQ_HZ, "min_vt_sys_clk_freq_hz" }, /* 25 */ + { SMIAPP_REG_F32_MAX_VT_SYS_CLK_FREQ_HZ, "max_vt_sys_clk_freq_hz" }, + { SMIAPP_REG_F32_MIN_VT_PIX_CLK_FREQ_HZ, "min_vt_pix_clk_freq_hz" }, + { SMIAPP_REG_F32_MAX_VT_PIX_CLK_FREQ_HZ, "max_vt_pix_clk_freq_hz" }, + { SMIAPP_REG_U16_MIN_VT_PIX_CLK_DIV, "min_vt_pix_clk_div" }, + { SMIAPP_REG_U16_MAX_VT_PIX_CLK_DIV, "max_vt_pix_clk_div" }, /* 30 */ + { SMIAPP_REG_U16_MIN_FRAME_LENGTH_LINES, "min_frame_length_lines" }, + { SMIAPP_REG_U16_MAX_FRAME_LENGTH_LINES, "max_frame_length_lines" }, + { SMIAPP_REG_U16_MIN_LINE_LENGTH_PCK, "min_line_length_pck" }, + { SMIAPP_REG_U16_MAX_LINE_LENGTH_PCK, "max_line_length_pck" }, + { SMIAPP_REG_U16_MIN_LINE_BLANKING_PCK, "min_line_blanking_pck" }, /* 35 */ + { SMIAPP_REG_U16_MIN_FRAME_BLANKING_LINES, "min_frame_blanking_lines" }, + { SMIAPP_REG_U8_MIN_LINE_LENGTH_PCK_STEP_SIZE, "min_line_length_pck_step_size" }, + { SMIAPP_REG_U16_MIN_OP_SYS_CLK_DIV, "min_op_sys_clk_div" }, + { SMIAPP_REG_U16_MAX_OP_SYS_CLK_DIV, "max_op_sys_clk_div" }, + { SMIAPP_REG_F32_MIN_OP_SYS_CLK_FREQ_HZ, "min_op_sys_clk_freq_hz" }, /* 40 */ + { SMIAPP_REG_F32_MAX_OP_SYS_CLK_FREQ_HZ, "max_op_sys_clk_freq_hz" }, + { SMIAPP_REG_U16_MIN_OP_PIX_CLK_DIV, "min_op_pix_clk_div" }, + { SMIAPP_REG_U16_MAX_OP_PIX_CLK_DIV, "max_op_pix_clk_div" }, + { SMIAPP_REG_F32_MIN_OP_PIX_CLK_FREQ_HZ, "min_op_pix_clk_freq_hz" }, + { SMIAPP_REG_F32_MAX_OP_PIX_CLK_FREQ_HZ, "max_op_pix_clk_freq_hz" }, /* 45 */ + { SMIAPP_REG_U16_X_ADDR_MIN, "x_addr_min" }, + { SMIAPP_REG_U16_Y_ADDR_MIN, "y_addr_min" }, + { SMIAPP_REG_U16_X_ADDR_MAX, "x_addr_max" }, + { SMIAPP_REG_U16_Y_ADDR_MAX, "y_addr_max" }, + { SMIAPP_REG_U16_MIN_X_OUTPUT_SIZE, "min_x_output_size" }, /* 50 */ + { SMIAPP_REG_U16_MIN_Y_OUTPUT_SIZE, "min_y_output_size" }, + { SMIAPP_REG_U16_MAX_X_OUTPUT_SIZE, "max_x_output_size" }, + { SMIAPP_REG_U16_MAX_Y_OUTPUT_SIZE, "max_y_output_size" }, + { SMIAPP_REG_U16_MIN_EVEN_INC, "min_even_inc" }, + { SMIAPP_REG_U16_MAX_EVEN_INC, "max_even_inc" }, /* 55 */ + { SMIAPP_REG_U16_MIN_ODD_INC, "min_odd_inc" }, + { SMIAPP_REG_U16_MAX_ODD_INC, "max_odd_inc" }, + { SMIAPP_REG_U16_SCALING_CAPABILITY, "scaling_capability" }, + { SMIAPP_REG_U16_SCALER_M_MIN, "scaler_m_min" }, + { SMIAPP_REG_U16_SCALER_M_MAX, "scaler_m_max" }, /* 60 */ + { SMIAPP_REG_U16_SCALER_N_MIN, "scaler_n_min" }, + { SMIAPP_REG_U16_SCALER_N_MAX, "scaler_n_max" }, + { SMIAPP_REG_U16_SPATIAL_SAMPLING_CAPABILITY, "spatial_sampling_capability" }, + { SMIAPP_REG_U8_DIGITAL_CROP_CAPABILITY, "digital_crop_capability" }, + { SMIAPP_REG_U16_COMPRESSION_CAPABILITY, "compression_capability" }, /* 65 */ + { SMIAPP_REG_U8_FIFO_SUPPORT_CAPABILITY, "fifo_support_capability" }, + { SMIAPP_REG_U8_DPHY_CTRL_CAPABILITY, "dphy_ctrl_capability" }, + { SMIAPP_REG_U8_CSI_LANE_MODE_CAPABILITY, "csi_lane_mode_capability" }, + { SMIAPP_REG_U8_CSI_SIGNALLING_MODE_CAPABILITY, "csi_signalling_mode_capability" }, + { SMIAPP_REG_U8_FAST_STANDBY_CAPABILITY, "fast_standby_capability" }, /* 70 */ + { SMIAPP_REG_U8_CCI_ADDRESS_CONTROL_CAPABILITY, "cci_address_control_capability" }, + { SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_1_LANE_MODE_MBPS, "max_per_lane_bitrate_1_lane_mode_mbps" }, + { SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_2_LANE_MODE_MBPS, "max_per_lane_bitrate_2_lane_mode_mbps" }, + { SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_3_LANE_MODE_MBPS, "max_per_lane_bitrate_3_lane_mode_mbps" }, + { SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_4_LANE_MODE_MBPS, "max_per_lane_bitrate_4_lane_mode_mbps" }, /* 75 */ + { SMIAPP_REG_U8_TEMP_SENSOR_CAPABILITY, "temp_sensor_capability" }, + { SMIAPP_REG_U16_MIN_FRAME_LENGTH_LINES_BIN, "min_frame_length_lines_bin" }, + { SMIAPP_REG_U16_MAX_FRAME_LENGTH_LINES_BIN, "max_frame_length_lines_bin" }, + { SMIAPP_REG_U16_MIN_LINE_LENGTH_PCK_BIN, "min_line_length_pck_bin" }, + { SMIAPP_REG_U16_MAX_LINE_LENGTH_PCK_BIN, "max_line_length_pck_bin" }, /* 80 */ + { SMIAPP_REG_U16_MIN_LINE_BLANKING_PCK_BIN, "min_line_blanking_pck_bin" }, + { SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MIN_BIN, "fine_integration_time_min_bin" }, + { SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN, "fine_integration_time_max_margin_bin" }, + { SMIAPP_REG_U8_BINNING_CAPABILITY, "binning_capability" }, + { SMIAPP_REG_U8_BINNING_WEIGHTING_CAPABILITY, "binning_weighting_capability" }, /* 85 */ + { SMIAPP_REG_U8_DATA_TRANSFER_IF_CAPABILITY, "data_transfer_if_capability" }, + { SMIAPP_REG_U8_SHADING_CORRECTION_CAPABILITY, "shading_correction_capability" }, + { SMIAPP_REG_U8_GREEN_IMBALANCE_CAPABILITY, "green_imbalance_capability" }, + { SMIAPP_REG_U8_BLACK_LEVEL_CAPABILITY, "black_level_capability" }, + { SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_CAPABILITY, "module_specific_correction_capability" }, /* 90 */ + { SMIAPP_REG_U16_DEFECT_CORRECTION_CAPABILITY, "defect_correction_capability" }, + { SMIAPP_REG_U16_DEFECT_CORRECTION_CAPABILITY_2, "defect_correction_capability_2" }, + { SMIAPP_REG_U8_EDOF_CAPABILITY, "edof_capability" }, + { SMIAPP_REG_U8_COLOUR_FEEDBACK_CAPABILITY, "colour_feedback_capability" }, + { SMIAPP_REG_U8_ESTIMATION_MODE_CAPABILITY, "estimation_mode_capability" }, /* 95 */ + { SMIAPP_REG_U8_ESTIMATION_ZONE_CAPABILITY, "estimation_zone_capability" }, + { SMIAPP_REG_U16_CAPABILITY_TRDY_MIN, "capability_trdy_min" }, + { SMIAPP_REG_U8_FLASH_MODE_CAPABILITY, "flash_mode_capability" }, + { SMIAPP_REG_U8_ACTUATOR_CAPABILITY, "actuator_capability" }, + { SMIAPP_REG_U8_BRACKETING_LUT_CAPABILITY_1, "bracketing_lut_capability_1" }, /* 100 */ + { SMIAPP_REG_U8_BRACKETING_LUT_CAPABILITY_2, "bracketing_lut_capability_2" }, + { SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_STEP, "analogue_gain_code_step" }, + { 0, NULL }, +}; diff --git a/drivers/media/i2c/smiapp/smiapp-limits.h b/drivers/media/i2c/smiapp/smiapp-limits.h new file mode 100644 index 000000000..dbac0b497 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-limits.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/smiapp/smiapp-limits.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ + +#define SMIAPP_LIMIT_ANALOGUE_GAIN_CAPABILITY 0 +#define SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MIN 1 +#define SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MAX 2 +#define SMIAPP_LIMIT_THS_ZERO_MIN 3 +#define SMIAPP_LIMIT_TCLK_TRAIL_MIN 4 +#define SMIAPP_LIMIT_INTEGRATION_TIME_CAPABILITY 5 +#define SMIAPP_LIMIT_COARSE_INTEGRATION_TIME_MIN 6 +#define SMIAPP_LIMIT_COARSE_INTEGRATION_TIME_MAX_MARGIN 7 +#define SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MIN 8 +#define SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MAX_MARGIN 9 +#define SMIAPP_LIMIT_DIGITAL_GAIN_CAPABILITY 10 +#define SMIAPP_LIMIT_DIGITAL_GAIN_MIN 11 +#define SMIAPP_LIMIT_DIGITAL_GAIN_MAX 12 +#define SMIAPP_LIMIT_MIN_EXT_CLK_FREQ_HZ 13 +#define SMIAPP_LIMIT_MAX_EXT_CLK_FREQ_HZ 14 +#define SMIAPP_LIMIT_MIN_PRE_PLL_CLK_DIV 15 +#define SMIAPP_LIMIT_MAX_PRE_PLL_CLK_DIV 16 +#define SMIAPP_LIMIT_MIN_PLL_IP_FREQ_HZ 17 +#define SMIAPP_LIMIT_MAX_PLL_IP_FREQ_HZ 18 +#define SMIAPP_LIMIT_MIN_PLL_MULTIPLIER 19 +#define SMIAPP_LIMIT_MAX_PLL_MULTIPLIER 20 +#define SMIAPP_LIMIT_MIN_PLL_OP_FREQ_HZ 21 +#define SMIAPP_LIMIT_MAX_PLL_OP_FREQ_HZ 22 +#define SMIAPP_LIMIT_MIN_VT_SYS_CLK_DIV 23 +#define SMIAPP_LIMIT_MAX_VT_SYS_CLK_DIV 24 +#define SMIAPP_LIMIT_MIN_VT_SYS_CLK_FREQ_HZ 25 +#define SMIAPP_LIMIT_MAX_VT_SYS_CLK_FREQ_HZ 26 +#define SMIAPP_LIMIT_MIN_VT_PIX_CLK_FREQ_HZ 27 +#define SMIAPP_LIMIT_MAX_VT_PIX_CLK_FREQ_HZ 28 +#define SMIAPP_LIMIT_MIN_VT_PIX_CLK_DIV 29 +#define SMIAPP_LIMIT_MAX_VT_PIX_CLK_DIV 30 +#define SMIAPP_LIMIT_MIN_FRAME_LENGTH_LINES 31 +#define SMIAPP_LIMIT_MAX_FRAME_LENGTH_LINES 32 +#define SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK 33 +#define SMIAPP_LIMIT_MAX_LINE_LENGTH_PCK 34 +#define SMIAPP_LIMIT_MIN_LINE_BLANKING_PCK 35 +#define SMIAPP_LIMIT_MIN_FRAME_BLANKING_LINES 36 +#define SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK_STEP_SIZE 37 +#define SMIAPP_LIMIT_MIN_OP_SYS_CLK_DIV 38 +#define SMIAPP_LIMIT_MAX_OP_SYS_CLK_DIV 39 +#define SMIAPP_LIMIT_MIN_OP_SYS_CLK_FREQ_HZ 40 +#define SMIAPP_LIMIT_MAX_OP_SYS_CLK_FREQ_HZ 41 +#define SMIAPP_LIMIT_MIN_OP_PIX_CLK_DIV 42 +#define SMIAPP_LIMIT_MAX_OP_PIX_CLK_DIV 43 +#define SMIAPP_LIMIT_MIN_OP_PIX_CLK_FREQ_HZ 44 +#define SMIAPP_LIMIT_MAX_OP_PIX_CLK_FREQ_HZ 45 +#define SMIAPP_LIMIT_X_ADDR_MIN 46 +#define SMIAPP_LIMIT_Y_ADDR_MIN 47 +#define SMIAPP_LIMIT_X_ADDR_MAX 48 +#define SMIAPP_LIMIT_Y_ADDR_MAX 49 +#define SMIAPP_LIMIT_MIN_X_OUTPUT_SIZE 50 +#define SMIAPP_LIMIT_MIN_Y_OUTPUT_SIZE 51 +#define SMIAPP_LIMIT_MAX_X_OUTPUT_SIZE 52 +#define SMIAPP_LIMIT_MAX_Y_OUTPUT_SIZE 53 +#define SMIAPP_LIMIT_MIN_EVEN_INC 54 +#define SMIAPP_LIMIT_MAX_EVEN_INC 55 +#define SMIAPP_LIMIT_MIN_ODD_INC 56 +#define SMIAPP_LIMIT_MAX_ODD_INC 57 +#define SMIAPP_LIMIT_SCALING_CAPABILITY 58 +#define SMIAPP_LIMIT_SCALER_M_MIN 59 +#define SMIAPP_LIMIT_SCALER_M_MAX 60 +#define SMIAPP_LIMIT_SCALER_N_MIN 61 +#define SMIAPP_LIMIT_SCALER_N_MAX 62 +#define SMIAPP_LIMIT_SPATIAL_SAMPLING_CAPABILITY 63 +#define SMIAPP_LIMIT_DIGITAL_CROP_CAPABILITY 64 +#define SMIAPP_LIMIT_COMPRESSION_CAPABILITY 65 +#define SMIAPP_LIMIT_FIFO_SUPPORT_CAPABILITY 66 +#define SMIAPP_LIMIT_DPHY_CTRL_CAPABILITY 67 +#define SMIAPP_LIMIT_CSI_LANE_MODE_CAPABILITY 68 +#define SMIAPP_LIMIT_CSI_SIGNALLING_MODE_CAPABILITY 69 +#define SMIAPP_LIMIT_FAST_STANDBY_CAPABILITY 70 +#define SMIAPP_LIMIT_CCI_ADDRESS_CONTROL_CAPABILITY 71 +#define SMIAPP_LIMIT_MAX_PER_LANE_BITRATE_1_LANE_MODE_MBPS 72 +#define SMIAPP_LIMIT_MAX_PER_LANE_BITRATE_2_LANE_MODE_MBPS 73 +#define SMIAPP_LIMIT_MAX_PER_LANE_BITRATE_3_LANE_MODE_MBPS 74 +#define SMIAPP_LIMIT_MAX_PER_LANE_BITRATE_4_LANE_MODE_MBPS 75 +#define SMIAPP_LIMIT_TEMP_SENSOR_CAPABILITY 76 +#define SMIAPP_LIMIT_MIN_FRAME_LENGTH_LINES_BIN 77 +#define SMIAPP_LIMIT_MAX_FRAME_LENGTH_LINES_BIN 78 +#define SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK_BIN 79 +#define SMIAPP_LIMIT_MAX_LINE_LENGTH_PCK_BIN 80 +#define SMIAPP_LIMIT_MIN_LINE_BLANKING_PCK_BIN 81 +#define SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MIN_BIN 82 +#define SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN 83 +#define SMIAPP_LIMIT_BINNING_CAPABILITY 84 +#define SMIAPP_LIMIT_BINNING_WEIGHTING_CAPABILITY 85 +#define SMIAPP_LIMIT_DATA_TRANSFER_IF_CAPABILITY 86 +#define SMIAPP_LIMIT_SHADING_CORRECTION_CAPABILITY 87 +#define SMIAPP_LIMIT_GREEN_IMBALANCE_CAPABILITY 88 +#define SMIAPP_LIMIT_BLACK_LEVEL_CAPABILITY 89 +#define SMIAPP_LIMIT_MODULE_SPECIFIC_CORRECTION_CAPABILITY 90 +#define SMIAPP_LIMIT_DEFECT_CORRECTION_CAPABILITY 91 +#define SMIAPP_LIMIT_DEFECT_CORRECTION_CAPABILITY_2 92 +#define SMIAPP_LIMIT_EDOF_CAPABILITY 93 +#define SMIAPP_LIMIT_COLOUR_FEEDBACK_CAPABILITY 94 +#define SMIAPP_LIMIT_ESTIMATION_MODE_CAPABILITY 95 +#define SMIAPP_LIMIT_ESTIMATION_ZONE_CAPABILITY 96 +#define SMIAPP_LIMIT_CAPABILITY_TRDY_MIN 97 +#define SMIAPP_LIMIT_FLASH_MODE_CAPABILITY 98 +#define SMIAPP_LIMIT_ACTUATOR_CAPABILITY 99 +#define SMIAPP_LIMIT_BRACKETING_LUT_CAPABILITY_1 100 +#define SMIAPP_LIMIT_BRACKETING_LUT_CAPABILITY_2 101 +#define SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_STEP 102 +#define SMIAPP_LIMIT_LAST 103 diff --git a/drivers/media/i2c/smiapp/smiapp-quirk.c b/drivers/media/i2c/smiapp/smiapp-quirk.c new file mode 100644 index 000000000..ab96d6067 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-quirk.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/smiapp/smiapp-quirk.c + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ + +#include <linux/delay.h> + +#include "smiapp.h" + +static int smiapp_write_8(struct smiapp_sensor *sensor, u16 reg, u8 val) +{ + return smiapp_write(sensor, SMIAPP_REG_MK_U8(reg), val); +} + +static int smiapp_write_8s(struct smiapp_sensor *sensor, + const struct smiapp_reg_8 *regs, int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + for (; len > 0; len--, regs++) { + rval = smiapp_write_8(sensor, regs->reg, regs->val); + if (rval < 0) { + dev_err(&client->dev, + "error %d writing reg 0x%4.4x, val 0x%2.2x", + rval, regs->reg, regs->val); + return rval; + } + } + + return 0; +} + +void smiapp_replace_limit(struct smiapp_sensor *sensor, + u32 limit, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + + dev_dbg(&client->dev, "quirk: 0x%8.8x \"%s\" = %d, 0x%x\n", + smiapp_reg_limits[limit].addr, + smiapp_reg_limits[limit].what, val, val); + sensor->limits[limit] = val; +} + +static int jt8ew9_limits(struct smiapp_sensor *sensor) +{ + if (sensor->minfo.revision_number_major < 0x03) + sensor->frame_skip = 1; + + /* Below 24 gain doesn't have effect at all, */ + /* but ~59 is needed for full dynamic range */ + smiapp_replace_limit(sensor, SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MIN, 59); + smiapp_replace_limit( + sensor, SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MAX, 6000); + + return 0; +} + +static int jt8ew9_post_poweron(struct smiapp_sensor *sensor) +{ + static const struct smiapp_reg_8 regs[] = { + { 0x30a3, 0xd8 }, /* Output port control : LVDS ports only */ + { 0x30ae, 0x00 }, /* 0x0307 pll_multiplier maximum value on PLL input 9.6MHz ( 19.2MHz is divided on pre_pll_div) */ + { 0x30af, 0xd0 }, /* 0x0307 pll_multiplier maximum value on PLL input 9.6MHz ( 19.2MHz is divided on pre_pll_div) */ + { 0x322d, 0x04 }, /* Adjusting Processing Image Size to Scaler Toshiba Recommendation Setting */ + { 0x3255, 0x0f }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */ + { 0x3256, 0x15 }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */ + { 0x3258, 0x70 }, /* Analog Gain Control Toshiba Recommendation Setting */ + { 0x3259, 0x70 }, /* Analog Gain Control Toshiba Recommendation Setting */ + { 0x325f, 0x7c }, /* Analog Gain Control Toshiba Recommendation Setting */ + { 0x3302, 0x06 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x3304, 0x00 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x3307, 0x22 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x3308, 0x8d }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x331e, 0x0f }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3320, 0x30 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3321, 0x11 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3322, 0x98 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3323, 0x64 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3325, 0x83 }, /* Read Out Timing Control Toshiba Recommendation Setting */ + { 0x3330, 0x18 }, /* Read Out Timing Control Toshiba Recommendation Setting */ + { 0x333c, 0x01 }, /* Read Out Timing Control Toshiba Recommendation Setting */ + { 0x3345, 0x2f }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x33de, 0x38 }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */ + /* Taken from v03. No idea what the rest are. */ + { 0x32e0, 0x05 }, + { 0x32e1, 0x05 }, + { 0x32e2, 0x04 }, + { 0x32e5, 0x04 }, + { 0x32e6, 0x04 }, + + }; + + return smiapp_write_8s(sensor, regs, ARRAY_SIZE(regs)); +} + +const struct smiapp_quirk smiapp_jt8ew9_quirk = { + .limits = jt8ew9_limits, + .post_poweron = jt8ew9_post_poweron, +}; + +static int imx125es_post_poweron(struct smiapp_sensor *sensor) +{ + /* Taken from v02. No idea what the other two are. */ + static const struct smiapp_reg_8 regs[] = { + /* + * 0x3302: clk during frame blanking: + * 0x00 - HS mode, 0x01 - LP11 + */ + { 0x3302, 0x01 }, + { 0x302d, 0x00 }, + { 0x3b08, 0x8c }, + }; + + return smiapp_write_8s(sensor, regs, ARRAY_SIZE(regs)); +} + +const struct smiapp_quirk smiapp_imx125es_quirk = { + .post_poweron = imx125es_post_poweron, +}; + +static int jt8ev1_limits(struct smiapp_sensor *sensor) +{ + smiapp_replace_limit(sensor, SMIAPP_LIMIT_X_ADDR_MAX, 4271); + smiapp_replace_limit(sensor, + SMIAPP_LIMIT_MIN_LINE_BLANKING_PCK_BIN, 184); + + return 0; +} + +static int jt8ev1_post_poweron(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + static const struct smiapp_reg_8 regs[] = { + { 0x3031, 0xcd }, /* For digital binning (EQ_MONI) */ + { 0x30a3, 0xd0 }, /* FLASH STROBE enable */ + { 0x3237, 0x00 }, /* For control of pulse timing for ADC */ + { 0x3238, 0x43 }, + { 0x3301, 0x06 }, /* For analog bias for sensor */ + { 0x3302, 0x06 }, + { 0x3304, 0x00 }, + { 0x3305, 0x88 }, + { 0x332a, 0x14 }, + { 0x332c, 0x6b }, + { 0x3336, 0x01 }, + { 0x333f, 0x1f }, + { 0x3355, 0x00 }, + { 0x3356, 0x20 }, + { 0x33bf, 0x20 }, /* Adjust the FBC speed */ + { 0x33c9, 0x20 }, + { 0x33ce, 0x30 }, /* Adjust the parameter for logic function */ + { 0x33cf, 0xec }, /* For Black sun */ + { 0x3328, 0x80 }, /* Ugh. No idea what's this. */ + }; + static const struct smiapp_reg_8 regs_96[] = { + { 0x30ae, 0x00 }, /* For control of ADC clock */ + { 0x30af, 0xd0 }, + { 0x30b0, 0x01 }, + }; + + rval = smiapp_write_8s(sensor, regs, ARRAY_SIZE(regs)); + if (rval < 0) + return rval; + + switch (sensor->hwcfg->ext_clk) { + case 9600000: + return smiapp_write_8s(sensor, regs_96, + ARRAY_SIZE(regs_96)); + default: + dev_warn(&client->dev, "no MSRs for %d Hz ext_clk\n", + sensor->hwcfg->ext_clk); + return 0; + } +} + +static int jt8ev1_pre_streamon(struct smiapp_sensor *sensor) +{ + return smiapp_write_8(sensor, 0x3328, 0x00); +} + +static int jt8ev1_post_streamoff(struct smiapp_sensor *sensor) +{ + int rval; + + /* Workaround: allows fast standby to work properly */ + rval = smiapp_write_8(sensor, 0x3205, 0x04); + if (rval < 0) + return rval; + + /* Wait for 1 ms + one line => 2 ms is likely enough */ + usleep_range(2000, 2050); + + /* Restore it */ + rval = smiapp_write_8(sensor, 0x3205, 0x00); + if (rval < 0) + return rval; + + return smiapp_write_8(sensor, 0x3328, 0x80); +} + +static int jt8ev1_init(struct smiapp_sensor *sensor) +{ + sensor->pll.flags |= SMIAPP_PLL_FLAG_OP_PIX_CLOCK_PER_LANE; + + return 0; +} + +const struct smiapp_quirk smiapp_jt8ev1_quirk = { + .limits = jt8ev1_limits, + .post_poweron = jt8ev1_post_poweron, + .pre_streamon = jt8ev1_pre_streamon, + .post_streamoff = jt8ev1_post_streamoff, + .init = jt8ev1_init, +}; + +static int tcm8500md_limits(struct smiapp_sensor *sensor) +{ + smiapp_replace_limit(sensor, SMIAPP_LIMIT_MIN_PLL_IP_FREQ_HZ, 2700000); + + return 0; +} + +const struct smiapp_quirk smiapp_tcm8500md_quirk = { + .limits = tcm8500md_limits, +}; diff --git a/drivers/media/i2c/smiapp/smiapp-quirk.h b/drivers/media/i2c/smiapp/smiapp-quirk.h new file mode 100644 index 000000000..17505be60 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-quirk.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/smiapp/smiapp-quirk.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ + +#ifndef __SMIAPP_QUIRK__ +#define __SMIAPP_QUIRK__ + +struct smiapp_sensor; + +/** + * struct smiapp_quirk - quirks for sensors that deviate from SMIA++ standard + * + * @limits: Replace sensor->limits with values which can't be read from + * sensor registers. Called the first time the sensor is powered up. + * @post_poweron: Called always after the sensor has been fully powered on. + * @pre_streamon: Called just before streaming is enabled. + * @post_streamon: Called right after stopping streaming. + * @pll_flags: Return flags for the PLL calculator. + * @init: Quirk initialisation, called the last in probe(). This is + * also appropriate for adding sensor specific controls, for instance. + * @reg_access: Register access quirk. The quirk may divert the access + * to another register, or no register at all. + * + * @write: Is this read (false) or write (true) access? + * @reg: Pointer to the register to access + * @value: Register value, set by the caller on write, or + * by the quirk on read + * + * @return: 0 on success, -ENOIOCTLCMD if no register + * access may be done by the caller (default read + * value is zero), else negative error code on error + */ +struct smiapp_quirk { + int (*limits)(struct smiapp_sensor *sensor); + int (*post_poweron)(struct smiapp_sensor *sensor); + int (*pre_streamon)(struct smiapp_sensor *sensor); + int (*post_streamoff)(struct smiapp_sensor *sensor); + unsigned long (*pll_flags)(struct smiapp_sensor *sensor); + int (*init)(struct smiapp_sensor *sensor); + int (*reg_access)(struct smiapp_sensor *sensor, bool write, u32 *reg, + u32 *val); + unsigned long flags; +}; + +#define SMIAPP_QUIRK_FLAG_8BIT_READ_ONLY (1 << 0) + +struct smiapp_reg_8 { + u16 reg; + u8 val; +}; + +void smiapp_replace_limit(struct smiapp_sensor *sensor, + u32 limit, u32 val); + +#define SMIAPP_MK_QUIRK_REG_8(_reg, _val) \ + { \ + .reg = (u16)_reg, \ + .val = _val, \ + } + +#define smiapp_call_quirk(sensor, _quirk, ...) \ + ((sensor)->minfo.quirk && \ + (sensor)->minfo.quirk->_quirk ? \ + (sensor)->minfo.quirk->_quirk(sensor, ##__VA_ARGS__) : 0) + +#define smiapp_needs_quirk(sensor, _quirk) \ + ((sensor)->minfo.quirk ? \ + (sensor)->minfo.quirk->flags & _quirk : 0) + +extern const struct smiapp_quirk smiapp_jt8ev1_quirk; +extern const struct smiapp_quirk smiapp_imx125es_quirk; +extern const struct smiapp_quirk smiapp_jt8ew9_quirk; +extern const struct smiapp_quirk smiapp_tcm8500md_quirk; + +#endif /* __SMIAPP_QUIRK__ */ diff --git a/drivers/media/i2c/smiapp/smiapp-reg-defs.h b/drivers/media/i2c/smiapp/smiapp-reg-defs.h new file mode 100644 index 000000000..865488bef --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-reg-defs.h @@ -0,0 +1,489 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/smiapp/smiapp-reg-defs.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ +#define SMIAPP_REG_MK_U8(r) ((SMIAPP_REG_8BIT << 16) | (r)) +#define SMIAPP_REG_MK_U16(r) ((SMIAPP_REG_16BIT << 16) | (r)) +#define SMIAPP_REG_MK_U32(r) ((SMIAPP_REG_32BIT << 16) | (r)) + +#define SMIAPP_REG_MK_F32(r) (SMIAPP_REG_FLAG_FLOAT | (SMIAPP_REG_32BIT << 16) | (r)) + +#define SMIAPP_REG_U16_MODEL_ID SMIAPP_REG_MK_U16(0x0000) +#define SMIAPP_REG_U8_REVISION_NUMBER_MAJOR SMIAPP_REG_MK_U8(0x0002) +#define SMIAPP_REG_U8_MANUFACTURER_ID SMIAPP_REG_MK_U8(0x0003) +#define SMIAPP_REG_U8_SMIA_VERSION SMIAPP_REG_MK_U8(0x0004) +#define SMIAPP_REG_U8_FRAME_COUNT SMIAPP_REG_MK_U8(0x0005) +#define SMIAPP_REG_U8_PIXEL_ORDER SMIAPP_REG_MK_U8(0x0006) +#define SMIAPP_REG_U16_DATA_PEDESTAL SMIAPP_REG_MK_U16(0x0008) +#define SMIAPP_REG_U8_PIXEL_DEPTH SMIAPP_REG_MK_U8(0x000c) +#define SMIAPP_REG_U8_REVISION_NUMBER_MINOR SMIAPP_REG_MK_U8(0x0010) +#define SMIAPP_REG_U8_SMIAPP_VERSION SMIAPP_REG_MK_U8(0x0011) +#define SMIAPP_REG_U8_MODULE_DATE_YEAR SMIAPP_REG_MK_U8(0x0012) +#define SMIAPP_REG_U8_MODULE_DATE_MONTH SMIAPP_REG_MK_U8(0x0013) +#define SMIAPP_REG_U8_MODULE_DATE_DAY SMIAPP_REG_MK_U8(0x0014) +#define SMIAPP_REG_U8_MODULE_DATE_PHASE SMIAPP_REG_MK_U8(0x0015) +#define SMIAPP_REG_U16_SENSOR_MODEL_ID SMIAPP_REG_MK_U16(0x0016) +#define SMIAPP_REG_U8_SENSOR_REVISION_NUMBER SMIAPP_REG_MK_U8(0x0018) +#define SMIAPP_REG_U8_SENSOR_MANUFACTURER_ID SMIAPP_REG_MK_U8(0x0019) +#define SMIAPP_REG_U8_SENSOR_FIRMWARE_VERSION SMIAPP_REG_MK_U8(0x001a) +#define SMIAPP_REG_U32_SERIAL_NUMBER SMIAPP_REG_MK_U32(0x001c) +#define SMIAPP_REG_U8_FRAME_FORMAT_MODEL_TYPE SMIAPP_REG_MK_U8(0x0040) +#define SMIAPP_REG_U8_FRAME_FORMAT_MODEL_SUBTYPE SMIAPP_REG_MK_U8(0x0041) +#define SMIAPP_REG_U16_FRAME_FORMAT_DESCRIPTOR_2(n) SMIAPP_REG_MK_U16(0x0042 + ((n) << 1)) /* 0 <= n <= 14 */ +#define SMIAPP_REG_U32_FRAME_FORMAT_DESCRIPTOR_4(n) SMIAPP_REG_MK_U32(0x0060 + ((n) << 2)) /* 0 <= n <= 7 */ +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CAPABILITY SMIAPP_REG_MK_U16(0x0080) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_MIN SMIAPP_REG_MK_U16(0x0084) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_MAX SMIAPP_REG_MK_U16(0x0086) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_STEP SMIAPP_REG_MK_U16(0x0088) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_TYPE SMIAPP_REG_MK_U16(0x008a) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_M0 SMIAPP_REG_MK_U16(0x008c) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_C0 SMIAPP_REG_MK_U16(0x008e) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_M1 SMIAPP_REG_MK_U16(0x0090) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_C1 SMIAPP_REG_MK_U16(0x0092) +#define SMIAPP_REG_U8_DATA_FORMAT_MODEL_TYPE SMIAPP_REG_MK_U8(0x00c0) +#define SMIAPP_REG_U8_DATA_FORMAT_MODEL_SUBTYPE SMIAPP_REG_MK_U8(0x00c1) +#define SMIAPP_REG_U16_DATA_FORMAT_DESCRIPTOR(n) SMIAPP_REG_MK_U16(0x00c2 + ((n) << 1)) +#define SMIAPP_REG_U8_MODE_SELECT SMIAPP_REG_MK_U8(0x0100) +#define SMIAPP_REG_U8_IMAGE_ORIENTATION SMIAPP_REG_MK_U8(0x0101) +#define SMIAPP_REG_U8_SOFTWARE_RESET SMIAPP_REG_MK_U8(0x0103) +#define SMIAPP_REG_U8_GROUPED_PARAMETER_HOLD SMIAPP_REG_MK_U8(0x0104) +#define SMIAPP_REG_U8_MASK_CORRUPTED_FRAMES SMIAPP_REG_MK_U8(0x0105) +#define SMIAPP_REG_U8_FAST_STANDBY_CTRL SMIAPP_REG_MK_U8(0x0106) +#define SMIAPP_REG_U8_CCI_ADDRESS_CONTROL SMIAPP_REG_MK_U8(0x0107) +#define SMIAPP_REG_U8_2ND_CCI_IF_CONTROL SMIAPP_REG_MK_U8(0x0108) +#define SMIAPP_REG_U8_2ND_CCI_ADDRESS_CONTROL SMIAPP_REG_MK_U8(0x0109) +#define SMIAPP_REG_U8_CSI_CHANNEL_IDENTIFIER SMIAPP_REG_MK_U8(0x0110) +#define SMIAPP_REG_U8_CSI_SIGNALLING_MODE SMIAPP_REG_MK_U8(0x0111) +#define SMIAPP_REG_U16_CSI_DATA_FORMAT SMIAPP_REG_MK_U16(0x0112) +#define SMIAPP_REG_U8_CSI_LANE_MODE SMIAPP_REG_MK_U8(0x0114) +#define SMIAPP_REG_U8_CSI2_10_TO_8_DT SMIAPP_REG_MK_U8(0x0115) +#define SMIAPP_REG_U8_CSI2_10_TO_7_DT SMIAPP_REG_MK_U8(0x0116) +#define SMIAPP_REG_U8_CSI2_10_TO_6_DT SMIAPP_REG_MK_U8(0x0117) +#define SMIAPP_REG_U8_CSI2_12_TO_8_DT SMIAPP_REG_MK_U8(0x0118) +#define SMIAPP_REG_U8_CSI2_12_TO_7_DT SMIAPP_REG_MK_U8(0x0119) +#define SMIAPP_REG_U8_CSI2_12_TO_6_DT SMIAPP_REG_MK_U8(0x011a) +#define SMIAPP_REG_U8_CSI2_14_TO_10_DT SMIAPP_REG_MK_U8(0x011b) +#define SMIAPP_REG_U8_CSI2_14_TO_8_DT SMIAPP_REG_MK_U8(0x011c) +#define SMIAPP_REG_U8_CSI2_16_TO_10_DT SMIAPP_REG_MK_U8(0x011d) +#define SMIAPP_REG_U8_CSI2_16_TO_8_DT SMIAPP_REG_MK_U8(0x011e) +#define SMIAPP_REG_U8_GAIN_MODE SMIAPP_REG_MK_U8(0x0120) +#define SMIAPP_REG_U16_VANA_VOLTAGE SMIAPP_REG_MK_U16(0x0130) +#define SMIAPP_REG_U16_VDIG_VOLTAGE SMIAPP_REG_MK_U16(0x0132) +#define SMIAPP_REG_U16_VIO_VOLTAGE SMIAPP_REG_MK_U16(0x0134) +#define SMIAPP_REG_U16_EXTCLK_FREQUENCY_MHZ SMIAPP_REG_MK_U16(0x0136) +#define SMIAPP_REG_U8_TEMP_SENSOR_CONTROL SMIAPP_REG_MK_U8(0x0138) +#define SMIAPP_REG_U8_TEMP_SENSOR_MODE SMIAPP_REG_MK_U8(0x0139) +#define SMIAPP_REG_U8_TEMP_SENSOR_OUTPUT SMIAPP_REG_MK_U8(0x013a) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME SMIAPP_REG_MK_U16(0x0200) +#define SMIAPP_REG_U16_COARSE_INTEGRATION_TIME SMIAPP_REG_MK_U16(0x0202) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GLOBAL SMIAPP_REG_MK_U16(0x0204) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GREENR SMIAPP_REG_MK_U16(0x0206) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_RED SMIAPP_REG_MK_U16(0x0208) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_BLUE SMIAPP_REG_MK_U16(0x020a) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GREENB SMIAPP_REG_MK_U16(0x020c) +#define SMIAPP_REG_U16_DIGITAL_GAIN_GREENR SMIAPP_REG_MK_U16(0x020e) +#define SMIAPP_REG_U16_DIGITAL_GAIN_RED SMIAPP_REG_MK_U16(0x0210) +#define SMIAPP_REG_U16_DIGITAL_GAIN_BLUE SMIAPP_REG_MK_U16(0x0212) +#define SMIAPP_REG_U16_DIGITAL_GAIN_GREENB SMIAPP_REG_MK_U16(0x0214) +#define SMIAPP_REG_U16_VT_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x0300) +#define SMIAPP_REG_U16_VT_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x0302) +#define SMIAPP_REG_U16_PRE_PLL_CLK_DIV SMIAPP_REG_MK_U16(0x0304) +#define SMIAPP_REG_U16_PLL_MULTIPLIER SMIAPP_REG_MK_U16(0x0306) +#define SMIAPP_REG_U16_OP_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x0308) +#define SMIAPP_REG_U16_OP_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x030a) +#define SMIAPP_REG_U16_FRAME_LENGTH_LINES SMIAPP_REG_MK_U16(0x0340) +#define SMIAPP_REG_U16_LINE_LENGTH_PCK SMIAPP_REG_MK_U16(0x0342) +#define SMIAPP_REG_U16_X_ADDR_START SMIAPP_REG_MK_U16(0x0344) +#define SMIAPP_REG_U16_Y_ADDR_START SMIAPP_REG_MK_U16(0x0346) +#define SMIAPP_REG_U16_X_ADDR_END SMIAPP_REG_MK_U16(0x0348) +#define SMIAPP_REG_U16_Y_ADDR_END SMIAPP_REG_MK_U16(0x034a) +#define SMIAPP_REG_U16_X_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x034c) +#define SMIAPP_REG_U16_Y_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x034e) +#define SMIAPP_REG_U16_X_EVEN_INC SMIAPP_REG_MK_U16(0x0380) +#define SMIAPP_REG_U16_X_ODD_INC SMIAPP_REG_MK_U16(0x0382) +#define SMIAPP_REG_U16_Y_EVEN_INC SMIAPP_REG_MK_U16(0x0384) +#define SMIAPP_REG_U16_Y_ODD_INC SMIAPP_REG_MK_U16(0x0386) +#define SMIAPP_REG_U16_SCALING_MODE SMIAPP_REG_MK_U16(0x0400) +#define SMIAPP_REG_U16_SPATIAL_SAMPLING SMIAPP_REG_MK_U16(0x0402) +#define SMIAPP_REG_U16_SCALE_M SMIAPP_REG_MK_U16(0x0404) +#define SMIAPP_REG_U16_SCALE_N SMIAPP_REG_MK_U16(0x0406) +#define SMIAPP_REG_U16_DIGITAL_CROP_X_OFFSET SMIAPP_REG_MK_U16(0x0408) +#define SMIAPP_REG_U16_DIGITAL_CROP_Y_OFFSET SMIAPP_REG_MK_U16(0x040a) +#define SMIAPP_REG_U16_DIGITAL_CROP_IMAGE_WIDTH SMIAPP_REG_MK_U16(0x040c) +#define SMIAPP_REG_U16_DIGITAL_CROP_IMAGE_HEIGHT SMIAPP_REG_MK_U16(0x040e) +#define SMIAPP_REG_U16_COMPRESSION_MODE SMIAPP_REG_MK_U16(0x0500) +#define SMIAPP_REG_U16_TEST_PATTERN_MODE SMIAPP_REG_MK_U16(0x0600) +#define SMIAPP_REG_U16_TEST_DATA_RED SMIAPP_REG_MK_U16(0x0602) +#define SMIAPP_REG_U16_TEST_DATA_GREENR SMIAPP_REG_MK_U16(0x0604) +#define SMIAPP_REG_U16_TEST_DATA_BLUE SMIAPP_REG_MK_U16(0x0606) +#define SMIAPP_REG_U16_TEST_DATA_GREENB SMIAPP_REG_MK_U16(0x0608) +#define SMIAPP_REG_U16_HORIZONTAL_CURSOR_WIDTH SMIAPP_REG_MK_U16(0x060a) +#define SMIAPP_REG_U16_HORIZONTAL_CURSOR_POSITION SMIAPP_REG_MK_U16(0x060c) +#define SMIAPP_REG_U16_VERTICAL_CURSOR_WIDTH SMIAPP_REG_MK_U16(0x060e) +#define SMIAPP_REG_U16_VERTICAL_CURSOR_POSITION SMIAPP_REG_MK_U16(0x0610) +#define SMIAPP_REG_U16_FIFO_WATER_MARK_PIXELS SMIAPP_REG_MK_U16(0x0700) +#define SMIAPP_REG_U8_TCLK_POST SMIAPP_REG_MK_U8(0x0800) +#define SMIAPP_REG_U8_THS_PREPARE SMIAPP_REG_MK_U8(0x0801) +#define SMIAPP_REG_U8_THS_ZERO_MIN SMIAPP_REG_MK_U8(0x0802) +#define SMIAPP_REG_U8_THS_TRAIL SMIAPP_REG_MK_U8(0x0803) +#define SMIAPP_REG_U8_TCLK_TRAIL_MIN SMIAPP_REG_MK_U8(0x0804) +#define SMIAPP_REG_U8_TCLK_PREPARE SMIAPP_REG_MK_U8(0x0805) +#define SMIAPP_REG_U8_TCLK_ZERO SMIAPP_REG_MK_U8(0x0806) +#define SMIAPP_REG_U8_TLPX SMIAPP_REG_MK_U8(0x0807) +#define SMIAPP_REG_U8_DPHY_CTRL SMIAPP_REG_MK_U8(0x0808) +#define SMIAPP_REG_U32_REQUESTED_LINK_BIT_RATE_MBPS SMIAPP_REG_MK_U32(0x0820) +#define SMIAPP_REG_U8_BINNING_MODE SMIAPP_REG_MK_U8(0x0900) +#define SMIAPP_REG_U8_BINNING_TYPE SMIAPP_REG_MK_U8(0x0901) +#define SMIAPP_REG_U8_BINNING_WEIGHTING SMIAPP_REG_MK_U8(0x0902) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_CTRL SMIAPP_REG_MK_U8(0x0a00) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_STATUS SMIAPP_REG_MK_U8(0x0a01) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_PAGE_SELECT SMIAPP_REG_MK_U8(0x0a02) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_0 SMIAPP_REG_MK_U8(0x0a04) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_1 SMIAPP_REG_MK_U8(0x0a05) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_2 SMIAPP_REG_MK_U8(0x0a06) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_3 SMIAPP_REG_MK_U8(0x0a07) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_4 SMIAPP_REG_MK_U8(0x0a08) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_5 SMIAPP_REG_MK_U8(0x0a09) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_12 SMIAPP_REG_MK_U8(0x0a10) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_13 SMIAPP_REG_MK_U8(0x0a11) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_14 SMIAPP_REG_MK_U8(0x0a12) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_15 SMIAPP_REG_MK_U8(0x0a13) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_16 SMIAPP_REG_MK_U8(0x0a14) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_17 SMIAPP_REG_MK_U8(0x0a15) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_18 SMIAPP_REG_MK_U8(0x0a16) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_19 SMIAPP_REG_MK_U8(0x0a17) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_20 SMIAPP_REG_MK_U8(0x0a18) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_21 SMIAPP_REG_MK_U8(0x0a19) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_22 SMIAPP_REG_MK_U8(0x0a1a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_23 SMIAPP_REG_MK_U8(0x0a1b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_24 SMIAPP_REG_MK_U8(0x0a1c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_25 SMIAPP_REG_MK_U8(0x0a1d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_26 SMIAPP_REG_MK_U8(0x0a1e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_27 SMIAPP_REG_MK_U8(0x0a1f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_28 SMIAPP_REG_MK_U8(0x0a20) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_29 SMIAPP_REG_MK_U8(0x0a21) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_30 SMIAPP_REG_MK_U8(0x0a22) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_31 SMIAPP_REG_MK_U8(0x0a23) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_32 SMIAPP_REG_MK_U8(0x0a24) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_33 SMIAPP_REG_MK_U8(0x0a25) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_34 SMIAPP_REG_MK_U8(0x0a26) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_35 SMIAPP_REG_MK_U8(0x0a27) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_36 SMIAPP_REG_MK_U8(0x0a28) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_37 SMIAPP_REG_MK_U8(0x0a29) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_38 SMIAPP_REG_MK_U8(0x0a2a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_39 SMIAPP_REG_MK_U8(0x0a2b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_40 SMIAPP_REG_MK_U8(0x0a2c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_41 SMIAPP_REG_MK_U8(0x0a2d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_42 SMIAPP_REG_MK_U8(0x0a2e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_43 SMIAPP_REG_MK_U8(0x0a2f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_44 SMIAPP_REG_MK_U8(0x0a30) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_45 SMIAPP_REG_MK_U8(0x0a31) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_46 SMIAPP_REG_MK_U8(0x0a32) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_47 SMIAPP_REG_MK_U8(0x0a33) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_48 SMIAPP_REG_MK_U8(0x0a34) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_49 SMIAPP_REG_MK_U8(0x0a35) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_50 SMIAPP_REG_MK_U8(0x0a36) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_51 SMIAPP_REG_MK_U8(0x0a37) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_52 SMIAPP_REG_MK_U8(0x0a38) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_53 SMIAPP_REG_MK_U8(0x0a39) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_54 SMIAPP_REG_MK_U8(0x0a3a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_55 SMIAPP_REG_MK_U8(0x0a3b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_56 SMIAPP_REG_MK_U8(0x0a3c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_57 SMIAPP_REG_MK_U8(0x0a3d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_58 SMIAPP_REG_MK_U8(0x0a3e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_59 SMIAPP_REG_MK_U8(0x0a3f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_60 SMIAPP_REG_MK_U8(0x0a40) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_61 SMIAPP_REG_MK_U8(0x0a41) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_62 SMIAPP_REG_MK_U8(0x0a42) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_63 SMIAPP_REG_MK_U8(0x0a43) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_CTRL SMIAPP_REG_MK_U8(0x0a44) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_STATUS SMIAPP_REG_MK_U8(0x0a45) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_PAGE_SELECT SMIAPP_REG_MK_U8(0x0a46) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_0 SMIAPP_REG_MK_U8(0x0a48) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_1 SMIAPP_REG_MK_U8(0x0a49) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_2 SMIAPP_REG_MK_U8(0x0a4a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_3 SMIAPP_REG_MK_U8(0x0a4b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_4 SMIAPP_REG_MK_U8(0x0a4c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_5 SMIAPP_REG_MK_U8(0x0a4d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_6 SMIAPP_REG_MK_U8(0x0a4e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_7 SMIAPP_REG_MK_U8(0x0a4f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_8 SMIAPP_REG_MK_U8(0x0a50) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_9 SMIAPP_REG_MK_U8(0x0a51) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_10 SMIAPP_REG_MK_U8(0x0a52) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_11 SMIAPP_REG_MK_U8(0x0a53) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_12 SMIAPP_REG_MK_U8(0x0a54) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_13 SMIAPP_REG_MK_U8(0x0a55) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_14 SMIAPP_REG_MK_U8(0x0a56) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_15 SMIAPP_REG_MK_U8(0x0a57) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_16 SMIAPP_REG_MK_U8(0x0a58) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_17 SMIAPP_REG_MK_U8(0x0a59) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_18 SMIAPP_REG_MK_U8(0x0a5a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_19 SMIAPP_REG_MK_U8(0x0a5b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_20 SMIAPP_REG_MK_U8(0x0a5c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_21 SMIAPP_REG_MK_U8(0x0a5d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_22 SMIAPP_REG_MK_U8(0x0a5e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_23 SMIAPP_REG_MK_U8(0x0a5f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_24 SMIAPP_REG_MK_U8(0x0a60) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_25 SMIAPP_REG_MK_U8(0x0a61) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_26 SMIAPP_REG_MK_U8(0x0a62) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_27 SMIAPP_REG_MK_U8(0x0a63) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_28 SMIAPP_REG_MK_U8(0x0a64) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_29 SMIAPP_REG_MK_U8(0x0a65) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_30 SMIAPP_REG_MK_U8(0x0a66) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_31 SMIAPP_REG_MK_U8(0x0a67) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_32 SMIAPP_REG_MK_U8(0x0a68) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_33 SMIAPP_REG_MK_U8(0x0a69) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_34 SMIAPP_REG_MK_U8(0x0a6a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_35 SMIAPP_REG_MK_U8(0x0a6b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_36 SMIAPP_REG_MK_U8(0x0a6c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_37 SMIAPP_REG_MK_U8(0x0a6d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_38 SMIAPP_REG_MK_U8(0x0a6e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_39 SMIAPP_REG_MK_U8(0x0a6f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_40 SMIAPP_REG_MK_U8(0x0a70) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_41 SMIAPP_REG_MK_U8(0x0a71) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_42 SMIAPP_REG_MK_U8(0x0a72) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_43 SMIAPP_REG_MK_U8(0x0a73) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_44 SMIAPP_REG_MK_U8(0x0a74) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_45 SMIAPP_REG_MK_U8(0x0a75) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_46 SMIAPP_REG_MK_U8(0x0a76) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_47 SMIAPP_REG_MK_U8(0x0a77) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_48 SMIAPP_REG_MK_U8(0x0a78) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_49 SMIAPP_REG_MK_U8(0x0a79) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_50 SMIAPP_REG_MK_U8(0x0a7a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_51 SMIAPP_REG_MK_U8(0x0a7b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_52 SMIAPP_REG_MK_U8(0x0a7c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_53 SMIAPP_REG_MK_U8(0x0a7d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_54 SMIAPP_REG_MK_U8(0x0a7e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_55 SMIAPP_REG_MK_U8(0x0a7f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_56 SMIAPP_REG_MK_U8(0x0a80) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_57 SMIAPP_REG_MK_U8(0x0a81) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_58 SMIAPP_REG_MK_U8(0x0a82) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_59 SMIAPP_REG_MK_U8(0x0a83) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_60 SMIAPP_REG_MK_U8(0x0a84) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_61 SMIAPP_REG_MK_U8(0x0a85) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_62 SMIAPP_REG_MK_U8(0x0a86) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_63 SMIAPP_REG_MK_U8(0x0a87) +#define SMIAPP_REG_U8_SHADING_CORRECTION_ENABLE SMIAPP_REG_MK_U8(0x0b00) +#define SMIAPP_REG_U8_LUMINANCE_CORRECTION_LEVEL SMIAPP_REG_MK_U8(0x0b01) +#define SMIAPP_REG_U8_GREEN_IMBALANCE_FILTER_ENABLE SMIAPP_REG_MK_U8(0x0b02) +#define SMIAPP_REG_U8_GREEN_IMBALANCE_FILTER_WEIGHT SMIAPP_REG_MK_U8(0x0b03) +#define SMIAPP_REG_U8_BLACK_LEVEL_CORRECTION_ENABLE SMIAPP_REG_MK_U8(0x0b04) +#define SMIAPP_REG_U8_MAPPED_COUPLET_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b05) +#define SMIAPP_REG_U8_SINGLE_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b06) +#define SMIAPP_REG_U8_SINGLE_DEFECT_CORRECT_WEIGHT SMIAPP_REG_MK_U8(0x0b07) +#define SMIAPP_REG_U8_DYNAMIC_COUPLET_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b08) +#define SMIAPP_REG_U8_DYNAMIC_COUPLET_CORRECT_WEIGHT SMIAPP_REG_MK_U8(0x0b09) +#define SMIAPP_REG_U8_COMBINED_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b0a) +#define SMIAPP_REG_U8_COMBINED_DEFECT_CORRECT_WEIGHT SMIAPP_REG_MK_U8(0x0b0b) +#define SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_ENABLE SMIAPP_REG_MK_U8(0x0b0c) +#define SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_WEIGHT SMIAPP_REG_MK_U8(0x0b0d) +#define SMIAPP_REG_U8_MAPPED_LINE_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b0e) +#define SMIAPP_REG_U8_MAPPED_LINE_DEFECT_CORRECT_ADJUST SMIAPP_REG_MK_U8(0x0b0f) +#define SMIAPP_REG_U8_MAPPED_COUPLET_CORRECT_ADJUST SMIAPP_REG_MK_U8(0x0b10) +#define SMIAPP_REG_U8_MAPPED_TRIPLET_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b11) +#define SMIAPP_REG_U8_MAPPED_TRIPLET_DEFECT_CORRECT_ADJUST SMIAPP_REG_MK_U8(0x0b12) +#define SMIAPP_REG_U8_DYNAMIC_TRIPLET_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b13) +#define SMIAPP_REG_U8_DYNAMIC_TRIPLET_DEFECT_CORRECT_ADJUST SMIAPP_REG_MK_U8(0x0b14) +#define SMIAPP_REG_U8_DYNAMIC_LINE_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b15) +#define SMIAPP_REG_U8_DYNAMIC_LINE_DEFECT_CORRECT_ADJUST SMIAPP_REG_MK_U8(0x0b16) +#define SMIAPP_REG_U8_EDOF_MODE SMIAPP_REG_MK_U8(0x0b80) +#define SMIAPP_REG_U8_SHARPNESS SMIAPP_REG_MK_U8(0x0b83) +#define SMIAPP_REG_U8_DENOISING SMIAPP_REG_MK_U8(0x0b84) +#define SMIAPP_REG_U8_MODULE_SPECIFIC SMIAPP_REG_MK_U8(0x0b85) +#define SMIAPP_REG_U16_DEPTH_OF_FIELD SMIAPP_REG_MK_U16(0x0b86) +#define SMIAPP_REG_U16_FOCUS_DISTANCE SMIAPP_REG_MK_U16(0x0b88) +#define SMIAPP_REG_U8_ESTIMATION_MODE_CTRL SMIAPP_REG_MK_U8(0x0b8a) +#define SMIAPP_REG_U16_COLOUR_TEMPERATURE SMIAPP_REG_MK_U16(0x0b8c) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_GREENR SMIAPP_REG_MK_U16(0x0b8e) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_RED SMIAPP_REG_MK_U16(0x0b90) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_BLUE SMIAPP_REG_MK_U16(0x0b92) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_GREENB SMIAPP_REG_MK_U16(0x0b94) +#define SMIAPP_REG_U8_ESTIMATION_ZONE_MODE SMIAPP_REG_MK_U8(0x0bc0) +#define SMIAPP_REG_U16_FIXED_ZONE_WEIGHTING SMIAPP_REG_MK_U16(0x0bc2) +#define SMIAPP_REG_U16_CUSTOM_ZONE_X_START SMIAPP_REG_MK_U16(0x0bc4) +#define SMIAPP_REG_U16_CUSTOM_ZONE_Y_START SMIAPP_REG_MK_U16(0x0bc6) +#define SMIAPP_REG_U16_CUSTOM_ZONE_WIDTH SMIAPP_REG_MK_U16(0x0bc8) +#define SMIAPP_REG_U16_CUSTOM_ZONE_HEIGHT SMIAPP_REG_MK_U16(0x0bca) +#define SMIAPP_REG_U8_GLOBAL_RESET_CTRL1 SMIAPP_REG_MK_U8(0x0c00) +#define SMIAPP_REG_U8_GLOBAL_RESET_CTRL2 SMIAPP_REG_MK_U8(0x0c01) +#define SMIAPP_REG_U8_GLOBAL_RESET_MODE_CONFIG_1 SMIAPP_REG_MK_U8(0x0c02) +#define SMIAPP_REG_U8_GLOBAL_RESET_MODE_CONFIG_2 SMIAPP_REG_MK_U8(0x0c03) +#define SMIAPP_REG_U16_TRDY_CTRL SMIAPP_REG_MK_U16(0x0c04) +#define SMIAPP_REG_U16_TRDOUT_CTRL SMIAPP_REG_MK_U16(0x0c06) +#define SMIAPP_REG_U16_TSHUTTER_STROBE_DELAY_CTRL SMIAPP_REG_MK_U16(0x0c08) +#define SMIAPP_REG_U16_TSHUTTER_STROBE_WIDTH_CTRL SMIAPP_REG_MK_U16(0x0c0a) +#define SMIAPP_REG_U16_TFLASH_STROBE_DELAY_CTRL SMIAPP_REG_MK_U16(0x0c0c) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_HIGH_CTRL SMIAPP_REG_MK_U16(0x0c0e) +#define SMIAPP_REG_U16_TGRST_INTERVAL_CTRL SMIAPP_REG_MK_U16(0x0c10) +#define SMIAPP_REG_U8_FLASH_STROBE_ADJUSTMENT SMIAPP_REG_MK_U8(0x0c12) +#define SMIAPP_REG_U16_FLASH_STROBE_START_POINT SMIAPP_REG_MK_U16(0x0c14) +#define SMIAPP_REG_U16_TFLASH_STROBE_DELAY_RS_CTRL SMIAPP_REG_MK_U16(0x0c16) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_HIGH_RS_CTRL SMIAPP_REG_MK_U16(0x0c18) +#define SMIAPP_REG_U8_FLASH_MODE_RS SMIAPP_REG_MK_U8(0x0c1a) +#define SMIAPP_REG_U8_FLASH_TRIGGER_RS SMIAPP_REG_MK_U8(0x0c1b) +#define SMIAPP_REG_U8_FLASH_STATUS SMIAPP_REG_MK_U8(0x0c1c) +#define SMIAPP_REG_U8_SA_STROBE_MODE SMIAPP_REG_MK_U8(0x0c1d) +#define SMIAPP_REG_U16_SA_STROBE_START_POINT SMIAPP_REG_MK_U16(0x0c1e) +#define SMIAPP_REG_U16_TSA_STROBE_DELAY_CTRL SMIAPP_REG_MK_U16(0x0c20) +#define SMIAPP_REG_U16_TSA_STROBE_WIDTH_CTRL SMIAPP_REG_MK_U16(0x0c22) +#define SMIAPP_REG_U8_SA_STROBE_TRIGGER SMIAPP_REG_MK_U8(0x0c24) +#define SMIAPP_REG_U8_SPECIAL_ACTUATOR_STATUS SMIAPP_REG_MK_U8(0x0c25) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH2_HIGH_RS_CTRL SMIAPP_REG_MK_U16(0x0c26) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_LOW_RS_CTRL SMIAPP_REG_MK_U16(0x0c28) +#define SMIAPP_REG_U8_TFLASH_STROBE_COUNT_RS_CTRL SMIAPP_REG_MK_U8(0x0c2a) +#define SMIAPP_REG_U8_TFLASH_STROBE_COUNT_CTRL SMIAPP_REG_MK_U8(0x0c2b) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH2_HIGH_CTRL SMIAPP_REG_MK_U16(0x0c2c) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_LOW_CTRL SMIAPP_REG_MK_U16(0x0c2e) +#define SMIAPP_REG_U8_LOW_LEVEL_CTRL SMIAPP_REG_MK_U8(0x0c80) +#define SMIAPP_REG_U16_MAIN_TRIGGER_REF_POINT SMIAPP_REG_MK_U16(0x0c82) +#define SMIAPP_REG_U16_MAIN_TRIGGER_T3 SMIAPP_REG_MK_U16(0x0c84) +#define SMIAPP_REG_U8_MAIN_TRIGGER_COUNT SMIAPP_REG_MK_U8(0x0c86) +#define SMIAPP_REG_U16_PHASE1_TRIGGER_T3 SMIAPP_REG_MK_U16(0x0c88) +#define SMIAPP_REG_U8_PHASE1_TRIGGER_COUNT SMIAPP_REG_MK_U8(0x0c8a) +#define SMIAPP_REG_U16_PHASE2_TRIGGER_T3 SMIAPP_REG_MK_U16(0x0c8c) +#define SMIAPP_REG_U8_PHASE2_TRIGGER_COUNT SMIAPP_REG_MK_U8(0x0c8e) +#define SMIAPP_REG_U8_MECH_SHUTTER_CTRL SMIAPP_REG_MK_U8(0x0d00) +#define SMIAPP_REG_U8_OPERATION_MODE SMIAPP_REG_MK_U8(0x0d01) +#define SMIAPP_REG_U8_ACT_STATE1 SMIAPP_REG_MK_U8(0x0d02) +#define SMIAPP_REG_U8_ACT_STATE2 SMIAPP_REG_MK_U8(0x0d03) +#define SMIAPP_REG_U16_FOCUS_CHANGE SMIAPP_REG_MK_U16(0x0d80) +#define SMIAPP_REG_U16_FOCUS_CHANGE_CONTROL SMIAPP_REG_MK_U16(0x0d82) +#define SMIAPP_REG_U16_FOCUS_CHANGE_NUMBER_PHASE1 SMIAPP_REG_MK_U16(0x0d84) +#define SMIAPP_REG_U16_FOCUS_CHANGE_NUMBER_PHASE2 SMIAPP_REG_MK_U16(0x0d86) +#define SMIAPP_REG_U8_STROBE_COUNT_PHASE1 SMIAPP_REG_MK_U8(0x0d88) +#define SMIAPP_REG_U8_STROBE_COUNT_PHASE2 SMIAPP_REG_MK_U8(0x0d89) +#define SMIAPP_REG_U8_POSITION SMIAPP_REG_MK_U8(0x0d8a) +#define SMIAPP_REG_U8_BRACKETING_LUT_CONTROL SMIAPP_REG_MK_U8(0x0e00) +#define SMIAPP_REG_U8_BRACKETING_LUT_MODE SMIAPP_REG_MK_U8(0x0e01) +#define SMIAPP_REG_U8_BRACKETING_LUT_ENTRY_CONTROL SMIAPP_REG_MK_U8(0x0e02) +#define SMIAPP_REG_U8_LUT_PARAMETERS_START SMIAPP_REG_MK_U8(0x0e10) +#define SMIAPP_REG_U8_LUT_PARAMETERS_END SMIAPP_REG_MK_U8(0x0eff) +#define SMIAPP_REG_U16_INTEGRATION_TIME_CAPABILITY SMIAPP_REG_MK_U16(0x1000) +#define SMIAPP_REG_U16_COARSE_INTEGRATION_TIME_MIN SMIAPP_REG_MK_U16(0x1004) +#define SMIAPP_REG_U16_COARSE_INTEGRATION_TIME_MAX_MARGIN SMIAPP_REG_MK_U16(0x1006) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MIN SMIAPP_REG_MK_U16(0x1008) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MAX_MARGIN SMIAPP_REG_MK_U16(0x100a) +#define SMIAPP_REG_U16_DIGITAL_GAIN_CAPABILITY SMIAPP_REG_MK_U16(0x1080) +#define SMIAPP_REG_U16_DIGITAL_GAIN_MIN SMIAPP_REG_MK_U16(0x1084) +#define SMIAPP_REG_U16_DIGITAL_GAIN_MAX SMIAPP_REG_MK_U16(0x1086) +#define SMIAPP_REG_U16_DIGITAL_GAIN_STEP_SIZE SMIAPP_REG_MK_U16(0x1088) +#define SMIAPP_REG_F32_MIN_EXT_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1100) +#define SMIAPP_REG_F32_MAX_EXT_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1104) +#define SMIAPP_REG_U16_MIN_PRE_PLL_CLK_DIV SMIAPP_REG_MK_U16(0x1108) +#define SMIAPP_REG_U16_MAX_PRE_PLL_CLK_DIV SMIAPP_REG_MK_U16(0x110a) +#define SMIAPP_REG_F32_MIN_PLL_IP_FREQ_HZ SMIAPP_REG_MK_F32(0x110c) +#define SMIAPP_REG_F32_MAX_PLL_IP_FREQ_HZ SMIAPP_REG_MK_F32(0x1110) +#define SMIAPP_REG_U16_MIN_PLL_MULTIPLIER SMIAPP_REG_MK_U16(0x1114) +#define SMIAPP_REG_U16_MAX_PLL_MULTIPLIER SMIAPP_REG_MK_U16(0x1116) +#define SMIAPP_REG_F32_MIN_PLL_OP_FREQ_HZ SMIAPP_REG_MK_F32(0x1118) +#define SMIAPP_REG_F32_MAX_PLL_OP_FREQ_HZ SMIAPP_REG_MK_F32(0x111c) +#define SMIAPP_REG_U16_MIN_VT_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x1120) +#define SMIAPP_REG_U16_MAX_VT_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x1122) +#define SMIAPP_REG_F32_MIN_VT_SYS_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1124) +#define SMIAPP_REG_F32_MAX_VT_SYS_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1128) +#define SMIAPP_REG_F32_MIN_VT_PIX_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x112c) +#define SMIAPP_REG_F32_MAX_VT_PIX_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1130) +#define SMIAPP_REG_U16_MIN_VT_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x1134) +#define SMIAPP_REG_U16_MAX_VT_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x1136) +#define SMIAPP_REG_U16_MIN_FRAME_LENGTH_LINES SMIAPP_REG_MK_U16(0x1140) +#define SMIAPP_REG_U16_MAX_FRAME_LENGTH_LINES SMIAPP_REG_MK_U16(0x1142) +#define SMIAPP_REG_U16_MIN_LINE_LENGTH_PCK SMIAPP_REG_MK_U16(0x1144) +#define SMIAPP_REG_U16_MAX_LINE_LENGTH_PCK SMIAPP_REG_MK_U16(0x1146) +#define SMIAPP_REG_U16_MIN_LINE_BLANKING_PCK SMIAPP_REG_MK_U16(0x1148) +#define SMIAPP_REG_U16_MIN_FRAME_BLANKING_LINES SMIAPP_REG_MK_U16(0x114a) +#define SMIAPP_REG_U8_MIN_LINE_LENGTH_PCK_STEP_SIZE SMIAPP_REG_MK_U8(0x114c) +#define SMIAPP_REG_U16_MIN_OP_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x1160) +#define SMIAPP_REG_U16_MAX_OP_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x1162) +#define SMIAPP_REG_F32_MIN_OP_SYS_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1164) +#define SMIAPP_REG_F32_MAX_OP_SYS_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1168) +#define SMIAPP_REG_U16_MIN_OP_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x116c) +#define SMIAPP_REG_U16_MAX_OP_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x116e) +#define SMIAPP_REG_F32_MIN_OP_PIX_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1170) +#define SMIAPP_REG_F32_MAX_OP_PIX_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1174) +#define SMIAPP_REG_U16_X_ADDR_MIN SMIAPP_REG_MK_U16(0x1180) +#define SMIAPP_REG_U16_Y_ADDR_MIN SMIAPP_REG_MK_U16(0x1182) +#define SMIAPP_REG_U16_X_ADDR_MAX SMIAPP_REG_MK_U16(0x1184) +#define SMIAPP_REG_U16_Y_ADDR_MAX SMIAPP_REG_MK_U16(0x1186) +#define SMIAPP_REG_U16_MIN_X_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x1188) +#define SMIAPP_REG_U16_MIN_Y_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x118a) +#define SMIAPP_REG_U16_MAX_X_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x118c) +#define SMIAPP_REG_U16_MAX_Y_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x118e) +#define SMIAPP_REG_U16_MIN_EVEN_INC SMIAPP_REG_MK_U16(0x11c0) +#define SMIAPP_REG_U16_MAX_EVEN_INC SMIAPP_REG_MK_U16(0x11c2) +#define SMIAPP_REG_U16_MIN_ODD_INC SMIAPP_REG_MK_U16(0x11c4) +#define SMIAPP_REG_U16_MAX_ODD_INC SMIAPP_REG_MK_U16(0x11c6) +#define SMIAPP_REG_U16_SCALING_CAPABILITY SMIAPP_REG_MK_U16(0x1200) +#define SMIAPP_REG_U16_SCALER_M_MIN SMIAPP_REG_MK_U16(0x1204) +#define SMIAPP_REG_U16_SCALER_M_MAX SMIAPP_REG_MK_U16(0x1206) +#define SMIAPP_REG_U16_SCALER_N_MIN SMIAPP_REG_MK_U16(0x1208) +#define SMIAPP_REG_U16_SCALER_N_MAX SMIAPP_REG_MK_U16(0x120a) +#define SMIAPP_REG_U16_SPATIAL_SAMPLING_CAPABILITY SMIAPP_REG_MK_U16(0x120c) +#define SMIAPP_REG_U8_DIGITAL_CROP_CAPABILITY SMIAPP_REG_MK_U8(0x120e) +#define SMIAPP_REG_U16_COMPRESSION_CAPABILITY SMIAPP_REG_MK_U16(0x1300) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_REDINRED SMIAPP_REG_MK_U16(0x1400) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_GREENINRED SMIAPP_REG_MK_U16(0x1402) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_BLUEINRED SMIAPP_REG_MK_U16(0x1404) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_REDINGREEN SMIAPP_REG_MK_U16(0x1406) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_GREENINGREEN SMIAPP_REG_MK_U16(0x1408) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_BLUEINGREEN SMIAPP_REG_MK_U16(0x140a) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_REDINBLUE SMIAPP_REG_MK_U16(0x140c) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_GREENINBLUE SMIAPP_REG_MK_U16(0x140e) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_BLUEINBLUE SMIAPP_REG_MK_U16(0x1410) +#define SMIAPP_REG_U16_FIFO_SIZE_PIXELS SMIAPP_REG_MK_U16(0x1500) +#define SMIAPP_REG_U8_FIFO_SUPPORT_CAPABILITY SMIAPP_REG_MK_U8(0x1502) +#define SMIAPP_REG_U8_DPHY_CTRL_CAPABILITY SMIAPP_REG_MK_U8(0x1600) +#define SMIAPP_REG_U8_CSI_LANE_MODE_CAPABILITY SMIAPP_REG_MK_U8(0x1601) +#define SMIAPP_REG_U8_CSI_SIGNALLING_MODE_CAPABILITY SMIAPP_REG_MK_U8(0x1602) +#define SMIAPP_REG_U8_FAST_STANDBY_CAPABILITY SMIAPP_REG_MK_U8(0x1603) +#define SMIAPP_REG_U8_CCI_ADDRESS_CONTROL_CAPABILITY SMIAPP_REG_MK_U8(0x1604) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_1_LANE_MODE_MBPS SMIAPP_REG_MK_U32(0x1608) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_2_LANE_MODE_MBPS SMIAPP_REG_MK_U32(0x160c) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_3_LANE_MODE_MBPS SMIAPP_REG_MK_U32(0x1610) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_4_LANE_MODE_MBPS SMIAPP_REG_MK_U32(0x1614) +#define SMIAPP_REG_U8_TEMP_SENSOR_CAPABILITY SMIAPP_REG_MK_U8(0x1618) +#define SMIAPP_REG_U16_MIN_FRAME_LENGTH_LINES_BIN SMIAPP_REG_MK_U16(0x1700) +#define SMIAPP_REG_U16_MAX_FRAME_LENGTH_LINES_BIN SMIAPP_REG_MK_U16(0x1702) +#define SMIAPP_REG_U16_MIN_LINE_LENGTH_PCK_BIN SMIAPP_REG_MK_U16(0x1704) +#define SMIAPP_REG_U16_MAX_LINE_LENGTH_PCK_BIN SMIAPP_REG_MK_U16(0x1706) +#define SMIAPP_REG_U16_MIN_LINE_BLANKING_PCK_BIN SMIAPP_REG_MK_U16(0x1708) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MIN_BIN SMIAPP_REG_MK_U16(0x170a) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN SMIAPP_REG_MK_U16(0x170c) +#define SMIAPP_REG_U8_BINNING_CAPABILITY SMIAPP_REG_MK_U8(0x1710) +#define SMIAPP_REG_U8_BINNING_WEIGHTING_CAPABILITY SMIAPP_REG_MK_U8(0x1711) +#define SMIAPP_REG_U8_BINNING_SUBTYPES SMIAPP_REG_MK_U8(0x1712) +#define SMIAPP_REG_U8_BINNING_TYPE_n(n) SMIAPP_REG_MK_U8(0x1713 + (n)) /* 1 <= n <= 237 */ +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_CAPABILITY SMIAPP_REG_MK_U8(0x1800) +#define SMIAPP_REG_U8_SHADING_CORRECTION_CAPABILITY SMIAPP_REG_MK_U8(0x1900) +#define SMIAPP_REG_U8_GREEN_IMBALANCE_CAPABILITY SMIAPP_REG_MK_U8(0x1901) +#define SMIAPP_REG_U8_BLACK_LEVEL_CAPABILITY SMIAPP_REG_MK_U8(0x1902) +#define SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_CAPABILITY SMIAPP_REG_MK_U8(0x1903) +#define SMIAPP_REG_U16_DEFECT_CORRECTION_CAPABILITY SMIAPP_REG_MK_U16(0x1904) +#define SMIAPP_REG_U16_DEFECT_CORRECTION_CAPABILITY_2 SMIAPP_REG_MK_U16(0x1906) +#define SMIAPP_REG_U8_EDOF_CAPABILITY SMIAPP_REG_MK_U8(0x1980) +#define SMIAPP_REG_U8_ESTIMATION_FRAMES SMIAPP_REG_MK_U8(0x1981) +#define SMIAPP_REG_U8_SUPPORTS_SHARPNESS_ADJ SMIAPP_REG_MK_U8(0x1982) +#define SMIAPP_REG_U8_SUPPORTS_DENOISING_ADJ SMIAPP_REG_MK_U8(0x1983) +#define SMIAPP_REG_U8_SUPPORTS_MODULE_SPECIFIC_ADJ SMIAPP_REG_MK_U8(0x1984) +#define SMIAPP_REG_U8_SUPPORTS_DEPTH_OF_FIELD_ADJ SMIAPP_REG_MK_U8(0x1985) +#define SMIAPP_REG_U8_SUPPORTS_FOCUS_DISTANCE_ADJ SMIAPP_REG_MK_U8(0x1986) +#define SMIAPP_REG_U8_COLOUR_FEEDBACK_CAPABILITY SMIAPP_REG_MK_U8(0x1987) +#define SMIAPP_REG_U8_EDOF_SUPPORT_AB_NXM SMIAPP_REG_MK_U8(0x1988) +#define SMIAPP_REG_U8_ESTIMATION_MODE_CAPABILITY SMIAPP_REG_MK_U8(0x19c0) +#define SMIAPP_REG_U8_ESTIMATION_ZONE_CAPABILITY SMIAPP_REG_MK_U8(0x19c1) +#define SMIAPP_REG_U16_EST_DEPTH_OF_FIELD SMIAPP_REG_MK_U16(0x19c2) +#define SMIAPP_REG_U16_EST_FOCUS_DISTANCE SMIAPP_REG_MK_U16(0x19c4) +#define SMIAPP_REG_U16_CAPABILITY_TRDY_MIN SMIAPP_REG_MK_U16(0x1a00) +#define SMIAPP_REG_U8_FLASH_MODE_CAPABILITY SMIAPP_REG_MK_U8(0x1a02) +#define SMIAPP_REG_U16_MECH_SHUT_AND_ACT_START_ADDR SMIAPP_REG_MK_U16(0x1b02) +#define SMIAPP_REG_U8_ACTUATOR_CAPABILITY SMIAPP_REG_MK_U8(0x1b04) +#define SMIAPP_REG_U16_ACTUATOR_TYPE SMIAPP_REG_MK_U16(0x1b40) +#define SMIAPP_REG_U8_AF_DEVICE_ADDRESS SMIAPP_REG_MK_U8(0x1b42) +#define SMIAPP_REG_U16_FOCUS_CHANGE_ADDRESS SMIAPP_REG_MK_U16(0x1b44) +#define SMIAPP_REG_U8_BRACKETING_LUT_CAPABILITY_1 SMIAPP_REG_MK_U8(0x1c00) +#define SMIAPP_REG_U8_BRACKETING_LUT_CAPABILITY_2 SMIAPP_REG_MK_U8(0x1c01) +#define SMIAPP_REG_U8_BRACKETING_LUT_SIZE SMIAPP_REG_MK_U8(0x1c02) diff --git a/drivers/media/i2c/smiapp/smiapp-reg.h b/drivers/media/i2c/smiapp/smiapp-reg.h new file mode 100644 index 000000000..e6f963097 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-reg.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/smiapp/smiapp-reg.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ + +#ifndef __SMIAPP_REG_H_ +#define __SMIAPP_REG_H_ + +#include <linux/bits.h> + +#include "smiapp-reg-defs.h" + +/* Bits for above register */ +#define SMIAPP_IMAGE_ORIENTATION_HFLIP BIT(0) +#define SMIAPP_IMAGE_ORIENTATION_VFLIP BIT(1) + +#define SMIAPP_DATA_TRANSFER_IF_1_CTRL_EN BIT(0) +#define SMIAPP_DATA_TRANSFER_IF_1_CTRL_WR_EN BIT(1) +#define SMIAPP_DATA_TRANSFER_IF_1_CTRL_ERR_CLEAR BIT(2) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_RD_READY BIT(0) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_WR_READY BIT(1) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_EDATA BIT(2) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_EUSAGE BIT(3) + +#define SMIAPP_DATA_TRANSFER_IF_CAPABILITY_SUPPORTED BIT(0) +#define SMIAPP_DATA_TRANSFER_IF_CAPABILITY_POLL BIT(2) + +#define SMIAPP_SOFTWARE_RESET BIT(0) + +#define SMIAPP_FLASH_MODE_CAPABILITY_SINGLE_STROBE BIT(0) +#define SMIAPP_FLASH_MODE_CAPABILITY_MULTIPLE_STROBE BIT(1) + +#define SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK 0 +#define SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE 1 +#define SMIAPP_CSI_SIGNALLING_MODE_CSI2 2 + +#define SMIAPP_DPHY_CTRL_AUTOMATIC 0 +/* DPHY control based on REQUESTED_LINK_BIT_RATE_MBPS */ +#define SMIAPP_DPHY_CTRL_UI 1 +#define SMIAPP_DPHY_CTRL_REGISTER 2 + +#define SMIAPP_COMPRESSION_MODE_SIMPLE_PREDICTOR 1 +#define SMIAPP_COMPRESSION_MODE_ADVANCED_PREDICTOR 2 + +#define SMIAPP_MODE_SELECT_SOFTWARE_STANDBY 0 +#define SMIAPP_MODE_SELECT_STREAMING 1 + +#define SMIAPP_SCALING_MODE_NONE 0 +#define SMIAPP_SCALING_MODE_HORIZONTAL 1 +#define SMIAPP_SCALING_MODE_BOTH 2 + +#define SMIAPP_SCALING_CAPABILITY_NONE 0 +#define SMIAPP_SCALING_CAPABILITY_HORIZONTAL 1 +#define SMIAPP_SCALING_CAPABILITY_BOTH 2 /* horizontal/both */ + +/* digital crop right before scaler */ +#define SMIAPP_DIGITAL_CROP_CAPABILITY_NONE 0 +#define SMIAPP_DIGITAL_CROP_CAPABILITY_INPUT_CROP 1 + +#define SMIAPP_BINNING_CAPABILITY_NO 0 +#define SMIAPP_BINNING_CAPABILITY_YES 1 + +/* Maximum number of binning subtypes */ +#define SMIAPP_BINNING_SUBTYPES 253 + +#define SMIAPP_PIXEL_ORDER_GRBG 0 +#define SMIAPP_PIXEL_ORDER_RGGB 1 +#define SMIAPP_PIXEL_ORDER_BGGR 2 +#define SMIAPP_PIXEL_ORDER_GBRG 3 + +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL 1 +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_EXTENDED 2 +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL_N 8 +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_EXTENDED_N 16 + +#define SMIAPP_FRAME_FORMAT_MODEL_TYPE_2BYTE 0x01 +#define SMIAPP_FRAME_FORMAT_MODEL_TYPE_4BYTE 0x02 +#define SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NROWS_MASK 0x0f +#define SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NCOLS_MASK 0xf0 +#define SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NCOLS_SHIFT 4 + +#define SMIAPP_FRAME_FORMAT_DESC_2_PIXELCODE_MASK 0xf000 +#define SMIAPP_FRAME_FORMAT_DESC_2_PIXELCODE_SHIFT 12 +#define SMIAPP_FRAME_FORMAT_DESC_2_PIXELS_MASK 0x0fff + +#define SMIAPP_FRAME_FORMAT_DESC_4_PIXELCODE_MASK 0xf0000000 +#define SMIAPP_FRAME_FORMAT_DESC_4_PIXELCODE_SHIFT 28 +#define SMIAPP_FRAME_FORMAT_DESC_4_PIXELS_MASK 0x0000ffff + +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_EMBEDDED 1 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_DUMMY 2 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_BLACK 3 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_DARK 4 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_VISIBLE 5 + +#define SMIAPP_FAST_STANDBY_CTRL_COMPLETE_FRAMES 0 +#define SMIAPP_FAST_STANDBY_CTRL_IMMEDIATE 1 + +/* Scaling N factor */ +#define SMIAPP_SCALE_N 16 + +/* Image statistics registers */ +/* Registers 0x2000 to 0x2fff are reserved for future + * use for statistics features. + */ + +/* Manufacturer Specific Registers: 0x3000 to 0x3fff + * The manufacturer specifies these as a black box. + */ + +#endif /* __SMIAPP_REG_H_ */ diff --git a/drivers/media/i2c/smiapp/smiapp-regs.c b/drivers/media/i2c/smiapp/smiapp-regs.c new file mode 100644 index 000000000..1b58b7c6c --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-regs.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/smiapp/smiapp-regs.c + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ + +#include <asm/unaligned.h> + +#include <linux/delay.h> +#include <linux/i2c.h> + +#include "smiapp.h" +#include "smiapp-regs.h" + +static uint32_t float_to_u32_mul_1000000(struct i2c_client *client, + uint32_t phloat) +{ + int32_t exp; + uint64_t man; + + if (phloat >= 0x80000000) { + dev_err(&client->dev, "this is a negative number\n"); + return 0; + } + + if (phloat == 0x7f800000) + return ~0; /* Inf. */ + + if ((phloat & 0x7f800000) == 0x7f800000) { + dev_err(&client->dev, "NaN or other special number\n"); + return 0; + } + + /* Valid cases begin here */ + if (phloat == 0) + return 0; /* Valid zero */ + + if (phloat > 0x4f800000) + return ~0; /* larger than 4294967295 */ + + /* + * Unbias exponent (note how phloat is now guaranteed to + * have 0 in the high bit) + */ + exp = ((int32_t)phloat >> 23) - 127; + + /* Extract mantissa, add missing '1' bit and it's in MHz */ + man = ((phloat & 0x7fffff) | 0x800000) * 1000000ULL; + + if (exp < 0) + man >>= -exp; + else + man <<= exp; + + man >>= 23; /* Remove mantissa bias */ + + return man & 0xffffffff; +} + + +/* + * Read a 8/16/32-bit i2c register. The value is returned in 'val'. + * Returns zero if successful, or non-zero otherwise. + */ +static int ____smiapp_read(struct smiapp_sensor *sensor, u16 reg, + u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct i2c_msg msg; + unsigned char data_buf[sizeof(u32)] = { 0 }; + unsigned char offset_buf[sizeof(u16)]; + int r; + + if (len > sizeof(data_buf)) + return -EINVAL; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = sizeof(offset_buf); + msg.buf = offset_buf; + put_unaligned_be16(reg, offset_buf); + + r = i2c_transfer(client->adapter, &msg, 1); + if (r != 1) { + if (r >= 0) + r = -EBUSY; + goto err; + } + + msg.len = len; + msg.flags = I2C_M_RD; + msg.buf = &data_buf[sizeof(data_buf) - len]; + + r = i2c_transfer(client->adapter, &msg, 1); + if (r != 1) { + if (r >= 0) + r = -EBUSY; + goto err; + } + + *val = get_unaligned_be32(data_buf); + + return 0; + +err: + dev_err(&client->dev, "read from offset 0x%x error %d\n", reg, r); + + return r; +} + +/* Read a register using 8-bit access only. */ +static int ____smiapp_read_8only(struct smiapp_sensor *sensor, u16 reg, + u16 len, u32 *val) +{ + unsigned int i; + int rval; + + *val = 0; + + for (i = 0; i < len; i++) { + u32 val8; + + rval = ____smiapp_read(sensor, reg + i, 1, &val8); + if (rval < 0) + return rval; + *val |= val8 << ((len - i - 1) << 3); + } + + return 0; +} + +/* + * Read a 8/16/32-bit i2c register. The value is returned in 'val'. + * Returns zero if successful, or non-zero otherwise. + */ +static int __smiapp_read(struct smiapp_sensor *sensor, u32 reg, u32 *val, + bool only8) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + u8 len = SMIAPP_REG_WIDTH(reg); + int rval; + + if (len != SMIAPP_REG_8BIT && len != SMIAPP_REG_16BIT + && len != SMIAPP_REG_32BIT) + return -EINVAL; + + if (!only8) + rval = ____smiapp_read(sensor, SMIAPP_REG_ADDR(reg), len, val); + else + rval = ____smiapp_read_8only(sensor, SMIAPP_REG_ADDR(reg), len, + val); + if (rval < 0) + return rval; + + if (reg & SMIAPP_REG_FLAG_FLOAT) + *val = float_to_u32_mul_1000000(client, *val); + + return 0; +} + +int smiapp_read_no_quirk(struct smiapp_sensor *sensor, u32 reg, u32 *val) +{ + return __smiapp_read( + sensor, reg, val, + smiapp_needs_quirk(sensor, + SMIAPP_QUIRK_FLAG_8BIT_READ_ONLY)); +} + +static int smiapp_read_quirk(struct smiapp_sensor *sensor, u32 reg, u32 *val, + bool force8) +{ + int rval; + + *val = 0; + rval = smiapp_call_quirk(sensor, reg_access, false, ®, val); + if (rval == -ENOIOCTLCMD) + return 0; + if (rval < 0) + return rval; + + if (force8) + return __smiapp_read(sensor, reg, val, true); + + return smiapp_read_no_quirk(sensor, reg, val); +} + +int smiapp_read(struct smiapp_sensor *sensor, u32 reg, u32 *val) +{ + return smiapp_read_quirk(sensor, reg, val, false); +} + +int smiapp_read_8only(struct smiapp_sensor *sensor, u32 reg, u32 *val) +{ + return smiapp_read_quirk(sensor, reg, val, true); +} + +int smiapp_write_no_quirk(struct smiapp_sensor *sensor, u32 reg, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct i2c_msg msg; + unsigned char data[6]; + unsigned int retries; + u8 len = SMIAPP_REG_WIDTH(reg); + int r; + + if (len > sizeof(data) - 2) + return -EINVAL; + + msg.addr = client->addr; + msg.flags = 0; /* Write */ + msg.len = 2 + len; + msg.buf = data; + + put_unaligned_be16(SMIAPP_REG_ADDR(reg), data); + put_unaligned_be32(val << (8 * (sizeof(val) - len)), data + 2); + + for (retries = 0; retries < 5; retries++) { + /* + * Due to unknown reason sensor stops responding. This + * loop is a temporaty solution until the root cause + * is found. + */ + r = i2c_transfer(client->adapter, &msg, 1); + if (r == 1) { + if (retries) + dev_err(&client->dev, + "sensor i2c stall encountered. retries: %d\n", + retries); + return 0; + } + + usleep_range(2000, 2000); + } + + dev_err(&client->dev, + "wrote 0x%x to offset 0x%x error %d\n", val, + SMIAPP_REG_ADDR(reg), r); + + return r; +} + +/* + * Write to a 8/16-bit register. + * Returns zero if successful, or non-zero otherwise. + */ +int smiapp_write(struct smiapp_sensor *sensor, u32 reg, u32 val) +{ + int rval; + + rval = smiapp_call_quirk(sensor, reg_access, true, ®, &val); + if (rval == -ENOIOCTLCMD) + return 0; + if (rval < 0) + return rval; + + return smiapp_write_no_quirk(sensor, reg, val); +} diff --git a/drivers/media/i2c/smiapp/smiapp-regs.h b/drivers/media/i2c/smiapp/smiapp-regs.h new file mode 100644 index 000000000..8fda6ed56 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-regs.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * include/media/smiapp/smiapp-regs.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ + +#ifndef SMIAPP_REGS_H +#define SMIAPP_REGS_H + +#include <linux/i2c.h> +#include <linux/types.h> + +#define SMIAPP_REG_ADDR(reg) ((u16)reg) +#define SMIAPP_REG_WIDTH(reg) ((u8)(reg >> 16)) +#define SMIAPP_REG_FLAGS(reg) ((u8)(reg >> 24)) + +/* Use upper 8 bits of the type field for flags */ +#define SMIAPP_REG_FLAG_FLOAT (1 << 24) + +#define SMIAPP_REG_8BIT 1 +#define SMIAPP_REG_16BIT 2 +#define SMIAPP_REG_32BIT 4 + +struct smiapp_sensor; + +int smiapp_read_no_quirk(struct smiapp_sensor *sensor, u32 reg, u32 *val); +int smiapp_read(struct smiapp_sensor *sensor, u32 reg, u32 *val); +int smiapp_read_8only(struct smiapp_sensor *sensor, u32 reg, u32 *val); +int smiapp_write_no_quirk(struct smiapp_sensor *sensor, u32 reg, u32 val); +int smiapp_write(struct smiapp_sensor *sensor, u32 reg, u32 val); + +#endif diff --git a/drivers/media/i2c/smiapp/smiapp.h b/drivers/media/i2c/smiapp/smiapp.h new file mode 100644 index 000000000..6f469934f --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp.h @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/smiapp/smiapp.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2010--2012 Nokia Corporation + * Contact: Sakari Ailus <sakari.ailus@iki.fi> + */ + +#ifndef __SMIAPP_PRIV_H_ +#define __SMIAPP_PRIV_H_ + +#include <linux/mutex.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#include "smiapp-pll.h" +#include "smiapp-reg.h" +#include "smiapp-regs.h" +#include "smiapp-quirk.h" + +/* + * Standard SMIA++ constants + */ +#define SMIA_VERSION_1 10 +#define SMIAPP_VERSION_0_8 8 /* Draft 0.8 */ +#define SMIAPP_VERSION_0_9 9 /* Draft 0.9 */ +#define SMIAPP_VERSION_1 10 + +#define SMIAPP_PROFILE_0 0 +#define SMIAPP_PROFILE_1 1 +#define SMIAPP_PROFILE_2 2 + +#define SMIAPP_NVM_PAGE_SIZE 64 /* bytes */ + +#define SMIAPP_RESET_DELAY_CLOCKS 2400 +#define SMIAPP_RESET_DELAY(clk) \ + (1000 + (SMIAPP_RESET_DELAY_CLOCKS * 1000 \ + + (clk) / 1000 - 1) / ((clk) / 1000)) + +#define SMIAPP_COLOUR_COMPONENTS 4 + +#define SMIAPP_NAME "smiapp" + +#define SMIAPP_DFL_I2C_ADDR (0x20 >> 1) /* Default I2C Address */ +#define SMIAPP_ALT_I2C_ADDR (0x6e >> 1) /* Alternate I2C Address */ + +/* + * Sometimes due to board layout considerations the camera module can be + * mounted rotated. The typical rotation used is 180 degrees which can be + * corrected by giving a default H-FLIP and V-FLIP in the sensor readout. + * FIXME: rotation also changes the bayer pattern. + */ +enum smiapp_module_board_orient { + SMIAPP_MODULE_BOARD_ORIENT_0 = 0, + SMIAPP_MODULE_BOARD_ORIENT_180, +}; + +struct smiapp_flash_strobe_parms { + u8 mode; + u32 strobe_width_high_us; + u16 strobe_delay; + u16 stobe_start_point; + u8 trigger; +}; + +struct smiapp_hwconfig { + /* + * Change the cci address if i2c_addr_alt is set. + * Both default and alternate cci addr need to be present + */ + unsigned short i2c_addr_dfl; /* Default i2c addr */ + unsigned short i2c_addr_alt; /* Alternate i2c addr */ + + uint32_t ext_clk; /* sensor external clk */ + + unsigned int lanes; /* Number of CSI-2 lanes */ + uint32_t csi_signalling_mode; /* SMIAPP_CSI_SIGNALLING_MODE_* */ + uint64_t *op_sys_clock; + + enum smiapp_module_board_orient module_board_orient; + + struct smiapp_flash_strobe_parms *strobe_setup; +}; + +#include "smiapp-limits.h" + +struct smiapp_quirk; + +#define SMIAPP_MODULE_IDENT_FLAG_REV_LE (1 << 0) + +struct smiapp_module_ident { + u8 manufacturer_id; + u16 model_id; + u8 revision_number_major; + + u8 flags; + + char *name; + const struct smiapp_quirk *quirk; +}; + +struct smiapp_module_info { + u32 manufacturer_id; + u32 model_id; + u32 revision_number_major; + u32 revision_number_minor; + + u32 module_year; + u32 module_month; + u32 module_day; + + u32 sensor_manufacturer_id; + u32 sensor_model_id; + u32 sensor_revision_number; + u32 sensor_firmware_version; + + u32 smia_version; + u32 smiapp_version; + + u32 smiapp_profile; + + char *name; + const struct smiapp_quirk *quirk; +}; + +#define SMIAPP_IDENT_FQ(manufacturer, model, rev, fl, _name, _quirk) \ + { .manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = fl, \ + .name = _name, \ + .quirk = _quirk, } + +#define SMIAPP_IDENT_LQ(manufacturer, model, rev, _name, _quirk) \ + { .manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = SMIAPP_MODULE_IDENT_FLAG_REV_LE, \ + .name = _name, \ + .quirk = _quirk, } + +#define SMIAPP_IDENT_L(manufacturer, model, rev, _name) \ + { .manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = SMIAPP_MODULE_IDENT_FLAG_REV_LE, \ + .name = _name, } + +#define SMIAPP_IDENT_Q(manufacturer, model, rev, _name, _quirk) \ + { .manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = 0, \ + .name = _name, \ + .quirk = _quirk, } + +#define SMIAPP_IDENT(manufacturer, model, rev, _name) \ + { .manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = 0, \ + .name = _name, } + +struct smiapp_reg_limits { + u32 addr; + char *what; +}; + +extern struct smiapp_reg_limits smiapp_reg_limits[]; + +struct smiapp_csi_data_format { + u32 code; + u8 width; + u8 compressed; + u8 pixel_order; +}; + +#define SMIAPP_SUBDEVS 3 + +#define SMIAPP_PA_PAD_SRC 0 +#define SMIAPP_PAD_SINK 0 +#define SMIAPP_PAD_SRC 1 +#define SMIAPP_PADS 2 + +struct smiapp_binning_subtype { + u8 horizontal:4; + u8 vertical:4; +} __packed; + +struct smiapp_subdev { + struct v4l2_subdev sd; + struct media_pad pads[SMIAPP_PADS]; + struct v4l2_rect sink_fmt; + struct v4l2_rect crop[SMIAPP_PADS]; + struct v4l2_rect compose; /* compose on sink */ + unsigned short sink_pad; + unsigned short source_pad; + int npads; + struct smiapp_sensor *sensor; + struct v4l2_ctrl_handler ctrl_handler; +}; + +/* + * struct smiapp_sensor - Main device structure + */ +struct smiapp_sensor { + /* + * "mutex" is used to serialise access to all fields here + * except v4l2_ctrls at the end of the struct. "mutex" is also + * used to serialise access to file handle specific + * information. + */ + struct mutex mutex; + struct smiapp_subdev ssds[SMIAPP_SUBDEVS]; + u32 ssds_used; + struct smiapp_subdev *src; + struct smiapp_subdev *binner; + struct smiapp_subdev *scaler; + struct smiapp_subdev *pixel_array; + struct smiapp_hwconfig *hwcfg; + struct regulator *vana; + struct clk *ext_clk; + struct gpio_desc *xshutdown; + u32 limits[SMIAPP_LIMIT_LAST]; + u8 nbinning_subtypes; + struct smiapp_binning_subtype binning_subtypes[SMIAPP_BINNING_SUBTYPES]; + u32 mbus_frame_fmts; + const struct smiapp_csi_data_format *csi_format; + const struct smiapp_csi_data_format *internal_csi_format; + u32 default_mbus_frame_fmts; + int default_pixel_order; + + u8 binning_horizontal; + u8 binning_vertical; + + u8 scale_m; + u8 scaling_mode; + + u8 hvflip_inv_mask; /* H/VFLIP inversion due to sensor orientation */ + u8 frame_skip; + u16 embedded_start; /* embedded data start line */ + u16 embedded_end; + u16 image_start; /* image data start line */ + u16 visible_pixel_start; /* start pixel of the visible image */ + + bool streaming; + bool dev_init_done; + u8 compressed_min_bpp; + + struct smiapp_module_info minfo; + + struct smiapp_pll pll; + + /* Is a default format supported for a given BPP? */ + unsigned long *valid_link_freqs; + + /* Pixel array controls */ + struct v4l2_ctrl *analog_gain; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *pixel_rate_parray; + /* src controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate_csi; + /* test pattern colour components */ + struct v4l2_ctrl *test_data[SMIAPP_COLOUR_COMPONENTS]; +}; + +#define to_smiapp_subdev(_sd) \ + container_of(_sd, struct smiapp_subdev, sd) + +#define to_smiapp_sensor(_sd) \ + (to_smiapp_subdev(_sd)->sensor) + +#endif /* __SMIAPP_PRIV_H_ */ diff --git a/drivers/media/i2c/sony-btf-mpx.c b/drivers/media/i2c/sony-btf-mpx.c new file mode 100644 index 000000000..ad239280c --- /dev/null +++ b/drivers/media/i2c/sony-btf-mpx.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/tuner.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-device.h> +#include <linux/slab.h> + +MODULE_DESCRIPTION("sony-btf-mpx driver"); +MODULE_LICENSE("GPL v2"); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level 0=off(default) 1=on"); + +/* #define MPX_DEBUG */ + +/* + * Note: + * + * AS(IF/MPX) pin: LOW HIGH/OPEN + * IF/MPX address: 0x42/0x40 0x43/0x44 + */ + + +static int force_mpx_mode = -1; +module_param(force_mpx_mode, int, 0644); + +struct sony_btf_mpx { + struct v4l2_subdev sd; + int mpxmode; + u32 audmode; +}; + +static inline struct sony_btf_mpx *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct sony_btf_mpx, sd); +} + +static int mpx_write(struct i2c_client *client, int dev, int addr, int val) +{ + u8 buffer[5]; + struct i2c_msg msg; + + buffer[0] = dev; + buffer[1] = addr >> 8; + buffer[2] = addr & 0xff; + buffer[3] = val >> 8; + buffer[4] = val & 0xff; + msg.addr = client->addr; + msg.flags = 0; + msg.len = 5; + msg.buf = buffer; + i2c_transfer(client->adapter, &msg, 1); + return 0; +} + +/* + * MPX register values for the BTF-PG472Z: + * + * FM_ NICAM_ SCART_ + * MODUS SOURCE ACB PRESCAL PRESCAL PRESCAL SYSTEM VOLUME + * 10/0030 12/0008 12/0013 12/000E 12/0010 12/0000 10/0020 12/0000 + * --------------------------------------------------------------- + * Auto 1003 0020 0100 2603 5000 XXXX 0001 7500 + * + * B/G + * Mono 1003 0020 0100 2603 5000 XXXX 0003 7500 + * A2 1003 0020 0100 2601 5000 XXXX 0003 7500 + * NICAM 1003 0120 0100 2603 5000 XXXX 0008 7500 + * + * I + * Mono 1003 0020 0100 2603 7900 XXXX 000A 7500 + * NICAM 1003 0120 0100 2603 7900 XXXX 000A 7500 + * + * D/K + * Mono 1003 0020 0100 2603 5000 XXXX 0004 7500 + * A2-1 1003 0020 0100 2601 5000 XXXX 0004 7500 + * A2-2 1003 0020 0100 2601 5000 XXXX 0005 7500 + * A2-3 1003 0020 0100 2601 5000 XXXX 0007 7500 + * NICAM 1003 0120 0100 2603 5000 XXXX 000B 7500 + * + * L/L' + * Mono 0003 0200 0100 7C03 5000 2200 0009 7500 + * NICAM 0003 0120 0100 7C03 5000 XXXX 0009 7500 + * + * M + * Mono 1003 0200 0100 2B03 5000 2B00 0002 7500 + * + * For Asia, replace the 0x26XX in FM_PRESCALE with 0x14XX. + * + * Bilingual selection in A2/NICAM: + * + * High byte of SOURCE Left chan Right chan + * 0x01 MAIN SUB + * 0x03 MAIN MAIN + * 0x04 SUB SUB + * + * Force mono in NICAM by setting the high byte of SOURCE to 0x02 (L/L') or + * 0x00 (all other bands). Force mono in A2 with FMONO_A2: + * + * FMONO_A2 + * 10/0022 + * -------- + * Forced mono ON 07F0 + * Forced mono OFF 0190 + */ + +static const struct { + enum { AUD_MONO, AUD_A2, AUD_NICAM, AUD_NICAM_L } audio_mode; + u16 modus; + u16 source; + u16 acb; + u16 fm_prescale; + u16 nicam_prescale; + u16 scart_prescale; + u16 system; + u16 volume; +} mpx_audio_modes[] = { + /* Auto */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0001, 0x7500 }, + /* B/G Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0003, 0x7500 }, + /* B/G A2 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0003, 0x7500 }, + /* B/G NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0008, 0x7500 }, + /* I Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x7900, 0x0000, 0x000A, 0x7500 }, + /* I NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, + 0x7900, 0x0000, 0x000A, 0x7500 }, + /* D/K Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0004, 0x7500 }, + /* D/K A2-1 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0004, 0x7500 }, + /* D/K A2-2 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0005, 0x7500 }, + /* D/K A2-3 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0007, 0x7500 }, + /* D/K NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, + 0x5000, 0x0000, 0x000B, 0x7500 }, + /* L/L' Mono */ { AUD_MONO, 0x0003, 0x0200, 0x0100, 0x7C03, + 0x5000, 0x2200, 0x0009, 0x7500 }, + /* L/L' NICAM */{ AUD_NICAM_L, 0x0003, 0x0120, 0x0100, 0x7C03, + 0x5000, 0x0000, 0x0009, 0x7500 }, +}; + +#define MPX_NUM_MODES ARRAY_SIZE(mpx_audio_modes) + +static int mpx_setup(struct sony_btf_mpx *t) +{ + struct i2c_client *client = v4l2_get_subdevdata(&t->sd); + u16 source = 0; + u8 buffer[3]; + struct i2c_msg msg; + int mode = t->mpxmode; + + /* reset MPX */ + buffer[0] = 0x00; + buffer[1] = 0x80; + buffer[2] = 0x00; + msg.addr = client->addr; + msg.flags = 0; + msg.len = 3; + msg.buf = buffer; + i2c_transfer(client->adapter, &msg, 1); + buffer[1] = 0x00; + i2c_transfer(client->adapter, &msg, 1); + + if (t->audmode != V4L2_TUNER_MODE_MONO) + mode++; + + if (mpx_audio_modes[mode].audio_mode != AUD_MONO) { + switch (t->audmode) { + case V4L2_TUNER_MODE_MONO: + switch (mpx_audio_modes[mode].audio_mode) { + case AUD_A2: + source = mpx_audio_modes[mode].source; + break; + case AUD_NICAM: + source = 0x0000; + break; + case AUD_NICAM_L: + source = 0x0200; + break; + default: + break; + } + break; + case V4L2_TUNER_MODE_STEREO: + source = mpx_audio_modes[mode].source; + break; + case V4L2_TUNER_MODE_LANG1: + source = 0x0300; + break; + case V4L2_TUNER_MODE_LANG2: + source = 0x0400; + break; + } + source |= mpx_audio_modes[mode].source & 0x00ff; + } else + source = mpx_audio_modes[mode].source; + + mpx_write(client, 0x10, 0x0030, mpx_audio_modes[mode].modus); + mpx_write(client, 0x12, 0x0008, source); + mpx_write(client, 0x12, 0x0013, mpx_audio_modes[mode].acb); + mpx_write(client, 0x12, 0x000e, + mpx_audio_modes[mode].fm_prescale); + mpx_write(client, 0x12, 0x0010, + mpx_audio_modes[mode].nicam_prescale); + mpx_write(client, 0x12, 0x000d, + mpx_audio_modes[mode].scart_prescale); + mpx_write(client, 0x10, 0x0020, mpx_audio_modes[mode].system); + mpx_write(client, 0x12, 0x0000, mpx_audio_modes[mode].volume); + if (mpx_audio_modes[mode].audio_mode == AUD_A2) + mpx_write(client, 0x10, 0x0022, + t->audmode == V4L2_TUNER_MODE_MONO ? 0x07f0 : 0x0190); + +#ifdef MPX_DEBUG + { + u8 buf1[3], buf2[2]; + struct i2c_msg msgs[2]; + + v4l2_info(client, + "MPX registers: %04x %04x %04x %04x %04x %04x %04x %04x\n", + mpx_audio_modes[mode].modus, + source, + mpx_audio_modes[mode].acb, + mpx_audio_modes[mode].fm_prescale, + mpx_audio_modes[mode].nicam_prescale, + mpx_audio_modes[mode].scart_prescale, + mpx_audio_modes[mode].system, + mpx_audio_modes[mode].volume); + buf1[0] = 0x11; + buf1[1] = 0x00; + buf1[2] = 0x7e; + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 3; + msgs[0].buf = buf1; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 2; + msgs[1].buf = buf2; + i2c_transfer(client->adapter, msgs, 2); + v4l2_info(client, "MPX system: %02x%02x\n", + buf2[0], buf2[1]); + buf1[0] = 0x11; + buf1[1] = 0x02; + buf1[2] = 0x00; + i2c_transfer(client->adapter, msgs, 2); + v4l2_info(client, "MPX status: %02x%02x\n", + buf2[0], buf2[1]); + } +#endif + return 0; +} + + +static int sony_btf_mpx_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct sony_btf_mpx *t = to_state(sd); + int default_mpx_mode = 0; + + if (std & V4L2_STD_PAL_BG) + default_mpx_mode = 1; + else if (std & V4L2_STD_PAL_I) + default_mpx_mode = 4; + else if (std & V4L2_STD_PAL_DK) + default_mpx_mode = 6; + else if (std & V4L2_STD_SECAM_L) + default_mpx_mode = 11; + + if (default_mpx_mode != t->mpxmode) { + t->mpxmode = default_mpx_mode; + mpx_setup(t); + } + return 0; +} + +static int sony_btf_mpx_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct sony_btf_mpx *t = to_state(sd); + + vt->capability = V4L2_TUNER_CAP_NORM | + V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2; + vt->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + vt->audmode = t->audmode; + return 0; +} + +static int sony_btf_mpx_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + struct sony_btf_mpx *t = to_state(sd); + + if (vt->type != V4L2_TUNER_ANALOG_TV) + return -EINVAL; + + if (vt->audmode != t->audmode) { + t->audmode = vt->audmode; + mpx_setup(t); + } + return 0; +} + +/* --------------------------------------------------------------------------*/ + +static const struct v4l2_subdev_tuner_ops sony_btf_mpx_tuner_ops = { + .s_tuner = sony_btf_mpx_s_tuner, + .g_tuner = sony_btf_mpx_g_tuner, +}; + +static const struct v4l2_subdev_video_ops sony_btf_mpx_video_ops = { + .s_std = sony_btf_mpx_s_std, +}; + +static const struct v4l2_subdev_ops sony_btf_mpx_ops = { + .tuner = &sony_btf_mpx_tuner_ops, + .video = &sony_btf_mpx_video_ops, +}; + +/* --------------------------------------------------------------------------*/ + +static int sony_btf_mpx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sony_btf_mpx *t; + struct v4l2_subdev *sd; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + t = devm_kzalloc(&client->dev, sizeof(*t), GFP_KERNEL); + if (t == NULL) + return -ENOMEM; + + sd = &t->sd; + v4l2_i2c_subdev_init(sd, client, &sony_btf_mpx_ops); + + /* Initialize sony_btf_mpx */ + t->mpxmode = 0; + t->audmode = V4L2_TUNER_MODE_STEREO; + + return 0; +} + +static int sony_btf_mpx_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id sony_btf_mpx_id[] = { + { "sony-btf-mpx", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sony_btf_mpx_id); + +static struct i2c_driver sony_btf_mpx_driver = { + .driver = { + .name = "sony-btf-mpx", + }, + .probe = sony_btf_mpx_probe, + .remove = sony_btf_mpx_remove, + .id_table = sony_btf_mpx_id, +}; +module_i2c_driver(sony_btf_mpx_driver); diff --git a/drivers/media/i2c/sr030pc30.c b/drivers/media/i2c/sr030pc30.c new file mode 100644 index 000000000..46924024f --- /dev/null +++ b/drivers/media/i2c/sr030pc30.c @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for SiliconFile SR030PC30 VGA (1/10-Inch) Image Sensor with ISP + * + * Copyright (C) 2010 Samsung Electronics Co., Ltd + * Author: Sylwester Nawrocki, s.nawrocki@samsung.com + * + * Based on original driver authored by Dongsoo Nathaniel Kim + * and HeungJun Kim <riverful.kim@samsung.com>. + * + * Based on mt9v011 Micron Digital Image Sensor driver + * Copyright (c) 2009 Mauro Carvalho Chehab + */ + +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-mediabus.h> +#include <media/v4l2-ctrls.h> +#include <media/i2c/sr030pc30.h> + +static int debug; +module_param(debug, int, 0644); + +#define MODULE_NAME "SR030PC30" + +/* + * Register offsets within a page + * b15..b8 - page id, b7..b0 - register address + */ +#define POWER_CTRL_REG 0x0001 +#define PAGEMODE_REG 0x03 +#define DEVICE_ID_REG 0x0004 +#define NOON010PC30_ID 0x86 +#define SR030PC30_ID 0x8C +#define VDO_CTL1_REG 0x0010 +#define SUBSAMPL_NONE_VGA 0 +#define SUBSAMPL_QVGA 0x10 +#define SUBSAMPL_QQVGA 0x20 +#define VDO_CTL2_REG 0x0011 +#define SYNC_CTL_REG 0x0012 +#define WIN_ROWH_REG 0x0020 +#define WIN_ROWL_REG 0x0021 +#define WIN_COLH_REG 0x0022 +#define WIN_COLL_REG 0x0023 +#define WIN_HEIGHTH_REG 0x0024 +#define WIN_HEIGHTL_REG 0x0025 +#define WIN_WIDTHH_REG 0x0026 +#define WIN_WIDTHL_REG 0x0027 +#define HBLANKH_REG 0x0040 +#define HBLANKL_REG 0x0041 +#define VSYNCH_REG 0x0042 +#define VSYNCL_REG 0x0043 +/* page 10 */ +#define ISP_CTL_REG(n) (0x1010 + (n)) +#define YOFS_REG 0x1040 +#define DARK_YOFS_REG 0x1041 +#define AG_ABRTH_REG 0x1050 +#define SAT_CTL_REG 0x1060 +#define BSAT_REG 0x1061 +#define RSAT_REG 0x1062 +#define AG_SAT_TH_REG 0x1063 +/* page 11 */ +#define ZLPF_CTRL_REG 0x1110 +#define ZLPF_CTRL2_REG 0x1112 +#define ZLPF_AGH_THR_REG 0x1121 +#define ZLPF_THR_REG 0x1160 +#define ZLPF_DYN_THR_REG 0x1160 +/* page 12 */ +#define YCLPF_CTL1_REG 0x1240 +#define YCLPF_CTL2_REG 0x1241 +#define YCLPF_THR_REG 0x1250 +#define BLPF_CTL_REG 0x1270 +#define BLPF_THR1_REG 0x1274 +#define BLPF_THR2_REG 0x1275 +/* page 14 - Lens Shading Compensation */ +#define LENS_CTRL_REG 0x1410 +#define LENS_XCEN_REG 0x1420 +#define LENS_YCEN_REG 0x1421 +#define LENS_R_COMP_REG 0x1422 +#define LENS_G_COMP_REG 0x1423 +#define LENS_B_COMP_REG 0x1424 +/* page 15 - Color correction */ +#define CMC_CTL_REG 0x1510 +#define CMC_OFSGH_REG 0x1514 +#define CMC_OFSGL_REG 0x1516 +#define CMC_SIGN_REG 0x1517 +/* Color correction coefficients */ +#define CMC_COEF_REG(n) (0x1530 + (n)) +/* Color correction offset coefficients */ +#define CMC_OFS_REG(n) (0x1540 + (n)) +/* page 16 - Gamma correction */ +#define GMA_CTL_REG 0x1610 +/* Gamma correction coefficients 0.14 */ +#define GMA_COEF_REG(n) (0x1630 + (n)) +/* page 20 - Auto Exposure */ +#define AE_CTL1_REG 0x2010 +#define AE_CTL2_REG 0x2011 +#define AE_FRM_CTL_REG 0x2020 +#define AE_FINE_CTL_REG(n) (0x2028 + (n)) +#define EXP_TIMEH_REG 0x2083 +#define EXP_TIMEM_REG 0x2084 +#define EXP_TIMEL_REG 0x2085 +#define EXP_MMINH_REG 0x2086 +#define EXP_MMINL_REG 0x2087 +#define EXP_MMAXH_REG 0x2088 +#define EXP_MMAXM_REG 0x2089 +#define EXP_MMAXL_REG 0x208A +/* page 22 - Auto White Balance */ +#define AWB_CTL1_REG 0x2210 +#define AWB_ENABLE 0x80 +#define AWB_CTL2_REG 0x2211 +#define MWB_ENABLE 0x01 +/* RGB gain control (manual WB) when AWB_CTL1[7]=0 */ +#define AWB_RGAIN_REG 0x2280 +#define AWB_GGAIN_REG 0x2281 +#define AWB_BGAIN_REG 0x2282 +#define AWB_RMAX_REG 0x2283 +#define AWB_RMIN_REG 0x2284 +#define AWB_BMAX_REG 0x2285 +#define AWB_BMIN_REG 0x2286 +/* R, B gain range in bright light conditions */ +#define AWB_RMAXB_REG 0x2287 +#define AWB_RMINB_REG 0x2288 +#define AWB_BMAXB_REG 0x2289 +#define AWB_BMINB_REG 0x228A +/* manual white balance, when AWB_CTL2[0]=1 */ +#define MWB_RGAIN_REG 0x22B2 +#define MWB_BGAIN_REG 0x22B3 +/* the token to mark an array end */ +#define REG_TERM 0xFFFF + +/* Minimum and maximum exposure time in ms */ +#define EXPOS_MIN_MS 1 +#define EXPOS_MAX_MS 125 + +struct sr030pc30_info { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + const struct sr030pc30_platform_data *pdata; + const struct sr030pc30_format *curr_fmt; + const struct sr030pc30_frmsize *curr_win; + unsigned int hflip:1; + unsigned int vflip:1; + unsigned int sleep:1; + struct { + /* auto whitebalance control cluster */ + struct v4l2_ctrl *awb; + struct v4l2_ctrl *red; + struct v4l2_ctrl *blue; + }; + struct { + /* auto exposure control cluster */ + struct v4l2_ctrl *autoexp; + struct v4l2_ctrl *exp; + }; + u8 i2c_reg_page; +}; + +struct sr030pc30_format { + u32 code; + enum v4l2_colorspace colorspace; + u16 ispctl1_reg; +}; + +struct sr030pc30_frmsize { + u16 width; + u16 height; + int vid_ctl1; +}; + +struct i2c_regval { + u16 addr; + u16 val; +}; + +/* supported resolutions */ +static const struct sr030pc30_frmsize sr030pc30_sizes[] = { + { + .width = 640, + .height = 480, + .vid_ctl1 = SUBSAMPL_NONE_VGA, + }, { + .width = 320, + .height = 240, + .vid_ctl1 = SUBSAMPL_QVGA, + }, { + .width = 160, + .height = 120, + .vid_ctl1 = SUBSAMPL_QQVGA, + }, +}; + +/* supported pixel formats */ +static const struct sr030pc30_format sr030pc30_formats[] = { + { + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x03, + }, { + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x02, + }, { + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0, + }, { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x01, + }, { + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x40, + }, +}; + +static const struct i2c_regval sr030pc30_base_regs[] = { + /* Window size and position within pixel matrix */ + { WIN_ROWH_REG, 0x00 }, { WIN_ROWL_REG, 0x06 }, + { WIN_COLH_REG, 0x00 }, { WIN_COLL_REG, 0x06 }, + { WIN_HEIGHTH_REG, 0x01 }, { WIN_HEIGHTL_REG, 0xE0 }, + { WIN_WIDTHH_REG, 0x02 }, { WIN_WIDTHL_REG, 0x80 }, + { HBLANKH_REG, 0x01 }, { HBLANKL_REG, 0x50 }, + { VSYNCH_REG, 0x00 }, { VSYNCL_REG, 0x14 }, + { SYNC_CTL_REG, 0 }, + /* Color corection and saturation */ + { ISP_CTL_REG(0), 0x30 }, { YOFS_REG, 0x80 }, + { DARK_YOFS_REG, 0x04 }, { AG_ABRTH_REG, 0x78 }, + { SAT_CTL_REG, 0x1F }, { BSAT_REG, 0x90 }, + { AG_SAT_TH_REG, 0xF0 }, { 0x1064, 0x80 }, + { CMC_CTL_REG, 0x03 }, { CMC_OFSGH_REG, 0x3C }, + { CMC_OFSGL_REG, 0x2C }, { CMC_SIGN_REG, 0x2F }, + { CMC_COEF_REG(0), 0xCB }, { CMC_OFS_REG(0), 0x87 }, + { CMC_COEF_REG(1), 0x61 }, { CMC_OFS_REG(1), 0x18 }, + { CMC_COEF_REG(2), 0x16 }, { CMC_OFS_REG(2), 0x91 }, + { CMC_COEF_REG(3), 0x23 }, { CMC_OFS_REG(3), 0x94 }, + { CMC_COEF_REG(4), 0xCE }, { CMC_OFS_REG(4), 0x9f }, + { CMC_COEF_REG(5), 0x2B }, { CMC_OFS_REG(5), 0x33 }, + { CMC_COEF_REG(6), 0x01 }, { CMC_OFS_REG(6), 0x00 }, + { CMC_COEF_REG(7), 0x34 }, { CMC_OFS_REG(7), 0x94 }, + { CMC_COEF_REG(8), 0x75 }, { CMC_OFS_REG(8), 0x14 }, + /* Color corection coefficients */ + { GMA_CTL_REG, 0x03 }, { GMA_COEF_REG(0), 0x00 }, + { GMA_COEF_REG(1), 0x19 }, { GMA_COEF_REG(2), 0x26 }, + { GMA_COEF_REG(3), 0x3B }, { GMA_COEF_REG(4), 0x5D }, + { GMA_COEF_REG(5), 0x79 }, { GMA_COEF_REG(6), 0x8E }, + { GMA_COEF_REG(7), 0x9F }, { GMA_COEF_REG(8), 0xAF }, + { GMA_COEF_REG(9), 0xBD }, { GMA_COEF_REG(10), 0xCA }, + { GMA_COEF_REG(11), 0xDD }, { GMA_COEF_REG(12), 0xEC }, + { GMA_COEF_REG(13), 0xF7 }, { GMA_COEF_REG(14), 0xFF }, + /* Noise reduction, Z-LPF, YC-LPF and BLPF filters setup */ + { ZLPF_CTRL_REG, 0x99 }, { ZLPF_CTRL2_REG, 0x0E }, + { ZLPF_AGH_THR_REG, 0x29 }, { ZLPF_THR_REG, 0x0F }, + { ZLPF_DYN_THR_REG, 0x63 }, { YCLPF_CTL1_REG, 0x23 }, + { YCLPF_CTL2_REG, 0x3B }, { YCLPF_THR_REG, 0x05 }, + { BLPF_CTL_REG, 0x1D }, { BLPF_THR1_REG, 0x05 }, + { BLPF_THR2_REG, 0x04 }, + /* Automatic white balance */ + { AWB_CTL1_REG, 0xFB }, { AWB_CTL2_REG, 0x26 }, + { AWB_RMAX_REG, 0x54 }, { AWB_RMIN_REG, 0x2B }, + { AWB_BMAX_REG, 0x57 }, { AWB_BMIN_REG, 0x29 }, + { AWB_RMAXB_REG, 0x50 }, { AWB_RMINB_REG, 0x43 }, + { AWB_BMAXB_REG, 0x30 }, { AWB_BMINB_REG, 0x22 }, + /* Auto exposure */ + { AE_CTL1_REG, 0x8C }, { AE_CTL2_REG, 0x04 }, + { AE_FRM_CTL_REG, 0x01 }, { AE_FINE_CTL_REG(0), 0x3F }, + { AE_FINE_CTL_REG(1), 0xA3 }, { AE_FINE_CTL_REG(3), 0x34 }, + /* Lens shading compensation */ + { LENS_CTRL_REG, 0x01 }, { LENS_XCEN_REG, 0x80 }, + { LENS_YCEN_REG, 0x70 }, { LENS_R_COMP_REG, 0x53 }, + { LENS_G_COMP_REG, 0x40 }, { LENS_B_COMP_REG, 0x3e }, + { REG_TERM, 0 }, +}; + +static inline struct sr030pc30_info *to_sr030pc30(struct v4l2_subdev *sd) +{ + return container_of(sd, struct sr030pc30_info, sd); +} + +static inline int set_i2c_page(struct sr030pc30_info *info, + struct i2c_client *client, unsigned int reg) +{ + int ret = 0; + u32 page = reg >> 8 & 0xFF; + + if (info->i2c_reg_page != page && (reg & 0xFF) != 0x03) { + ret = i2c_smbus_write_byte_data(client, PAGEMODE_REG, page); + if (!ret) + info->i2c_reg_page = page; + } + return ret; +} + +static int cam_i2c_read(struct v4l2_subdev *sd, u32 reg_addr) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct sr030pc30_info *info = to_sr030pc30(sd); + + int ret = set_i2c_page(info, client, reg_addr); + if (!ret) + ret = i2c_smbus_read_byte_data(client, reg_addr & 0xFF); + return ret; +} + +static int cam_i2c_write(struct v4l2_subdev *sd, u32 reg_addr, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct sr030pc30_info *info = to_sr030pc30(sd); + + int ret = set_i2c_page(info, client, reg_addr); + if (!ret) + ret = i2c_smbus_write_byte_data( + client, reg_addr & 0xFF, val); + return ret; +} + +static inline int sr030pc30_bulk_write_reg(struct v4l2_subdev *sd, + const struct i2c_regval *msg) +{ + while (msg->addr != REG_TERM) { + int ret = cam_i2c_write(sd, msg->addr, msg->val); + if (ret) + return ret; + msg++; + } + return 0; +} + +/* Device reset and sleep mode control */ +static int sr030pc30_pwr_ctrl(struct v4l2_subdev *sd, + bool reset, bool sleep) +{ + struct sr030pc30_info *info = to_sr030pc30(sd); + u8 reg = sleep ? 0xF1 : 0xF0; + int ret = 0; + + if (reset) + ret = cam_i2c_write(sd, POWER_CTRL_REG, reg | 0x02); + if (!ret) { + ret = cam_i2c_write(sd, POWER_CTRL_REG, reg); + if (!ret) { + info->sleep = sleep; + if (reset) + info->i2c_reg_page = -1; + } + } + return ret; +} + +static int sr030pc30_set_flip(struct v4l2_subdev *sd) +{ + struct sr030pc30_info *info = to_sr030pc30(sd); + + s32 reg = cam_i2c_read(sd, VDO_CTL2_REG); + if (reg < 0) + return reg; + + reg &= 0x7C; + if (info->hflip) + reg |= 0x01; + if (info->vflip) + reg |= 0x02; + return cam_i2c_write(sd, VDO_CTL2_REG, reg | 0x80); +} + +/* Configure resolution, color format and image flip */ +static int sr030pc30_set_params(struct v4l2_subdev *sd) +{ + struct sr030pc30_info *info = to_sr030pc30(sd); + int ret; + + if (!info->curr_win) + return -EINVAL; + + /* Configure the resolution through subsampling */ + ret = cam_i2c_write(sd, VDO_CTL1_REG, + info->curr_win->vid_ctl1); + + if (!ret && info->curr_fmt) + ret = cam_i2c_write(sd, ISP_CTL_REG(0), + info->curr_fmt->ispctl1_reg); + if (!ret) + ret = sr030pc30_set_flip(sd); + + return ret; +} + +/* Find nearest matching image pixel size. */ +static int sr030pc30_try_frame_size(struct v4l2_mbus_framefmt *mf) +{ + unsigned int min_err = ~0; + int i = ARRAY_SIZE(sr030pc30_sizes); + const struct sr030pc30_frmsize *fsize = &sr030pc30_sizes[0], + *match = NULL; + while (i--) { + int err = abs(fsize->width - mf->width) + + abs(fsize->height - mf->height); + if (err < min_err) { + min_err = err; + match = fsize; + } + fsize++; + } + if (match) { + mf->width = match->width; + mf->height = match->height; + return 0; + } + return -EINVAL; +} + +static int sr030pc30_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct sr030pc30_info *info = + container_of(ctrl->handler, struct sr030pc30_info, hdl); + struct v4l2_subdev *sd = &info->sd; + int ret = 0; + + v4l2_dbg(1, debug, sd, "%s: ctrl_id: %d, value: %d\n", + __func__, ctrl->id, ctrl->val); + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + if (ctrl->is_new) { + ret = cam_i2c_write(sd, AWB_CTL2_REG, + ctrl->val ? 0x2E : 0x2F); + if (!ret) + ret = cam_i2c_write(sd, AWB_CTL1_REG, + ctrl->val ? 0xFB : 0x7B); + } + if (!ret && info->blue->is_new) + ret = cam_i2c_write(sd, MWB_BGAIN_REG, info->blue->val); + if (!ret && info->red->is_new) + ret = cam_i2c_write(sd, MWB_RGAIN_REG, info->red->val); + return ret; + + case V4L2_CID_EXPOSURE_AUTO: + /* auto anti-flicker is also enabled here */ + if (ctrl->is_new) + ret = cam_i2c_write(sd, AE_CTL1_REG, + ctrl->val == V4L2_EXPOSURE_AUTO ? 0xDC : 0x0C); + if (info->exp->is_new) { + unsigned long expos = info->exp->val; + + expos = expos * info->pdata->clk_rate / (8 * 1000); + + if (!ret) + ret = cam_i2c_write(sd, EXP_TIMEH_REG, + expos >> 16 & 0xFF); + if (!ret) + ret = cam_i2c_write(sd, EXP_TIMEM_REG, + expos >> 8 & 0xFF); + if (!ret) + ret = cam_i2c_write(sd, EXP_TIMEL_REG, + expos & 0xFF); + } + return ret; + default: + return -EINVAL; + } + + return 0; +} + +static int sr030pc30_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (!code || code->pad || + code->index >= ARRAY_SIZE(sr030pc30_formats)) + return -EINVAL; + + code->code = sr030pc30_formats[code->index].code; + return 0; +} + +static int sr030pc30_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf; + struct sr030pc30_info *info = to_sr030pc30(sd); + + if (!format || format->pad) + return -EINVAL; + + mf = &format->format; + + if (!info->curr_win || !info->curr_fmt) + return -EINVAL; + + mf->width = info->curr_win->width; + mf->height = info->curr_win->height; + mf->code = info->curr_fmt->code; + mf->colorspace = info->curr_fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +/* Return nearest media bus frame format. */ +static const struct sr030pc30_format *try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + int i; + + sr030pc30_try_frame_size(mf); + + for (i = 0; i < ARRAY_SIZE(sr030pc30_formats); i++) { + if (mf->code == sr030pc30_formats[i].code) + break; + } + if (i == ARRAY_SIZE(sr030pc30_formats)) + i = 0; + + mf->code = sr030pc30_formats[i].code; + + return &sr030pc30_formats[i]; +} + +/* Return nearest media bus frame format. */ +static int sr030pc30_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct sr030pc30_info *info = sd ? to_sr030pc30(sd) : NULL; + const struct sr030pc30_format *fmt; + struct v4l2_mbus_framefmt *mf; + + if (!sd || !format) + return -EINVAL; + + mf = &format->format; + if (format->pad) + return -EINVAL; + + fmt = try_fmt(sd, mf); + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + cfg->try_fmt = *mf; + return 0; + } + + info->curr_fmt = fmt; + + return sr030pc30_set_params(sd); +} + +static int sr030pc30_base_config(struct v4l2_subdev *sd) +{ + struct sr030pc30_info *info = to_sr030pc30(sd); + int ret; + unsigned long expmin, expmax; + + ret = sr030pc30_bulk_write_reg(sd, sr030pc30_base_regs); + if (!ret) { + info->curr_fmt = &sr030pc30_formats[0]; + info->curr_win = &sr030pc30_sizes[0]; + ret = sr030pc30_set_params(sd); + } + if (!ret) + ret = sr030pc30_pwr_ctrl(sd, false, false); + + if (ret) + return ret; + + expmin = EXPOS_MIN_MS * info->pdata->clk_rate / (8 * 1000); + expmax = EXPOS_MAX_MS * info->pdata->clk_rate / (8 * 1000); + + v4l2_dbg(1, debug, sd, "%s: expmin= %lx, expmax= %lx", __func__, + expmin, expmax); + + /* Setting up manual exposure time range */ + ret = cam_i2c_write(sd, EXP_MMINH_REG, expmin >> 8 & 0xFF); + if (!ret) + ret = cam_i2c_write(sd, EXP_MMINL_REG, expmin & 0xFF); + if (!ret) + ret = cam_i2c_write(sd, EXP_MMAXH_REG, expmax >> 16 & 0xFF); + if (!ret) + ret = cam_i2c_write(sd, EXP_MMAXM_REG, expmax >> 8 & 0xFF); + if (!ret) + ret = cam_i2c_write(sd, EXP_MMAXL_REG, expmax & 0xFF); + + return ret; +} + +static int sr030pc30_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct sr030pc30_info *info = to_sr030pc30(sd); + const struct sr030pc30_platform_data *pdata = info->pdata; + int ret; + + if (pdata == NULL) { + WARN(1, "No platform data!\n"); + return -EINVAL; + } + + /* + * Put sensor into power sleep mode before switching off + * power and disabling MCLK. + */ + if (!on) + sr030pc30_pwr_ctrl(sd, false, true); + + /* set_power controls sensor's power and clock */ + if (pdata->set_power) { + ret = pdata->set_power(&client->dev, on); + if (ret) + return ret; + } + + if (on) { + ret = sr030pc30_base_config(sd); + } else { + ret = 0; + info->curr_win = NULL; + info->curr_fmt = NULL; + } + + return ret; +} + +static const struct v4l2_ctrl_ops sr030pc30_ctrl_ops = { + .s_ctrl = sr030pc30_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops sr030pc30_core_ops = { + .s_power = sr030pc30_s_power, +}; + +static const struct v4l2_subdev_pad_ops sr030pc30_pad_ops = { + .enum_mbus_code = sr030pc30_enum_mbus_code, + .get_fmt = sr030pc30_get_fmt, + .set_fmt = sr030pc30_set_fmt, +}; + +static const struct v4l2_subdev_ops sr030pc30_ops = { + .core = &sr030pc30_core_ops, + .pad = &sr030pc30_pad_ops, +}; + +/* + * Detect sensor type. Return 0 if SR030PC30 was detected + * or -ENODEV otherwise. + */ +static int sr030pc30_detect(struct i2c_client *client) +{ + const struct sr030pc30_platform_data *pdata + = client->dev.platform_data; + int ret; + + /* Enable sensor's power and clock */ + if (pdata->set_power) { + ret = pdata->set_power(&client->dev, 1); + if (ret) + return ret; + } + + ret = i2c_smbus_read_byte_data(client, DEVICE_ID_REG); + + if (pdata->set_power) + pdata->set_power(&client->dev, 0); + + if (ret < 0) { + dev_err(&client->dev, "%s: I2C read failed\n", __func__); + return ret; + } + + return ret == SR030PC30_ID ? 0 : -ENODEV; +} + + +static int sr030pc30_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sr030pc30_info *info; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + const struct sr030pc30_platform_data *pdata + = client->dev.platform_data; + int ret; + + if (!pdata) { + dev_err(&client->dev, "No platform data!"); + return -EIO; + } + + ret = sr030pc30_detect(client); + if (ret) + return ret; + + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + sd = &info->sd; + info->pdata = client->dev.platform_data; + + v4l2_i2c_subdev_init(sd, client, &sr030pc30_ops); + + hdl = &info->hdl; + v4l2_ctrl_handler_init(hdl, 6); + info->awb = v4l2_ctrl_new_std(hdl, &sr030pc30_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + info->red = v4l2_ctrl_new_std(hdl, &sr030pc30_ctrl_ops, + V4L2_CID_RED_BALANCE, 0, 127, 1, 64); + info->blue = v4l2_ctrl_new_std(hdl, &sr030pc30_ctrl_ops, + V4L2_CID_BLUE_BALANCE, 0, 127, 1, 64); + info->autoexp = v4l2_ctrl_new_std(hdl, &sr030pc30_ctrl_ops, + V4L2_CID_EXPOSURE_AUTO, 0, 1, 1, 1); + info->exp = v4l2_ctrl_new_std(hdl, &sr030pc30_ctrl_ops, + V4L2_CID_EXPOSURE, EXPOS_MIN_MS, EXPOS_MAX_MS, 1, 30); + sd->ctrl_handler = hdl; + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return err; + } + v4l2_ctrl_auto_cluster(3, &info->awb, 0, false); + v4l2_ctrl_auto_cluster(2, &info->autoexp, V4L2_EXPOSURE_MANUAL, false); + v4l2_ctrl_handler_setup(hdl); + + info->i2c_reg_page = -1; + info->hflip = 1; + + return 0; +} + +static int sr030pc30_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return 0; +} + +static const struct i2c_device_id sr030pc30_id[] = { + { MODULE_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, sr030pc30_id); + + +static struct i2c_driver sr030pc30_i2c_driver = { + .driver = { + .name = MODULE_NAME + }, + .probe = sr030pc30_probe, + .remove = sr030pc30_remove, + .id_table = sr030pc30_id, +}; + +module_i2c_driver(sr030pc30_i2c_driver); + +MODULE_DESCRIPTION("Siliconfile SR030PC30 camera driver"); +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/st-mipid02.c b/drivers/media/i2c/st-mipid02.c new file mode 100644 index 000000000..003ba2233 --- /dev/null +++ b/drivers/media/i2c/st-mipid02.c @@ -0,0 +1,1080 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for ST MIPID02 CSI-2 to PARALLEL bridge + * + * Copyright (C) STMicroelectronics SA 2019 + * Authors: Mickael Guene <mickael.guene@st.com> + * for STMicroelectronics. + * + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define MIPID02_CLK_LANE_WR_REG1 0x01 +#define MIPID02_CLK_LANE_REG1 0x02 +#define MIPID02_CLK_LANE_REG3 0x04 +#define MIPID02_DATA_LANE0_REG1 0x05 +#define MIPID02_DATA_LANE0_REG2 0x06 +#define MIPID02_DATA_LANE1_REG1 0x09 +#define MIPID02_DATA_LANE1_REG2 0x0a +#define MIPID02_MODE_REG1 0x14 +#define MIPID02_MODE_REG2 0x15 +#define MIPID02_DATA_ID_RREG 0x17 +#define MIPID02_DATA_SELECTION_CTRL 0x19 +#define MIPID02_PIX_WIDTH_CTRL 0x1e +#define MIPID02_PIX_WIDTH_CTRL_EMB 0x1f + +/* Bits definition for MIPID02_CLK_LANE_REG1 */ +#define CLK_ENABLE BIT(0) +/* Bits definition for MIPID02_CLK_LANE_REG3 */ +#define CLK_MIPI_CSI BIT(1) +/* Bits definition for MIPID02_DATA_LANE0_REG1 */ +#define DATA_ENABLE BIT(0) +/* Bits definition for MIPID02_DATA_LANEx_REG2 */ +#define DATA_MIPI_CSI BIT(0) +/* Bits definition for MIPID02_MODE_REG1 */ +#define MODE_DATA_SWAP BIT(2) +#define MODE_NO_BYPASS BIT(6) +/* Bits definition for MIPID02_MODE_REG2 */ +#define MODE_HSYNC_ACTIVE_HIGH BIT(1) +#define MODE_VSYNC_ACTIVE_HIGH BIT(2) +/* Bits definition for MIPID02_DATA_SELECTION_CTRL */ +#define SELECTION_MANUAL_DATA BIT(2) +#define SELECTION_MANUAL_WIDTH BIT(3) + +static const u32 mipid02_supported_fmt_codes[] = { + MEDIA_BUS_FMT_SBGGR8_1X8, MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SBGGR12_1X12, MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_UYVY8_1X16, MEDIA_BUS_FMT_BGR888_1X24, + MEDIA_BUS_FMT_RGB565_2X8_LE, MEDIA_BUS_FMT_RGB565_2X8_BE, + MEDIA_BUS_FMT_YUYV8_2X8, MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_JPEG_1X8 +}; + +/* regulator supplies */ +static const char * const mipid02_supply_name[] = { + "VDDE", /* 1.8V digital I/O supply */ + "VDDIN", /* 1V8 voltage regulator supply */ +}; + +#define MIPID02_NUM_SUPPLIES ARRAY_SIZE(mipid02_supply_name) + +#define MIPID02_SINK_0 0 +#define MIPID02_SINK_1 1 +#define MIPID02_SOURCE 2 +#define MIPID02_PAD_NB 3 + +struct mipid02_dev { + struct i2c_client *i2c_client; + struct regulator_bulk_data supplies[MIPID02_NUM_SUPPLIES]; + struct v4l2_subdev sd; + struct media_pad pad[MIPID02_PAD_NB]; + struct clk *xclk; + struct gpio_desc *reset_gpio; + /* endpoints info */ + struct v4l2_fwnode_endpoint rx; + u64 link_frequency; + struct v4l2_fwnode_endpoint tx; + /* remote source */ + struct v4l2_async_subdev asd; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *s_subdev; + /* registers */ + struct { + u8 clk_lane_reg1; + u8 data_lane0_reg1; + u8 data_lane1_reg1; + u8 mode_reg1; + u8 mode_reg2; + u8 data_selection_ctrl; + u8 data_id_rreg; + u8 pix_width_ctrl; + u8 pix_width_ctrl_emb; + } r; + /* lock to protect all members below */ + struct mutex lock; + bool streaming; + struct v4l2_mbus_framefmt fmt; +}; + +static int bpp_from_code(__u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + return 8; + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + return 10; + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + return 12; + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_RGB565_2X8_LE: + case MEDIA_BUS_FMT_RGB565_2X8_BE: + return 16; + case MEDIA_BUS_FMT_BGR888_1X24: + return 24; + default: + return 0; + } +} + +static u8 data_type_from_code(__u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + return 0x2a; + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + return 0x2b; + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + return 0x2c; + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_UYVY8_2X8: + return 0x1e; + case MEDIA_BUS_FMT_BGR888_1X24: + return 0x24; + case MEDIA_BUS_FMT_RGB565_2X8_LE: + case MEDIA_BUS_FMT_RGB565_2X8_BE: + return 0x22; + default: + return 0; + } +} + +static void init_format(struct v4l2_mbus_framefmt *fmt) +{ + fmt->code = MEDIA_BUS_FMT_SBGGR8_1X8; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SRGB); + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SRGB); + fmt->width = 640; + fmt->height = 480; +} + +static __u32 get_fmt_code(__u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mipid02_supported_fmt_codes); i++) { + if (code == mipid02_supported_fmt_codes[i]) + return code; + } + + return mipid02_supported_fmt_codes[0]; +} + +static __u32 serial_to_parallel_code(__u32 serial) +{ + if (serial == MEDIA_BUS_FMT_UYVY8_1X16) + return MEDIA_BUS_FMT_UYVY8_2X8; + if (serial == MEDIA_BUS_FMT_BGR888_1X24) + return MEDIA_BUS_FMT_BGR888_3X8; + + return serial; +} + +static inline struct mipid02_dev *to_mipid02_dev(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mipid02_dev, sd); +} + +static int mipid02_read_reg(struct mipid02_dev *bridge, u16 reg, u8 *val) +{ + struct i2c_client *client = bridge->i2c_client; + struct i2c_msg msg[2]; + u8 buf[2]; + int ret; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].buf = buf; + msg[0].len = sizeof(buf); + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].buf = val; + msg[1].len = 1; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", + __func__, client->addr, reg, ret); + return ret; + } + + return 0; +} + +static int mipid02_write_reg(struct mipid02_dev *bridge, u16 reg, u8 val) +{ + struct i2c_client *client = bridge->i2c_client; + struct i2c_msg msg; + u8 buf[3]; + int ret; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + buf[2] = val; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.buf = buf; + msg.len = sizeof(buf); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", + __func__, reg, ret); + return ret; + } + + return 0; +} + +static int mipid02_get_regulators(struct mipid02_dev *bridge) +{ + unsigned int i; + + for (i = 0; i < MIPID02_NUM_SUPPLIES; i++) + bridge->supplies[i].supply = mipid02_supply_name[i]; + + return devm_regulator_bulk_get(&bridge->i2c_client->dev, + MIPID02_NUM_SUPPLIES, + bridge->supplies); +} + +static void mipid02_apply_reset(struct mipid02_dev *bridge) +{ + gpiod_set_value_cansleep(bridge->reset_gpio, 0); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(bridge->reset_gpio, 1); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(bridge->reset_gpio, 0); + usleep_range(5000, 10000); +} + +static int mipid02_set_power_on(struct mipid02_dev *bridge) +{ + struct i2c_client *client = bridge->i2c_client; + int ret; + + ret = clk_prepare_enable(bridge->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", __func__); + return ret; + } + + ret = regulator_bulk_enable(MIPID02_NUM_SUPPLIES, + bridge->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + goto xclk_off; + } + + if (bridge->reset_gpio) { + dev_dbg(&client->dev, "apply reset"); + mipid02_apply_reset(bridge); + } else { + dev_dbg(&client->dev, "don't apply reset"); + usleep_range(5000, 10000); + } + + return 0; + +xclk_off: + clk_disable_unprepare(bridge->xclk); + return ret; +} + +static void mipid02_set_power_off(struct mipid02_dev *bridge) +{ + regulator_bulk_disable(MIPID02_NUM_SUPPLIES, bridge->supplies); + clk_disable_unprepare(bridge->xclk); +} + +static int mipid02_detect(struct mipid02_dev *bridge) +{ + u8 reg; + + /* + * There is no version registers. Just try to read register + * MIPID02_CLK_LANE_WR_REG1. + */ + return mipid02_read_reg(bridge, MIPID02_CLK_LANE_WR_REG1, ®); +} + +static u32 mipid02_get_link_freq_from_cid_link_freq(struct mipid02_dev *bridge, + struct v4l2_subdev *subdev) +{ + struct v4l2_querymenu qm = {.id = V4L2_CID_LINK_FREQ, }; + struct v4l2_ctrl *ctrl; + int ret; + + ctrl = v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_LINK_FREQ); + if (!ctrl) + return 0; + qm.index = v4l2_ctrl_g_ctrl(ctrl); + + ret = v4l2_querymenu(subdev->ctrl_handler, &qm); + if (ret) + return 0; + + return qm.value; +} + +static u32 mipid02_get_link_freq_from_cid_pixel_rate(struct mipid02_dev *bridge, + struct v4l2_subdev *subdev) +{ + struct v4l2_fwnode_endpoint *ep = &bridge->rx; + struct v4l2_ctrl *ctrl; + u32 pixel_clock; + u32 bpp = bpp_from_code(bridge->fmt.code); + + ctrl = v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_PIXEL_RATE); + if (!ctrl) + return 0; + pixel_clock = v4l2_ctrl_g_ctrl_int64(ctrl); + + return pixel_clock * bpp / (2 * ep->bus.mipi_csi2.num_data_lanes); +} + +/* + * We need to know link frequency to setup clk_lane_reg1 timings. Link frequency + * will be computed using connected device V4L2_CID_PIXEL_RATE, bit per pixel + * and number of lanes. + */ +static int mipid02_configure_from_rx_speed(struct mipid02_dev *bridge) +{ + struct i2c_client *client = bridge->i2c_client; + struct v4l2_subdev *subdev = bridge->s_subdev; + u32 link_freq; + + link_freq = mipid02_get_link_freq_from_cid_link_freq(bridge, subdev); + if (!link_freq) { + link_freq = mipid02_get_link_freq_from_cid_pixel_rate(bridge, + subdev); + if (!link_freq) { + dev_err(&client->dev, "Failed to get link frequency"); + return -EINVAL; + } + } + + dev_dbg(&client->dev, "detect link_freq = %d Hz", link_freq); + bridge->r.clk_lane_reg1 |= (2000000000 / link_freq) << 2; + + return 0; +} + +static int mipid02_configure_clk_lane(struct mipid02_dev *bridge) +{ + struct i2c_client *client = bridge->i2c_client; + struct v4l2_fwnode_endpoint *ep = &bridge->rx; + bool *polarities = ep->bus.mipi_csi2.lane_polarities; + + /* midid02 doesn't support clock lane remapping */ + if (ep->bus.mipi_csi2.clock_lane != 0) { + dev_err(&client->dev, "clk lane must be map to lane 0\n"); + return -EINVAL; + } + bridge->r.clk_lane_reg1 |= (polarities[0] << 1) | CLK_ENABLE; + + return 0; +} + +static int mipid02_configure_data0_lane(struct mipid02_dev *bridge, int nb, + bool are_lanes_swap, bool *polarities) +{ + bool are_pin_swap = are_lanes_swap ? polarities[2] : polarities[1]; + + if (nb == 1 && are_lanes_swap) + return 0; + + /* + * data lane 0 as pin swap polarity reversed compared to clock and + * data lane 1 + */ + if (!are_pin_swap) + bridge->r.data_lane0_reg1 = 1 << 1; + bridge->r.data_lane0_reg1 |= DATA_ENABLE; + + return 0; +} + +static int mipid02_configure_data1_lane(struct mipid02_dev *bridge, int nb, + bool are_lanes_swap, bool *polarities) +{ + bool are_pin_swap = are_lanes_swap ? polarities[1] : polarities[2]; + + if (nb == 1 && !are_lanes_swap) + return 0; + + if (are_pin_swap) + bridge->r.data_lane1_reg1 = 1 << 1; + bridge->r.data_lane1_reg1 |= DATA_ENABLE; + + return 0; +} + +static int mipid02_configure_from_rx(struct mipid02_dev *bridge) +{ + struct v4l2_fwnode_endpoint *ep = &bridge->rx; + bool are_lanes_swap = ep->bus.mipi_csi2.data_lanes[0] == 2; + bool *polarities = ep->bus.mipi_csi2.lane_polarities; + int nb = ep->bus.mipi_csi2.num_data_lanes; + int ret; + + ret = mipid02_configure_clk_lane(bridge); + if (ret) + return ret; + + ret = mipid02_configure_data0_lane(bridge, nb, are_lanes_swap, + polarities); + if (ret) + return ret; + + ret = mipid02_configure_data1_lane(bridge, nb, are_lanes_swap, + polarities); + if (ret) + return ret; + + bridge->r.mode_reg1 |= are_lanes_swap ? MODE_DATA_SWAP : 0; + bridge->r.mode_reg1 |= (nb - 1) << 1; + + return mipid02_configure_from_rx_speed(bridge); +} + +static int mipid02_configure_from_tx(struct mipid02_dev *bridge) +{ + struct v4l2_fwnode_endpoint *ep = &bridge->tx; + + bridge->r.data_selection_ctrl = SELECTION_MANUAL_WIDTH; + bridge->r.pix_width_ctrl = ep->bus.parallel.bus_width; + bridge->r.pix_width_ctrl_emb = ep->bus.parallel.bus_width; + if (ep->bus.parallel.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + bridge->r.mode_reg2 |= MODE_HSYNC_ACTIVE_HIGH; + if (ep->bus.parallel.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + bridge->r.mode_reg2 |= MODE_VSYNC_ACTIVE_HIGH; + + return 0; +} + +static int mipid02_configure_from_code(struct mipid02_dev *bridge) +{ + u8 data_type; + + bridge->r.data_id_rreg = 0; + + if (bridge->fmt.code != MEDIA_BUS_FMT_JPEG_1X8) { + bridge->r.data_selection_ctrl |= SELECTION_MANUAL_DATA; + + data_type = data_type_from_code(bridge->fmt.code); + if (!data_type) + return -EINVAL; + bridge->r.data_id_rreg = data_type; + } + + return 0; +} + +static int mipid02_stream_disable(struct mipid02_dev *bridge) +{ + struct i2c_client *client = bridge->i2c_client; + int ret; + + /* Disable all lanes */ + ret = mipid02_write_reg(bridge, MIPID02_CLK_LANE_REG1, 0); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE0_REG1, 0); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE1_REG1, 0); + if (ret) + goto error; +error: + if (ret) + dev_err(&client->dev, "failed to stream off %d", ret); + + return ret; +} + +static int mipid02_stream_enable(struct mipid02_dev *bridge) +{ + struct i2c_client *client = bridge->i2c_client; + int ret = -EINVAL; + + if (!bridge->s_subdev) + goto error; + + memset(&bridge->r, 0, sizeof(bridge->r)); + /* build registers content */ + ret = mipid02_configure_from_rx(bridge); + if (ret) + goto error; + ret = mipid02_configure_from_tx(bridge); + if (ret) + goto error; + ret = mipid02_configure_from_code(bridge); + if (ret) + goto error; + + /* write mipi registers */ + ret = mipid02_write_reg(bridge, MIPID02_CLK_LANE_REG1, + bridge->r.clk_lane_reg1); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_CLK_LANE_REG3, CLK_MIPI_CSI); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE0_REG1, + bridge->r.data_lane0_reg1); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE0_REG2, + DATA_MIPI_CSI); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE1_REG1, + bridge->r.data_lane1_reg1); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE1_REG2, + DATA_MIPI_CSI); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_MODE_REG1, + MODE_NO_BYPASS | bridge->r.mode_reg1); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_MODE_REG2, + bridge->r.mode_reg2); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_DATA_ID_RREG, + bridge->r.data_id_rreg); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_DATA_SELECTION_CTRL, + bridge->r.data_selection_ctrl); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_PIX_WIDTH_CTRL, + bridge->r.pix_width_ctrl); + if (ret) + goto error; + ret = mipid02_write_reg(bridge, MIPID02_PIX_WIDTH_CTRL_EMB, + bridge->r.pix_width_ctrl_emb); + if (ret) + goto error; + + return 0; + +error: + dev_err(&client->dev, "failed to stream on %d", ret); + mipid02_stream_disable(bridge); + + return ret; +} + +static int mipid02_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct mipid02_dev *bridge = to_mipid02_dev(sd); + struct i2c_client *client = bridge->i2c_client; + int ret = 0; + + dev_dbg(&client->dev, "%s : requested %d / current = %d", __func__, + enable, bridge->streaming); + mutex_lock(&bridge->lock); + + if (bridge->streaming == enable) + goto out; + + ret = enable ? mipid02_stream_enable(bridge) : + mipid02_stream_disable(bridge); + if (!ret) + bridge->streaming = enable; + +out: + dev_dbg(&client->dev, "%s current now = %d / %d", __func__, + bridge->streaming, ret); + mutex_unlock(&bridge->lock); + + return ret; +} + +static int mipid02_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct mipid02_dev *bridge = to_mipid02_dev(sd); + int ret = 0; + + switch (code->pad) { + case MIPID02_SINK_0: + if (code->index >= ARRAY_SIZE(mipid02_supported_fmt_codes)) + ret = -EINVAL; + else + code->code = mipid02_supported_fmt_codes[code->index]; + break; + case MIPID02_SOURCE: + if (code->index == 0) + code->code = serial_to_parallel_code(bridge->fmt.code); + else + ret = -EINVAL; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int mipid02_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mbus_fmt = &format->format; + struct mipid02_dev *bridge = to_mipid02_dev(sd); + struct i2c_client *client = bridge->i2c_client; + struct v4l2_mbus_framefmt *fmt; + + dev_dbg(&client->dev, "%s probe %d", __func__, format->pad); + + if (format->pad >= MIPID02_PAD_NB) + return -EINVAL; + /* second CSI-2 pad not yet supported */ + if (format->pad == MIPID02_SINK_1) + return -EINVAL; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + fmt = v4l2_subdev_get_try_format(&bridge->sd, cfg, format->pad); + else + fmt = &bridge->fmt; + + mutex_lock(&bridge->lock); + + *mbus_fmt = *fmt; + /* code may need to be converted for source */ + if (format->pad == MIPID02_SOURCE) + mbus_fmt->code = serial_to_parallel_code(mbus_fmt->code); + + mutex_unlock(&bridge->lock); + + return 0; +} + +static void mipid02_set_fmt_source(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct mipid02_dev *bridge = to_mipid02_dev(sd); + + /* source pad mirror active sink pad */ + format->format = bridge->fmt; + /* but code may need to be converted */ + format->format.code = serial_to_parallel_code(format->format.code); + + /* only apply format for V4L2_SUBDEV_FORMAT_TRY case */ + if (format->which != V4L2_SUBDEV_FORMAT_TRY) + return; + + *v4l2_subdev_get_try_format(sd, cfg, format->pad) = format->format; +} + +static void mipid02_set_fmt_sink(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct mipid02_dev *bridge = to_mipid02_dev(sd); + struct v4l2_mbus_framefmt *fmt; + + format->format.code = get_fmt_code(format->format.code); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + else + fmt = &bridge->fmt; + + *fmt = format->format; +} + +static int mipid02_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct mipid02_dev *bridge = to_mipid02_dev(sd); + struct i2c_client *client = bridge->i2c_client; + int ret = 0; + + dev_dbg(&client->dev, "%s for %d", __func__, format->pad); + + if (format->pad >= MIPID02_PAD_NB) + return -EINVAL; + /* second CSI-2 pad not yet supported */ + if (format->pad == MIPID02_SINK_1) + return -EINVAL; + + mutex_lock(&bridge->lock); + + if (bridge->streaming) { + ret = -EBUSY; + goto error; + } + + if (format->pad == MIPID02_SOURCE) + mipid02_set_fmt_source(sd, cfg, format); + else + mipid02_set_fmt_sink(sd, cfg, format); + +error: + mutex_unlock(&bridge->lock); + + return ret; +} + +static const struct v4l2_subdev_video_ops mipid02_video_ops = { + .s_stream = mipid02_s_stream, +}; + +static const struct v4l2_subdev_pad_ops mipid02_pad_ops = { + .enum_mbus_code = mipid02_enum_mbus_code, + .get_fmt = mipid02_get_fmt, + .set_fmt = mipid02_set_fmt, +}; + +static const struct v4l2_subdev_ops mipid02_subdev_ops = { + .video = &mipid02_video_ops, + .pad = &mipid02_pad_ops, +}; + +static const struct media_entity_operations mipid02_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int mipid02_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *s_subdev, + struct v4l2_async_subdev *asd) +{ + struct mipid02_dev *bridge = to_mipid02_dev(notifier->sd); + struct i2c_client *client = bridge->i2c_client; + int source_pad; + int ret; + + dev_dbg(&client->dev, "sensor_async_bound call %p", s_subdev); + + source_pad = media_entity_get_fwnode_pad(&s_subdev->entity, + s_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (source_pad < 0) { + dev_err(&client->dev, "Couldn't find output pad for subdev %s\n", + s_subdev->name); + return source_pad; + } + + ret = media_create_pad_link(&s_subdev->entity, source_pad, + &bridge->sd.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(&client->dev, "Couldn't create media link %d", ret); + return ret; + } + + bridge->s_subdev = s_subdev; + + return 0; +} + +static void mipid02_async_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *s_subdev, + struct v4l2_async_subdev *asd) +{ + struct mipid02_dev *bridge = to_mipid02_dev(notifier->sd); + + bridge->s_subdev = NULL; +} + +static const struct v4l2_async_notifier_operations mipid02_notifier_ops = { + .bound = mipid02_async_bound, + .unbind = mipid02_async_unbind, +}; + +static int mipid02_parse_rx_ep(struct mipid02_dev *bridge) +{ + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; + struct i2c_client *client = bridge->i2c_client; + struct device_node *ep_node; + int ret; + + /* parse rx (endpoint 0) */ + ep_node = of_graph_get_endpoint_by_regs(bridge->i2c_client->dev.of_node, + 0, 0); + if (!ep_node) { + dev_err(&client->dev, "unable to find port0 ep"); + ret = -EINVAL; + goto error; + } + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep_node), &ep); + if (ret) { + dev_err(&client->dev, "Could not parse v4l2 endpoint %d\n", + ret); + goto error_of_node_put; + } + + /* do some sanity checks */ + if (ep.bus.mipi_csi2.num_data_lanes > 2) { + dev_err(&client->dev, "max supported data lanes is 2 / got %d", + ep.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto error_of_node_put; + } + + /* register it for later use */ + bridge->rx = ep; + + /* register async notifier so we get noticed when sensor is connected */ + bridge->asd.match.fwnode = + fwnode_graph_get_remote_port_parent(of_fwnode_handle(ep_node)); + bridge->asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + of_node_put(ep_node); + + v4l2_async_notifier_init(&bridge->notifier); + ret = v4l2_async_notifier_add_subdev(&bridge->notifier, &bridge->asd); + if (ret) { + dev_err(&client->dev, "fail to register asd to notifier %d", + ret); + fwnode_handle_put(bridge->asd.match.fwnode); + return ret; + } + bridge->notifier.ops = &mipid02_notifier_ops; + + ret = v4l2_async_subdev_notifier_register(&bridge->sd, + &bridge->notifier); + if (ret) + v4l2_async_notifier_cleanup(&bridge->notifier); + + return ret; + +error_of_node_put: + of_node_put(ep_node); +error: + + return ret; +} + +static int mipid02_parse_tx_ep(struct mipid02_dev *bridge) +{ + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_PARALLEL }; + struct i2c_client *client = bridge->i2c_client; + struct device_node *ep_node; + int ret; + + /* parse tx (endpoint 2) */ + ep_node = of_graph_get_endpoint_by_regs(bridge->i2c_client->dev.of_node, + 2, 0); + if (!ep_node) { + dev_err(&client->dev, "unable to find port1 ep"); + ret = -EINVAL; + goto error; + } + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep_node), &ep); + if (ret) { + dev_err(&client->dev, "Could not parse v4l2 endpoint\n"); + goto error_of_node_put; + } + + of_node_put(ep_node); + bridge->tx = ep; + + return 0; + +error_of_node_put: + of_node_put(ep_node); +error: + + return -EINVAL; +} + +static int mipid02_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct mipid02_dev *bridge; + u32 clk_freq; + int ret; + + bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL); + if (!bridge) + return -ENOMEM; + + init_format(&bridge->fmt); + + bridge->i2c_client = client; + v4l2_i2c_subdev_init(&bridge->sd, client, &mipid02_subdev_ops); + + /* got and check clock */ + bridge->xclk = devm_clk_get(dev, "xclk"); + if (IS_ERR(bridge->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(bridge->xclk); + } + + clk_freq = clk_get_rate(bridge->xclk); + if (clk_freq < 6000000 || clk_freq > 27000000) { + dev_err(dev, "xclk freq must be in 6-27 Mhz range. got %d Hz\n", + clk_freq); + return -EINVAL; + } + + bridge->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + if (IS_ERR(bridge->reset_gpio)) { + dev_err(dev, "failed to get reset GPIO\n"); + return PTR_ERR(bridge->reset_gpio); + } + + ret = mipid02_get_regulators(bridge); + if (ret) { + dev_err(dev, "failed to get regulators %d", ret); + return ret; + } + + mutex_init(&bridge->lock); + bridge->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + bridge->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + bridge->sd.entity.ops = &mipid02_subdev_entity_ops; + bridge->pad[0].flags = MEDIA_PAD_FL_SINK; + bridge->pad[1].flags = MEDIA_PAD_FL_SINK; + bridge->pad[2].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&bridge->sd.entity, MIPID02_PAD_NB, + bridge->pad); + if (ret) { + dev_err(&client->dev, "pads init failed %d", ret); + goto mutex_cleanup; + } + + /* enable clock, power and reset device if available */ + ret = mipid02_set_power_on(bridge); + if (ret) + goto entity_cleanup; + + ret = mipid02_detect(bridge); + if (ret) { + dev_err(&client->dev, "failed to detect mipid02 %d", ret); + goto power_off; + } + + ret = mipid02_parse_tx_ep(bridge); + if (ret) { + dev_err(&client->dev, "failed to parse tx %d", ret); + goto power_off; + } + + ret = mipid02_parse_rx_ep(bridge); + if (ret) { + dev_err(&client->dev, "failed to parse rx %d", ret); + goto power_off; + } + + ret = v4l2_async_register_subdev(&bridge->sd); + if (ret < 0) { + dev_err(&client->dev, "v4l2_async_register_subdev failed %d", + ret); + goto unregister_notifier; + } + + dev_info(&client->dev, "mipid02 device probe successfully"); + + return 0; + +unregister_notifier: + v4l2_async_notifier_unregister(&bridge->notifier); + v4l2_async_notifier_cleanup(&bridge->notifier); +power_off: + mipid02_set_power_off(bridge); +entity_cleanup: + media_entity_cleanup(&bridge->sd.entity); +mutex_cleanup: + mutex_destroy(&bridge->lock); + + return ret; +} + +static int mipid02_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct mipid02_dev *bridge = to_mipid02_dev(sd); + + v4l2_async_notifier_unregister(&bridge->notifier); + v4l2_async_notifier_cleanup(&bridge->notifier); + v4l2_async_unregister_subdev(&bridge->sd); + mipid02_set_power_off(bridge); + media_entity_cleanup(&bridge->sd.entity); + mutex_destroy(&bridge->lock); + + return 0; +} + +static const struct of_device_id mipid02_dt_ids[] = { + { .compatible = "st,st-mipid02" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mipid02_dt_ids); + +static struct i2c_driver mipid02_i2c_driver = { + .driver = { + .name = "st-mipid02", + .of_match_table = mipid02_dt_ids, + }, + .probe_new = mipid02_probe, + .remove = mipid02_remove, +}; + +module_i2c_driver(mipid02_i2c_driver); + +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics MIPID02 CSI-2 bridge driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c new file mode 100644 index 000000000..f21da11ca --- /dev/null +++ b/drivers/media/i2c/tc358743.c @@ -0,0 +1,2233 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tc358743 - Toshiba HDMI to CSI-2 bridge + * + * Copyright 2015 Cisco Systems, Inc. and/or its affiliates. All rights + * reserved. + */ + +/* + * References (c = chapter, p = page): + * REF_01 - Toshiba, TC358743XBG (H2C), Functional Specification, Rev 0.60 + * REF_02 - Toshiba, TC358743XBG_HDMI-CSI_Tv11p_nm.xls + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/of_graph.h> +#include <linux/videodev2.h> +#include <linux/workqueue.h> +#include <linux/v4l2-dv-timings.h> +#include <linux/hdmi.h> +#include <media/cec.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/i2c/tc358743.h> + +#include "tc358743_regs.h" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-3)"); + +MODULE_DESCRIPTION("Toshiba TC358743 HDMI to CSI-2 bridge driver"); +MODULE_AUTHOR("Ramakrishnan Muthukrishnan <ram@rkrishnan.org>"); +MODULE_AUTHOR("Mikhail Khelik <mkhelik@cisco.com>"); +MODULE_AUTHOR("Mats Randgaard <matrandg@cisco.com>"); +MODULE_LICENSE("GPL"); + +#define EDID_NUM_BLOCKS_MAX 8 +#define EDID_BLOCK_SIZE 128 + +#define I2C_MAX_XFER_SIZE (EDID_BLOCK_SIZE + 2) + +#define POLL_INTERVAL_CEC_MS 10 +#define POLL_INTERVAL_MS 1000 + +static const struct v4l2_dv_timings_cap tc358743_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + /* Pixel clock from REF_01 p. 20. Min/max height/width are unknown */ + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1200, 13000000, 165000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +struct tc358743_state { + struct tc358743_platform_data pdata; + struct v4l2_fwnode_bus_mipi_csi2 bus; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + struct i2c_client *i2c_client; + /* CONFCTL is modified in ops and tc358743_hdmi_sys_int_handler */ + struct mutex confctl_mutex; + + /* controls */ + struct v4l2_ctrl *detect_tx_5v_ctrl; + struct v4l2_ctrl *audio_sampling_rate_ctrl; + struct v4l2_ctrl *audio_present_ctrl; + + struct delayed_work delayed_work_enable_hotplug; + + struct timer_list timer; + struct work_struct work_i2c_poll; + + /* edid */ + u8 edid_blocks_written; + + struct v4l2_dv_timings timings; + u32 mbus_fmt_code; + u8 csi_lanes_in_use; + + struct gpio_desc *reset_gpio; + + struct cec_adapter *cec_adap; +}; + +static void tc358743_enable_interrupts(struct v4l2_subdev *sd, + bool cable_connected); +static int tc358743_s_ctrl_detect_tx_5v(struct v4l2_subdev *sd); + +static inline struct tc358743_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tc358743_state, sd); +} + +/* --------------- I2C --------------- */ + +static void i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) +{ + struct tc358743_state *state = to_state(sd); + struct i2c_client *client = state->i2c_client; + int err; + u8 buf[2] = { reg >> 8, reg & 0xff }; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = buf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = n, + .buf = values, + }, + }; + + err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (err != ARRAY_SIZE(msgs)) { + v4l2_err(sd, "%s: reading register 0x%x from 0x%x failed\n", + __func__, reg, client->addr); + } +} + +static void i2c_wr(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) +{ + struct tc358743_state *state = to_state(sd); + struct i2c_client *client = state->i2c_client; + int err, i; + struct i2c_msg msg; + u8 data[I2C_MAX_XFER_SIZE]; + + if ((2 + n) > I2C_MAX_XFER_SIZE) { + n = I2C_MAX_XFER_SIZE - 2; + v4l2_warn(sd, "i2c wr reg=%04x: len=%d is too big!\n", + reg, 2 + n); + } + + msg.addr = client->addr; + msg.buf = data; + msg.len = 2 + n; + msg.flags = 0; + + data[0] = reg >> 8; + data[1] = reg & 0xff; + + for (i = 0; i < n; i++) + data[2 + i] = values[i]; + + err = i2c_transfer(client->adapter, &msg, 1); + if (err != 1) { + v4l2_err(sd, "%s: writing register 0x%x from 0x%x failed\n", + __func__, reg, client->addr); + return; + } + + if (debug < 3) + return; + + switch (n) { + case 1: + v4l2_info(sd, "I2C write 0x%04x = 0x%02x", + reg, data[2]); + break; + case 2: + v4l2_info(sd, "I2C write 0x%04x = 0x%02x%02x", + reg, data[3], data[2]); + break; + case 4: + v4l2_info(sd, "I2C write 0x%04x = 0x%02x%02x%02x%02x", + reg, data[5], data[4], data[3], data[2]); + break; + default: + v4l2_info(sd, "I2C write %d bytes from address 0x%04x\n", + n, reg); + } +} + +static noinline u32 i2c_rdreg(struct v4l2_subdev *sd, u16 reg, u32 n) +{ + __le32 val = 0; + + i2c_rd(sd, reg, (u8 __force *)&val, n); + + return le32_to_cpu(val); +} + +static noinline void i2c_wrreg(struct v4l2_subdev *sd, u16 reg, u32 val, u32 n) +{ + __le32 raw = cpu_to_le32(val); + + i2c_wr(sd, reg, (u8 __force *)&raw, n); +} + +static u8 i2c_rd8(struct v4l2_subdev *sd, u16 reg) +{ + return i2c_rdreg(sd, reg, 1); +} + +static void i2c_wr8(struct v4l2_subdev *sd, u16 reg, u8 val) +{ + i2c_wrreg(sd, reg, val, 1); +} + +static void i2c_wr8_and_or(struct v4l2_subdev *sd, u16 reg, + u8 mask, u8 val) +{ + i2c_wrreg(sd, reg, (i2c_rdreg(sd, reg, 1) & mask) | val, 1); +} + +static u16 i2c_rd16(struct v4l2_subdev *sd, u16 reg) +{ + return i2c_rdreg(sd, reg, 2); +} + +static void i2c_wr16(struct v4l2_subdev *sd, u16 reg, u16 val) +{ + i2c_wrreg(sd, reg, val, 2); +} + +static void i2c_wr16_and_or(struct v4l2_subdev *sd, u16 reg, u16 mask, u16 val) +{ + i2c_wrreg(sd, reg, (i2c_rdreg(sd, reg, 2) & mask) | val, 2); +} + +static u32 i2c_rd32(struct v4l2_subdev *sd, u16 reg) +{ + return i2c_rdreg(sd, reg, 4); +} + +static void i2c_wr32(struct v4l2_subdev *sd, u16 reg, u32 val) +{ + i2c_wrreg(sd, reg, val, 4); +} + +/* --------------- STATUS --------------- */ + +static inline bool is_hdmi(struct v4l2_subdev *sd) +{ + return i2c_rd8(sd, SYS_STATUS) & MASK_S_HDMI; +} + +static inline bool tx_5v_power_present(struct v4l2_subdev *sd) +{ + return i2c_rd8(sd, SYS_STATUS) & MASK_S_DDC5V; +} + +static inline bool no_signal(struct v4l2_subdev *sd) +{ + return !(i2c_rd8(sd, SYS_STATUS) & MASK_S_TMDS); +} + +static inline bool no_sync(struct v4l2_subdev *sd) +{ + return !(i2c_rd8(sd, SYS_STATUS) & MASK_S_SYNC); +} + +static inline bool audio_present(struct v4l2_subdev *sd) +{ + return i2c_rd8(sd, AU_STATUS0) & MASK_S_A_SAMPLE; +} + +static int get_audio_sampling_rate(struct v4l2_subdev *sd) +{ + static const int code_to_rate[] = { + 44100, 0, 48000, 32000, 22050, 384000, 24000, 352800, + 88200, 768000, 96000, 705600, 176400, 0, 192000, 0 + }; + + /* Register FS_SET is not cleared when the cable is disconnected */ + if (no_signal(sd)) + return 0; + + return code_to_rate[i2c_rd8(sd, FS_SET) & MASK_FS]; +} + +/* --------------- TIMINGS --------------- */ + +static inline unsigned fps(const struct v4l2_bt_timings *t) +{ + if (!V4L2_DV_BT_FRAME_HEIGHT(t) || !V4L2_DV_BT_FRAME_WIDTH(t)) + return 0; + + return DIV_ROUND_CLOSEST((unsigned)t->pixelclock, + V4L2_DV_BT_FRAME_HEIGHT(t) * V4L2_DV_BT_FRAME_WIDTH(t)); +} + +static int tc358743_get_detected_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct v4l2_bt_timings *bt = &timings->bt; + unsigned width, height, frame_width, frame_height, frame_interval, fps; + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + + if (no_signal(sd)) { + v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); + return -ENOLINK; + } + if (no_sync(sd)) { + v4l2_dbg(1, debug, sd, "%s: no sync on signal\n", __func__); + return -ENOLCK; + } + + timings->type = V4L2_DV_BT_656_1120; + bt->interlaced = i2c_rd8(sd, VI_STATUS1) & MASK_S_V_INTERLACE ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + + width = ((i2c_rd8(sd, DE_WIDTH_H_HI) & 0x1f) << 8) + + i2c_rd8(sd, DE_WIDTH_H_LO); + height = ((i2c_rd8(sd, DE_WIDTH_V_HI) & 0x1f) << 8) + + i2c_rd8(sd, DE_WIDTH_V_LO); + frame_width = ((i2c_rd8(sd, H_SIZE_HI) & 0x1f) << 8) + + i2c_rd8(sd, H_SIZE_LO); + frame_height = (((i2c_rd8(sd, V_SIZE_HI) & 0x3f) << 8) + + i2c_rd8(sd, V_SIZE_LO)) / 2; + /* frame interval in milliseconds * 10 + * Require SYS_FREQ0 and SYS_FREQ1 are precisely set */ + frame_interval = ((i2c_rd8(sd, FV_CNT_HI) & 0x3) << 8) + + i2c_rd8(sd, FV_CNT_LO); + fps = (frame_interval > 0) ? + DIV_ROUND_CLOSEST(10000, frame_interval) : 0; + + bt->width = width; + bt->height = height; + bt->vsync = frame_height - height; + bt->hsync = frame_width - width; + bt->pixelclock = frame_width * frame_height * fps; + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height *= 2; + bt->il_vsync = bt->vsync + 1; + bt->pixelclock /= 2; + } + + return 0; +} + +/* --------------- HOTPLUG / HDCP / EDID --------------- */ + +static void tc358743_delayed_work_enable_hotplug(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct tc358743_state *state = container_of(dwork, + struct tc358743_state, delayed_work_enable_hotplug); + struct v4l2_subdev *sd = &state->sd; + + v4l2_dbg(2, debug, sd, "%s:\n", __func__); + + i2c_wr8_and_or(sd, HPD_CTL, ~MASK_HPD_OUT0, MASK_HPD_OUT0); +} + +static void tc358743_set_hdmi_hdcp(struct v4l2_subdev *sd, bool enable) +{ + v4l2_dbg(2, debug, sd, "%s: %s\n", __func__, enable ? + "enable" : "disable"); + + if (enable) { + i2c_wr8_and_or(sd, HDCP_REG3, ~KEY_RD_CMD, KEY_RD_CMD); + + i2c_wr8_and_or(sd, HDCP_MODE, ~MASK_MANUAL_AUTHENTICATION, 0); + + i2c_wr8_and_or(sd, HDCP_REG1, 0xff, + MASK_AUTH_UNAUTH_SEL_16_FRAMES | + MASK_AUTH_UNAUTH_AUTO); + + i2c_wr8_and_or(sd, HDCP_REG2, ~MASK_AUTO_P3_RESET, + SET_AUTO_P3_RESET_FRAMES(0x0f)); + } else { + i2c_wr8_and_or(sd, HDCP_MODE, ~MASK_MANUAL_AUTHENTICATION, + MASK_MANUAL_AUTHENTICATION); + } +} + +static void tc358743_disable_edid(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + + v4l2_dbg(2, debug, sd, "%s:\n", __func__); + + cancel_delayed_work_sync(&state->delayed_work_enable_hotplug); + + /* DDC access to EDID is also disabled when hotplug is disabled. See + * register DDC_CTL */ + i2c_wr8_and_or(sd, HPD_CTL, ~MASK_HPD_OUT0, 0x0); +} + +static void tc358743_enable_edid(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + + if (state->edid_blocks_written == 0) { + v4l2_dbg(2, debug, sd, "%s: no EDID -> no hotplug\n", __func__); + tc358743_s_ctrl_detect_tx_5v(sd); + return; + } + + v4l2_dbg(2, debug, sd, "%s:\n", __func__); + + /* Enable hotplug after 100 ms. DDC access to EDID is also enabled when + * hotplug is enabled. See register DDC_CTL */ + schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 10); + + tc358743_enable_interrupts(sd, true); + tc358743_s_ctrl_detect_tx_5v(sd); +} + +static void tc358743_erase_bksv(struct v4l2_subdev *sd) +{ + int i; + + for (i = 0; i < 5; i++) + i2c_wr8(sd, BKSV + i, 0); +} + +/* --------------- AVI infoframe --------------- */ + +static void print_avi_infoframe(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct device *dev = &client->dev; + union hdmi_infoframe frame; + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; + + if (!is_hdmi(sd)) { + v4l2_info(sd, "DVI-D signal - AVI infoframe not supported\n"); + return; + } + + i2c_rd(sd, PK_AVI_0HEAD, buffer, HDMI_INFOFRAME_SIZE(AVI)); + + if (hdmi_infoframe_unpack(&frame, buffer, sizeof(buffer)) < 0) { + v4l2_err(sd, "%s: unpack of AVI infoframe failed\n", __func__); + return; + } + + hdmi_infoframe_log(KERN_INFO, dev, &frame); +} + +/* --------------- CTRLS --------------- */ + +static int tc358743_s_ctrl_detect_tx_5v(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, + tx_5v_power_present(sd)); +} + +static int tc358743_s_ctrl_audio_sampling_rate(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + + return v4l2_ctrl_s_ctrl(state->audio_sampling_rate_ctrl, + get_audio_sampling_rate(sd)); +} + +static int tc358743_s_ctrl_audio_present(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + + return v4l2_ctrl_s_ctrl(state->audio_present_ctrl, + audio_present(sd)); +} + +static int tc358743_update_controls(struct v4l2_subdev *sd) +{ + int ret = 0; + + ret |= tc358743_s_ctrl_detect_tx_5v(sd); + ret |= tc358743_s_ctrl_audio_sampling_rate(sd); + ret |= tc358743_s_ctrl_audio_present(sd); + + return ret; +} + +/* --------------- INIT --------------- */ + +static void tc358743_reset_phy(struct v4l2_subdev *sd) +{ + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + i2c_wr8_and_or(sd, PHY_RST, ~MASK_RESET_CTRL, 0); + i2c_wr8_and_or(sd, PHY_RST, ~MASK_RESET_CTRL, MASK_RESET_CTRL); +} + +static void tc358743_reset(struct v4l2_subdev *sd, uint16_t mask) +{ + u16 sysctl = i2c_rd16(sd, SYSCTL); + + i2c_wr16(sd, SYSCTL, sysctl | mask); + i2c_wr16(sd, SYSCTL, sysctl & ~mask); +} + +static inline void tc358743_sleep_mode(struct v4l2_subdev *sd, bool enable) +{ + i2c_wr16_and_or(sd, SYSCTL, ~MASK_SLEEP, + enable ? MASK_SLEEP : 0); +} + +static inline void enable_stream(struct v4l2_subdev *sd, bool enable) +{ + struct tc358743_state *state = to_state(sd); + + v4l2_dbg(3, debug, sd, "%s: %sable\n", + __func__, enable ? "en" : "dis"); + + if (enable) { + /* It is critical for CSI receiver to see lane transition + * LP11->HS. Set to non-continuous mode to enable clock lane + * LP11 state. */ + i2c_wr32(sd, TXOPTIONCNTRL, 0); + /* Set to continuous mode to trigger LP11->HS transition */ + i2c_wr32(sd, TXOPTIONCNTRL, MASK_CONTCLKMODE); + /* Unmute video */ + i2c_wr8(sd, VI_MUTE, MASK_AUTO_MUTE); + } else { + /* Mute video so that all data lanes go to LSP11 state. + * No data is output to CSI Tx block. */ + i2c_wr8(sd, VI_MUTE, MASK_AUTO_MUTE | MASK_VI_MUTE); + } + + mutex_lock(&state->confctl_mutex); + i2c_wr16_and_or(sd, CONFCTL, ~(MASK_VBUFEN | MASK_ABUFEN), + enable ? (MASK_VBUFEN | MASK_ABUFEN) : 0x0); + mutex_unlock(&state->confctl_mutex); +} + +static void tc358743_set_pll(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + struct tc358743_platform_data *pdata = &state->pdata; + u16 pllctl0 = i2c_rd16(sd, PLLCTL0); + u16 pllctl1 = i2c_rd16(sd, PLLCTL1); + u16 pllctl0_new = SET_PLL_PRD(pdata->pll_prd) | + SET_PLL_FBD(pdata->pll_fbd); + u32 hsck = (pdata->refclk_hz / pdata->pll_prd) * pdata->pll_fbd; + + v4l2_dbg(2, debug, sd, "%s:\n", __func__); + + /* Only rewrite when needed (new value or disabled), since rewriting + * triggers another format change event. */ + if ((pllctl0 != pllctl0_new) || ((pllctl1 & MASK_PLL_EN) == 0)) { + u16 pll_frs; + + if (hsck > 500000000) + pll_frs = 0x0; + else if (hsck > 250000000) + pll_frs = 0x1; + else if (hsck > 125000000) + pll_frs = 0x2; + else + pll_frs = 0x3; + + v4l2_dbg(1, debug, sd, "%s: updating PLL clock\n", __func__); + tc358743_sleep_mode(sd, true); + i2c_wr16(sd, PLLCTL0, pllctl0_new); + i2c_wr16_and_or(sd, PLLCTL1, + ~(MASK_PLL_FRS | MASK_RESETB | MASK_PLL_EN), + (SET_PLL_FRS(pll_frs) | MASK_RESETB | + MASK_PLL_EN)); + udelay(10); /* REF_02, Sheet "Source HDMI" */ + i2c_wr16_and_or(sd, PLLCTL1, ~MASK_CKEN, MASK_CKEN); + tc358743_sleep_mode(sd, false); + } +} + +static void tc358743_set_ref_clk(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + struct tc358743_platform_data *pdata = &state->pdata; + u32 sys_freq; + u32 lockdet_ref; + u32 cec_freq; + u16 fh_min; + u16 fh_max; + + BUG_ON(!(pdata->refclk_hz == 26000000 || + pdata->refclk_hz == 27000000 || + pdata->refclk_hz == 42000000)); + + sys_freq = pdata->refclk_hz / 10000; + i2c_wr8(sd, SYS_FREQ0, sys_freq & 0x00ff); + i2c_wr8(sd, SYS_FREQ1, (sys_freq & 0xff00) >> 8); + + i2c_wr8_and_or(sd, PHY_CTL0, ~MASK_PHY_SYSCLK_IND, + (pdata->refclk_hz == 42000000) ? + MASK_PHY_SYSCLK_IND : 0x0); + + fh_min = pdata->refclk_hz / 100000; + i2c_wr8(sd, FH_MIN0, fh_min & 0x00ff); + i2c_wr8(sd, FH_MIN1, (fh_min & 0xff00) >> 8); + + fh_max = (fh_min * 66) / 10; + i2c_wr8(sd, FH_MAX0, fh_max & 0x00ff); + i2c_wr8(sd, FH_MAX1, (fh_max & 0xff00) >> 8); + + lockdet_ref = pdata->refclk_hz / 100; + i2c_wr8(sd, LOCKDET_REF0, lockdet_ref & 0x0000ff); + i2c_wr8(sd, LOCKDET_REF1, (lockdet_ref & 0x00ff00) >> 8); + i2c_wr8(sd, LOCKDET_REF2, (lockdet_ref & 0x0f0000) >> 16); + + i2c_wr8_and_or(sd, NCO_F0_MOD, ~MASK_NCO_F0_MOD, + (pdata->refclk_hz == 27000000) ? + MASK_NCO_F0_MOD_27MHZ : 0x0); + + /* + * Trial and error suggests that the default register value + * of 656 is for a 42 MHz reference clock. Use that to derive + * a new value based on the actual reference clock. + */ + cec_freq = (656 * sys_freq) / 4200; + i2c_wr16(sd, CECHCLK, cec_freq); + i2c_wr16(sd, CECLCLK, cec_freq); +} + +static void tc358743_set_csi_color_space(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + + switch (state->mbus_fmt_code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + v4l2_dbg(2, debug, sd, "%s: YCbCr 422 16-bit\n", __func__); + i2c_wr8_and_or(sd, VOUT_SET2, + ~(MASK_SEL422 | MASK_VOUT_422FIL_100) & 0xff, + MASK_SEL422 | MASK_VOUT_422FIL_100); + i2c_wr8_and_or(sd, VI_REP, ~MASK_VOUT_COLOR_SEL & 0xff, + MASK_VOUT_COLOR_601_YCBCR_LIMITED); + mutex_lock(&state->confctl_mutex); + i2c_wr16_and_or(sd, CONFCTL, ~MASK_YCBCRFMT, + MASK_YCBCRFMT_422_8_BIT); + mutex_unlock(&state->confctl_mutex); + break; + case MEDIA_BUS_FMT_RGB888_1X24: + v4l2_dbg(2, debug, sd, "%s: RGB 888 24-bit\n", __func__); + i2c_wr8_and_or(sd, VOUT_SET2, + ~(MASK_SEL422 | MASK_VOUT_422FIL_100) & 0xff, + 0x00); + i2c_wr8_and_or(sd, VI_REP, ~MASK_VOUT_COLOR_SEL & 0xff, + MASK_VOUT_COLOR_RGB_FULL); + mutex_lock(&state->confctl_mutex); + i2c_wr16_and_or(sd, CONFCTL, ~MASK_YCBCRFMT, 0); + mutex_unlock(&state->confctl_mutex); + break; + default: + v4l2_dbg(2, debug, sd, "%s: Unsupported format code 0x%x\n", + __func__, state->mbus_fmt_code); + } +} + +static unsigned tc358743_num_csi_lanes_needed(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + struct v4l2_bt_timings *bt = &state->timings.bt; + struct tc358743_platform_data *pdata = &state->pdata; + u32 bits_pr_pixel = + (state->mbus_fmt_code == MEDIA_BUS_FMT_UYVY8_1X16) ? 16 : 24; + u32 bps = bt->width * bt->height * fps(bt) * bits_pr_pixel; + u32 bps_pr_lane = (pdata->refclk_hz / pdata->pll_prd) * pdata->pll_fbd; + + return DIV_ROUND_UP(bps, bps_pr_lane); +} + +static void tc358743_set_csi(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + struct tc358743_platform_data *pdata = &state->pdata; + unsigned lanes = tc358743_num_csi_lanes_needed(sd); + + v4l2_dbg(3, debug, sd, "%s:\n", __func__); + + state->csi_lanes_in_use = lanes; + + tc358743_reset(sd, MASK_CTXRST); + + if (lanes < 1) + i2c_wr32(sd, CLW_CNTRL, MASK_CLW_LANEDISABLE); + if (lanes < 1) + i2c_wr32(sd, D0W_CNTRL, MASK_D0W_LANEDISABLE); + if (lanes < 2) + i2c_wr32(sd, D1W_CNTRL, MASK_D1W_LANEDISABLE); + if (lanes < 3) + i2c_wr32(sd, D2W_CNTRL, MASK_D2W_LANEDISABLE); + if (lanes < 4) + i2c_wr32(sd, D3W_CNTRL, MASK_D3W_LANEDISABLE); + + i2c_wr32(sd, LINEINITCNT, pdata->lineinitcnt); + i2c_wr32(sd, LPTXTIMECNT, pdata->lptxtimecnt); + i2c_wr32(sd, TCLK_HEADERCNT, pdata->tclk_headercnt); + i2c_wr32(sd, TCLK_TRAILCNT, pdata->tclk_trailcnt); + i2c_wr32(sd, THS_HEADERCNT, pdata->ths_headercnt); + i2c_wr32(sd, TWAKEUP, pdata->twakeup); + i2c_wr32(sd, TCLK_POSTCNT, pdata->tclk_postcnt); + i2c_wr32(sd, THS_TRAILCNT, pdata->ths_trailcnt); + i2c_wr32(sd, HSTXVREGCNT, pdata->hstxvregcnt); + + i2c_wr32(sd, HSTXVREGEN, + ((lanes > 0) ? MASK_CLM_HSTXVREGEN : 0x0) | + ((lanes > 0) ? MASK_D0M_HSTXVREGEN : 0x0) | + ((lanes > 1) ? MASK_D1M_HSTXVREGEN : 0x0) | + ((lanes > 2) ? MASK_D2M_HSTXVREGEN : 0x0) | + ((lanes > 3) ? MASK_D3M_HSTXVREGEN : 0x0)); + + i2c_wr32(sd, TXOPTIONCNTRL, (state->bus.flags & + V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) ? MASK_CONTCLKMODE : 0); + i2c_wr32(sd, STARTCNTRL, MASK_START); + i2c_wr32(sd, CSI_START, MASK_STRT); + + i2c_wr32(sd, CSI_CONFW, MASK_MODE_SET | + MASK_ADDRESS_CSI_CONTROL | + MASK_CSI_MODE | + MASK_TXHSMD | + ((lanes == 4) ? MASK_NOL_4 : + (lanes == 3) ? MASK_NOL_3 : + (lanes == 2) ? MASK_NOL_2 : MASK_NOL_1)); + + i2c_wr32(sd, CSI_CONFW, MASK_MODE_SET | + MASK_ADDRESS_CSI_ERR_INTENA | MASK_TXBRK | MASK_QUNK | + MASK_WCER | MASK_INER); + + i2c_wr32(sd, CSI_CONFW, MASK_MODE_CLEAR | + MASK_ADDRESS_CSI_ERR_HALT | MASK_TXBRK | MASK_QUNK); + + i2c_wr32(sd, CSI_CONFW, MASK_MODE_SET | + MASK_ADDRESS_CSI_INT_ENA | MASK_INTER); +} + +static void tc358743_set_hdmi_phy(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + struct tc358743_platform_data *pdata = &state->pdata; + + /* Default settings from REF_02, sheet "Source HDMI" + * and custom settings as platform data */ + i2c_wr8_and_or(sd, PHY_EN, ~MASK_ENABLE_PHY, 0x0); + i2c_wr8(sd, PHY_CTL1, SET_PHY_AUTO_RST1_US(1600) | + SET_FREQ_RANGE_MODE_CYCLES(1)); + i2c_wr8_and_or(sd, PHY_CTL2, ~MASK_PHY_AUTO_RSTn, + (pdata->hdmi_phy_auto_reset_tmds_detected ? + MASK_PHY_AUTO_RST2 : 0) | + (pdata->hdmi_phy_auto_reset_tmds_in_range ? + MASK_PHY_AUTO_RST3 : 0) | + (pdata->hdmi_phy_auto_reset_tmds_valid ? + MASK_PHY_AUTO_RST4 : 0)); + i2c_wr8(sd, PHY_BIAS, 0x40); + i2c_wr8(sd, PHY_CSQ, SET_CSQ_CNT_LEVEL(0x0a)); + i2c_wr8(sd, AVM_CTL, 45); + i2c_wr8_and_or(sd, HDMI_DET, ~MASK_HDMI_DET_V, + pdata->hdmi_detection_delay << 4); + i2c_wr8_and_or(sd, HV_RST, ~(MASK_H_PI_RST | MASK_V_PI_RST), + (pdata->hdmi_phy_auto_reset_hsync_out_of_range ? + MASK_H_PI_RST : 0) | + (pdata->hdmi_phy_auto_reset_vsync_out_of_range ? + MASK_V_PI_RST : 0)); + i2c_wr8_and_or(sd, PHY_EN, ~MASK_ENABLE_PHY, MASK_ENABLE_PHY); +} + +static void tc358743_set_hdmi_audio(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + + /* Default settings from REF_02, sheet "Source HDMI" */ + i2c_wr8(sd, FORCE_MUTE, 0x00); + i2c_wr8(sd, AUTO_CMD0, MASK_AUTO_MUTE7 | MASK_AUTO_MUTE6 | + MASK_AUTO_MUTE5 | MASK_AUTO_MUTE4 | + MASK_AUTO_MUTE1 | MASK_AUTO_MUTE0); + i2c_wr8(sd, AUTO_CMD1, MASK_AUTO_MUTE9); + i2c_wr8(sd, AUTO_CMD2, MASK_AUTO_PLAY3 | MASK_AUTO_PLAY2); + i2c_wr8(sd, BUFINIT_START, SET_BUFINIT_START_MS(500)); + i2c_wr8(sd, FS_MUTE, 0x00); + i2c_wr8(sd, FS_IMODE, MASK_NLPCM_SMODE | MASK_FS_SMODE); + i2c_wr8(sd, ACR_MODE, MASK_CTS_MODE); + i2c_wr8(sd, ACR_MDF0, MASK_ACR_L2MDF_1976_PPM | MASK_ACR_L1MDF_976_PPM); + i2c_wr8(sd, ACR_MDF1, MASK_ACR_L3MDF_3906_PPM); + i2c_wr8(sd, SDO_MODE1, MASK_SDO_FMT_I2S); + i2c_wr8(sd, DIV_MODE, SET_DIV_DLY_MS(100)); + + mutex_lock(&state->confctl_mutex); + i2c_wr16_and_or(sd, CONFCTL, 0xffff, MASK_AUDCHNUM_2 | + MASK_AUDOUTSEL_I2S | MASK_AUTOINDEX); + mutex_unlock(&state->confctl_mutex); +} + +static void tc358743_set_hdmi_info_frame_mode(struct v4l2_subdev *sd) +{ + /* Default settings from REF_02, sheet "Source HDMI" */ + i2c_wr8(sd, PK_INT_MODE, MASK_ISRC2_INT_MODE | MASK_ISRC_INT_MODE | + MASK_ACP_INT_MODE | MASK_VS_INT_MODE | + MASK_SPD_INT_MODE | MASK_MS_INT_MODE | + MASK_AUD_INT_MODE | MASK_AVI_INT_MODE); + i2c_wr8(sd, NO_PKT_LIMIT, 0x2c); + i2c_wr8(sd, NO_PKT_CLR, 0x53); + i2c_wr8(sd, ERR_PK_LIMIT, 0x01); + i2c_wr8(sd, NO_PKT_LIMIT2, 0x30); + i2c_wr8(sd, NO_GDB_LIMIT, 0x10); +} + +static void tc358743_initial_setup(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + struct tc358743_platform_data *pdata = &state->pdata; + + /* + * IR is not supported by this driver. + * CEC is only enabled if needed. + */ + i2c_wr16_and_or(sd, SYSCTL, ~(MASK_IRRST | MASK_CECRST), + (MASK_IRRST | MASK_CECRST)); + + tc358743_reset(sd, MASK_CTXRST | MASK_HDMIRST); +#ifdef CONFIG_VIDEO_TC358743_CEC + tc358743_reset(sd, MASK_CECRST); +#endif + tc358743_sleep_mode(sd, false); + + i2c_wr16(sd, FIFOCTL, pdata->fifo_level); + + tc358743_set_ref_clk(sd); + + i2c_wr8_and_or(sd, DDC_CTL, ~MASK_DDC5V_MODE, + pdata->ddc5v_delay & MASK_DDC5V_MODE); + i2c_wr8_and_or(sd, EDID_MODE, ~MASK_EDID_MODE, MASK_EDID_MODE_E_DDC); + + tc358743_set_hdmi_phy(sd); + tc358743_set_hdmi_hdcp(sd, pdata->enable_hdcp); + tc358743_set_hdmi_audio(sd); + tc358743_set_hdmi_info_frame_mode(sd); + + /* All CE and IT formats are detected as RGB full range in DVI mode */ + i2c_wr8_and_or(sd, VI_MODE, ~MASK_RGB_DVI, 0); + + i2c_wr8_and_or(sd, VOUT_SET2, ~MASK_VOUTCOLORMODE, + MASK_VOUTCOLORMODE_AUTO); + i2c_wr8(sd, VOUT_SET3, MASK_VOUT_EXTCNT); +} + +/* --------------- CEC --------------- */ + +#ifdef CONFIG_VIDEO_TC358743_CEC +static int tc358743_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct tc358743_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + + i2c_wr32(sd, CECIMSK, enable ? MASK_CECTIM | MASK_CECRIM : 0); + i2c_wr32(sd, CECICLR, MASK_CECTICLR | MASK_CECRICLR); + i2c_wr32(sd, CECEN, enable); + if (enable) + i2c_wr32(sd, CECREN, MASK_CECREN); + return 0; +} + +static int tc358743_cec_adap_monitor_all_enable(struct cec_adapter *adap, + bool enable) +{ + struct tc358743_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + u32 reg; + + reg = i2c_rd32(sd, CECRCTL1); + if (enable) + reg |= MASK_CECOTH; + else + reg &= ~MASK_CECOTH; + i2c_wr32(sd, CECRCTL1, reg); + return 0; +} + +static int tc358743_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) +{ + struct tc358743_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + unsigned int la = 0; + + if (log_addr != CEC_LOG_ADDR_INVALID) { + la = i2c_rd32(sd, CECADD); + la |= 1 << log_addr; + } + i2c_wr32(sd, CECADD, la); + return 0; +} + +static int tc358743_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct tc358743_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + unsigned int i; + + i2c_wr32(sd, CECTCTL, + (cec_msg_is_broadcast(msg) ? MASK_CECBRD : 0) | + (signal_free_time - 1)); + for (i = 0; i < msg->len; i++) + i2c_wr32(sd, CECTBUF1 + i * 4, + msg->msg[i] | ((i == msg->len - 1) ? MASK_CECTEOM : 0)); + i2c_wr32(sd, CECTEN, MASK_CECTEN); + return 0; +} + +static const struct cec_adap_ops tc358743_cec_adap_ops = { + .adap_enable = tc358743_cec_adap_enable, + .adap_log_addr = tc358743_cec_adap_log_addr, + .adap_transmit = tc358743_cec_adap_transmit, + .adap_monitor_all_enable = tc358743_cec_adap_monitor_all_enable, +}; + +static void tc358743_cec_handler(struct v4l2_subdev *sd, u16 intstatus, + bool *handled) +{ + struct tc358743_state *state = to_state(sd); + unsigned int cec_rxint, cec_txint; + unsigned int clr = 0; + + cec_rxint = i2c_rd32(sd, CECRSTAT); + cec_txint = i2c_rd32(sd, CECTSTAT); + + if (intstatus & MASK_CEC_RINT) + clr |= MASK_CECRICLR; + if (intstatus & MASK_CEC_TINT) + clr |= MASK_CECTICLR; + i2c_wr32(sd, CECICLR, clr); + + if ((intstatus & MASK_CEC_TINT) && cec_txint) { + if (cec_txint & MASK_CECTIEND) + cec_transmit_attempt_done(state->cec_adap, + CEC_TX_STATUS_OK); + else if (cec_txint & MASK_CECTIAL) + cec_transmit_attempt_done(state->cec_adap, + CEC_TX_STATUS_ARB_LOST); + else if (cec_txint & MASK_CECTIACK) + cec_transmit_attempt_done(state->cec_adap, + CEC_TX_STATUS_NACK); + else if (cec_txint & MASK_CECTIUR) { + /* + * Not sure when this bit is set. Treat + * it as an error for now. + */ + cec_transmit_attempt_done(state->cec_adap, + CEC_TX_STATUS_ERROR); + } + if (handled) + *handled = true; + } + if ((intstatus & MASK_CEC_RINT) && + (cec_rxint & MASK_CECRIEND)) { + struct cec_msg msg = {}; + unsigned int i; + unsigned int v; + + v = i2c_rd32(sd, CECRCTR); + msg.len = v & 0x1f; + for (i = 0; i < msg.len; i++) { + v = i2c_rd32(sd, CECRBUF1 + i * 4); + msg.msg[i] = v & 0xff; + } + cec_received_msg(state->cec_adap, &msg); + if (handled) + *handled = true; + } + i2c_wr16(sd, INTSTATUS, + intstatus & (MASK_CEC_RINT | MASK_CEC_TINT)); +} + +#endif + +/* --------------- IRQ --------------- */ + +static void tc358743_format_change(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + struct v4l2_dv_timings timings; + const struct v4l2_event tc358743_ev_fmt = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + if (tc358743_get_detected_timings(sd, &timings)) { + enable_stream(sd, false); + + v4l2_dbg(1, debug, sd, "%s: No signal\n", + __func__); + } else { + if (!v4l2_match_dv_timings(&state->timings, &timings, 0, false)) + enable_stream(sd, false); + + if (debug) + v4l2_print_dv_timings(sd->name, + "tc358743_format_change: New format: ", + &timings, false); + } + + if (sd->devnode) + v4l2_subdev_notify_event(sd, &tc358743_ev_fmt); +} + +static void tc358743_init_interrupts(struct v4l2_subdev *sd) +{ + u16 i; + + /* clear interrupt status registers */ + for (i = SYS_INT; i <= KEY_INT; i++) + i2c_wr8(sd, i, 0xff); + + i2c_wr16(sd, INTSTATUS, 0xffff); +} + +static void tc358743_enable_interrupts(struct v4l2_subdev *sd, + bool cable_connected) +{ + v4l2_dbg(2, debug, sd, "%s: cable connected = %d\n", __func__, + cable_connected); + + if (cable_connected) { + i2c_wr8(sd, SYS_INTM, ~(MASK_M_DDC | MASK_M_DVI_DET | + MASK_M_HDMI_DET) & 0xff); + i2c_wr8(sd, CLK_INTM, ~MASK_M_IN_DE_CHG); + i2c_wr8(sd, CBIT_INTM, ~(MASK_M_CBIT_FS | MASK_M_AF_LOCK | + MASK_M_AF_UNLOCK) & 0xff); + i2c_wr8(sd, AUDIO_INTM, ~MASK_M_BUFINIT_END); + i2c_wr8(sd, MISC_INTM, ~MASK_M_SYNC_CHG); + } else { + i2c_wr8(sd, SYS_INTM, ~MASK_M_DDC & 0xff); + i2c_wr8(sd, CLK_INTM, 0xff); + i2c_wr8(sd, CBIT_INTM, 0xff); + i2c_wr8(sd, AUDIO_INTM, 0xff); + i2c_wr8(sd, MISC_INTM, 0xff); + } +} + +static void tc358743_hdmi_audio_int_handler(struct v4l2_subdev *sd, + bool *handled) +{ + u8 audio_int_mask = i2c_rd8(sd, AUDIO_INTM); + u8 audio_int = i2c_rd8(sd, AUDIO_INT) & ~audio_int_mask; + + i2c_wr8(sd, AUDIO_INT, audio_int); + + v4l2_dbg(3, debug, sd, "%s: AUDIO_INT = 0x%02x\n", __func__, audio_int); + + tc358743_s_ctrl_audio_sampling_rate(sd); + tc358743_s_ctrl_audio_present(sd); +} + +static void tc358743_csi_err_int_handler(struct v4l2_subdev *sd, bool *handled) +{ + v4l2_err(sd, "%s: CSI_ERR = 0x%x\n", __func__, i2c_rd32(sd, CSI_ERR)); + + i2c_wr32(sd, CSI_INT_CLR, MASK_ICRER); +} + +static void tc358743_hdmi_misc_int_handler(struct v4l2_subdev *sd, + bool *handled) +{ + u8 misc_int_mask = i2c_rd8(sd, MISC_INTM); + u8 misc_int = i2c_rd8(sd, MISC_INT) & ~misc_int_mask; + + i2c_wr8(sd, MISC_INT, misc_int); + + v4l2_dbg(3, debug, sd, "%s: MISC_INT = 0x%02x\n", __func__, misc_int); + + if (misc_int & MASK_I_SYNC_CHG) { + /* Reset the HDMI PHY to try to trigger proper lock on the + * incoming video format. Erase BKSV to prevent that old keys + * are used when a new source is connected. */ + if (no_sync(sd) || no_signal(sd)) { + tc358743_reset_phy(sd); + tc358743_erase_bksv(sd); + } + + tc358743_format_change(sd); + + misc_int &= ~MASK_I_SYNC_CHG; + if (handled) + *handled = true; + } + + if (misc_int) { + v4l2_err(sd, "%s: Unhandled MISC_INT interrupts: 0x%02x\n", + __func__, misc_int); + } +} + +static void tc358743_hdmi_cbit_int_handler(struct v4l2_subdev *sd, + bool *handled) +{ + u8 cbit_int_mask = i2c_rd8(sd, CBIT_INTM); + u8 cbit_int = i2c_rd8(sd, CBIT_INT) & ~cbit_int_mask; + + i2c_wr8(sd, CBIT_INT, cbit_int); + + v4l2_dbg(3, debug, sd, "%s: CBIT_INT = 0x%02x\n", __func__, cbit_int); + + if (cbit_int & MASK_I_CBIT_FS) { + + v4l2_dbg(1, debug, sd, "%s: Audio sample rate changed\n", + __func__); + tc358743_s_ctrl_audio_sampling_rate(sd); + + cbit_int &= ~MASK_I_CBIT_FS; + if (handled) + *handled = true; + } + + if (cbit_int & (MASK_I_AF_LOCK | MASK_I_AF_UNLOCK)) { + + v4l2_dbg(1, debug, sd, "%s: Audio present changed\n", + __func__); + tc358743_s_ctrl_audio_present(sd); + + cbit_int &= ~(MASK_I_AF_LOCK | MASK_I_AF_UNLOCK); + if (handled) + *handled = true; + } + + if (cbit_int) { + v4l2_err(sd, "%s: Unhandled CBIT_INT interrupts: 0x%02x\n", + __func__, cbit_int); + } +} + +static void tc358743_hdmi_clk_int_handler(struct v4l2_subdev *sd, bool *handled) +{ + u8 clk_int_mask = i2c_rd8(sd, CLK_INTM); + u8 clk_int = i2c_rd8(sd, CLK_INT) & ~clk_int_mask; + + /* Bit 7 and bit 6 are set even when they are masked */ + i2c_wr8(sd, CLK_INT, clk_int | 0x80 | MASK_I_OUT_H_CHG); + + v4l2_dbg(3, debug, sd, "%s: CLK_INT = 0x%02x\n", __func__, clk_int); + + if (clk_int & (MASK_I_IN_DE_CHG)) { + + v4l2_dbg(1, debug, sd, "%s: DE size or position has changed\n", + __func__); + + /* If the source switch to a new resolution with the same pixel + * frequency as the existing (e.g. 1080p25 -> 720p50), the + * I_SYNC_CHG interrupt is not always triggered, while the + * I_IN_DE_CHG interrupt seems to work fine. Format change + * notifications are only sent when the signal is stable to + * reduce the number of notifications. */ + if (!no_signal(sd) && !no_sync(sd)) + tc358743_format_change(sd); + + clk_int &= ~(MASK_I_IN_DE_CHG); + if (handled) + *handled = true; + } + + if (clk_int) { + v4l2_err(sd, "%s: Unhandled CLK_INT interrupts: 0x%02x\n", + __func__, clk_int); + } +} + +static void tc358743_hdmi_sys_int_handler(struct v4l2_subdev *sd, bool *handled) +{ + struct tc358743_state *state = to_state(sd); + u8 sys_int_mask = i2c_rd8(sd, SYS_INTM); + u8 sys_int = i2c_rd8(sd, SYS_INT) & ~sys_int_mask; + + i2c_wr8(sd, SYS_INT, sys_int); + + v4l2_dbg(3, debug, sd, "%s: SYS_INT = 0x%02x\n", __func__, sys_int); + + if (sys_int & MASK_I_DDC) { + bool tx_5v = tx_5v_power_present(sd); + + v4l2_dbg(1, debug, sd, "%s: Tx 5V power present: %s\n", + __func__, tx_5v ? "yes" : "no"); + + if (tx_5v) { + tc358743_enable_edid(sd); + } else { + tc358743_enable_interrupts(sd, false); + tc358743_disable_edid(sd); + memset(&state->timings, 0, sizeof(state->timings)); + tc358743_erase_bksv(sd); + tc358743_update_controls(sd); + } + + sys_int &= ~MASK_I_DDC; + if (handled) + *handled = true; + } + + if (sys_int & MASK_I_DVI) { + v4l2_dbg(1, debug, sd, "%s: HDMI->DVI change detected\n", + __func__); + + /* Reset the HDMI PHY to try to trigger proper lock on the + * incoming video format. Erase BKSV to prevent that old keys + * are used when a new source is connected. */ + if (no_sync(sd) || no_signal(sd)) { + tc358743_reset_phy(sd); + tc358743_erase_bksv(sd); + } + + sys_int &= ~MASK_I_DVI; + if (handled) + *handled = true; + } + + if (sys_int & MASK_I_HDMI) { + v4l2_dbg(1, debug, sd, "%s: DVI->HDMI change detected\n", + __func__); + + /* Register is reset in DVI mode (REF_01, c. 6.6.41) */ + i2c_wr8(sd, ANA_CTL, MASK_APPL_PCSX_NORMAL | MASK_ANALOG_ON); + + sys_int &= ~MASK_I_HDMI; + if (handled) + *handled = true; + } + + if (sys_int) { + v4l2_err(sd, "%s: Unhandled SYS_INT interrupts: 0x%02x\n", + __func__, sys_int); + } +} + +/* --------------- CORE OPS --------------- */ + +static int tc358743_log_status(struct v4l2_subdev *sd) +{ + struct tc358743_state *state = to_state(sd); + struct v4l2_dv_timings timings; + uint8_t hdmi_sys_status = i2c_rd8(sd, SYS_STATUS); + uint16_t sysctl = i2c_rd16(sd, SYSCTL); + u8 vi_status3 = i2c_rd8(sd, VI_STATUS3); + const int deep_color_mode[4] = { 8, 10, 12, 16 }; + static const char * const input_color_space[] = { + "RGB", "YCbCr 601", "opRGB", "YCbCr 709", "NA (4)", + "xvYCC 601", "NA(6)", "xvYCC 709", "NA(8)", "sYCC601", + "NA(10)", "NA(11)", "NA(12)", "opYCC 601"}; + + v4l2_info(sd, "-----Chip status-----\n"); + v4l2_info(sd, "Chip ID: 0x%02x\n", + (i2c_rd16(sd, CHIPID) & MASK_CHIPID) >> 8); + v4l2_info(sd, "Chip revision: 0x%02x\n", + i2c_rd16(sd, CHIPID) & MASK_REVID); + v4l2_info(sd, "Reset: IR: %d, CEC: %d, CSI TX: %d, HDMI: %d\n", + !!(sysctl & MASK_IRRST), + !!(sysctl & MASK_CECRST), + !!(sysctl & MASK_CTXRST), + !!(sysctl & MASK_HDMIRST)); + v4l2_info(sd, "Sleep mode: %s\n", sysctl & MASK_SLEEP ? "on" : "off"); + v4l2_info(sd, "Cable detected (+5V power): %s\n", + hdmi_sys_status & MASK_S_DDC5V ? "yes" : "no"); + v4l2_info(sd, "DDC lines enabled: %s\n", + (i2c_rd8(sd, EDID_MODE) & MASK_EDID_MODE_E_DDC) ? + "yes" : "no"); + v4l2_info(sd, "Hotplug enabled: %s\n", + (i2c_rd8(sd, HPD_CTL) & MASK_HPD_OUT0) ? + "yes" : "no"); + v4l2_info(sd, "CEC enabled: %s\n", + (i2c_rd16(sd, CECEN) & MASK_CECEN) ? "yes" : "no"); + v4l2_info(sd, "-----Signal status-----\n"); + v4l2_info(sd, "TMDS signal detected: %s\n", + hdmi_sys_status & MASK_S_TMDS ? "yes" : "no"); + v4l2_info(sd, "Stable sync signal: %s\n", + hdmi_sys_status & MASK_S_SYNC ? "yes" : "no"); + v4l2_info(sd, "PHY PLL locked: %s\n", + hdmi_sys_status & MASK_S_PHY_PLL ? "yes" : "no"); + v4l2_info(sd, "PHY DE detected: %s\n", + hdmi_sys_status & MASK_S_PHY_SCDT ? "yes" : "no"); + + if (tc358743_get_detected_timings(sd, &timings)) { + v4l2_info(sd, "No video detected\n"); + } else { + v4l2_print_dv_timings(sd->name, "Detected format: ", &timings, + true); + } + v4l2_print_dv_timings(sd->name, "Configured format: ", &state->timings, + true); + + v4l2_info(sd, "-----CSI-TX status-----\n"); + v4l2_info(sd, "Lanes needed: %d\n", + tc358743_num_csi_lanes_needed(sd)); + v4l2_info(sd, "Lanes in use: %d\n", + state->csi_lanes_in_use); + v4l2_info(sd, "Waiting for particular sync signal: %s\n", + (i2c_rd16(sd, CSI_STATUS) & MASK_S_WSYNC) ? + "yes" : "no"); + v4l2_info(sd, "Transmit mode: %s\n", + (i2c_rd16(sd, CSI_STATUS) & MASK_S_TXACT) ? + "yes" : "no"); + v4l2_info(sd, "Receive mode: %s\n", + (i2c_rd16(sd, CSI_STATUS) & MASK_S_RXACT) ? + "yes" : "no"); + v4l2_info(sd, "Stopped: %s\n", + (i2c_rd16(sd, CSI_STATUS) & MASK_S_HLT) ? + "yes" : "no"); + v4l2_info(sd, "Color space: %s\n", + state->mbus_fmt_code == MEDIA_BUS_FMT_UYVY8_1X16 ? + "YCbCr 422 16-bit" : + state->mbus_fmt_code == MEDIA_BUS_FMT_RGB888_1X24 ? + "RGB 888 24-bit" : "Unsupported"); + + v4l2_info(sd, "-----%s status-----\n", is_hdmi(sd) ? "HDMI" : "DVI-D"); + v4l2_info(sd, "HDCP encrypted content: %s\n", + hdmi_sys_status & MASK_S_HDCP ? "yes" : "no"); + v4l2_info(sd, "Input color space: %s %s range\n", + input_color_space[(vi_status3 & MASK_S_V_COLOR) >> 1], + (vi_status3 & MASK_LIMITED) ? "limited" : "full"); + if (!is_hdmi(sd)) + return 0; + v4l2_info(sd, "AV Mute: %s\n", hdmi_sys_status & MASK_S_AVMUTE ? "on" : + "off"); + v4l2_info(sd, "Deep color mode: %d-bits per channel\n", + deep_color_mode[(i2c_rd8(sd, VI_STATUS1) & + MASK_S_DEEPCOLOR) >> 2]); + print_avi_infoframe(sd); + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static void tc358743_print_register_map(struct v4l2_subdev *sd) +{ + v4l2_info(sd, "0x0000-0x00FF: Global Control Register\n"); + v4l2_info(sd, "0x0100-0x01FF: CSI2-TX PHY Register\n"); + v4l2_info(sd, "0x0200-0x03FF: CSI2-TX PPI Register\n"); + v4l2_info(sd, "0x0400-0x05FF: Reserved\n"); + v4l2_info(sd, "0x0600-0x06FF: CEC Register\n"); + v4l2_info(sd, "0x0700-0x84FF: Reserved\n"); + v4l2_info(sd, "0x8500-0x85FF: HDMIRX System Control Register\n"); + v4l2_info(sd, "0x8600-0x86FF: HDMIRX Audio Control Register\n"); + v4l2_info(sd, "0x8700-0x87FF: HDMIRX InfoFrame packet data Register\n"); + v4l2_info(sd, "0x8800-0x88FF: HDMIRX HDCP Port Register\n"); + v4l2_info(sd, "0x8900-0x89FF: HDMIRX Video Output Port & 3D Register\n"); + v4l2_info(sd, "0x8A00-0x8BFF: Reserved\n"); + v4l2_info(sd, "0x8C00-0x8FFF: HDMIRX EDID-RAM (1024bytes)\n"); + v4l2_info(sd, "0x9000-0x90FF: HDMIRX GBD Extraction Control\n"); + v4l2_info(sd, "0x9100-0x92FF: HDMIRX GBD RAM read\n"); + v4l2_info(sd, "0x9300- : Reserved\n"); +} + +static int tc358743_get_reg_size(u16 address) +{ + /* REF_01 p. 66-72 */ + if (address <= 0x00ff) + return 2; + else if ((address >= 0x0100) && (address <= 0x06FF)) + return 4; + else if ((address >= 0x0700) && (address <= 0x84ff)) + return 2; + else + return 1; +} + +static int tc358743_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + if (reg->reg > 0xffff) { + tc358743_print_register_map(sd); + return -EINVAL; + } + + reg->size = tc358743_get_reg_size(reg->reg); + + reg->val = i2c_rdreg(sd, reg->reg, reg->size); + + return 0; +} + +static int tc358743_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + if (reg->reg > 0xffff) { + tc358743_print_register_map(sd); + return -EINVAL; + } + + /* It should not be possible for the user to enable HDCP with a simple + * v4l2-dbg command. + * + * DO NOT REMOVE THIS unless all other issues with HDCP have been + * resolved. + */ + if (reg->reg == HDCP_MODE || + reg->reg == HDCP_REG1 || + reg->reg == HDCP_REG2 || + reg->reg == HDCP_REG3 || + reg->reg == BCAPS) + return 0; + + i2c_wrreg(sd, (u16)reg->reg, reg->val, + tc358743_get_reg_size(reg->reg)); + + return 0; +} +#endif + +static int tc358743_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + u16 intstatus = i2c_rd16(sd, INTSTATUS); + + v4l2_dbg(1, debug, sd, "%s: IntStatus = 0x%04x\n", __func__, intstatus); + + if (intstatus & MASK_HDMI_INT) { + u8 hdmi_int0 = i2c_rd8(sd, HDMI_INT0); + u8 hdmi_int1 = i2c_rd8(sd, HDMI_INT1); + + if (hdmi_int0 & MASK_I_MISC) + tc358743_hdmi_misc_int_handler(sd, handled); + if (hdmi_int1 & MASK_I_CBIT) + tc358743_hdmi_cbit_int_handler(sd, handled); + if (hdmi_int1 & MASK_I_CLK) + tc358743_hdmi_clk_int_handler(sd, handled); + if (hdmi_int1 & MASK_I_SYS) + tc358743_hdmi_sys_int_handler(sd, handled); + if (hdmi_int1 & MASK_I_AUD) + tc358743_hdmi_audio_int_handler(sd, handled); + + i2c_wr16(sd, INTSTATUS, MASK_HDMI_INT); + intstatus &= ~MASK_HDMI_INT; + } + +#ifdef CONFIG_VIDEO_TC358743_CEC + if (intstatus & (MASK_CEC_RINT | MASK_CEC_TINT)) { + tc358743_cec_handler(sd, intstatus, handled); + i2c_wr16(sd, INTSTATUS, + intstatus & (MASK_CEC_RINT | MASK_CEC_TINT)); + intstatus &= ~(MASK_CEC_RINT | MASK_CEC_TINT); + } +#endif + + if (intstatus & MASK_CSI_INT) { + u32 csi_int = i2c_rd32(sd, CSI_INT); + + if (csi_int & MASK_INTER) + tc358743_csi_err_int_handler(sd, handled); + + i2c_wr16(sd, INTSTATUS, MASK_CSI_INT); + } + + intstatus = i2c_rd16(sd, INTSTATUS); + if (intstatus) { + v4l2_dbg(1, debug, sd, + "%s: Unhandled IntStatus interrupts: 0x%02x\n", + __func__, intstatus); + } + + return 0; +} + +static irqreturn_t tc358743_irq_handler(int irq, void *dev_id) +{ + struct tc358743_state *state = dev_id; + bool handled = false; + + tc358743_isr(&state->sd, 0, &handled); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static void tc358743_irq_poll_timer(struct timer_list *t) +{ + struct tc358743_state *state = from_timer(state, t, timer); + unsigned int msecs; + + schedule_work(&state->work_i2c_poll); + /* + * If CEC is present, then we need to poll more frequently, + * otherwise we will miss CEC messages. + */ + msecs = state->cec_adap ? POLL_INTERVAL_CEC_MS : POLL_INTERVAL_MS; + mod_timer(&state->timer, jiffies + msecs_to_jiffies(msecs)); +} + +static void tc358743_work_i2c_poll(struct work_struct *work) +{ + struct tc358743_state *state = container_of(work, + struct tc358743_state, work_i2c_poll); + bool handled; + + tc358743_isr(&state->sd, 0, &handled); +} + +static int tc358743_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +/* --------------- VIDEO OPS --------------- */ + +static int tc358743_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + *status = 0; + *status |= no_signal(sd) ? V4L2_IN_ST_NO_SIGNAL : 0; + *status |= no_sync(sd) ? V4L2_IN_ST_NO_SYNC : 0; + + v4l2_dbg(1, debug, sd, "%s: status = 0x%x\n", __func__, *status); + + return 0; +} + +static int tc358743_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct tc358743_state *state = to_state(sd); + + if (!timings) + return -EINVAL; + + if (debug) + v4l2_print_dv_timings(sd->name, "tc358743_s_dv_timings: ", + timings, false); + + if (v4l2_match_dv_timings(&state->timings, timings, 0, false)) { + v4l2_dbg(1, debug, sd, "%s: no change\n", __func__); + return 0; + } + + if (!v4l2_valid_dv_timings(timings, + &tc358743_timings_cap, NULL, NULL)) { + v4l2_dbg(1, debug, sd, "%s: timings out of range\n", __func__); + return -ERANGE; + } + + state->timings = *timings; + + enable_stream(sd, false); + tc358743_set_pll(sd); + tc358743_set_csi(sd); + + return 0; +} + +static int tc358743_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct tc358743_state *state = to_state(sd); + + *timings = state->timings; + + return 0; +} + +static int tc358743_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->pad != 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, + &tc358743_timings_cap, NULL, NULL); +} + +static int tc358743_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + int ret; + + ret = tc358743_get_detected_timings(sd, timings); + if (ret) + return ret; + + if (debug) + v4l2_print_dv_timings(sd->name, "tc358743_query_dv_timings: ", + timings, false); + + if (!v4l2_valid_dv_timings(timings, + &tc358743_timings_cap, NULL, NULL)) { + v4l2_dbg(1, debug, sd, "%s: timings out of range\n", __func__); + return -ERANGE; + } + + return 0; +} + +static int tc358743_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + if (cap->pad != 0) + return -EINVAL; + + *cap = tc358743_timings_cap; + + return 0; +} + +static int tc358743_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct tc358743_state *state = to_state(sd); + + cfg->type = V4L2_MBUS_CSI2_DPHY; + + /* Support for non-continuous CSI-2 clock is missing in the driver */ + cfg->flags = V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; + + switch (state->csi_lanes_in_use) { + case 1: + cfg->flags |= V4L2_MBUS_CSI2_1_LANE; + break; + case 2: + cfg->flags |= V4L2_MBUS_CSI2_2_LANE; + break; + case 3: + cfg->flags |= V4L2_MBUS_CSI2_3_LANE; + break; + case 4: + cfg->flags |= V4L2_MBUS_CSI2_4_LANE; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tc358743_s_stream(struct v4l2_subdev *sd, int enable) +{ + enable_stream(sd, enable); + if (!enable) { + /* Put all lanes in LP-11 state (STOPSTATE) */ + tc358743_set_csi(sd); + } + + return 0; +} + +/* --------------- PAD OPS --------------- */ + +static int tc358743_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->index) { + case 0: + code->code = MEDIA_BUS_FMT_RGB888_1X24; + break; + case 1: + code->code = MEDIA_BUS_FMT_UYVY8_1X16; + break; + default: + return -EINVAL; + } + return 0; +} + +static int tc358743_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct tc358743_state *state = to_state(sd); + u8 vi_rep = i2c_rd8(sd, VI_REP); + + if (format->pad != 0) + return -EINVAL; + + format->format.code = state->mbus_fmt_code; + format->format.width = state->timings.bt.width; + format->format.height = state->timings.bt.height; + format->format.field = V4L2_FIELD_NONE; + + switch (vi_rep & MASK_VOUT_COLOR_SEL) { + case MASK_VOUT_COLOR_RGB_FULL: + case MASK_VOUT_COLOR_RGB_LIMITED: + format->format.colorspace = V4L2_COLORSPACE_SRGB; + break; + case MASK_VOUT_COLOR_601_YCBCR_LIMITED: + case MASK_VOUT_COLOR_601_YCBCR_FULL: + format->format.colorspace = V4L2_COLORSPACE_SMPTE170M; + break; + case MASK_VOUT_COLOR_709_YCBCR_FULL: + case MASK_VOUT_COLOR_709_YCBCR_LIMITED: + format->format.colorspace = V4L2_COLORSPACE_REC709; + break; + default: + format->format.colorspace = 0; + break; + } + + return 0; +} + +static int tc358743_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct tc358743_state *state = to_state(sd); + + u32 code = format->format.code; /* is overwritten by get_fmt */ + int ret = tc358743_get_fmt(sd, cfg, format); + + format->format.code = code; + + if (ret) + return ret; + + switch (code) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_UYVY8_1X16: + break; + default: + return -EINVAL; + } + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + state->mbus_fmt_code = format->format.code; + + enable_stream(sd, false); + tc358743_set_pll(sd); + tc358743_set_csi(sd); + tc358743_set_csi_color_space(sd); + + return 0; +} + +static int tc358743_g_edid(struct v4l2_subdev *sd, + struct v4l2_subdev_edid *edid) +{ + struct tc358743_state *state = to_state(sd); + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->pad != 0) + return -EINVAL; + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = state->edid_blocks_written; + return 0; + } + + if (state->edid_blocks_written == 0) + return -ENODATA; + + if (edid->start_block >= state->edid_blocks_written || + edid->blocks == 0) + return -EINVAL; + + if (edid->start_block + edid->blocks > state->edid_blocks_written) + edid->blocks = state->edid_blocks_written - edid->start_block; + + i2c_rd(sd, EDID_RAM + (edid->start_block * EDID_BLOCK_SIZE), edid->edid, + edid->blocks * EDID_BLOCK_SIZE); + + return 0; +} + +static int tc358743_s_edid(struct v4l2_subdev *sd, + struct v4l2_subdev_edid *edid) +{ + struct tc358743_state *state = to_state(sd); + u16 edid_len = edid->blocks * EDID_BLOCK_SIZE; + u16 pa; + int err; + int i; + + v4l2_dbg(2, debug, sd, "%s, pad %d, start block %d, blocks %d\n", + __func__, edid->pad, edid->start_block, edid->blocks); + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->pad != 0) + return -EINVAL; + + if (edid->start_block != 0) + return -EINVAL; + + if (edid->blocks > EDID_NUM_BLOCKS_MAX) { + edid->blocks = EDID_NUM_BLOCKS_MAX; + return -E2BIG; + } + pa = cec_get_edid_phys_addr(edid->edid, edid->blocks * 128, NULL); + err = v4l2_phys_addr_validate(pa, &pa, NULL); + if (err) + return err; + + cec_phys_addr_invalidate(state->cec_adap); + + tc358743_disable_edid(sd); + + i2c_wr8(sd, EDID_LEN1, edid_len & 0xff); + i2c_wr8(sd, EDID_LEN2, edid_len >> 8); + + if (edid->blocks == 0) { + state->edid_blocks_written = 0; + return 0; + } + + for (i = 0; i < edid_len; i += EDID_BLOCK_SIZE) + i2c_wr(sd, EDID_RAM + i, edid->edid + i, EDID_BLOCK_SIZE); + + state->edid_blocks_written = edid->blocks; + + cec_s_phys_addr(state->cec_adap, pa, false); + + if (tx_5v_power_present(sd)) + tc358743_enable_edid(sd); + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops tc358743_core_ops = { + .log_status = tc358743_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = tc358743_g_register, + .s_register = tc358743_s_register, +#endif + .interrupt_service_routine = tc358743_isr, + .subscribe_event = tc358743_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops tc358743_video_ops = { + .g_input_status = tc358743_g_input_status, + .s_dv_timings = tc358743_s_dv_timings, + .g_dv_timings = tc358743_g_dv_timings, + .query_dv_timings = tc358743_query_dv_timings, + .s_stream = tc358743_s_stream, +}; + +static const struct v4l2_subdev_pad_ops tc358743_pad_ops = { + .enum_mbus_code = tc358743_enum_mbus_code, + .set_fmt = tc358743_set_fmt, + .get_fmt = tc358743_get_fmt, + .get_edid = tc358743_g_edid, + .set_edid = tc358743_s_edid, + .enum_dv_timings = tc358743_enum_dv_timings, + .dv_timings_cap = tc358743_dv_timings_cap, + .get_mbus_config = tc358743_get_mbus_config, +}; + +static const struct v4l2_subdev_ops tc358743_ops = { + .core = &tc358743_core_ops, + .video = &tc358743_video_ops, + .pad = &tc358743_pad_ops, +}; + +/* --------------- CUSTOM CTRLS --------------- */ + +static const struct v4l2_ctrl_config tc358743_ctrl_audio_sampling_rate = { + .id = TC358743_CID_AUDIO_SAMPLING_RATE, + .name = "Audio sampling rate", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 768000, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config tc358743_ctrl_audio_present = { + .id = TC358743_CID_AUDIO_PRESENT, + .name = "Audio present", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +/* --------------- PROBE / REMOVE --------------- */ + +#ifdef CONFIG_OF +static void tc358743_gpio_reset(struct tc358743_state *state) +{ + usleep_range(5000, 10000); + gpiod_set_value(state->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value(state->reset_gpio, 0); + msleep(20); +} + +static int tc358743_probe_of(struct tc358743_state *state) +{ + struct device *dev = &state->i2c_client->dev; + struct v4l2_fwnode_endpoint endpoint = { .bus_type = 0 }; + struct device_node *ep; + struct clk *refclk; + u32 bps_pr_lane; + int ret; + + refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(refclk)) { + if (PTR_ERR(refclk) != -EPROBE_DEFER) + dev_err(dev, "failed to get refclk: %ld\n", + PTR_ERR(refclk)); + return PTR_ERR(refclk); + } + + ep = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!ep) { + dev_err(dev, "missing endpoint node\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(ep), &endpoint); + if (ret) { + dev_err(dev, "failed to parse endpoint\n"); + goto put_node; + } + + if (endpoint.bus_type != V4L2_MBUS_CSI2_DPHY || + endpoint.bus.mipi_csi2.num_data_lanes == 0 || + endpoint.nr_of_link_frequencies == 0) { + dev_err(dev, "missing CSI-2 properties in endpoint\n"); + ret = -EINVAL; + goto free_endpoint; + } + + if (endpoint.bus.mipi_csi2.num_data_lanes > 4) { + dev_err(dev, "invalid number of lanes\n"); + ret = -EINVAL; + goto free_endpoint; + } + + state->bus = endpoint.bus.mipi_csi2; + + ret = clk_prepare_enable(refclk); + if (ret) { + dev_err(dev, "Failed! to enable clock\n"); + goto free_endpoint; + } + + state->pdata.refclk_hz = clk_get_rate(refclk); + state->pdata.ddc5v_delay = DDC5V_DELAY_100_MS; + state->pdata.enable_hdcp = false; + /* A FIFO level of 16 should be enough for 2-lane 720p60 at 594 MHz. */ + state->pdata.fifo_level = 16; + /* + * The PLL input clock is obtained by dividing refclk by pll_prd. + * It must be between 6 MHz and 40 MHz, lower frequency is better. + */ + switch (state->pdata.refclk_hz) { + case 26000000: + case 27000000: + case 42000000: + state->pdata.pll_prd = state->pdata.refclk_hz / 6000000; + break; + default: + dev_err(dev, "unsupported refclk rate: %u Hz\n", + state->pdata.refclk_hz); + goto disable_clk; + } + + /* + * The CSI bps per lane must be between 62.5 Mbps and 1 Gbps. + * The default is 594 Mbps for 4-lane 1080p60 or 2-lane 720p60. + */ + bps_pr_lane = 2 * endpoint.link_frequencies[0]; + if (bps_pr_lane < 62500000U || bps_pr_lane > 1000000000U) { + dev_err(dev, "unsupported bps per lane: %u bps\n", bps_pr_lane); + ret = -EINVAL; + goto disable_clk; + } + + /* The CSI speed per lane is refclk / pll_prd * pll_fbd */ + state->pdata.pll_fbd = bps_pr_lane / + state->pdata.refclk_hz * state->pdata.pll_prd; + + /* + * FIXME: These timings are from REF_02 for 594 Mbps per lane (297 MHz + * link frequency). In principle it should be possible to calculate + * them based on link frequency and resolution. + */ + if (bps_pr_lane != 594000000U) + dev_warn(dev, "untested bps per lane: %u bps\n", bps_pr_lane); + state->pdata.lineinitcnt = 0xe80; + state->pdata.lptxtimecnt = 0x003; + /* tclk-preparecnt: 3, tclk-zerocnt: 20 */ + state->pdata.tclk_headercnt = 0x1403; + state->pdata.tclk_trailcnt = 0x00; + /* ths-preparecnt: 3, ths-zerocnt: 1 */ + state->pdata.ths_headercnt = 0x0103; + state->pdata.twakeup = 0x4882; + state->pdata.tclk_postcnt = 0x008; + state->pdata.ths_trailcnt = 0x2; + state->pdata.hstxvregcnt = 0; + + state->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(state->reset_gpio)) { + dev_err(dev, "failed to get reset gpio\n"); + ret = PTR_ERR(state->reset_gpio); + goto disable_clk; + } + + if (state->reset_gpio) + tc358743_gpio_reset(state); + + ret = 0; + goto free_endpoint; + +disable_clk: + clk_disable_unprepare(refclk); +free_endpoint: + v4l2_fwnode_endpoint_free(&endpoint); +put_node: + of_node_put(ep); + return ret; +} +#else +static inline int tc358743_probe_of(struct tc358743_state *state) +{ + return -ENODEV; +} +#endif + +static int tc358743_probe(struct i2c_client *client) +{ + static struct v4l2_dv_timings default_timing = + V4L2_DV_BT_CEA_640X480P59_94; + struct tc358743_state *state; + struct tc358743_platform_data *pdata = client->dev.platform_data; + struct v4l2_subdev *sd; + u16 irq_mask = MASK_HDMI_MSK | MASK_CSI_MSK; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + v4l_dbg(1, debug, client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(struct tc358743_state), + GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->i2c_client = client; + + /* platform data */ + if (pdata) { + state->pdata = *pdata; + state->bus.flags = V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; + } else { + err = tc358743_probe_of(state); + if (err == -ENODEV) + v4l_err(client, "No platform data!\n"); + if (err) + return err; + } + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &tc358743_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + + /* i2c access */ + if ((i2c_rd16(sd, CHIPID) & MASK_CHIPID) != 0) { + v4l2_info(sd, "not a TC358743 on address 0x%x\n", + client->addr << 1); + return -ENODEV; + } + + /* control handlers */ + v4l2_ctrl_handler_init(&state->hdl, 3); + + state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(&state->hdl, NULL, + V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0); + + /* custom controls */ + state->audio_sampling_rate_ctrl = v4l2_ctrl_new_custom(&state->hdl, + &tc358743_ctrl_audio_sampling_rate, NULL); + + state->audio_present_ctrl = v4l2_ctrl_new_custom(&state->hdl, + &tc358743_ctrl_audio_present, NULL); + + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + err = state->hdl.error; + goto err_hdl; + } + + if (tc358743_update_controls(sd)) { + err = -ENODEV; + goto err_hdl; + } + + state->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + err = media_entity_pads_init(&sd->entity, 1, &state->pad); + if (err < 0) + goto err_hdl; + + state->mbus_fmt_code = MEDIA_BUS_FMT_RGB888_1X24; + + sd->dev = &client->dev; + err = v4l2_async_register_subdev(sd); + if (err < 0) + goto err_hdl; + + mutex_init(&state->confctl_mutex); + + INIT_DELAYED_WORK(&state->delayed_work_enable_hotplug, + tc358743_delayed_work_enable_hotplug); + +#ifdef CONFIG_VIDEO_TC358743_CEC + state->cec_adap = cec_allocate_adapter(&tc358743_cec_adap_ops, + state, dev_name(&client->dev), + CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL, CEC_MAX_LOG_ADDRS); + if (IS_ERR(state->cec_adap)) { + err = PTR_ERR(state->cec_adap); + goto err_hdl; + } + irq_mask |= MASK_CEC_RMSK | MASK_CEC_TMSK; +#endif + + tc358743_initial_setup(sd); + + tc358743_s_dv_timings(sd, &default_timing); + + tc358743_set_csi_color_space(sd); + + tc358743_init_interrupts(sd); + + if (state->i2c_client->irq) { + err = devm_request_threaded_irq(&client->dev, + state->i2c_client->irq, + NULL, tc358743_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "tc358743", state); + if (err) + goto err_work_queues; + } else { + INIT_WORK(&state->work_i2c_poll, + tc358743_work_i2c_poll); + timer_setup(&state->timer, tc358743_irq_poll_timer, 0); + state->timer.expires = jiffies + + msecs_to_jiffies(POLL_INTERVAL_MS); + add_timer(&state->timer); + } + + err = cec_register_adapter(state->cec_adap, &client->dev); + if (err < 0) { + pr_err("%s: failed to register the cec device\n", __func__); + cec_delete_adapter(state->cec_adap); + state->cec_adap = NULL; + goto err_work_queues; + } + + tc358743_enable_interrupts(sd, tx_5v_power_present(sd)); + i2c_wr16(sd, INTMASK, ~irq_mask); + + err = v4l2_ctrl_handler_setup(sd->ctrl_handler); + if (err) + goto err_work_queues; + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + return 0; + +err_work_queues: + cec_unregister_adapter(state->cec_adap); + if (!state->i2c_client->irq) + flush_work(&state->work_i2c_poll); + cancel_delayed_work(&state->delayed_work_enable_hotplug); + mutex_destroy(&state->confctl_mutex); +err_hdl: + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&state->hdl); + return err; +} + +static int tc358743_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tc358743_state *state = to_state(sd); + + if (!state->i2c_client->irq) { + del_timer_sync(&state->timer); + flush_work(&state->work_i2c_poll); + } + cancel_delayed_work_sync(&state->delayed_work_enable_hotplug); + cec_unregister_adapter(state->cec_adap); + v4l2_async_unregister_subdev(sd); + v4l2_device_unregister_subdev(sd); + mutex_destroy(&state->confctl_mutex); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&state->hdl); + + return 0; +} + +static const struct i2c_device_id tc358743_id[] = { + {"tc358743", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tc358743_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tc358743_of_match[] = { + { .compatible = "toshiba,tc358743" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tc358743_of_match); +#endif + +static struct i2c_driver tc358743_driver = { + .driver = { + .name = "tc358743", + .of_match_table = of_match_ptr(tc358743_of_match), + }, + .probe_new = tc358743_probe, + .remove = tc358743_remove, + .id_table = tc358743_id, +}; + +module_i2c_driver(tc358743_driver); diff --git a/drivers/media/i2c/tc358743_regs.h b/drivers/media/i2c/tc358743_regs.h new file mode 100644 index 000000000..2495878dc --- /dev/null +++ b/drivers/media/i2c/tc358743_regs.h @@ -0,0 +1,759 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tc358743 - Toshiba HDMI to CSI-2 bridge - register names and bit masks + * + * Copyright 2015 Cisco Systems, Inc. and/or its affiliates. All rights + * reserved. + */ + +/* + * References (c = chapter, p = page): + * REF_01 - Toshiba, TC358743XBG (H2C), Functional Specification, Rev 0.60 + */ + +/* Bit masks has prefix 'MASK_' and options after '_'. */ + +#ifndef __TC358743_REGS_H +#define __TC358743_REGS_H + +#define CHIPID 0x0000 +#define MASK_CHIPID 0xff00 +#define MASK_REVID 0x00ff + +#define SYSCTL 0x0002 +#define MASK_IRRST 0x0800 +#define MASK_CECRST 0x0400 +#define MASK_CTXRST 0x0200 +#define MASK_HDMIRST 0x0100 +#define MASK_SLEEP 0x0001 + +#define CONFCTL 0x0004 +#define MASK_PWRISO 0x8000 +#define MASK_ACLKOPT 0x1000 +#define MASK_AUDCHNUM 0x0c00 +#define MASK_AUDCHNUM_8 0x0000 +#define MASK_AUDCHNUM_6 0x0400 +#define MASK_AUDCHNUM_4 0x0800 +#define MASK_AUDCHNUM_2 0x0c00 +#define MASK_AUDCHSEL 0x0200 +#define MASK_I2SDLYOPT 0x0100 +#define MASK_YCBCRFMT 0x00c0 +#define MASK_YCBCRFMT_444 0x0000 +#define MASK_YCBCRFMT_422_12_BIT 0x0040 +#define MASK_YCBCRFMT_COLORBAR 0x0080 +#define MASK_YCBCRFMT_422_8_BIT 0x00c0 +#define MASK_INFRMEN 0x0020 +#define MASK_AUDOUTSEL 0x0018 +#define MASK_AUDOUTSEL_CSI 0x0000 +#define MASK_AUDOUTSEL_I2S 0x0010 +#define MASK_AUDOUTSEL_TDM 0x0018 +#define MASK_AUTOINDEX 0x0004 +#define MASK_ABUFEN 0x0002 +#define MASK_VBUFEN 0x0001 + +#define FIFOCTL 0x0006 + +#define INTSTATUS 0x0014 +#define MASK_AMUTE_INT 0x0400 +#define MASK_HDMI_INT 0x0200 +#define MASK_CSI_INT 0x0100 +#define MASK_SYS_INT 0x0020 +#define MASK_CEC_EINT 0x0010 +#define MASK_CEC_TINT 0x0008 +#define MASK_CEC_RINT 0x0004 +#define MASK_IR_EINT 0x0002 +#define MASK_IR_DINT 0x0001 + +#define INTMASK 0x0016 +#define MASK_AMUTE_MSK 0x0400 +#define MASK_HDMI_MSK 0x0200 +#define MASK_CSI_MSK 0x0100 +#define MASK_SYS_MSK 0x0020 +#define MASK_CEC_EMSK 0x0010 +#define MASK_CEC_TMSK 0x0008 +#define MASK_CEC_RMSK 0x0004 +#define MASK_IR_EMSK 0x0002 +#define MASK_IR_DMSK 0x0001 + +#define INTFLAG 0x0018 +#define INTSYSSTATUS 0x001A + +#define PLLCTL0 0x0020 +#define MASK_PLL_PRD 0xf000 +#define SET_PLL_PRD(prd) ((((prd) - 1) << 12) &\ + MASK_PLL_PRD) +#define MASK_PLL_FBD 0x01ff +#define SET_PLL_FBD(fbd) (((fbd) - 1) & MASK_PLL_FBD) + +#define PLLCTL1 0x0022 +#define MASK_PLL_FRS 0x0c00 +#define SET_PLL_FRS(frs) (((frs) << 10) & MASK_PLL_FRS) +#define MASK_PLL_LBWS 0x0300 +#define MASK_LFBREN 0x0040 +#define MASK_BYPCKEN 0x0020 +#define MASK_CKEN 0x0010 +#define MASK_RESETB 0x0002 +#define MASK_PLL_EN 0x0001 + +#define CLW_CNTRL 0x0140 +#define MASK_CLW_LANEDISABLE 0x0001 + +#define D0W_CNTRL 0x0144 +#define MASK_D0W_LANEDISABLE 0x0001 + +#define D1W_CNTRL 0x0148 +#define MASK_D1W_LANEDISABLE 0x0001 + +#define D2W_CNTRL 0x014C +#define MASK_D2W_LANEDISABLE 0x0001 + +#define D3W_CNTRL 0x0150 +#define MASK_D3W_LANEDISABLE 0x0001 + +#define STARTCNTRL 0x0204 +#define MASK_START 0x00000001 + +#define LINEINITCNT 0x0210 +#define LPTXTIMECNT 0x0214 +#define TCLK_HEADERCNT 0x0218 +#define TCLK_TRAILCNT 0x021C +#define THS_HEADERCNT 0x0220 +#define TWAKEUP 0x0224 +#define TCLK_POSTCNT 0x0228 +#define THS_TRAILCNT 0x022C +#define HSTXVREGCNT 0x0230 + +#define HSTXVREGEN 0x0234 +#define MASK_D3M_HSTXVREGEN 0x0010 +#define MASK_D2M_HSTXVREGEN 0x0008 +#define MASK_D1M_HSTXVREGEN 0x0004 +#define MASK_D0M_HSTXVREGEN 0x0002 +#define MASK_CLM_HSTXVREGEN 0x0001 + + +#define TXOPTIONCNTRL 0x0238 +#define MASK_CONTCLKMODE 0x00000001 + +#define CSI_CONTROL 0x040C +#define MASK_CSI_MODE 0x8000 +#define MASK_HTXTOEN 0x0400 +#define MASK_TXHSMD 0x0080 +#define MASK_HSCKMD 0x0020 +#define MASK_NOL 0x0006 +#define MASK_NOL_1 0x0000 +#define MASK_NOL_2 0x0002 +#define MASK_NOL_3 0x0004 +#define MASK_NOL_4 0x0006 +#define MASK_EOTDIS 0x0001 + +#define CSI_INT 0x0414 +#define MASK_INTHLT 0x00000008 +#define MASK_INTER 0x00000004 + +#define CSI_INT_ENA 0x0418 +#define MASK_IENHLT 0x00000008 +#define MASK_IENER 0x00000004 + +#define CSI_ERR 0x044C +#define MASK_INER 0x00000200 +#define MASK_WCER 0x00000100 +#define MASK_QUNK 0x00000010 +#define MASK_TXBRK 0x00000002 + +#define CSI_ERR_INTENA 0x0450 +#define CSI_ERR_HALT 0x0454 + +#define CSI_CONFW 0x0500 +#define MASK_MODE 0xe0000000 +#define MASK_MODE_SET 0xa0000000 +#define MASK_MODE_CLEAR 0xc0000000 +#define MASK_ADDRESS 0x1f000000 +#define MASK_ADDRESS_CSI_CONTROL 0x03000000 +#define MASK_ADDRESS_CSI_INT_ENA 0x06000000 +#define MASK_ADDRESS_CSI_ERR_INTENA 0x14000000 +#define MASK_ADDRESS_CSI_ERR_HALT 0x15000000 +#define MASK_DATA 0x0000ffff + +#define CSI_INT_CLR 0x050C +#define MASK_ICRER 0x00000004 + +#define CSI_START 0x0518 +#define MASK_STRT 0x00000001 + +/* *** CEC (32 bit) *** */ +#define CECHCLK 0x0028 /* 16 bits */ +#define MASK_CECHCLK (0x7ff << 0) + +#define CECLCLK 0x002a /* 16 bits */ +#define MASK_CECLCLK (0x7ff << 0) + +#define CECEN 0x0600 +#define MASK_CECEN 0x0001 + +#define CECADD 0x0604 +#define CECRST 0x0608 +#define MASK_CECRESET 0x0001 + +#define CECREN 0x060c +#define MASK_CECREN 0x0001 + +#define CECRCTL1 0x0614 +#define MASK_CECACKDIS (1 << 24) +#define MASK_CECHNC (3 << 20) +#define MASK_CECLNC (7 << 16) +#define MASK_CECMIN (7 << 12) +#define MASK_CECMAX (7 << 8) +#define MASK_CECDAT (7 << 4) +#define MASK_CECTOUT (3 << 2) +#define MASK_CECRIHLD (1 << 1) +#define MASK_CECOTH (1 << 0) + +#define CECRCTL2 0x0618 +#define MASK_CECSWAV3 (7 << 12) +#define MASK_CECSWAV2 (7 << 8) +#define MASK_CECSWAV1 (7 << 4) +#define MASK_CECSWAV0 (7 << 0) + +#define CECRCTL3 0x061c +#define MASK_CECWAV3 (7 << 20) +#define MASK_CECWAV2 (7 << 16) +#define MASK_CECWAV1 (7 << 12) +#define MASK_CECWAV0 (7 << 8) +#define MASK_CECACKEI (1 << 4) +#define MASK_CECMINEI (1 << 3) +#define MASK_CECMAXEI (1 << 2) +#define MASK_CECRSTEI (1 << 1) +#define MASK_CECWAVEI (1 << 0) + +#define CECTEN 0x0620 +#define MASK_CECTBUSY (1 << 1) +#define MASK_CECTEN (1 << 0) + +#define CECTCTL 0x0628 +#define MASK_CECSTRS (7 << 20) +#define MASK_CECSPRD (7 << 16) +#define MASK_CECDTRS (7 << 12) +#define MASK_CECDPRD (15 << 8) +#define MASK_CECBRD (1 << 4) +#define MASK_CECFREE (15 << 0) + +#define CECRSTAT 0x062c +#define MASK_CECRIWA (1 << 6) +#define MASK_CECRIOR (1 << 5) +#define MASK_CECRIACK (1 << 4) +#define MASK_CECRIMIN (1 << 3) +#define MASK_CECRIMAX (1 << 2) +#define MASK_CECRISTA (1 << 1) +#define MASK_CECRIEND (1 << 0) + +#define CECTSTAT 0x0630 +#define MASK_CECTIUR (1 << 4) +#define MASK_CECTIACK (1 << 3) +#define MASK_CECTIAL (1 << 2) +#define MASK_CECTIEND (1 << 1) + +#define CECRBUF1 0x0634 +#define MASK_CECRACK (1 << 9) +#define MASK_CECEOM (1 << 8) +#define MASK_CECRBYTE (0xff << 0) + +#define CECTBUF1 0x0674 +#define MASK_CECTEOM (1 << 8) +#define MASK_CECTBYTE (0xff << 0) + +#define CECRCTR 0x06b4 +#define MASK_CECRCTR (0x1f << 0) + +#define CECIMSK 0x06c0 +#define MASK_CECTIM (1 << 1) +#define MASK_CECRIM (1 << 0) + +#define CECICLR 0x06cc +#define MASK_CECTICLR (1 << 1) +#define MASK_CECRICLR (1 << 0) + + +#define HDMI_INT0 0x8500 +#define MASK_I_KEY 0x80 +#define MASK_I_MISC 0x02 +#define MASK_I_PHYERR 0x01 + +#define HDMI_INT1 0x8501 +#define MASK_I_GBD 0x80 +#define MASK_I_HDCP 0x40 +#define MASK_I_ERR 0x20 +#define MASK_I_AUD 0x10 +#define MASK_I_CBIT 0x08 +#define MASK_I_PACKET 0x04 +#define MASK_I_CLK 0x02 +#define MASK_I_SYS 0x01 + +#define SYS_INT 0x8502 +#define MASK_I_ACR_CTS 0x80 +#define MASK_I_ACRN 0x40 +#define MASK_I_DVI 0x20 +#define MASK_I_HDMI 0x10 +#define MASK_I_NOPMBDET 0x08 +#define MASK_I_DPMBDET 0x04 +#define MASK_I_TMDS 0x02 +#define MASK_I_DDC 0x01 + +#define CLK_INT 0x8503 +#define MASK_I_OUT_H_CHG 0x40 +#define MASK_I_IN_DE_CHG 0x20 +#define MASK_I_IN_HV_CHG 0x10 +#define MASK_I_DC_CHG 0x08 +#define MASK_I_PXCLK_CHG 0x04 +#define MASK_I_PHYCLK_CHG 0x02 +#define MASK_I_TMDSCLK_CHG 0x01 + +#define CBIT_INT 0x8505 +#define MASK_I_AF_LOCK 0x80 +#define MASK_I_AF_UNLOCK 0x40 +#define MASK_I_CBIT_FS 0x02 + +#define AUDIO_INT 0x8506 + +#define ERR_INT 0x8507 +#define MASK_I_EESS_ERR 0x80 + +#define HDCP_INT 0x8508 +#define MASK_I_AVM_SET 0x80 +#define MASK_I_AVM_CLR 0x40 +#define MASK_I_LINKERR 0x20 +#define MASK_I_SHA_END 0x10 +#define MASK_I_R0_END 0x08 +#define MASK_I_KM_END 0x04 +#define MASK_I_AKSV_END 0x02 +#define MASK_I_AN_END 0x01 + +#define MISC_INT 0x850B +#define MASK_I_AS_LAYOUT 0x10 +#define MASK_I_NO_SPD 0x08 +#define MASK_I_NO_VS 0x03 +#define MASK_I_SYNC_CHG 0x02 +#define MASK_I_AUDIO_MUTE 0x01 + +#define KEY_INT 0x850F + +#define SYS_INTM 0x8512 +#define MASK_M_ACR_CTS 0x80 +#define MASK_M_ACR_N 0x40 +#define MASK_M_DVI_DET 0x20 +#define MASK_M_HDMI_DET 0x10 +#define MASK_M_NOPMBDET 0x08 +#define MASK_M_BPMBDET 0x04 +#define MASK_M_TMDS 0x02 +#define MASK_M_DDC 0x01 + +#define CLK_INTM 0x8513 +#define MASK_M_OUT_H_CHG 0x40 +#define MASK_M_IN_DE_CHG 0x20 +#define MASK_M_IN_HV_CHG 0x10 +#define MASK_M_DC_CHG 0x08 +#define MASK_M_PXCLK_CHG 0x04 +#define MASK_M_PHYCLK_CHG 0x02 +#define MASK_M_TMDS_CHG 0x01 + +#define PACKET_INTM 0x8514 + +#define CBIT_INTM 0x8515 +#define MASK_M_AF_LOCK 0x80 +#define MASK_M_AF_UNLOCK 0x40 +#define MASK_M_CBIT_FS 0x02 + +#define AUDIO_INTM 0x8516 +#define MASK_M_BUFINIT_END 0x01 + +#define ERR_INTM 0x8517 +#define MASK_M_EESS_ERR 0x80 + +#define HDCP_INTM 0x8518 +#define MASK_M_AVM_SET 0x80 +#define MASK_M_AVM_CLR 0x40 +#define MASK_M_LINKERR 0x20 +#define MASK_M_SHA_END 0x10 +#define MASK_M_R0_END 0x08 +#define MASK_M_KM_END 0x04 +#define MASK_M_AKSV_END 0x02 +#define MASK_M_AN_END 0x01 + +#define MISC_INTM 0x851B +#define MASK_M_AS_LAYOUT 0x10 +#define MASK_M_NO_SPD 0x08 +#define MASK_M_NO_VS 0x03 +#define MASK_M_SYNC_CHG 0x02 +#define MASK_M_AUDIO_MUTE 0x01 + +#define KEY_INTM 0x851F + +#define SYS_STATUS 0x8520 +#define MASK_S_SYNC 0x80 +#define MASK_S_AVMUTE 0x40 +#define MASK_S_HDCP 0x20 +#define MASK_S_HDMI 0x10 +#define MASK_S_PHY_SCDT 0x08 +#define MASK_S_PHY_PLL 0x04 +#define MASK_S_TMDS 0x02 +#define MASK_S_DDC5V 0x01 + +#define CSI_STATUS 0x0410 +#define MASK_S_WSYNC 0x0400 +#define MASK_S_TXACT 0x0200 +#define MASK_S_RXACT 0x0100 +#define MASK_S_HLT 0x0001 + +#define VI_STATUS1 0x8522 +#define MASK_S_V_GBD 0x08 +#define MASK_S_DEEPCOLOR 0x0c +#define MASK_S_V_422 0x02 +#define MASK_S_V_INTERLACE 0x01 + +#define AU_STATUS0 0x8523 +#define MASK_S_A_SAMPLE 0x01 + +#define VI_STATUS3 0x8528 +#define MASK_S_V_COLOR 0x1e +#define MASK_LIMITED 0x01 + +#define PHY_CTL0 0x8531 +#define MASK_PHY_SYSCLK_IND 0x02 +#define MASK_PHY_CTL 0x01 + + +#define PHY_CTL1 0x8532 /* Not in REF_01 */ +#define MASK_PHY_AUTO_RST1 0xf0 +#define MASK_PHY_AUTO_RST1_OFF 0x00 +#define SET_PHY_AUTO_RST1_US(us) ((((us) / 200) << 4) & \ + MASK_PHY_AUTO_RST1) +#define MASK_FREQ_RANGE_MODE 0x0f +#define SET_FREQ_RANGE_MODE_CYCLES(cycles) (((cycles) - 1) & \ + MASK_FREQ_RANGE_MODE) + +#define PHY_CTL2 0x8533 /* Not in REF_01 */ +#define MASK_PHY_AUTO_RST4 0x04 +#define MASK_PHY_AUTO_RST3 0x02 +#define MASK_PHY_AUTO_RST2 0x01 +#define MASK_PHY_AUTO_RSTn (MASK_PHY_AUTO_RST4 | \ + MASK_PHY_AUTO_RST3 | \ + MASK_PHY_AUTO_RST2) + +#define PHY_EN 0x8534 +#define MASK_ENABLE_PHY 0x01 + +#define PHY_RST 0x8535 +#define MASK_RESET_CTRL 0x01 /* Reset active low */ + +#define PHY_BIAS 0x8536 /* Not in REF_01 */ + +#define PHY_CSQ 0x853F /* Not in REF_01 */ +#define MASK_CSQ_CNT 0x0f +#define SET_CSQ_CNT_LEVEL(n) (n & MASK_CSQ_CNT) + +#define SYS_FREQ0 0x8540 +#define SYS_FREQ1 0x8541 + +#define SYS_CLK 0x8542 /* Not in REF_01 */ +#define MASK_CLK_DIFF 0x0C +#define MASK_CLK_DIV 0x03 + +#define DDC_CTL 0x8543 +#define MASK_DDC_ACK_POL 0x08 +#define MASK_DDC_ACTION 0x04 +#define MASK_DDC5V_MODE 0x03 +#define MASK_DDC5V_MODE_0MS 0x00 +#define MASK_DDC5V_MODE_50MS 0x01 +#define MASK_DDC5V_MODE_100MS 0x02 +#define MASK_DDC5V_MODE_200MS 0x03 + +#define HPD_CTL 0x8544 +#define MASK_HPD_CTL0 0x10 +#define MASK_HPD_OUT0 0x01 + +#define ANA_CTL 0x8545 +#define MASK_APPL_PCSX 0x30 +#define MASK_APPL_PCSX_HIZ 0x00 +#define MASK_APPL_PCSX_L_FIX 0x10 +#define MASK_APPL_PCSX_H_FIX 0x20 +#define MASK_APPL_PCSX_NORMAL 0x30 +#define MASK_ANALOG_ON 0x01 + +#define AVM_CTL 0x8546 + +#define INIT_END 0x854A +#define MASK_INIT_END 0x01 + +#define HDMI_DET 0x8552 /* Not in REF_01 */ +#define MASK_HDMI_DET_MOD1 0x80 +#define MASK_HDMI_DET_MOD0 0x40 +#define MASK_HDMI_DET_V 0x30 +#define MASK_HDMI_DET_V_SYNC 0x00 +#define MASK_HDMI_DET_V_ASYNC_25MS 0x10 +#define MASK_HDMI_DET_V_ASYNC_50MS 0x20 +#define MASK_HDMI_DET_V_ASYNC_100MS 0x30 +#define MASK_HDMI_DET_NUM 0x0f + +#define HDCP_MODE 0x8560 +#define MASK_MODE_RST_TN 0x20 +#define MASK_LINE_REKEY 0x10 +#define MASK_AUTO_CLR 0x04 +#define MASK_MANUAL_AUTHENTICATION 0x02 /* Not in REF_01 */ + +#define HDCP_REG1 0x8563 /* Not in REF_01 */ +#define MASK_AUTH_UNAUTH_SEL 0x70 +#define MASK_AUTH_UNAUTH_SEL_12_FRAMES 0x70 +#define MASK_AUTH_UNAUTH_SEL_8_FRAMES 0x60 +#define MASK_AUTH_UNAUTH_SEL_4_FRAMES 0x50 +#define MASK_AUTH_UNAUTH_SEL_2_FRAMES 0x40 +#define MASK_AUTH_UNAUTH_SEL_64_FRAMES 0x30 +#define MASK_AUTH_UNAUTH_SEL_32_FRAMES 0x20 +#define MASK_AUTH_UNAUTH_SEL_16_FRAMES 0x10 +#define MASK_AUTH_UNAUTH_SEL_ONCE 0x00 +#define MASK_AUTH_UNAUTH 0x01 +#define MASK_AUTH_UNAUTH_AUTO 0x01 + +#define HDCP_REG2 0x8564 /* Not in REF_01 */ +#define MASK_AUTO_P3_RESET 0x0F +#define SET_AUTO_P3_RESET_FRAMES(n) (n & MASK_AUTO_P3_RESET) +#define MASK_AUTO_P3_RESET_OFF 0x00 + +#define VI_MODE 0x8570 +#define MASK_RGB_DVI 0x08 /* Not in REF_01 */ + +#define VOUT_SET2 0x8573 +#define MASK_SEL422 0x80 +#define MASK_VOUT_422FIL_100 0x40 +#define MASK_VOUTCOLORMODE 0x03 +#define MASK_VOUTCOLORMODE_THROUGH 0x00 +#define MASK_VOUTCOLORMODE_AUTO 0x01 +#define MASK_VOUTCOLORMODE_MANUAL 0x03 + +#define VOUT_SET3 0x8574 +#define MASK_VOUT_EXTCNT 0x08 + +#define VI_REP 0x8576 +#define MASK_VOUT_COLOR_SEL 0xe0 +#define MASK_VOUT_COLOR_RGB_FULL 0x00 +#define MASK_VOUT_COLOR_RGB_LIMITED 0x20 +#define MASK_VOUT_COLOR_601_YCBCR_FULL 0x40 +#define MASK_VOUT_COLOR_601_YCBCR_LIMITED 0x60 +#define MASK_VOUT_COLOR_709_YCBCR_FULL 0x80 +#define MASK_VOUT_COLOR_709_YCBCR_LIMITED 0xa0 +#define MASK_VOUT_COLOR_FULL_TO_LIMITED 0xc0 +#define MASK_VOUT_COLOR_LIMITED_TO_FULL 0xe0 +#define MASK_IN_REP_HEN 0x10 +#define MASK_IN_REP 0x0f + +#define VI_MUTE 0x857F +#define MASK_AUTO_MUTE 0xc0 +#define MASK_VI_MUTE 0x10 + +#define DE_WIDTH_H_LO 0x8582 /* Not in REF_01 */ +#define DE_WIDTH_H_HI 0x8583 /* Not in REF_01 */ +#define DE_WIDTH_V_LO 0x8588 /* Not in REF_01 */ +#define DE_WIDTH_V_HI 0x8589 /* Not in REF_01 */ +#define H_SIZE_LO 0x858A /* Not in REF_01 */ +#define H_SIZE_HI 0x858B /* Not in REF_01 */ +#define V_SIZE_LO 0x858C /* Not in REF_01 */ +#define V_SIZE_HI 0x858D /* Not in REF_01 */ +#define FV_CNT_LO 0x85A1 /* Not in REF_01 */ +#define FV_CNT_HI 0x85A2 /* Not in REF_01 */ + +#define FH_MIN0 0x85AA /* Not in REF_01 */ +#define FH_MIN1 0x85AB /* Not in REF_01 */ +#define FH_MAX0 0x85AC /* Not in REF_01 */ +#define FH_MAX1 0x85AD /* Not in REF_01 */ + +#define HV_RST 0x85AF /* Not in REF_01 */ +#define MASK_H_PI_RST 0x20 +#define MASK_V_PI_RST 0x10 + +#define EDID_MODE 0x85C7 +#define MASK_EDID_SPEED 0x40 +#define MASK_EDID_MODE 0x03 +#define MASK_EDID_MODE_DISABLE 0x00 +#define MASK_EDID_MODE_DDC2B 0x01 +#define MASK_EDID_MODE_E_DDC 0x02 + +#define EDID_LEN1 0x85CA +#define EDID_LEN2 0x85CB + +#define HDCP_REG3 0x85D1 /* Not in REF_01 */ +#define KEY_RD_CMD 0x01 + +#define FORCE_MUTE 0x8600 +#define MASK_FORCE_AMUTE 0x10 +#define MASK_FORCE_DMUTE 0x01 + +#define CMD_AUD 0x8601 +#define MASK_CMD_BUFINIT 0x04 +#define MASK_CMD_LOCKDET 0x02 +#define MASK_CMD_MUTE 0x01 + +#define AUTO_CMD0 0x8602 +#define MASK_AUTO_MUTE7 0x80 +#define MASK_AUTO_MUTE6 0x40 +#define MASK_AUTO_MUTE5 0x20 +#define MASK_AUTO_MUTE4 0x10 +#define MASK_AUTO_MUTE3 0x08 +#define MASK_AUTO_MUTE2 0x04 +#define MASK_AUTO_MUTE1 0x02 +#define MASK_AUTO_MUTE0 0x01 + +#define AUTO_CMD1 0x8603 +#define MASK_AUTO_MUTE10 0x04 +#define MASK_AUTO_MUTE9 0x02 +#define MASK_AUTO_MUTE8 0x01 + +#define AUTO_CMD2 0x8604 +#define MASK_AUTO_PLAY3 0x08 +#define MASK_AUTO_PLAY2 0x04 + +#define BUFINIT_START 0x8606 +#define SET_BUFINIT_START_MS(milliseconds) ((milliseconds) / 100) + +#define FS_MUTE 0x8607 +#define MASK_FS_ELSE_MUTE 0x80 +#define MASK_FS22_MUTE 0x40 +#define MASK_FS24_MUTE 0x20 +#define MASK_FS88_MUTE 0x10 +#define MASK_FS96_MUTE 0x08 +#define MASK_FS176_MUTE 0x04 +#define MASK_FS192_MUTE 0x02 +#define MASK_FS_NO_MUTE 0x01 + +#define FS_IMODE 0x8620 +#define MASK_NLPCM_HMODE 0x40 +#define MASK_NLPCM_SMODE 0x20 +#define MASK_NLPCM_IMODE 0x10 +#define MASK_FS_HMODE 0x08 +#define MASK_FS_AMODE 0x04 +#define MASK_FS_SMODE 0x02 +#define MASK_FS_IMODE 0x01 + +#define FS_SET 0x8621 +#define MASK_FS 0x0f + +#define LOCKDET_REF0 0x8630 +#define LOCKDET_REF1 0x8631 +#define LOCKDET_REF2 0x8632 + +#define ACR_MODE 0x8640 +#define MASK_ACR_LOAD 0x10 +#define MASK_N_MODE 0x04 +#define MASK_CTS_MODE 0x01 + +#define ACR_MDF0 0x8641 +#define MASK_ACR_L2MDF 0x70 +#define MASK_ACR_L2MDF_0_PPM 0x00 +#define MASK_ACR_L2MDF_61_PPM 0x10 +#define MASK_ACR_L2MDF_122_PPM 0x20 +#define MASK_ACR_L2MDF_244_PPM 0x30 +#define MASK_ACR_L2MDF_488_PPM 0x40 +#define MASK_ACR_L2MDF_976_PPM 0x50 +#define MASK_ACR_L2MDF_1976_PPM 0x60 +#define MASK_ACR_L2MDF_3906_PPM 0x70 +#define MASK_ACR_L1MDF 0x07 +#define MASK_ACR_L1MDF_0_PPM 0x00 +#define MASK_ACR_L1MDF_61_PPM 0x01 +#define MASK_ACR_L1MDF_122_PPM 0x02 +#define MASK_ACR_L1MDF_244_PPM 0x03 +#define MASK_ACR_L1MDF_488_PPM 0x04 +#define MASK_ACR_L1MDF_976_PPM 0x05 +#define MASK_ACR_L1MDF_1976_PPM 0x06 +#define MASK_ACR_L1MDF_3906_PPM 0x07 + +#define ACR_MDF1 0x8642 +#define MASK_ACR_L3MDF 0x07 +#define MASK_ACR_L3MDF_0_PPM 0x00 +#define MASK_ACR_L3MDF_61_PPM 0x01 +#define MASK_ACR_L3MDF_122_PPM 0x02 +#define MASK_ACR_L3MDF_244_PPM 0x03 +#define MASK_ACR_L3MDF_488_PPM 0x04 +#define MASK_ACR_L3MDF_976_PPM 0x05 +#define MASK_ACR_L3MDF_1976_PPM 0x06 +#define MASK_ACR_L3MDF_3906_PPM 0x07 + +#define SDO_MODE1 0x8652 +#define MASK_SDO_BIT_LENG 0x70 +#define MASK_SDO_FMT 0x03 +#define MASK_SDO_FMT_RIGHT 0x00 +#define MASK_SDO_FMT_LEFT 0x01 +#define MASK_SDO_FMT_I2S 0x02 + +#define DIV_MODE 0x8665 /* Not in REF_01 */ +#define MASK_DIV_DLY 0xf0 +#define SET_DIV_DLY_MS(milliseconds) ((((milliseconds) / 100) << 4) & \ + MASK_DIV_DLY) +#define MASK_DIV_MODE 0x01 + +#define NCO_F0_MOD 0x8670 +#define MASK_NCO_F0_MOD 0x03 +#define MASK_NCO_F0_MOD_42MHZ 0x00 +#define MASK_NCO_F0_MOD_27MHZ 0x01 + +#define PK_INT_MODE 0x8709 +#define MASK_ISRC2_INT_MODE 0x80 +#define MASK_ISRC_INT_MODE 0x40 +#define MASK_ACP_INT_MODE 0x20 +#define MASK_VS_INT_MODE 0x10 +#define MASK_SPD_INT_MODE 0x08 +#define MASK_MS_INT_MODE 0x04 +#define MASK_AUD_INT_MODE 0x02 +#define MASK_AVI_INT_MODE 0x01 + +#define NO_PKT_LIMIT 0x870B +#define MASK_NO_ACP_LIMIT 0xf0 +#define SET_NO_ACP_LIMIT_MS(milliseconds) ((((milliseconds) / 80) << 4) & \ + MASK_NO_ACP_LIMIT) +#define MASK_NO_AVI_LIMIT 0x0f +#define SET_NO_AVI_LIMIT_MS(milliseconds) (((milliseconds) / 80) & \ + MASK_NO_AVI_LIMIT) + +#define NO_PKT_CLR 0x870C +#define MASK_NO_VS_CLR 0x40 +#define MASK_NO_SPD_CLR 0x20 +#define MASK_NO_ACP_CLR 0x10 +#define MASK_NO_AVI_CLR1 0x02 +#define MASK_NO_AVI_CLR0 0x01 + +#define ERR_PK_LIMIT 0x870D +#define NO_PKT_LIMIT2 0x870E +#define PK_AVI_0HEAD 0x8710 +#define PK_AVI_1HEAD 0x8711 +#define PK_AVI_2HEAD 0x8712 +#define PK_AVI_0BYTE 0x8713 +#define PK_AVI_1BYTE 0x8714 +#define PK_AVI_2BYTE 0x8715 +#define PK_AVI_3BYTE 0x8716 +#define PK_AVI_4BYTE 0x8717 +#define PK_AVI_5BYTE 0x8718 +#define PK_AVI_6BYTE 0x8719 +#define PK_AVI_7BYTE 0x871A +#define PK_AVI_8BYTE 0x871B +#define PK_AVI_9BYTE 0x871C +#define PK_AVI_10BYTE 0x871D +#define PK_AVI_11BYTE 0x871E +#define PK_AVI_12BYTE 0x871F +#define PK_AVI_13BYTE 0x8720 +#define PK_AVI_14BYTE 0x8721 +#define PK_AVI_15BYTE 0x8722 +#define PK_AVI_16BYTE 0x8723 + +#define BKSV 0x8800 + +#define BCAPS 0x8840 +#define MASK_HDMI_RSVD 0x80 +#define MASK_REPEATER 0x40 +#define MASK_READY 0x20 +#define MASK_FASTI2C 0x10 +#define MASK_1_1_FEA 0x02 +#define MASK_FAST_REAU 0x01 + +#define BSTATUS1 0x8842 +#define MASK_MAX_EXCED 0x08 + +#define EDID_RAM 0x8C00 +#define NO_GDB_LIMIT 0x9007 + +#endif diff --git a/drivers/media/i2c/tda1997x.c b/drivers/media/i2c/tda1997x.c new file mode 100644 index 000000000..847633096 --- /dev/null +++ b/drivers/media/i2c/tda1997x.c @@ -0,0 +1,2832 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Gateworks Corporation + */ +#include <linux/delay.h> +#include <linux/hdmi.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> +#include <linux/v4l2-dv-timings.h> +#include <linux/videodev2.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/i2c/tda1997x.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <dt-bindings/media/tda1997x.h> + +#include "tda1997x_regs.h" + +#define TDA1997X_MBUS_CODES 5 + +/* debug level */ +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +/* Audio formats */ +static const char * const audtype_names[] = { + "PCM", /* PCM Samples */ + "HBR", /* High Bit Rate Audio */ + "OBA", /* One-Bit Audio */ + "DST" /* Direct Stream Transfer */ +}; + +/* Audio output port formats */ +enum audfmt_types { + AUDFMT_TYPE_DISABLED = 0, + AUDFMT_TYPE_I2S, + AUDFMT_TYPE_SPDIF, +}; +static const char * const audfmt_names[] = { + "Disabled", + "I2S", + "SPDIF", +}; + +/* Video input formats */ +static const char * const hdmi_colorspace_names[] = { + "RGB", "YUV422", "YUV444", "YUV420", "", "", "", "", +}; +static const char * const hdmi_colorimetry_names[] = { + "", "ITU601", "ITU709", "Extended", +}; +static const char * const v4l2_quantization_names[] = { + "Default", + "Full Range (0-255)", + "Limited Range (16-235)", +}; + +/* Video output port formats */ +static const char * const vidfmt_names[] = { + "RGB444/YUV444", /* RGB/YUV444 16bit data bus, 8bpp */ + "YUV422 semi-planar", /* YUV422 16bit data base, 8bpp */ + "YUV422 CCIR656", /* BT656 (YUV 8bpp 2 clock per pixel) */ + "Invalid", +}; + +/* + * Colorspace conversion matrices + */ +struct color_matrix_coefs { + const char *name; + /* Input offsets */ + s16 offint1; + s16 offint2; + s16 offint3; + /* Coeficients */ + s16 p11coef; + s16 p12coef; + s16 p13coef; + s16 p21coef; + s16 p22coef; + s16 p23coef; + s16 p31coef; + s16 p32coef; + s16 p33coef; + /* Output offsets */ + s16 offout1; + s16 offout2; + s16 offout3; +}; + +enum { + ITU709_RGBFULL, + ITU601_RGBFULL, + RGBLIMITED_RGBFULL, + RGBLIMITED_ITU601, + RGBLIMITED_ITU709, + RGBFULL_ITU601, + RGBFULL_ITU709, +}; + +/* NB: 4096 is 1.0 using fixed point numbers */ +static const struct color_matrix_coefs conv_matrix[] = { + { + "YUV709 -> RGB full", + -256, -2048, -2048, + 4769, -2183, -873, + 4769, 7343, 0, + 4769, 0, 8652, + 0, 0, 0, + }, + { + "YUV601 -> RGB full", + -256, -2048, -2048, + 4769, -3330, -1602, + 4769, 6538, 0, + 4769, 0, 8264, + 256, 256, 256, + }, + { + "RGB limited -> RGB full", + -256, -256, -256, + 0, 4769, 0, + 0, 0, 4769, + 4769, 0, 0, + 0, 0, 0, + }, + { + "RGB limited -> ITU601", + -256, -256, -256, + 2404, 1225, 467, + -1754, 2095, -341, + -1388, -707, 2095, + 256, 2048, 2048, + }, + { + "RGB limited -> ITU709", + -256, -256, -256, + 2918, 867, 295, + -1894, 2087, -190, + -1607, -477, 2087, + 256, 2048, 2048, + }, + { + "RGB full -> ITU601", + 0, 0, 0, + 2065, 1052, 401, + -1506, 1799, -293, + -1192, -607, 1799, + 256, 2048, 2048, + }, + { + "RGB full -> ITU709", + 0, 0, 0, + 2506, 745, 253, + -1627, 1792, -163, + -1380, -410, 1792, + 256, 2048, 2048, + }, +}; + +static const struct v4l2_dv_timings_cap tda1997x_dv_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + + V4L2_INIT_BT_TIMINGS( + 640, 1920, /* min/max width */ + 350, 1200, /* min/max height */ + 13000000, 165000000, /* min/max pixelclock */ + /* standards */ + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + /* capabilities */ + V4L2_DV_BT_CAP_INTERLACED | V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM + ) +}; + +/* regulator supplies */ +static const char * const tda1997x_supply_name[] = { + "DOVDD", /* Digital I/O supply */ + "DVDD", /* Digital Core supply */ + "AVDD", /* Analog supply */ +}; + +#define TDA1997X_NUM_SUPPLIES ARRAY_SIZE(tda1997x_supply_name) + +enum tda1997x_type { + TDA19971, + TDA19973, +}; + +enum tda1997x_hdmi_pads { + TDA1997X_PAD_SOURCE, + TDA1997X_NUM_PADS, +}; + +struct tda1997x_chip_info { + enum tda1997x_type type; + const char *name; +}; + +struct tda1997x_state { + const struct tda1997x_chip_info *info; + struct tda1997x_platform_data pdata; + struct i2c_client *client; + struct i2c_client *client_cec; + struct v4l2_subdev sd; + struct regulator_bulk_data supplies[TDA1997X_NUM_SUPPLIES]; + struct media_pad pads[TDA1997X_NUM_PADS]; + struct mutex lock; + struct mutex page_lock; + char page; + + /* detected info from chip */ + int chip_revision; + char port_30bit; + char output_2p5; + char tmdsb_clk; + char tmdsb_soc; + + /* status info */ + char hdmi_status; + char mptrw_in_progress; + char activity_status; + char input_detect[2]; + + /* video */ + struct hdmi_avi_infoframe avi_infoframe; + struct v4l2_hdmi_colorimetry colorimetry; + u32 rgb_quantization_range; + struct v4l2_dv_timings timings; + int fps; + const struct color_matrix_coefs *conv; + u32 mbus_codes[TDA1997X_MBUS_CODES]; /* available modes */ + u32 mbus_code; /* current mode */ + u8 vid_fmt; + + /* controls */ + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *detect_tx_5v_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; + + /* audio */ + u8 audio_ch_alloc; + int audio_samplerate; + int audio_channels; + int audio_samplesize; + int audio_type; + struct mutex audio_lock; + struct snd_pcm_substream *audio_stream; + + /* EDID */ + struct { + u8 edid[256]; + u32 present; + unsigned int blocks; + } edid; + struct delayed_work delayed_work_enable_hpd; +}; + +static const struct v4l2_event tda1997x_ev_fmt = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, +}; + +static const struct tda1997x_chip_info tda1997x_chip_info[] = { + [TDA19971] = { + .type = TDA19971, + .name = "tda19971", + }, + [TDA19973] = { + .type = TDA19973, + .name = "tda19973", + }, +}; + +static inline struct tda1997x_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tda1997x_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tda1997x_state, hdl)->sd; +} + +static int tda1997x_cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct tda1997x_state *state = to_state(sd); + int val; + + val = i2c_smbus_read_byte_data(state->client_cec, reg); + if (val < 0) { + v4l_err(state->client, "read reg error: reg=%2x\n", reg); + val = -1; + } + + return val; +} + +static int tda1997x_cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct tda1997x_state *state = to_state(sd); + int ret = 0; + + ret = i2c_smbus_write_byte_data(state->client_cec, reg, val); + if (ret < 0) { + v4l_err(state->client, "write reg error:reg=%2x,val=%2x\n", + reg, val); + ret = -1; + } + + return ret; +} + +/* ----------------------------------------------------------------------------- + * I2C transfer + */ + +static int tda1997x_setpage(struct v4l2_subdev *sd, u8 page) +{ + struct tda1997x_state *state = to_state(sd); + int ret; + + if (state->page != page) { + ret = i2c_smbus_write_byte_data(state->client, + REG_CURPAGE_00H, page); + if (ret < 0) { + v4l_err(state->client, + "write reg error:reg=%2x,val=%2x\n", + REG_CURPAGE_00H, page); + return ret; + } + state->page = page; + } + return 0; +} + +static inline int io_read(struct v4l2_subdev *sd, u16 reg) +{ + struct tda1997x_state *state = to_state(sd); + int val; + + mutex_lock(&state->page_lock); + if (tda1997x_setpage(sd, reg >> 8)) { + val = -1; + goto out; + } + + val = i2c_smbus_read_byte_data(state->client, reg&0xff); + if (val < 0) { + v4l_err(state->client, "read reg error: reg=%2x\n", reg & 0xff); + val = -1; + goto out; + } + +out: + mutex_unlock(&state->page_lock); + return val; +} + +static inline long io_read16(struct v4l2_subdev *sd, u16 reg) +{ + int val; + long lval = 0; + + val = io_read(sd, reg); + if (val < 0) + return val; + lval |= (val << 8); + val = io_read(sd, reg + 1); + if (val < 0) + return val; + lval |= val; + + return lval; +} + +static inline long io_read24(struct v4l2_subdev *sd, u16 reg) +{ + int val; + long lval = 0; + + val = io_read(sd, reg); + if (val < 0) + return val; + lval |= (val << 16); + val = io_read(sd, reg + 1); + if (val < 0) + return val; + lval |= (val << 8); + val = io_read(sd, reg + 2); + if (val < 0) + return val; + lval |= val; + + return lval; +} + +static unsigned int io_readn(struct v4l2_subdev *sd, u16 reg, u8 len, u8 *data) +{ + int i; + int sz = 0; + int val; + + for (i = 0; i < len; i++) { + val = io_read(sd, reg + i); + if (val < 0) + break; + data[i] = val; + sz++; + } + + return sz; +} + +static int io_write(struct v4l2_subdev *sd, u16 reg, u8 val) +{ + struct tda1997x_state *state = to_state(sd); + s32 ret = 0; + + mutex_lock(&state->page_lock); + if (tda1997x_setpage(sd, reg >> 8)) { + ret = -1; + goto out; + } + + ret = i2c_smbus_write_byte_data(state->client, reg & 0xff, val); + if (ret < 0) { + v4l_err(state->client, "write reg error:reg=%2x,val=%2x\n", + reg&0xff, val); + ret = -1; + goto out; + } + +out: + mutex_unlock(&state->page_lock); + return ret; +} + +static int io_write16(struct v4l2_subdev *sd, u16 reg, u16 val) +{ + int ret; + + ret = io_write(sd, reg, (val >> 8) & 0xff); + if (ret < 0) + return ret; + ret = io_write(sd, reg + 1, val & 0xff); + if (ret < 0) + return ret; + return 0; +} + +static int io_write24(struct v4l2_subdev *sd, u16 reg, u32 val) +{ + int ret; + + ret = io_write(sd, reg, (val >> 16) & 0xff); + if (ret < 0) + return ret; + ret = io_write(sd, reg + 1, (val >> 8) & 0xff); + if (ret < 0) + return ret; + ret = io_write(sd, reg + 2, val & 0xff); + if (ret < 0) + return ret; + return 0; +} + +/* ----------------------------------------------------------------------------- + * Hotplug + */ + +enum hpd_mode { + HPD_LOW_BP, /* HPD low and pulse of at least 100ms */ + HPD_LOW_OTHER, /* HPD low and pulse of at least 100ms */ + HPD_HIGH_BP, /* HIGH */ + HPD_HIGH_OTHER, + HPD_PULSE, /* HPD low pulse */ +}; + +/* manual HPD (Hot Plug Detect) control */ +static int tda1997x_manual_hpd(struct v4l2_subdev *sd, enum hpd_mode mode) +{ + u8 hpd_auto, hpd_pwr, hpd_man; + + hpd_auto = io_read(sd, REG_HPD_AUTO_CTRL); + hpd_pwr = io_read(sd, REG_HPD_POWER); + hpd_man = io_read(sd, REG_HPD_MAN_CTRL); + + /* mask out unused bits */ + hpd_man &= (HPD_MAN_CTRL_HPD_PULSE | + HPD_MAN_CTRL_5VEN | + HPD_MAN_CTRL_HPD_B | + HPD_MAN_CTRL_HPD_A); + + switch (mode) { + /* HPD low and pulse of at least 100ms */ + case HPD_LOW_BP: + /* hpd_bp=0 */ + hpd_pwr &= ~HPD_POWER_BP_MASK; + /* disable HPD_A and HPD_B */ + hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B); + io_write(sd, REG_HPD_POWER, hpd_pwr); + io_write(sd, REG_HPD_MAN_CTRL, hpd_man); + break; + /* HPD high */ + case HPD_HIGH_BP: + /* hpd_bp=1 */ + hpd_pwr &= ~HPD_POWER_BP_MASK; + hpd_pwr |= 1 << HPD_POWER_BP_SHIFT; + io_write(sd, REG_HPD_POWER, hpd_pwr); + break; + /* HPD low and pulse of at least 100ms */ + case HPD_LOW_OTHER: + /* disable HPD_A and HPD_B */ + hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B); + /* hp_other=0 */ + hpd_auto &= ~HPD_AUTO_HP_OTHER; + io_write(sd, REG_HPD_AUTO_CTRL, hpd_auto); + io_write(sd, REG_HPD_MAN_CTRL, hpd_man); + break; + /* HPD high */ + case HPD_HIGH_OTHER: + hpd_auto |= HPD_AUTO_HP_OTHER; + io_write(sd, REG_HPD_AUTO_CTRL, hpd_auto); + break; + /* HPD low pulse */ + case HPD_PULSE: + /* disable HPD_A and HPD_B */ + hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B); + io_write(sd, REG_HPD_MAN_CTRL, hpd_man); + break; + } + + return 0; +} + +static void tda1997x_delayed_work_enable_hpd(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct tda1997x_state *state = container_of(dwork, + struct tda1997x_state, + delayed_work_enable_hpd); + struct v4l2_subdev *sd = &state->sd; + + v4l2_dbg(2, debug, sd, "%s:\n", __func__); + + /* Set HPD high */ + tda1997x_manual_hpd(sd, HPD_HIGH_OTHER); + tda1997x_manual_hpd(sd, HPD_HIGH_BP); + + state->edid.present = 1; +} + +static void tda1997x_disable_edid(struct v4l2_subdev *sd) +{ + struct tda1997x_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + cancel_delayed_work_sync(&state->delayed_work_enable_hpd); + + /* Set HPD low */ + tda1997x_manual_hpd(sd, HPD_LOW_BP); +} + +static void tda1997x_enable_edid(struct v4l2_subdev *sd) +{ + struct tda1997x_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* Enable hotplug after 100ms */ + schedule_delayed_work(&state->delayed_work_enable_hpd, HZ / 10); +} + +/* ----------------------------------------------------------------------------- + * Signal Control + */ + +/* + * configure vid_fmt based on mbus_code + */ +static int +tda1997x_setup_format(struct tda1997x_state *state, u32 code) +{ + v4l_dbg(1, debug, state->client, "%s code=0x%x\n", __func__, code); + switch (code) { + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_YUV8_1X24: + state->vid_fmt = OF_FMT_444; + break; + case MEDIA_BUS_FMT_UYVY12_1X24: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYVY8_1X16: + state->vid_fmt = OF_FMT_422_SMPT; + break; + case MEDIA_BUS_FMT_UYVY12_2X12: + case MEDIA_BUS_FMT_UYVY10_2X10: + case MEDIA_BUS_FMT_UYVY8_2X8: + state->vid_fmt = OF_FMT_422_CCIR; + break; + default: + v4l_err(state->client, "incompatible format (0x%x)\n", code); + return -EINVAL; + } + v4l_dbg(1, debug, state->client, "%s code=0x%x fmt=%s\n", __func__, + code, vidfmt_names[state->vid_fmt]); + state->mbus_code = code; + + return 0; +} + +/* + * The color conversion matrix will convert between the colorimetry of the + * HDMI input to the desired output format RGB|YUV. RGB output is to be + * full-range and YUV is to be limited range. + * + * RGB full-range uses values from 0 to 255 which is recommended on a monitor + * and RGB Limited uses values from 16 to 236 (16=black, 235=white) which is + * typically recommended on a TV. + */ +static void +tda1997x_configure_csc(struct v4l2_subdev *sd) +{ + struct tda1997x_state *state = to_state(sd); + struct hdmi_avi_infoframe *avi = &state->avi_infoframe; + struct v4l2_hdmi_colorimetry *c = &state->colorimetry; + /* Blanking code values depend on output colorspace (RGB or YUV) */ + struct blanking_codes { + s16 code_gy; + s16 code_bu; + s16 code_rv; + }; + static const struct blanking_codes rgb_blanking = { 64, 64, 64 }; + static const struct blanking_codes yuv_blanking = { 64, 512, 512 }; + const struct blanking_codes *blanking_codes = NULL; + u8 reg; + + v4l_dbg(1, debug, state->client, "input:%s quant:%s output:%s\n", + hdmi_colorspace_names[avi->colorspace], + v4l2_quantization_names[c->quantization], + vidfmt_names[state->vid_fmt]); + state->conv = NULL; + switch (state->vid_fmt) { + /* RGB output */ + case OF_FMT_444: + blanking_codes = &rgb_blanking; + if (c->colorspace == V4L2_COLORSPACE_SRGB) { + if (c->quantization == V4L2_QUANTIZATION_LIM_RANGE) + state->conv = &conv_matrix[RGBLIMITED_RGBFULL]; + } else { + if (c->colorspace == V4L2_COLORSPACE_REC709) + state->conv = &conv_matrix[ITU709_RGBFULL]; + else if (c->colorspace == V4L2_COLORSPACE_SMPTE170M) + state->conv = &conv_matrix[ITU601_RGBFULL]; + } + break; + + /* YUV output */ + case OF_FMT_422_SMPT: /* semi-planar */ + case OF_FMT_422_CCIR: /* CCIR656 */ + blanking_codes = &yuv_blanking; + if ((c->colorspace == V4L2_COLORSPACE_SRGB) && + (c->quantization == V4L2_QUANTIZATION_FULL_RANGE)) { + if (state->timings.bt.height <= 576) + state->conv = &conv_matrix[RGBFULL_ITU601]; + else + state->conv = &conv_matrix[RGBFULL_ITU709]; + } else if ((c->colorspace == V4L2_COLORSPACE_SRGB) && + (c->quantization == V4L2_QUANTIZATION_LIM_RANGE)) { + if (state->timings.bt.height <= 576) + state->conv = &conv_matrix[RGBLIMITED_ITU601]; + else + state->conv = &conv_matrix[RGBLIMITED_ITU709]; + } + break; + } + + if (state->conv) { + v4l_dbg(1, debug, state->client, "%s\n", + state->conv->name); + /* enable matrix conversion */ + reg = io_read(sd, REG_VDP_CTRL); + reg &= ~VDP_CTRL_MATRIX_BP; + io_write(sd, REG_VDP_CTRL, reg); + /* offset inputs */ + io_write16(sd, REG_VDP_MATRIX + 0, state->conv->offint1); + io_write16(sd, REG_VDP_MATRIX + 2, state->conv->offint2); + io_write16(sd, REG_VDP_MATRIX + 4, state->conv->offint3); + /* coefficients */ + io_write16(sd, REG_VDP_MATRIX + 6, state->conv->p11coef); + io_write16(sd, REG_VDP_MATRIX + 8, state->conv->p12coef); + io_write16(sd, REG_VDP_MATRIX + 10, state->conv->p13coef); + io_write16(sd, REG_VDP_MATRIX + 12, state->conv->p21coef); + io_write16(sd, REG_VDP_MATRIX + 14, state->conv->p22coef); + io_write16(sd, REG_VDP_MATRIX + 16, state->conv->p23coef); + io_write16(sd, REG_VDP_MATRIX + 18, state->conv->p31coef); + io_write16(sd, REG_VDP_MATRIX + 20, state->conv->p32coef); + io_write16(sd, REG_VDP_MATRIX + 22, state->conv->p33coef); + /* offset outputs */ + io_write16(sd, REG_VDP_MATRIX + 24, state->conv->offout1); + io_write16(sd, REG_VDP_MATRIX + 26, state->conv->offout2); + io_write16(sd, REG_VDP_MATRIX + 28, state->conv->offout3); + } else { + /* disable matrix conversion */ + reg = io_read(sd, REG_VDP_CTRL); + reg |= VDP_CTRL_MATRIX_BP; + io_write(sd, REG_VDP_CTRL, reg); + } + + /* SetBlankingCodes */ + if (blanking_codes) { + io_write16(sd, REG_BLK_GY, blanking_codes->code_gy); + io_write16(sd, REG_BLK_BU, blanking_codes->code_bu); + io_write16(sd, REG_BLK_RV, blanking_codes->code_rv); + } +} + +/* Configure frame detection window and VHREF timing generator */ +static void +tda1997x_configure_vhref(struct v4l2_subdev *sd) +{ + struct tda1997x_state *state = to_state(sd); + const struct v4l2_bt_timings *bt = &state->timings.bt; + int width, lines; + u16 href_start, href_end; + u16 vref_f1_start, vref_f2_start; + u8 vref_f1_width, vref_f2_width; + u8 field_polarity; + u16 fieldref_f1_start, fieldref_f2_start; + u8 reg; + + href_start = bt->hbackporch + bt->hsync + 1; + href_end = href_start + bt->width; + vref_f1_start = bt->height + bt->vbackporch + bt->vsync + + bt->il_vbackporch + bt->il_vsync + + bt->il_vfrontporch; + vref_f1_width = bt->vbackporch + bt->vsync + bt->vfrontporch; + vref_f2_start = 0; + vref_f2_width = 0; + fieldref_f1_start = 0; + fieldref_f2_start = 0; + if (bt->interlaced) { + vref_f2_start = (bt->height / 2) + + (bt->il_vbackporch + bt->il_vsync - 1); + vref_f2_width = bt->il_vbackporch + bt->il_vsync + + bt->il_vfrontporch; + fieldref_f2_start = vref_f2_start + bt->il_vfrontporch + + fieldref_f1_start; + } + field_polarity = 0; + + width = V4L2_DV_BT_FRAME_WIDTH(bt); + lines = V4L2_DV_BT_FRAME_HEIGHT(bt); + + /* + * Configure Frame Detection Window: + * horiz area where the VHREF module consider a VSYNC a new frame + */ + io_write16(sd, REG_FDW_S, 0x2ef); /* start position */ + io_write16(sd, REG_FDW_E, 0x141); /* end position */ + + /* Set Pixel And Line Counters */ + if (state->chip_revision == 0) + io_write16(sd, REG_PXCNT_PR, 4); + else + io_write16(sd, REG_PXCNT_PR, 1); + io_write16(sd, REG_PXCNT_NPIX, width & MASK_VHREF); + io_write16(sd, REG_LCNT_PR, 1); + io_write16(sd, REG_LCNT_NLIN, lines & MASK_VHREF); + + /* + * Configure the VHRef timing generator responsible for rebuilding all + * horiz and vert synch and ref signals from its input allowing auto + * detection algorithms and forcing predefined modes (480i & 576i) + */ + reg = VHREF_STD_DET_OFF << VHREF_STD_DET_SHIFT; + io_write(sd, REG_VHREF_CTRL, reg); + + /* + * Configure the VHRef timing values. In case the VHREF generator has + * been configured in manual mode, this will allow to manually set all + * horiz and vert ref values (non-active pixel areas) of the generator + * and allows setting the frame reference params. + */ + /* horizontal reference start/end */ + io_write16(sd, REG_HREF_S, href_start & MASK_VHREF); + io_write16(sd, REG_HREF_E, href_end & MASK_VHREF); + /* vertical reference f1 start/end */ + io_write16(sd, REG_VREF_F1_S, vref_f1_start & MASK_VHREF); + io_write(sd, REG_VREF_F1_WIDTH, vref_f1_width); + /* vertical reference f2 start/end */ + io_write16(sd, REG_VREF_F2_S, vref_f2_start & MASK_VHREF); + io_write(sd, REG_VREF_F2_WIDTH, vref_f2_width); + + /* F1/F2 FREF, field polarity */ + reg = fieldref_f1_start & MASK_VHREF; + reg |= field_polarity << 8; + io_write16(sd, REG_FREF_F1_S, reg); + reg = fieldref_f2_start & MASK_VHREF; + io_write16(sd, REG_FREF_F2_S, reg); +} + +/* Configure Video Output port signals */ +static int +tda1997x_configure_vidout(struct tda1997x_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + struct tda1997x_platform_data *pdata = &state->pdata; + u8 prefilter; + u8 reg; + + /* Configure pixel clock generator: delay, polarity, rate */ + reg = (state->vid_fmt == OF_FMT_422_CCIR) ? + PCLK_SEL_X2 : PCLK_SEL_X1; + reg |= pdata->vidout_delay_pclk << PCLK_DELAY_SHIFT; + reg |= pdata->vidout_inv_pclk << PCLK_INV_SHIFT; + io_write(sd, REG_PCLK, reg); + + /* Configure pre-filter */ + prefilter = 0; /* filters off */ + /* YUV422 mode requires conversion */ + if ((state->vid_fmt == OF_FMT_422_SMPT) || + (state->vid_fmt == OF_FMT_422_CCIR)) { + /* 2/7 taps for Rv and Bu */ + prefilter = FILTERS_CTRL_2_7TAP << FILTERS_CTRL_BU_SHIFT | + FILTERS_CTRL_2_7TAP << FILTERS_CTRL_RV_SHIFT; + } + io_write(sd, REG_FILTERS_CTRL, prefilter); + + /* Configure video port */ + reg = state->vid_fmt & OF_FMT_MASK; + if (state->vid_fmt == OF_FMT_422_CCIR) + reg |= (OF_BLK | OF_TRC); + reg |= OF_VP_ENABLE; + io_write(sd, REG_OF, reg); + + /* Configure formatter and conversions */ + reg = io_read(sd, REG_VDP_CTRL); + /* pre-filter is needed unless (REG_FILTERS_CTRL == 0) */ + if (!prefilter) + reg |= VDP_CTRL_PREFILTER_BP; + else + reg &= ~VDP_CTRL_PREFILTER_BP; + /* formatter is needed for YUV422 and for trc/blc codes */ + if (state->vid_fmt == OF_FMT_444) + reg |= VDP_CTRL_FORMATTER_BP; + /* formatter and compdel needed for timing/blanking codes */ + else + reg &= ~(VDP_CTRL_FORMATTER_BP | VDP_CTRL_COMPDEL_BP); + /* activate compdel for small sync delays */ + if ((pdata->vidout_delay_vs < 4) || (pdata->vidout_delay_hs < 4)) + reg &= ~VDP_CTRL_COMPDEL_BP; + io_write(sd, REG_VDP_CTRL, reg); + + /* Configure DE output signal: delay, polarity, and source */ + reg = pdata->vidout_delay_de << DE_FREF_DELAY_SHIFT | + pdata->vidout_inv_de << DE_FREF_INV_SHIFT | + pdata->vidout_sel_de << DE_FREF_SEL_SHIFT; + io_write(sd, REG_DE_FREF, reg); + + /* Configure HS/HREF output signal: delay, polarity, and source */ + if (state->vid_fmt != OF_FMT_422_CCIR) { + reg = pdata->vidout_delay_hs << HS_HREF_DELAY_SHIFT | + pdata->vidout_inv_hs << HS_HREF_INV_SHIFT | + pdata->vidout_sel_hs << HS_HREF_SEL_SHIFT; + } else + reg = HS_HREF_SEL_NONE << HS_HREF_SEL_SHIFT; + io_write(sd, REG_HS_HREF, reg); + + /* Configure VS/VREF output signal: delay, polarity, and source */ + if (state->vid_fmt != OF_FMT_422_CCIR) { + reg = pdata->vidout_delay_vs << VS_VREF_DELAY_SHIFT | + pdata->vidout_inv_vs << VS_VREF_INV_SHIFT | + pdata->vidout_sel_vs << VS_VREF_SEL_SHIFT; + } else + reg = VS_VREF_SEL_NONE << VS_VREF_SEL_SHIFT; + io_write(sd, REG_VS_VREF, reg); + + return 0; +} + +/* Configure Audio output port signals */ +static int +tda1997x_configure_audout(struct v4l2_subdev *sd, u8 channel_assignment) +{ + struct tda1997x_state *state = to_state(sd); + struct tda1997x_platform_data *pdata = &state->pdata; + bool sp_used_by_fifo = true; + u8 reg; + + if (!pdata->audout_format) + return 0; + + /* channel assignment (CEA-861-D Table 20) */ + io_write(sd, REG_AUDIO_PATH, channel_assignment); + + /* Audio output configuration */ + reg = 0; + switch (pdata->audout_format) { + case AUDFMT_TYPE_I2S: + reg |= AUDCFG_BUS_I2S << AUDCFG_BUS_SHIFT; + break; + case AUDFMT_TYPE_SPDIF: + reg |= AUDCFG_BUS_SPDIF << AUDCFG_BUS_SHIFT; + break; + } + switch (state->audio_type) { + case AUDCFG_TYPE_PCM: + reg |= AUDCFG_TYPE_PCM << AUDCFG_TYPE_SHIFT; + break; + case AUDCFG_TYPE_OBA: + reg |= AUDCFG_TYPE_OBA << AUDCFG_TYPE_SHIFT; + break; + case AUDCFG_TYPE_DST: + reg |= AUDCFG_TYPE_DST << AUDCFG_TYPE_SHIFT; + sp_used_by_fifo = false; + break; + case AUDCFG_TYPE_HBR: + reg |= AUDCFG_TYPE_HBR << AUDCFG_TYPE_SHIFT; + if (pdata->audout_layout == 1) { + /* demuxed via AP0:AP3 */ + reg |= AUDCFG_HBR_DEMUX << AUDCFG_HBR_SHIFT; + if (pdata->audout_format == AUDFMT_TYPE_SPDIF) + sp_used_by_fifo = false; + } else { + /* straight via AP0 */ + reg |= AUDCFG_HBR_STRAIGHT << AUDCFG_HBR_SHIFT; + } + break; + } + if (pdata->audout_width == 32) + reg |= AUDCFG_I2SW_32 << AUDCFG_I2SW_SHIFT; + else + reg |= AUDCFG_I2SW_16 << AUDCFG_I2SW_SHIFT; + + /* automatic hardware mute */ + if (pdata->audio_auto_mute) + reg |= AUDCFG_AUTO_MUTE_EN; + /* clock polarity */ + if (pdata->audout_invert_clk) + reg |= AUDCFG_CLK_INVERT; + io_write(sd, REG_AUDCFG, reg); + + /* audio layout */ + reg = (pdata->audout_layout) ? AUDIO_LAYOUT_LAYOUT1 : 0; + if (!pdata->audout_layoutauto) + reg |= AUDIO_LAYOUT_MANUAL; + if (sp_used_by_fifo) + reg |= AUDIO_LAYOUT_SP_FLAG; + io_write(sd, REG_AUDIO_LAYOUT, reg); + + /* FIFO Latency value */ + io_write(sd, REG_FIFO_LATENCY_VAL, 0x80); + + /* Audio output port config */ + if (sp_used_by_fifo) { + reg = AUDIO_OUT_ENABLE_AP0; + if (channel_assignment >= 0x01) + reg |= AUDIO_OUT_ENABLE_AP1; + if (channel_assignment >= 0x04) + reg |= AUDIO_OUT_ENABLE_AP2; + if (channel_assignment >= 0x0c) + reg |= AUDIO_OUT_ENABLE_AP3; + /* specific cases where AP1 is not used */ + if ((channel_assignment == 0x04) + || (channel_assignment == 0x08) + || (channel_assignment == 0x0c) + || (channel_assignment == 0x10) + || (channel_assignment == 0x14) + || (channel_assignment == 0x18) + || (channel_assignment == 0x1c)) + reg &= ~AUDIO_OUT_ENABLE_AP1; + /* specific cases where AP2 is not used */ + if ((channel_assignment >= 0x14) + && (channel_assignment <= 0x17)) + reg &= ~AUDIO_OUT_ENABLE_AP2; + } else { + reg = AUDIO_OUT_ENABLE_AP3 | + AUDIO_OUT_ENABLE_AP2 | + AUDIO_OUT_ENABLE_AP1 | + AUDIO_OUT_ENABLE_AP0; + } + if (pdata->audout_format == AUDFMT_TYPE_I2S) + reg |= (AUDIO_OUT_ENABLE_ACLK | AUDIO_OUT_ENABLE_WS); + io_write(sd, REG_AUDIO_OUT_ENABLE, reg); + + /* reset test mode to normal audio freq auto selection */ + io_write(sd, REG_TEST_MODE, 0x00); + + return 0; +} + +/* Soft Reset of specific hdmi info */ +static int +tda1997x_hdmi_info_reset(struct v4l2_subdev *sd, u8 info_rst, bool reset_sus) +{ + u8 reg; + + /* reset infoframe engine packets */ + reg = io_read(sd, REG_HDMI_INFO_RST); + io_write(sd, REG_HDMI_INFO_RST, info_rst); + + /* if infoframe engine has been reset clear INT_FLG_MODE */ + if (reg & RESET_IF) { + reg = io_read(sd, REG_INT_FLG_CLR_MODE); + io_write(sd, REG_INT_FLG_CLR_MODE, reg); + } + + /* Disable REFTIM to restart start-up-sequencer (SUS) */ + reg = io_read(sd, REG_RATE_CTRL); + reg &= ~RATE_REFTIM_ENABLE; + if (!reset_sus) + reg |= RATE_REFTIM_ENABLE; + reg = io_write(sd, REG_RATE_CTRL, reg); + + return 0; +} + +static void +tda1997x_power_mode(struct tda1997x_state *state, bool enable) +{ + struct v4l2_subdev *sd = &state->sd; + u8 reg; + + if (enable) { + /* Automatic control of TMDS */ + io_write(sd, REG_PON_OVR_EN, PON_DIS); + /* Enable current bias unit */ + io_write(sd, REG_CFG1, PON_EN); + /* Enable deep color PLL */ + io_write(sd, REG_DEEP_PLL7_BYP, PON_DIS); + /* Output buffers active */ + reg = io_read(sd, REG_OF); + reg &= ~OF_VP_ENABLE; + io_write(sd, REG_OF, reg); + } else { + /* Power down EDID mode sequence */ + /* Output buffers in HiZ */ + reg = io_read(sd, REG_OF); + reg |= OF_VP_ENABLE; + io_write(sd, REG_OF, reg); + /* Disable deep color PLL */ + io_write(sd, REG_DEEP_PLL7_BYP, PON_EN); + /* Disable current bias unit */ + io_write(sd, REG_CFG1, PON_DIS); + /* Manual control of TMDS */ + io_write(sd, REG_PON_OVR_EN, PON_EN); + } +} + +static bool +tda1997x_detect_tx_5v(struct v4l2_subdev *sd) +{ + u8 reg = io_read(sd, REG_DETECT_5V); + + return ((reg & DETECT_5V_SEL) ? 1 : 0); +} + +static bool +tda1997x_detect_tx_hpd(struct v4l2_subdev *sd) +{ + u8 reg = io_read(sd, REG_DETECT_5V); + + return ((reg & DETECT_HPD) ? 1 : 0); +} + +static int +tda1997x_detect_std(struct tda1997x_state *state, + struct v4l2_dv_timings *timings) +{ + struct v4l2_subdev *sd = &state->sd; + u32 vper; + u16 hper; + u16 hsper; + int i; + + /* + * Read the FMT registers + * REG_V_PER: Period of a frame (or two fields) in MCLK(27MHz) cycles + * REG_H_PER: Period of a line in MCLK(27MHz) cycles + * REG_HS_WIDTH: Period of horiz sync pulse in MCLK(27MHz) cycles + */ + vper = io_read24(sd, REG_V_PER) & MASK_VPER; + hper = io_read16(sd, REG_H_PER) & MASK_HPER; + hsper = io_read16(sd, REG_HS_WIDTH) & MASK_HSWIDTH; + v4l2_dbg(1, debug, sd, "Signal Timings: %u/%u/%u\n", vper, hper, hsper); + if (!vper || !hper || !hsper) + return -ENOLINK; + + for (i = 0; v4l2_dv_timings_presets[i].bt.width; i++) { + const struct v4l2_bt_timings *bt; + u32 lines, width, _hper, _hsper; + u32 vmin, vmax, hmin, hmax, hsmin, hsmax; + bool vmatch, hmatch, hsmatch; + + bt = &v4l2_dv_timings_presets[i].bt; + width = V4L2_DV_BT_FRAME_WIDTH(bt); + lines = V4L2_DV_BT_FRAME_HEIGHT(bt); + _hper = (u32)bt->pixelclock / width; + if (bt->interlaced) + lines /= 2; + /* vper +/- 0.7% */ + vmin = ((27000000 / 1000) * 993) / _hper * lines; + vmax = ((27000000 / 1000) * 1007) / _hper * lines; + /* hper +/- 1.0% */ + hmin = ((27000000 / 100) * 99) / _hper; + hmax = ((27000000 / 100) * 101) / _hper; + /* hsper +/- 2 (take care to avoid 32bit overflow) */ + _hsper = 27000 * bt->hsync / ((u32)bt->pixelclock/1000); + hsmin = _hsper - 2; + hsmax = _hsper + 2; + + /* vmatch matches the framerate */ + vmatch = ((vper <= vmax) && (vper >= vmin)) ? 1 : 0; + /* hmatch matches the width */ + hmatch = ((hper <= hmax) && (hper >= hmin)) ? 1 : 0; + /* hsmatch matches the hswidth */ + hsmatch = ((hsper <= hsmax) && (hsper >= hsmin)) ? 1 : 0; + if (hmatch && vmatch && hsmatch) { + v4l2_print_dv_timings(sd->name, "Detected format: ", + &v4l2_dv_timings_presets[i], + false); + if (timings) + *timings = v4l2_dv_timings_presets[i]; + return 0; + } + } + + v4l_err(state->client, "no resolution match for timings: %d/%d/%d\n", + vper, hper, hsper); + return -ERANGE; +} + +/* some sort of errata workaround for chip revision 0 (N1) */ +static void tda1997x_reset_n1(struct tda1997x_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + u8 reg; + + /* clear HDMI mode flag in BCAPS */ + io_write(sd, REG_CLK_CFG, CLK_CFG_SEL_ACLK_EN | CLK_CFG_SEL_ACLK); + io_write(sd, REG_PON_OVR_EN, PON_EN); + io_write(sd, REG_PON_CBIAS, PON_EN); + io_write(sd, REG_PON_PLL, PON_EN); + + reg = io_read(sd, REG_MODE_REC_CFG1); + reg &= ~0x06; + reg |= 0x02; + io_write(sd, REG_MODE_REC_CFG1, reg); + io_write(sd, REG_CLK_CFG, CLK_CFG_DIS); + io_write(sd, REG_PON_OVR_EN, PON_DIS); + reg = io_read(sd, REG_MODE_REC_CFG1); + reg &= ~0x06; + io_write(sd, REG_MODE_REC_CFG1, reg); +} + +/* + * Activity detection must only be notified when stable_clk_x AND active_x + * bits are set to 1. If only stable_clk_x bit is set to 1 but not + * active_x, it means that the TMDS clock is not in the defined range + * and activity detection must not be notified. + */ +static u8 +tda1997x_read_activity_status_regs(struct v4l2_subdev *sd) +{ + u8 reg, status = 0; + + /* Read CLK_A_STATUS register */ + reg = io_read(sd, REG_CLK_A_STATUS); + /* ignore if not active */ + if ((reg & MASK_CLK_STABLE) && !(reg & MASK_CLK_ACTIVE)) + reg &= ~MASK_CLK_STABLE; + status |= ((reg & MASK_CLK_STABLE) >> 2); + + /* Read CLK_B_STATUS register */ + reg = io_read(sd, REG_CLK_B_STATUS); + /* ignore if not active */ + if ((reg & MASK_CLK_STABLE) && !(reg & MASK_CLK_ACTIVE)) + reg &= ~MASK_CLK_STABLE; + status |= ((reg & MASK_CLK_STABLE) >> 1); + + /* Read the SUS_STATUS register */ + reg = io_read(sd, REG_SUS_STATUS); + + /* If state = 5 => TMDS is locked */ + if ((reg & MASK_SUS_STATUS) == LAST_STATE_REACHED) + status |= MASK_SUS_STATE; + else + status &= ~MASK_SUS_STATE; + + return status; +} + +static void +set_rgb_quantization_range(struct tda1997x_state *state) +{ + struct v4l2_hdmi_colorimetry *c = &state->colorimetry; + + state->colorimetry = v4l2_hdmi_rx_colorimetry(&state->avi_infoframe, + NULL, + state->timings.bt.height); + /* If ycbcr_enc is V4L2_YCBCR_ENC_DEFAULT, we receive RGB */ + if (c->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) { + switch (state->rgb_quantization_range) { + case V4L2_DV_RGB_RANGE_LIMITED: + c->quantization = V4L2_QUANTIZATION_FULL_RANGE; + break; + case V4L2_DV_RGB_RANGE_FULL: + c->quantization = V4L2_QUANTIZATION_LIM_RANGE; + break; + } + } + v4l_dbg(1, debug, state->client, + "colorspace=%d/%d colorimetry=%d range=%s content=%d\n", + state->avi_infoframe.colorspace, c->colorspace, + state->avi_infoframe.colorimetry, + v4l2_quantization_names[c->quantization], + state->avi_infoframe.content_type); +} + +/* parse an infoframe and do some sanity checks on it */ +static unsigned int +tda1997x_parse_infoframe(struct tda1997x_state *state, u16 addr) +{ + struct v4l2_subdev *sd = &state->sd; + union hdmi_infoframe frame; + u8 buffer[40] = { 0 }; + u8 reg; + int len, err; + + /* read data */ + len = io_readn(sd, addr, sizeof(buffer), buffer); + err = hdmi_infoframe_unpack(&frame, buffer, len); + if (err) { + v4l_err(state->client, + "failed parsing %d byte infoframe: 0x%04x/0x%02x\n", + len, addr, buffer[0]); + return err; + } + hdmi_infoframe_log(KERN_INFO, &state->client->dev, &frame); + switch (frame.any.type) { + /* Audio InfoFrame: see HDMI spec 8.2.2 */ + case HDMI_INFOFRAME_TYPE_AUDIO: + /* sample rate */ + switch (frame.audio.sample_frequency) { + case HDMI_AUDIO_SAMPLE_FREQUENCY_32000: + state->audio_samplerate = 32000; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_44100: + state->audio_samplerate = 44100; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_48000: + state->audio_samplerate = 48000; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_88200: + state->audio_samplerate = 88200; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_96000: + state->audio_samplerate = 96000; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_176400: + state->audio_samplerate = 176400; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_192000: + state->audio_samplerate = 192000; + break; + default: + case HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM: + break; + } + + /* sample size */ + switch (frame.audio.sample_size) { + case HDMI_AUDIO_SAMPLE_SIZE_16: + state->audio_samplesize = 16; + break; + case HDMI_AUDIO_SAMPLE_SIZE_20: + state->audio_samplesize = 20; + break; + case HDMI_AUDIO_SAMPLE_SIZE_24: + state->audio_samplesize = 24; + break; + case HDMI_AUDIO_SAMPLE_SIZE_STREAM: + default: + break; + } + + /* Channel Count */ + state->audio_channels = frame.audio.channels; + if (frame.audio.channel_allocation && + frame.audio.channel_allocation != state->audio_ch_alloc) { + /* use the channel assignment from the infoframe */ + state->audio_ch_alloc = frame.audio.channel_allocation; + tda1997x_configure_audout(sd, state->audio_ch_alloc); + /* reset the audio FIFO */ + tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); + } + break; + + /* Auxiliary Video information (AVI) InfoFrame: see HDMI spec 8.2.1 */ + case HDMI_INFOFRAME_TYPE_AVI: + state->avi_infoframe = frame.avi; + set_rgb_quantization_range(state); + + /* configure upsampler: 0=bypass 1=repeatchroma 2=interpolate */ + reg = io_read(sd, REG_PIX_REPEAT); + reg &= ~PIX_REPEAT_MASK_UP_SEL; + if (frame.avi.colorspace == HDMI_COLORSPACE_YUV422) + reg |= (PIX_REPEAT_CHROMA << PIX_REPEAT_SHIFT); + io_write(sd, REG_PIX_REPEAT, reg); + + /* ConfigurePixelRepeater: repeat n-times each pixel */ + reg = io_read(sd, REG_PIX_REPEAT); + reg &= ~PIX_REPEAT_MASK_REP; + reg |= frame.avi.pixel_repeat; + io_write(sd, REG_PIX_REPEAT, reg); + + /* configure the receiver with the new colorspace */ + tda1997x_configure_csc(sd); + break; + default: + break; + } + return 0; +} + +static void tda1997x_irq_sus(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 reg, source; + + source = io_read(sd, REG_INT_FLG_CLR_SUS); + io_write(sd, REG_INT_FLG_CLR_SUS, source); + + if (source & MASK_MPT) { + /* reset MTP in use flag if set */ + if (state->mptrw_in_progress) + state->mptrw_in_progress = 0; + } + + if (source & MASK_SUS_END) { + /* reset audio FIFO */ + reg = io_read(sd, REG_HDMI_INFO_RST); + reg |= MASK_SR_FIFO_FIFO_CTRL; + io_write(sd, REG_HDMI_INFO_RST, reg); + reg &= ~MASK_SR_FIFO_FIFO_CTRL; + io_write(sd, REG_HDMI_INFO_RST, reg); + + /* reset HDMI flags */ + state->hdmi_status = 0; + } + + /* filter FMT interrupt based on SUS state */ + reg = io_read(sd, REG_SUS_STATUS); + if (((reg & MASK_SUS_STATUS) != LAST_STATE_REACHED) + || (source & MASK_MPT)) { + source &= ~MASK_FMT; + } + + if (source & (MASK_FMT | MASK_SUS_END)) { + reg = io_read(sd, REG_SUS_STATUS); + if ((reg & MASK_SUS_STATUS) != LAST_STATE_REACHED) { + v4l_err(state->client, "BAD SUS STATUS\n"); + return; + } + if (debug) + tda1997x_detect_std(state, NULL); + /* notify user of change in resolution */ + v4l2_subdev_notify_event(&state->sd, &tda1997x_ev_fmt); + } +} + +static void tda1997x_irq_ddc(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 source; + + source = io_read(sd, REG_INT_FLG_CLR_DDC); + io_write(sd, REG_INT_FLG_CLR_DDC, source); + if (source & MASK_EDID_MTP) { + /* reset MTP in use flag if set */ + if (state->mptrw_in_progress) + state->mptrw_in_progress = 0; + } + + /* Detection of +5V */ + if (source & MASK_DET_5V) { + v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, + tda1997x_detect_tx_5v(sd)); + } +} + +static void tda1997x_irq_rate(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 reg, source; + + u8 irq_status; + + source = io_read(sd, REG_INT_FLG_CLR_RATE); + io_write(sd, REG_INT_FLG_CLR_RATE, source); + + /* read status regs */ + irq_status = tda1997x_read_activity_status_regs(sd); + + /* + * read clock status reg until INT_FLG_CLR_RATE is still 0 + * after the read to make sure its the last one + */ + reg = source; + while (reg != 0) { + irq_status = tda1997x_read_activity_status_regs(sd); + reg = io_read(sd, REG_INT_FLG_CLR_RATE); + io_write(sd, REG_INT_FLG_CLR_RATE, reg); + source |= reg; + } + + /* we only pay attention to stability change events */ + if (source & (MASK_RATE_A_ST | MASK_RATE_B_ST)) { + int input = (source & MASK_RATE_A_ST)?0:1; + u8 mask = 1<<input; + + /* state change */ + if ((irq_status & mask) != (state->activity_status & mask)) { + /* activity lost */ + if ((irq_status & mask) == 0) { + v4l_info(state->client, + "HDMI-%c: Digital Activity Lost\n", + input+'A'); + + /* bypass up/down sampler and pixel repeater */ + reg = io_read(sd, REG_PIX_REPEAT); + reg &= ~PIX_REPEAT_MASK_UP_SEL; + reg &= ~PIX_REPEAT_MASK_REP; + io_write(sd, REG_PIX_REPEAT, reg); + + if (state->chip_revision == 0) + tda1997x_reset_n1(state); + + state->input_detect[input] = 0; + v4l2_subdev_notify_event(sd, &tda1997x_ev_fmt); + } + + /* activity detected */ + else { + v4l_info(state->client, + "HDMI-%c: Digital Activity Detected\n", + input+'A'); + state->input_detect[input] = 1; + } + + /* hold onto current state */ + state->activity_status = (irq_status & mask); + } + } +} + +static void tda1997x_irq_info(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 source; + + source = io_read(sd, REG_INT_FLG_CLR_INFO); + io_write(sd, REG_INT_FLG_CLR_INFO, source); + + /* Audio infoframe */ + if (source & MASK_AUD_IF) { + tda1997x_parse_infoframe(state, AUD_IF); + source &= ~MASK_AUD_IF; + } + + /* Source Product Descriptor infoframe change */ + if (source & MASK_SPD_IF) { + tda1997x_parse_infoframe(state, SPD_IF); + source &= ~MASK_SPD_IF; + } + + /* Auxiliary Video Information infoframe */ + if (source & MASK_AVI_IF) { + tda1997x_parse_infoframe(state, AVI_IF); + source &= ~MASK_AVI_IF; + } +} + +static void tda1997x_irq_audio(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 reg, source; + + source = io_read(sd, REG_INT_FLG_CLR_AUDIO); + io_write(sd, REG_INT_FLG_CLR_AUDIO, source); + + /* reset audio FIFO on FIFO pointer error or audio mute */ + if (source & MASK_ERROR_FIFO_PT || + source & MASK_MUTE_FLG) { + /* audio reset audio FIFO */ + reg = io_read(sd, REG_SUS_STATUS); + if ((reg & MASK_SUS_STATUS) == LAST_STATE_REACHED) { + reg = io_read(sd, REG_HDMI_INFO_RST); + reg |= MASK_SR_FIFO_FIFO_CTRL; + io_write(sd, REG_HDMI_INFO_RST, reg); + reg &= ~MASK_SR_FIFO_FIFO_CTRL; + io_write(sd, REG_HDMI_INFO_RST, reg); + /* reset channel status IT if present */ + source &= ~(MASK_CH_STATE); + } + } + if (source & MASK_AUDIO_FREQ_FLG) { + static const int freq[] = { + 0, 32000, 44100, 48000, 88200, 96000, 176400, 192000 + }; + + reg = io_read(sd, REG_AUDIO_FREQ); + state->audio_samplerate = freq[reg & 7]; + v4l_info(state->client, "Audio Frequency Change: %dHz\n", + state->audio_samplerate); + } + if (source & MASK_AUDIO_FLG) { + reg = io_read(sd, REG_AUDIO_FLAGS); + if (reg & BIT(AUDCFG_TYPE_DST)) + state->audio_type = AUDCFG_TYPE_DST; + if (reg & BIT(AUDCFG_TYPE_OBA)) + state->audio_type = AUDCFG_TYPE_OBA; + if (reg & BIT(AUDCFG_TYPE_HBR)) + state->audio_type = AUDCFG_TYPE_HBR; + if (reg & BIT(AUDCFG_TYPE_PCM)) + state->audio_type = AUDCFG_TYPE_PCM; + v4l_info(state->client, "Audio Type: %s\n", + audtype_names[state->audio_type]); + } +} + +static void tda1997x_irq_hdcp(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 reg, source; + + source = io_read(sd, REG_INT_FLG_CLR_HDCP); + io_write(sd, REG_INT_FLG_CLR_HDCP, source); + + /* reset MTP in use flag if set */ + if (source & MASK_HDCP_MTP) + state->mptrw_in_progress = 0; + if (source & MASK_STATE_C5) { + /* REPEATER: mask AUDIO and IF irqs to avoid IF during auth */ + reg = io_read(sd, REG_INT_MASK_TOP); + reg &= ~(INTERRUPT_AUDIO | INTERRUPT_INFO); + io_write(sd, REG_INT_MASK_TOP, reg); + *flags &= (INTERRUPT_AUDIO | INTERRUPT_INFO); + } +} + +static irqreturn_t tda1997x_isr_thread(int irq, void *d) +{ + struct tda1997x_state *state = d; + struct v4l2_subdev *sd = &state->sd; + u8 flags; + + mutex_lock(&state->lock); + do { + /* read interrupt flags */ + flags = io_read(sd, REG_INT_FLG_CLR_TOP); + if (flags == 0) + break; + + /* SUS interrupt source (Input activity events) */ + if (flags & INTERRUPT_SUS) + tda1997x_irq_sus(state, &flags); + /* DDC interrupt source (Display Data Channel) */ + else if (flags & INTERRUPT_DDC) + tda1997x_irq_ddc(state, &flags); + /* RATE interrupt source (Digital Input activity) */ + else if (flags & INTERRUPT_RATE) + tda1997x_irq_rate(state, &flags); + /* Infoframe change interrupt */ + else if (flags & INTERRUPT_INFO) + tda1997x_irq_info(state, &flags); + /* Audio interrupt source: + * freq change, DST,OBA,HBR,ASP flags, mute, FIFO err + */ + else if (flags & INTERRUPT_AUDIO) + tda1997x_irq_audio(state, &flags); + /* HDCP interrupt source (content protection) */ + if (flags & INTERRUPT_HDCP) + tda1997x_irq_hdcp(state, &flags); + } while (flags != 0); + mutex_unlock(&state->lock); + + return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_video_ops + */ + +static int +tda1997x_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct tda1997x_state *state = to_state(sd); + u32 vper; + u16 hper; + u16 hsper; + + mutex_lock(&state->lock); + vper = io_read24(sd, REG_V_PER) & MASK_VPER; + hper = io_read16(sd, REG_H_PER) & MASK_HPER; + hsper = io_read16(sd, REG_HS_WIDTH) & MASK_HSWIDTH; + /* + * The tda1997x supports A/B inputs but only a single output. + * The irq handler monitors for timing changes on both inputs and + * sets the input_detect array to 0|1 depending on signal presence. + * I believe selection of A vs B is automatic. + * + * The vper/hper/hsper registers provide the frame period, line period + * and horiz sync period (units of MCLK clock cycles (27MHz)) and + * testing shows these values to be random if no signal is present + * or locked. + */ + v4l2_dbg(1, debug, sd, "inputs:%d/%d timings:%d/%d/%d\n", + state->input_detect[0], state->input_detect[1], + vper, hper, hsper); + if (!state->input_detect[0] && !state->input_detect[1]) + *status = V4L2_IN_ST_NO_SIGNAL; + else if (!vper || !hper || !hsper) + *status = V4L2_IN_ST_NO_SYNC; + else + *status = 0; + mutex_unlock(&state->lock); + + return 0; +}; + +static int tda1997x_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct tda1997x_state *state = to_state(sd); + + v4l_dbg(1, debug, state->client, "%s\n", __func__); + + if (v4l2_match_dv_timings(&state->timings, timings, 0, false)) + return 0; /* no changes */ + + if (!v4l2_valid_dv_timings(timings, &tda1997x_dv_timings_cap, + NULL, NULL)) + return -ERANGE; + + mutex_lock(&state->lock); + state->timings = *timings; + /* setup frame detection window and VHREF timing generator */ + tda1997x_configure_vhref(sd); + /* configure colorspace conversion */ + tda1997x_configure_csc(sd); + mutex_unlock(&state->lock); + + return 0; +} + +static int tda1997x_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct tda1997x_state *state = to_state(sd); + + v4l_dbg(1, debug, state->client, "%s\n", __func__); + mutex_lock(&state->lock); + *timings = state->timings; + mutex_unlock(&state->lock); + + return 0; +} + +static int tda1997x_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct tda1997x_state *state = to_state(sd); + int ret; + + v4l_dbg(1, debug, state->client, "%s\n", __func__); + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + mutex_lock(&state->lock); + ret = tda1997x_detect_std(state, timings); + mutex_unlock(&state->lock); + + return ret; +} + +static const struct v4l2_subdev_video_ops tda1997x_video_ops = { + .g_input_status = tda1997x_g_input_status, + .s_dv_timings = tda1997x_s_dv_timings, + .g_dv_timings = tda1997x_g_dv_timings, + .query_dv_timings = tda1997x_query_dv_timings, +}; + + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_pad_ops + */ + +static int tda1997x_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct tda1997x_state *state = to_state(sd); + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + mf->code = state->mbus_codes[0]; + + return 0; +} + +static int tda1997x_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct tda1997x_state *state = to_state(sd); + + v4l_dbg(1, debug, state->client, "%s %d\n", __func__, code->index); + if (code->index >= ARRAY_SIZE(state->mbus_codes)) + return -EINVAL; + + if (!state->mbus_codes[code->index]) + return -EINVAL; + + code->code = state->mbus_codes[code->index]; + + return 0; +} + +static void tda1997x_fill_format(struct tda1997x_state *state, + struct v4l2_mbus_framefmt *format) +{ + const struct v4l2_bt_timings *bt; + + memset(format, 0, sizeof(*format)); + bt = &state->timings.bt; + format->width = bt->width; + format->height = bt->height; + format->colorspace = state->colorimetry.colorspace; + format->field = (bt->interlaced) ? + V4L2_FIELD_SEQ_TB : V4L2_FIELD_NONE; +} + +static int tda1997x_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct tda1997x_state *state = to_state(sd); + + v4l_dbg(1, debug, state->client, "%s pad=%d which=%d\n", + __func__, format->pad, format->which); + + tda1997x_fill_format(state, &format->format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + format->format.code = fmt->code; + } else + format->format.code = state->mbus_code; + + return 0; +} + +static int tda1997x_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct tda1997x_state *state = to_state(sd); + u32 code = 0; + int i; + + v4l_dbg(1, debug, state->client, "%s pad=%d which=%d fmt=0x%x\n", + __func__, format->pad, format->which, format->format.code); + + for (i = 0; i < ARRAY_SIZE(state->mbus_codes); i++) { + if (format->format.code == state->mbus_codes[i]) { + code = state->mbus_codes[i]; + break; + } + } + if (!code) + code = state->mbus_codes[0]; + + tda1997x_fill_format(state, &format->format); + format->format.code = code; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + *fmt = format->format; + } else { + int ret = tda1997x_setup_format(state, format->format.code); + + if (ret) + return ret; + /* mbus_code has changed - re-configure csc/vidout */ + tda1997x_configure_csc(sd); + tda1997x_configure_vidout(state); + } + + return 0; +} + +static int tda1997x_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct tda1997x_state *state = to_state(sd); + + v4l_dbg(1, debug, state->client, "%s pad=%d\n", __func__, edid->pad); + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = state->edid.blocks; + return 0; + } + + if (!state->edid.present) + return -ENODATA; + + if (edid->start_block >= state->edid.blocks) + return -EINVAL; + + if (edid->start_block + edid->blocks > state->edid.blocks) + edid->blocks = state->edid.blocks - edid->start_block; + + memcpy(edid->edid, state->edid.edid + edid->start_block * 128, + edid->blocks * 128); + + return 0; +} + +static int tda1997x_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct tda1997x_state *state = to_state(sd); + int i; + + v4l_dbg(1, debug, state->client, "%s pad=%d\n", __func__, edid->pad); + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->start_block != 0) + return -EINVAL; + + if (edid->blocks == 0) { + state->edid.blocks = 0; + state->edid.present = 0; + tda1997x_disable_edid(sd); + return 0; + } + + if (edid->blocks > 2) { + edid->blocks = 2; + return -E2BIG; + } + + tda1997x_disable_edid(sd); + + /* write base EDID */ + for (i = 0; i < 128; i++) + io_write(sd, REG_EDID_IN_BYTE0 + i, edid->edid[i]); + + /* write CEA Extension */ + for (i = 0; i < 128; i++) + io_write(sd, REG_EDID_IN_BYTE128 + i, edid->edid[i+128]); + + /* store state */ + memcpy(state->edid.edid, edid->edid, 256); + state->edid.blocks = edid->blocks; + + tda1997x_enable_edid(sd); + + return 0; +} + +static int tda1997x_get_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + *cap = tda1997x_dv_timings_cap; + return 0; +} + +static int tda1997x_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + return v4l2_enum_dv_timings_cap(timings, &tda1997x_dv_timings_cap, + NULL, NULL); +} + +static const struct v4l2_subdev_pad_ops tda1997x_pad_ops = { + .init_cfg = tda1997x_init_cfg, + .enum_mbus_code = tda1997x_enum_mbus_code, + .get_fmt = tda1997x_get_format, + .set_fmt = tda1997x_set_format, + .get_edid = tda1997x_get_edid, + .set_edid = tda1997x_set_edid, + .dv_timings_cap = tda1997x_get_dv_timings_cap, + .enum_dv_timings = tda1997x_enum_dv_timings, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_core_ops + */ + +static int tda1997x_log_infoframe(struct v4l2_subdev *sd, int addr) +{ + struct tda1997x_state *state = to_state(sd); + union hdmi_infoframe frame; + u8 buffer[40] = { 0 }; + int len, err; + + /* read data */ + len = io_readn(sd, addr, sizeof(buffer), buffer); + v4l2_dbg(1, debug, sd, "infoframe: addr=%d len=%d\n", addr, len); + err = hdmi_infoframe_unpack(&frame, buffer, len); + if (err) { + v4l_err(state->client, + "failed parsing %d byte infoframe: 0x%04x/0x%02x\n", + len, addr, buffer[0]); + return err; + } + hdmi_infoframe_log(KERN_INFO, &state->client->dev, &frame); + + return 0; +} + +static int tda1997x_log_status(struct v4l2_subdev *sd) +{ + struct tda1997x_state *state = to_state(sd); + struct v4l2_dv_timings timings; + struct hdmi_avi_infoframe *avi = &state->avi_infoframe; + + v4l2_info(sd, "-----Chip status-----\n"); + v4l2_info(sd, "Chip: %s N%d\n", state->info->name, + state->chip_revision + 1); + v4l2_info(sd, "EDID Enabled: %s\n", state->edid.present ? "yes" : "no"); + + v4l2_info(sd, "-----Signal status-----\n"); + v4l2_info(sd, "Cable detected (+5V power): %s\n", + tda1997x_detect_tx_5v(sd) ? "yes" : "no"); + v4l2_info(sd, "HPD detected: %s\n", + tda1997x_detect_tx_hpd(sd) ? "yes" : "no"); + + v4l2_info(sd, "-----Video Timings-----\n"); + switch (tda1997x_detect_std(state, &timings)) { + case -ENOLINK: + v4l2_info(sd, "No video detected\n"); + break; + case -ERANGE: + v4l2_info(sd, "Invalid signal detected\n"); + break; + } + v4l2_print_dv_timings(sd->name, "Configured format: ", + &state->timings, true); + + v4l2_info(sd, "-----Color space-----\n"); + v4l2_info(sd, "Input color space: %s %s %s", + hdmi_colorspace_names[avi->colorspace], + (avi->colorspace == HDMI_COLORSPACE_RGB) ? "" : + hdmi_colorimetry_names[avi->colorimetry], + v4l2_quantization_names[state->colorimetry.quantization]); + v4l2_info(sd, "Output color space: %s", + vidfmt_names[state->vid_fmt]); + v4l2_info(sd, "Color space conversion: %s", state->conv ? + state->conv->name : "None"); + + v4l2_info(sd, "-----Audio-----\n"); + if (state->audio_channels) { + v4l2_info(sd, "audio: %dch %dHz\n", state->audio_channels, + state->audio_samplerate); + } else { + v4l2_info(sd, "audio: none\n"); + } + + v4l2_info(sd, "-----Infoframes-----\n"); + tda1997x_log_infoframe(sd, AUD_IF); + tda1997x_log_infoframe(sd, SPD_IF); + tda1997x_log_infoframe(sd, AVI_IF); + + return 0; +} + +static int tda1997x_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_subdev_core_ops tda1997x_core_ops = { + .log_status = tda1997x_log_status, + .subscribe_event = tda1997x_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_ops + */ + +static const struct v4l2_subdev_ops tda1997x_subdev_ops = { + .core = &tda1997x_core_ops, + .video = &tda1997x_video_ops, + .pad = &tda1997x_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_controls + */ + +static int tda1997x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct tda1997x_state *state = to_state(sd); + + switch (ctrl->id) { + /* allow overriding the default RGB quantization range */ + case V4L2_CID_DV_RX_RGB_RANGE: + state->rgb_quantization_range = ctrl->val; + set_rgb_quantization_range(state); + tda1997x_configure_csc(sd); + return 0; + } + + return -EINVAL; +}; + +static int tda1997x_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct tda1997x_state *state = to_state(sd); + + if (ctrl->id == V4L2_CID_DV_RX_IT_CONTENT_TYPE) { + ctrl->val = state->avi_infoframe.content_type; + return 0; + } + return -EINVAL; +}; + +static const struct v4l2_ctrl_ops tda1997x_ctrl_ops = { + .s_ctrl = tda1997x_s_ctrl, + .g_volatile_ctrl = tda1997x_g_volatile_ctrl, +}; + +static int tda1997x_core_init(struct v4l2_subdev *sd) +{ + struct tda1997x_state *state = to_state(sd); + struct tda1997x_platform_data *pdata = &state->pdata; + u8 reg; + int i; + + /* disable HPD */ + io_write(sd, REG_HPD_AUTO_CTRL, HPD_AUTO_HPD_UNSEL); + if (state->chip_revision == 0) { + io_write(sd, REG_MAN_SUS_HDMI_SEL, MAN_DIS_HDCP | MAN_RST_HDCP); + io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); + } + + /* reset infoframe at end of start-up-sequencer */ + io_write(sd, REG_SUS_SET_RGB2, 0x06); + io_write(sd, REG_SUS_SET_RGB3, 0x06); + + /* Enable TMDS pull-ups */ + io_write(sd, REG_RT_MAN_CTRL, RT_MAN_CTRL_RT | + RT_MAN_CTRL_RT_B | RT_MAN_CTRL_RT_A); + + /* enable sync measurement timing */ + tda1997x_cec_write(sd, REG_PWR_CONTROL & 0xff, 0x04); + /* adjust CEC clock divider */ + tda1997x_cec_write(sd, REG_OSC_DIVIDER & 0xff, 0x03); + tda1997x_cec_write(sd, REG_EN_OSC_PERIOD_LSB & 0xff, 0xa0); + io_write(sd, REG_TIMER_D, 0x54); + /* enable power switch */ + reg = tda1997x_cec_read(sd, REG_CONTROL & 0xff); + reg |= 0x20; + tda1997x_cec_write(sd, REG_CONTROL & 0xff, reg); + mdelay(50); + + /* read the chip version */ + reg = io_read(sd, REG_VERSION); + /* get the chip configuration */ + reg = io_read(sd, REG_CMTP_REG10); + + /* enable interrupts we care about */ + io_write(sd, REG_INT_MASK_TOP, + INTERRUPT_HDCP | INTERRUPT_AUDIO | INTERRUPT_INFO | + INTERRUPT_RATE | INTERRUPT_SUS); + /* config_mtp,fmt,sus_end,sus_st */ + io_write(sd, REG_INT_MASK_SUS, MASK_MPT | MASK_FMT | MASK_SUS_END); + /* rate stability change for inputs A/B */ + io_write(sd, REG_INT_MASK_RATE, MASK_RATE_B_ST | MASK_RATE_A_ST); + /* aud,spd,avi*/ + io_write(sd, REG_INT_MASK_INFO, + MASK_AUD_IF | MASK_SPD_IF | MASK_AVI_IF); + /* audio_freq,audio_flg,mute_flg,fifo_err */ + io_write(sd, REG_INT_MASK_AUDIO, + MASK_AUDIO_FREQ_FLG | MASK_AUDIO_FLG | MASK_MUTE_FLG | + MASK_ERROR_FIFO_PT); + /* HDCP C5 state reached */ + io_write(sd, REG_INT_MASK_HDCP, MASK_STATE_C5); + /* 5V detect and HDP pulse end */ + io_write(sd, REG_INT_MASK_DDC, MASK_DET_5V); + /* don't care about AFE/MODE */ + io_write(sd, REG_INT_MASK_AFE, 0); + io_write(sd, REG_INT_MASK_MODE, 0); + + /* clear all interrupts */ + io_write(sd, REG_INT_FLG_CLR_TOP, 0xff); + io_write(sd, REG_INT_FLG_CLR_SUS, 0xff); + io_write(sd, REG_INT_FLG_CLR_DDC, 0xff); + io_write(sd, REG_INT_FLG_CLR_RATE, 0xff); + io_write(sd, REG_INT_FLG_CLR_MODE, 0xff); + io_write(sd, REG_INT_FLG_CLR_INFO, 0xff); + io_write(sd, REG_INT_FLG_CLR_AUDIO, 0xff); + io_write(sd, REG_INT_FLG_CLR_HDCP, 0xff); + io_write(sd, REG_INT_FLG_CLR_AFE, 0xff); + + /* init TMDS equalizer */ + if (state->chip_revision == 0) + io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); + io_write24(sd, REG_CLK_MIN_RATE, CLK_MIN_RATE); + io_write24(sd, REG_CLK_MAX_RATE, CLK_MAX_RATE); + if (state->chip_revision == 0) + io_write(sd, REG_WDL_CFG, WDL_CFG_VAL); + /* DC filter */ + io_write(sd, REG_DEEP_COLOR_CTRL, DC_FILTER_VAL); + /* disable test pattern */ + io_write(sd, REG_SVC_MODE, 0x00); + /* update HDMI INFO CTRL */ + io_write(sd, REG_INFO_CTRL, 0xff); + /* write HDMI INFO EXCEED value */ + io_write(sd, REG_INFO_EXCEED, 3); + + if (state->chip_revision == 0) + tda1997x_reset_n1(state); + + /* + * No HDCP acknowledge when HDCP is disabled + * and reset SUS to force format detection + */ + tda1997x_hdmi_info_reset(sd, NACK_HDCP, true); + + /* Set HPD low */ + tda1997x_manual_hpd(sd, HPD_LOW_BP); + + /* Configure receiver capabilities */ + io_write(sd, REG_HDCP_BCAPS, HDCP_HDMI | HDCP_FAST_REAUTH); + + /* Configure HDMI: Auto HDCP mode, packet controlled mute */ + reg = HDMI_CTRL_MUTE_AUTO << HDMI_CTRL_MUTE_SHIFT; + reg |= HDMI_CTRL_HDCP_AUTO << HDMI_CTRL_HDCP_SHIFT; + io_write(sd, REG_HDMI_CTRL, reg); + + /* reset start-up-sequencer to force format detection */ + tda1997x_hdmi_info_reset(sd, 0, true); + + /* disable matrix conversion */ + reg = io_read(sd, REG_VDP_CTRL); + reg |= VDP_CTRL_MATRIX_BP; + io_write(sd, REG_VDP_CTRL, reg); + + /* set video output mode */ + tda1997x_configure_vidout(state); + + /* configure video output port */ + for (i = 0; i < 9; i++) { + v4l_dbg(1, debug, state->client, "vidout_cfg[%d]=0x%02x\n", i, + pdata->vidout_port_cfg[i]); + io_write(sd, REG_VP35_32_CTRL + i, pdata->vidout_port_cfg[i]); + } + + /* configure audio output port */ + tda1997x_configure_audout(sd, 0); + + /* configure audio clock freq */ + switch (pdata->audout_mclk_fs) { + case 512: + reg = AUDIO_CLOCK_SEL_512FS; + break; + case 256: + reg = AUDIO_CLOCK_SEL_256FS; + break; + case 128: + reg = AUDIO_CLOCK_SEL_128FS; + break; + case 64: + reg = AUDIO_CLOCK_SEL_64FS; + break; + case 32: + reg = AUDIO_CLOCK_SEL_32FS; + break; + default: + reg = AUDIO_CLOCK_SEL_16FS; + break; + } + io_write(sd, REG_AUDIO_CLOCK, reg); + + /* reset advanced infoframes (ISRC1/ISRC2/ACP) */ + tda1997x_hdmi_info_reset(sd, RESET_AI, false); + /* reset infoframe */ + tda1997x_hdmi_info_reset(sd, RESET_IF, false); + /* reset audio infoframes */ + tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); + /* reset gamut */ + tda1997x_hdmi_info_reset(sd, RESET_GAMUT, false); + + /* get initial HDMI status */ + state->hdmi_status = io_read(sd, REG_HDMI_FLAGS); + + io_write(sd, REG_EDID_ENABLE, EDID_ENABLE_A_EN | EDID_ENABLE_B_EN); + return 0; +} + +static int tda1997x_set_power(struct tda1997x_state *state, bool on) +{ + int ret = 0; + + if (on) { + ret = regulator_bulk_enable(TDA1997X_NUM_SUPPLIES, + state->supplies); + msleep(300); + } else { + ret = regulator_bulk_disable(TDA1997X_NUM_SUPPLIES, + state->supplies); + } + + return ret; +} + +static const struct i2c_device_id tda1997x_i2c_id[] = { + {"tda19971", (kernel_ulong_t)&tda1997x_chip_info[TDA19971]}, + {"tda19973", (kernel_ulong_t)&tda1997x_chip_info[TDA19973]}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, tda1997x_i2c_id); + +static const struct of_device_id tda1997x_of_id[] __maybe_unused = { + { .compatible = "nxp,tda19971", .data = &tda1997x_chip_info[TDA19971] }, + { .compatible = "nxp,tda19973", .data = &tda1997x_chip_info[TDA19973] }, + { }, +}; +MODULE_DEVICE_TABLE(of, tda1997x_of_id); + +static int tda1997x_parse_dt(struct tda1997x_state *state) +{ + struct tda1997x_platform_data *pdata = &state->pdata; + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + struct device_node *ep; + struct device_node *np; + unsigned int flags; + const char *str; + int ret; + u32 v; + + /* + * setup default values: + * - HREF: active high from start to end of row + * - VS: Vertical Sync active high at beginning of frame + * - DE: Active high when data valid + * - A_CLK: 128*Fs + */ + pdata->vidout_sel_hs = HS_HREF_SEL_HREF_VHREF; + pdata->vidout_sel_vs = VS_VREF_SEL_VREF_HDMI; + pdata->vidout_sel_de = DE_FREF_SEL_DE_VHREF; + + np = state->client->dev.of_node; + ep = of_graph_get_next_endpoint(np, NULL); + if (!ep) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &bus_cfg); + if (ret) { + of_node_put(ep); + return ret; + } + of_node_put(ep); + pdata->vidout_bus_type = bus_cfg.bus_type; + + /* polarity of HS/VS/DE */ + flags = bus_cfg.bus.parallel.flags; + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + pdata->vidout_inv_hs = 1; + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + pdata->vidout_inv_vs = 1; + if (flags & V4L2_MBUS_DATA_ACTIVE_LOW) + pdata->vidout_inv_de = 1; + pdata->vidout_bus_width = bus_cfg.bus.parallel.bus_width; + + /* video output port config */ + ret = of_property_count_u32_elems(np, "nxp,vidout-portcfg"); + if (ret > 0) { + u32 reg, val, i; + + for (i = 0; i < ret / 2 && i < 9; i++) { + of_property_read_u32_index(np, "nxp,vidout-portcfg", + i * 2, ®); + of_property_read_u32_index(np, "nxp,vidout-portcfg", + i * 2 + 1, &val); + if (reg < 9) + pdata->vidout_port_cfg[reg] = val; + } + } else { + v4l_err(state->client, "nxp,vidout-portcfg missing\n"); + return -EINVAL; + } + + /* default to channel layout dictated by packet header */ + pdata->audout_layoutauto = true; + + pdata->audout_format = AUDFMT_TYPE_DISABLED; + if (!of_property_read_string(np, "nxp,audout-format", &str)) { + if (strcmp(str, "i2s") == 0) + pdata->audout_format = AUDFMT_TYPE_I2S; + else if (strcmp(str, "spdif") == 0) + pdata->audout_format = AUDFMT_TYPE_SPDIF; + else { + v4l_err(state->client, "nxp,audout-format invalid\n"); + return -EINVAL; + } + if (!of_property_read_u32(np, "nxp,audout-layout", &v)) { + switch (v) { + case 0: + case 1: + break; + default: + v4l_err(state->client, + "nxp,audout-layout invalid\n"); + return -EINVAL; + } + pdata->audout_layout = v; + } + if (!of_property_read_u32(np, "nxp,audout-width", &v)) { + switch (v) { + case 16: + case 32: + break; + default: + v4l_err(state->client, + "nxp,audout-width invalid\n"); + return -EINVAL; + } + pdata->audout_width = v; + } + if (!of_property_read_u32(np, "nxp,audout-mclk-fs", &v)) { + switch (v) { + case 512: + case 256: + case 128: + case 64: + case 32: + case 16: + break; + default: + v4l_err(state->client, + "nxp,audout-mclk-fs invalid\n"); + return -EINVAL; + } + pdata->audout_mclk_fs = v; + } + } + + return 0; +} + +static int tda1997x_get_regulators(struct tda1997x_state *state) +{ + int i; + + for (i = 0; i < TDA1997X_NUM_SUPPLIES; i++) + state->supplies[i].supply = tda1997x_supply_name[i]; + + return devm_regulator_bulk_get(&state->client->dev, + TDA1997X_NUM_SUPPLIES, + state->supplies); +} + +static int tda1997x_identify_module(struct tda1997x_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + enum tda1997x_type type; + u8 reg; + + /* Read chip configuration*/ + reg = io_read(sd, REG_CMTP_REG10); + state->tmdsb_clk = (reg >> 6) & 0x01; /* use tmds clock B_inv for B */ + state->tmdsb_soc = (reg >> 5) & 0x01; /* tmds of input B */ + state->port_30bit = (reg >> 2) & 0x03; /* 30bit vs 24bit */ + state->output_2p5 = (reg >> 1) & 0x01; /* output supply 2.5v */ + switch ((reg >> 4) & 0x03) { + case 0x00: + type = TDA19971; + break; + case 0x02: + case 0x03: + type = TDA19973; + break; + default: + dev_err(&state->client->dev, "unsupported chip ID\n"); + return -EIO; + } + if (state->info->type != type) { + dev_err(&state->client->dev, "chip id mismatch\n"); + return -EIO; + } + + /* read chip revision */ + state->chip_revision = io_read(sd, REG_CMTP_REG11); + + return 0; +} + +static const struct media_entity_operations tda1997x_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + + +/* ----------------------------------------------------------------------------- + * HDMI Audio Codec + */ + +/* refine sample-rate based on HDMI source */ +static int tda1997x_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tda1997x_state *state = snd_soc_dai_get_drvdata(dai); + struct snd_soc_component *component = dai->component; + struct snd_pcm_runtime *rtd = substream->runtime; + int rate, err; + + rate = state->audio_samplerate; + err = snd_pcm_hw_constraint_minmax(rtd, SNDRV_PCM_HW_PARAM_RATE, + rate, rate); + if (err < 0) { + dev_err(component->dev, "failed to constrain samplerate to %dHz\n", + rate); + return err; + } + dev_info(component->dev, "set samplerate constraint to %dHz\n", rate); + + return 0; +} + +static const struct snd_soc_dai_ops tda1997x_dai_ops = { + .startup = tda1997x_pcm_startup, +}; + +static struct snd_soc_dai_driver tda1997x_audio_dai = { + .name = "tda1997x", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + }, + .ops = &tda1997x_dai_ops, +}; + +static int tda1997x_codec_probe(struct snd_soc_component *component) +{ + return 0; +} + +static void tda1997x_codec_remove(struct snd_soc_component *component) +{ +} + +static struct snd_soc_component_driver tda1997x_codec_driver = { + .probe = tda1997x_codec_probe, + .remove = tda1997x_codec_remove, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int tda1997x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tda1997x_state *state; + struct tda1997x_platform_data *pdata; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + struct v4l2_ctrl *ctrl; + static const struct v4l2_dv_timings cea1920x1080 = + V4L2_DV_BT_CEA_1920X1080P60; + u32 *mbus_codes; + int i, ret; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + state = kzalloc(sizeof(struct tda1997x_state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->client = client; + pdata = &state->pdata; + if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) { + const struct of_device_id *oid; + + oid = of_match_node(tda1997x_of_id, client->dev.of_node); + state->info = oid->data; + + ret = tda1997x_parse_dt(state); + if (ret < 0) { + v4l_err(client, "DT parsing error\n"); + goto err_free_state; + } + } else if (client->dev.platform_data) { + struct tda1997x_platform_data *pdata = + client->dev.platform_data; + state->info = + (const struct tda1997x_chip_info *)id->driver_data; + state->pdata = *pdata; + } else { + v4l_err(client, "No platform data\n"); + ret = -ENODEV; + goto err_free_state; + } + + ret = tda1997x_get_regulators(state); + if (ret) + goto err_free_state; + + ret = tda1997x_set_power(state, 1); + if (ret) + goto err_free_state; + + mutex_init(&state->page_lock); + mutex_init(&state->lock); + state->page = 0xff; + + INIT_DELAYED_WORK(&state->delayed_work_enable_hpd, + tda1997x_delayed_work_enable_hpd); + + /* set video format based on chip and bus width */ + ret = tda1997x_identify_module(state); + if (ret) + goto err_free_mutex; + + /* initialize subdev */ + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &tda1997x_subdev_ops); + snprintf(sd->name, sizeof(sd->name), "%s %d-%04x", + id->name, i2c_adapter_id(client->adapter), + client->addr); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->entity.function = MEDIA_ENT_F_DV_DECODER; + sd->entity.ops = &tda1997x_media_ops; + + /* set allowed mbus modes based on chip, bus-type, and bus-width */ + i = 0; + mbus_codes = state->mbus_codes; + switch (state->info->type) { + case TDA19973: + switch (pdata->vidout_bus_type) { + case V4L2_MBUS_PARALLEL: + switch (pdata->vidout_bus_width) { + case 36: + mbus_codes[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + mbus_codes[i++] = MEDIA_BUS_FMT_YUV12_1X36; + fallthrough; + case 24: + mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + break; + } + break; + case V4L2_MBUS_BT656: + switch (pdata->vidout_bus_width) { + case 36: + case 24: + case 12: + mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_2X12; + mbus_codes[i++] = MEDIA_BUS_FMT_UYVY10_2X10; + mbus_codes[i++] = MEDIA_BUS_FMT_UYVY8_2X8; + break; + } + break; + default: + break; + } + break; + case TDA19971: + switch (pdata->vidout_bus_type) { + case V4L2_MBUS_PARALLEL: + switch (pdata->vidout_bus_width) { + case 24: + mbus_codes[i++] = MEDIA_BUS_FMT_RGB888_1X24; + mbus_codes[i++] = MEDIA_BUS_FMT_YUV8_1X24; + mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + fallthrough; + case 20: + mbus_codes[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + fallthrough; + case 16: + mbus_codes[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + break; + } + break; + case V4L2_MBUS_BT656: + switch (pdata->vidout_bus_width) { + case 24: + case 20: + case 16: + case 12: + mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_2X12; + fallthrough; + case 10: + mbus_codes[i++] = MEDIA_BUS_FMT_UYVY10_2X10; + fallthrough; + case 8: + mbus_codes[i++] = MEDIA_BUS_FMT_UYVY8_2X8; + break; + } + break; + default: + break; + } + break; + } + if (WARN_ON(i > ARRAY_SIZE(state->mbus_codes))) { + ret = -EINVAL; + goto err_free_mutex; + } + + /* default format */ + tda1997x_setup_format(state, state->mbus_codes[0]); + state->timings = cea1920x1080; + + /* + * default to SRGB full range quantization + * (in case we don't get an infoframe such as DVI signal + */ + state->colorimetry.colorspace = V4L2_COLORSPACE_SRGB; + state->colorimetry.quantization = V4L2_QUANTIZATION_FULL_RANGE; + + /* disable/reset HDCP to get correct I2C access to Rx HDMI */ + io_write(sd, REG_MAN_SUS_HDMI_SEL, MAN_RST_HDCP | MAN_DIS_HDCP); + + /* + * if N2 version, reset compdel_bp as it may generate some small pixel + * shifts in case of embedded sync/or delay lower than 4 + */ + if (state->chip_revision != 0) { + io_write(sd, REG_MAN_SUS_HDMI_SEL, 0x00); + io_write(sd, REG_VDP_CTRL, 0x1f); + } + + v4l_info(client, "NXP %s N%d detected\n", state->info->name, + state->chip_revision + 1); + v4l_info(client, "video: %dbit %s %d formats available\n", + pdata->vidout_bus_width, + (pdata->vidout_bus_type == V4L2_MBUS_PARALLEL) ? + "parallel" : "BT656", + i); + if (pdata->audout_format) { + v4l_info(client, "audio: %dch %s layout%d sysclk=%d*fs\n", + pdata->audout_layout ? 2 : 8, + audfmt_names[pdata->audout_format], + pdata->audout_layout, + pdata->audout_mclk_fs); + } + + ret = 0x34 + ((io_read(sd, REG_SLAVE_ADDR)>>4) & 0x03); + state->client_cec = devm_i2c_new_dummy_device(&client->dev, + client->adapter, ret); + if (IS_ERR(state->client_cec)) { + ret = PTR_ERR(state->client_cec); + goto err_free_mutex; + } + + v4l_info(client, "CEC slave address 0x%02x\n", ret); + + ret = tda1997x_core_init(sd); + if (ret) + goto err_free_mutex; + + /* control handlers */ + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 3); + ctrl = v4l2_ctrl_new_std_menu(hdl, &tda1997x_ctrl_ops, + V4L2_CID_DV_RX_IT_CONTENT_TYPE, + V4L2_DV_IT_CONTENT_TYPE_NO_ITC, 0, + V4L2_DV_IT_CONTENT_TYPE_NO_ITC); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + /* custom controls */ + state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0); + state->rgb_quantization_range_ctrl = v4l2_ctrl_new_std_menu(hdl, + &tda1997x_ctrl_ops, + V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, 0, + V4L2_DV_RGB_RANGE_AUTO); + state->sd.ctrl_handler = hdl; + if (hdl->error) { + ret = hdl->error; + goto err_free_handler; + } + v4l2_ctrl_handler_setup(hdl); + + /* initialize source pads */ + state->pads[TDA1997X_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, TDA1997X_NUM_PADS, + state->pads); + if (ret) { + v4l_err(client, "failed entity_init: %d", ret); + goto err_free_handler; + } + + ret = v4l2_async_register_subdev(sd); + if (ret) + goto err_free_media; + + /* register audio DAI */ + if (pdata->audout_format) { + u64 formats; + + if (pdata->audout_width == 32) + formats = SNDRV_PCM_FMTBIT_S32_LE; + else + formats = SNDRV_PCM_FMTBIT_S16_LE; + tda1997x_audio_dai.capture.formats = formats; + ret = devm_snd_soc_register_component(&state->client->dev, + &tda1997x_codec_driver, + &tda1997x_audio_dai, 1); + if (ret) { + dev_err(&client->dev, "register audio codec failed\n"); + goto err_free_media; + } + dev_set_drvdata(&state->client->dev, state); + v4l_info(state->client, "registered audio codec\n"); + } + + /* request irq */ + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, tda1997x_isr_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + KBUILD_MODNAME, state); + if (ret) { + v4l_err(client, "irq%d reg failed: %d\n", client->irq, ret); + goto err_free_media; + } + + return 0; + +err_free_media: + media_entity_cleanup(&sd->entity); +err_free_handler: + v4l2_ctrl_handler_free(&state->hdl); +err_free_mutex: + cancel_delayed_work(&state->delayed_work_enable_hpd); + mutex_destroy(&state->page_lock); + mutex_destroy(&state->lock); +err_free_state: + kfree(state); + dev_err(&client->dev, "%s failed: %d\n", __func__, ret); + + return ret; +} + +static int tda1997x_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tda1997x_state *state = to_state(sd); + struct tda1997x_platform_data *pdata = &state->pdata; + + if (pdata->audout_format) { + mutex_destroy(&state->audio_lock); + } + + disable_irq(state->client->irq); + tda1997x_power_mode(state, 0); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&state->hdl); + regulator_bulk_disable(TDA1997X_NUM_SUPPLIES, state->supplies); + cancel_delayed_work_sync(&state->delayed_work_enable_hpd); + mutex_destroy(&state->page_lock); + mutex_destroy(&state->lock); + + kfree(state); + + return 0; +} + +static struct i2c_driver tda1997x_i2c_driver = { + .driver = { + .name = "tda1997x", + .of_match_table = of_match_ptr(tda1997x_of_id), + }, + .probe = tda1997x_probe, + .remove = tda1997x_remove, + .id_table = tda1997x_i2c_id, +}; + +module_i2c_driver(tda1997x_i2c_driver); + +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>"); +MODULE_DESCRIPTION("TDA1997X HDMI Receiver driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/tda1997x_regs.h b/drivers/media/i2c/tda1997x_regs.h new file mode 100644 index 000000000..d9b3daada --- /dev/null +++ b/drivers/media/i2c/tda1997x_regs.h @@ -0,0 +1,641 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Gateworks Corporation + */ + +/* Page 0x00 - General Control */ +#define REG_VERSION 0x0000 +#define REG_INPUT_SEL 0x0001 +#define REG_SVC_MODE 0x0002 +#define REG_HPD_MAN_CTRL 0x0003 +#define REG_RT_MAN_CTRL 0x0004 +#define REG_STANDBY_SOFT_RST 0x000A +#define REG_HDMI_SOFT_RST 0x000B +#define REG_HDMI_INFO_RST 0x000C +#define REG_INT_FLG_CLR_TOP 0x000E +#define REG_INT_FLG_CLR_SUS 0x000F +#define REG_INT_FLG_CLR_DDC 0x0010 +#define REG_INT_FLG_CLR_RATE 0x0011 +#define REG_INT_FLG_CLR_MODE 0x0012 +#define REG_INT_FLG_CLR_INFO 0x0013 +#define REG_INT_FLG_CLR_AUDIO 0x0014 +#define REG_INT_FLG_CLR_HDCP 0x0015 +#define REG_INT_FLG_CLR_AFE 0x0016 +#define REG_INT_MASK_TOP 0x0017 +#define REG_INT_MASK_SUS 0x0018 +#define REG_INT_MASK_DDC 0x0019 +#define REG_INT_MASK_RATE 0x001A +#define REG_INT_MASK_MODE 0x001B +#define REG_INT_MASK_INFO 0x001C +#define REG_INT_MASK_AUDIO 0x001D +#define REG_INT_MASK_HDCP 0x001E +#define REG_INT_MASK_AFE 0x001F +#define REG_DETECT_5V 0x0020 +#define REG_SUS_STATUS 0x0021 +#define REG_V_PER 0x0022 +#define REG_H_PER 0x0025 +#define REG_HS_WIDTH 0x0027 +#define REG_FMT_H_TOT 0x0029 +#define REG_FMT_H_ACT 0x002b +#define REG_FMT_H_FRONT 0x002d +#define REG_FMT_H_SYNC 0x002f +#define REG_FMT_H_BACK 0x0031 +#define REG_FMT_V_TOT 0x0033 +#define REG_FMT_V_ACT 0x0035 +#define REG_FMT_V_FRONT_F1 0x0037 +#define REG_FMT_V_FRONT_F2 0x0038 +#define REG_FMT_V_SYNC 0x0039 +#define REG_FMT_V_BACK_F1 0x003a +#define REG_FMT_V_BACK_F2 0x003b +#define REG_FMT_DE_ACT 0x003c +#define REG_RATE_CTRL 0x0040 +#define REG_CLK_MIN_RATE 0x0043 +#define REG_CLK_MAX_RATE 0x0046 +#define REG_CLK_A_STATUS 0x0049 +#define REG_CLK_A_RATE 0x004A +#define REG_DRIFT_CLK_A_REG 0x004D +#define REG_CLK_B_STATUS 0x004E +#define REG_CLK_B_RATE 0x004F +#define REG_DRIFT_CLK_B_REG 0x0052 +#define REG_HDCP_CTRL 0x0060 +#define REG_HDCP_KDS 0x0061 +#define REG_HDCP_BCAPS 0x0063 +#define REG_HDCP_KEY_CTRL 0x0064 +#define REG_INFO_CTRL 0x0076 +#define REG_INFO_EXCEED 0x0077 +#define REG_PIX_REPEAT 0x007B +#define REG_AUDIO_PATH 0x007C +#define REG_AUDCFG 0x007D +#define REG_AUDIO_OUT_ENABLE 0x007E +#define REG_AUDIO_OUT_HIZ 0x007F +#define REG_VDP_CTRL 0x0080 +#define REG_VDP_MATRIX 0x0081 +#define REG_VHREF_CTRL 0x00A0 +#define REG_PXCNT_PR 0x00A2 +#define REG_PXCNT_NPIX 0x00A4 +#define REG_LCNT_PR 0x00A6 +#define REG_LCNT_NLIN 0x00A8 +#define REG_HREF_S 0x00AA +#define REG_HREF_E 0x00AC +#define REG_HS_S 0x00AE +#define REG_HS_E 0x00B0 +#define REG_VREF_F1_S 0x00B2 +#define REG_VREF_F1_WIDTH 0x00B4 +#define REG_VREF_F2_S 0x00B5 +#define REG_VREF_F2_WIDTH 0x00B7 +#define REG_VS_F1_LINE_S 0x00B8 +#define REG_VS_F1_LINE_WIDTH 0x00BA +#define REG_VS_F2_LINE_S 0x00BB +#define REG_VS_F2_LINE_WIDTH 0x00BD +#define REG_VS_F1_PIX_S 0x00BE +#define REG_VS_F1_PIX_E 0x00C0 +#define REG_VS_F2_PIX_S 0x00C2 +#define REG_VS_F2_PIX_E 0x00C4 +#define REG_FREF_F1_S 0x00C6 +#define REG_FREF_F2_S 0x00C8 +#define REG_FDW_S 0x00ca +#define REG_FDW_E 0x00cc +#define REG_BLK_GY 0x00da +#define REG_BLK_BU 0x00dc +#define REG_BLK_RV 0x00de +#define REG_FILTERS_CTRL 0x00e0 +#define REG_DITHERING_CTRL 0x00E9 +#define REG_OF 0x00EA +#define REG_PCLK 0x00EB +#define REG_HS_HREF 0x00EC +#define REG_VS_VREF 0x00ED +#define REG_DE_FREF 0x00EE +#define REG_VP35_32_CTRL 0x00EF +#define REG_VP31_28_CTRL 0x00F0 +#define REG_VP27_24_CTRL 0x00F1 +#define REG_VP23_20_CTRL 0x00F2 +#define REG_VP19_16_CTRL 0x00F3 +#define REG_VP15_12_CTRL 0x00F4 +#define REG_VP11_08_CTRL 0x00F5 +#define REG_VP07_04_CTRL 0x00F6 +#define REG_VP03_00_CTRL 0x00F7 +#define REG_CURPAGE_00H 0xFF + +#define MASK_VPER 0x3fffff +#define MASK_VHREF 0x3fff +#define MASK_HPER 0x0fff +#define MASK_HSWIDTH 0x03ff + +/* HPD Detection */ +#define DETECT_UTIL BIT(7) /* utility of HDMI level */ +#define DETECT_HPD BIT(6) /* HPD of HDMI level */ +#define DETECT_5V_SEL BIT(2) /* 5V present on selected input */ +#define DETECT_5V_B BIT(1) /* 5V present on input B */ +#define DETECT_5V_A BIT(0) /* 5V present on input A */ + +/* Input Select */ +#define INPUT_SEL_RST_FMT BIT(7) /* 1=reset format measurement */ +#define INPUT_SEL_RST_VDP BIT(2) /* 1=reset video data path */ +#define INPUT_SEL_OUT_MODE BIT(1) /* 0=loop 1=bypass */ +#define INPUT_SEL_B BIT(0) /* 0=inputA 1=inputB */ + +/* Service Mode */ +#define SVC_MODE_CLK2_MASK 0xc0 +#define SVC_MODE_CLK2_SHIFT 6 +#define SVC_MODE_CLK2_XTL 0L +#define SVC_MODE_CLK2_XTLDIV2 1L +#define SVC_MODE_CLK2_HDMIX2 3L +#define SVC_MODE_CLK1_MASK 0x30 +#define SVC_MODE_CLK1_SHIFT 4 +#define SVC_MODE_CLK1_XTAL 0L +#define SVC_MODE_CLK1_XTLDIV2 1L +#define SVC_MODE_CLK1_HDMI 3L +#define SVC_MODE_RAMP BIT(3) /* 0=colorbar 1=ramp */ +#define SVC_MODE_PAL BIT(2) /* 0=NTSC(480i/p) 1=PAL(576i/p) */ +#define SVC_MODE_INT_PROG BIT(1) /* 0=interlaced 1=progressive */ +#define SVC_MODE_SM_ON BIT(0) /* Enable color bars and tone gen */ + +/* HDP Manual Control */ +#define HPD_MAN_CTRL_HPD_PULSE BIT(7) /* HPD Pulse low 110ms */ +#define HPD_MAN_CTRL_5VEN BIT(2) /* Output 5V */ +#define HPD_MAN_CTRL_HPD_B BIT(1) /* Assert HPD High for Input A */ +#define HPD_MAN_CTRL_HPD_A BIT(0) /* Assert HPD High for Input A */ + +/* RT_MAN_CTRL */ +#define RT_MAN_CTRL_RT_AUTO BIT(7) +#define RT_MAN_CTRL_RT BIT(6) +#define RT_MAN_CTRL_RT_B BIT(1) /* enable TMDS pull-up on Input B */ +#define RT_MAN_CTRL_RT_A BIT(0) /* enable TMDS pull-up on Input A */ + +/* VDP_CTRL */ +#define VDP_CTRL_COMPDEL_BP BIT(5) /* bypass compdel */ +#define VDP_CTRL_FORMATTER_BP BIT(4) /* bypass formatter */ +#define VDP_CTRL_PREFILTER_BP BIT(1) /* bypass prefilter */ +#define VDP_CTRL_MATRIX_BP BIT(0) /* bypass matrix conversion */ + +/* REG_VHREF_CTRL */ +#define VHREF_INT_DET BIT(7) /* interlace detect: 1=alt 0=frame */ +#define VHREF_VSYNC_MASK 0x60 +#define VHREF_VSYNC_SHIFT 6 +#define VHREF_VSYNC_AUTO 0L +#define VHREF_VSYNC_FDW 1L +#define VHREF_VSYNC_EVEN 2L +#define VHREF_VSYNC_ODD 3L +#define VHREF_STD_DET_MASK 0x18 +#define VHREF_STD_DET_SHIFT 3 +#define VHREF_STD_DET_PAL 0L +#define VHREF_STD_DET_NTSC 1L +#define VHREF_STD_DET_AUTO 2L +#define VHREF_STD_DET_OFF 3L +#define VHREF_VREF_SRC_STD BIT(2) /* 1=from standard 0=manual */ +#define VHREF_HREF_SRC_STD BIT(1) /* 1=from standard 0=manual */ +#define VHREF_HSYNC_SEL_HS BIT(0) /* 1=HS 0=VS */ + +/* AUDIO_OUT_ENABLE */ +#define AUDIO_OUT_ENABLE_ACLK BIT(5) +#define AUDIO_OUT_ENABLE_WS BIT(4) +#define AUDIO_OUT_ENABLE_AP3 BIT(3) +#define AUDIO_OUT_ENABLE_AP2 BIT(2) +#define AUDIO_OUT_ENABLE_AP1 BIT(1) +#define AUDIO_OUT_ENABLE_AP0 BIT(0) + +/* Prefilter Control */ +#define FILTERS_CTRL_BU_MASK 0x0c +#define FILTERS_CTRL_BU_SHIFT 2 +#define FILTERS_CTRL_RV_MASK 0x03 +#define FILTERS_CTRL_RV_SHIFT 0 +#define FILTERS_CTRL_OFF 0L /* off */ +#define FILTERS_CTRL_2TAP 1L /* 2 Taps */ +#define FILTERS_CTRL_7TAP 2L /* 7 Taps */ +#define FILTERS_CTRL_2_7TAP 3L /* 2/7 Taps */ + +/* PCLK Configuration */ +#define PCLK_DELAY_MASK 0x70 +#define PCLK_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ +#define PCLK_INV_SHIFT 2 +#define PCLK_SEL_MASK 0x03 /* clock scaler */ +#define PCLK_SEL_SHIFT 0 +#define PCLK_SEL_X1 0L +#define PCLK_SEL_X2 1L +#define PCLK_SEL_DIV2 2L +#define PCLK_SEL_DIV4 3L + +/* Pixel Repeater */ +#define PIX_REPEAT_MASK_UP_SEL 0x30 +#define PIX_REPEAT_MASK_REP 0x0f +#define PIX_REPEAT_SHIFT 4 +#define PIX_REPEAT_CHROMA 1 + +/* Page 0x01 - HDMI info and packets */ +#define REG_HDMI_FLAGS 0x0100 +#define REG_DEEP_COLOR_MODE 0x0101 +#define REG_AUDIO_FLAGS 0x0108 +#define REG_AUDIO_FREQ 0x0109 +#define REG_ACP_PACKET_TYPE 0x0141 +#define REG_ISRC1_PACKET_TYPE 0x0161 +#define REG_ISRC2_PACKET_TYPE 0x0181 +#define REG_GBD_PACKET_TYPE 0x01a1 + +/* HDMI_FLAGS */ +#define HDMI_FLAGS_AUDIO BIT(7) /* Audio packet in last videoframe */ +#define HDMI_FLAGS_HDMI BIT(6) /* HDMI detected */ +#define HDMI_FLAGS_EESS BIT(5) /* EESS detected */ +#define HDMI_FLAGS_HDCP BIT(4) /* HDCP detected */ +#define HDMI_FLAGS_AVMUTE BIT(3) /* AVMUTE */ +#define HDMI_FLAGS_AUD_LAYOUT BIT(2) /* Layout status Audio sample packet */ +#define HDMI_FLAGS_AUD_FIFO_OF BIT(1) /* FIFO read/write pointers crossed */ +#define HDMI_FLAGS_AUD_FIFO_LOW BIT(0) /* FIFO read ptr within 2 of write */ + +/* Page 0x12 - HDMI Extra control and debug */ +#define REG_CLK_CFG 0x1200 +#define REG_CLK_OUT_CFG 0x1201 +#define REG_CFG1 0x1202 +#define REG_CFG2 0x1203 +#define REG_WDL_CFG 0x1210 +#define REG_DELOCK_DELAY 0x1212 +#define REG_PON_OVR_EN 0x12A0 +#define REG_PON_CBIAS 0x12A1 +#define REG_PON_RESCAL 0x12A2 +#define REG_PON_RES 0x12A3 +#define REG_PON_CLK 0x12A4 +#define REG_PON_PLL 0x12A5 +#define REG_PON_EQ 0x12A6 +#define REG_PON_DES 0x12A7 +#define REG_PON_OUT 0x12A8 +#define REG_PON_MUX 0x12A9 +#define REG_MODE_REC_CFG1 0x12F8 +#define REG_MODE_REC_CFG2 0x12F9 +#define REG_MODE_REC_STS 0x12FA +#define REG_AUDIO_LAYOUT 0x12D0 + +#define PON_EN 1 +#define PON_DIS 0 + +/* CLK CFG */ +#define CLK_CFG_INV_OUT_CLK BIT(7) +#define CLK_CFG_INV_BUS_CLK BIT(6) +#define CLK_CFG_SEL_ACLK_EN BIT(1) +#define CLK_CFG_SEL_ACLK BIT(0) +#define CLK_CFG_DIS 0 + +/* Page 0x13 - HDMI Extra control and debug */ +#define REG_DEEP_COLOR_CTRL 0x1300 +#define REG_CGU_DBG_SEL 0x1305 +#define REG_HDCP_DDC_ADDR 0x1310 +#define REG_HDCP_KIDX 0x1316 +#define REG_DEEP_PLL7_BYP 0x1347 +#define REG_HDCP_DE_CTRL 0x1370 +#define REG_HDCP_EP_FILT_CTRL 0x1371 +#define REG_HDMI_CTRL 0x1377 +#define REG_HMTP_CTRL 0x137a +#define REG_TIMER_D 0x13CF +#define REG_SUS_SET_RGB0 0x13E1 +#define REG_SUS_SET_RGB1 0x13E2 +#define REG_SUS_SET_RGB2 0x13E3 +#define REG_SUS_SET_RGB3 0x13E4 +#define REG_SUS_SET_RGB4 0x13E5 +#define REG_MAN_SUS_HDMI_SEL 0x13E8 +#define REG_MAN_HDMI_SET 0x13E9 +#define REG_SUS_CLOCK_GOOD 0x13EF + +/* HDCP DE Control */ +#define HDCP_DE_MODE_MASK 0xc0 /* DE Measurement mode */ +#define HDCP_DE_MODE_SHIFT 6 +#define HDCP_DE_REGEN_EN BIT(5) /* enable regen mode */ +#define HDCP_DE_FILTER_MASK 0x18 /* DE filter sensitivity */ +#define HDCP_DE_FILTER_SHIFT 3 +#define HDCP_DE_COMP_MASK 0x07 /* DE Composition mode */ +#define HDCP_DE_COMP_MIXED 6L +#define HDCP_DE_COMP_OR 5L +#define HDCP_DE_COMP_AND 4L +#define HDCP_DE_COMP_CH3 3L +#define HDCP_DE_COMP_CH2 2L +#define HDCP_DE_COMP_CH1 1L +#define HDCP_DE_COMP_CH0 0L + +/* HDCP EP Filter Control */ +#define HDCP_EP_FIL_CTL_MASK 0x30 +#define HDCP_EP_FIL_CTL_SHIFT 4 +#define HDCP_EP_FIL_VS_MASK 0x0c +#define HDCP_EP_FIL_VS_SHIFT 2 +#define HDCP_EP_FIL_HS_MASK 0x03 +#define HDCP_EP_FIL_HS_SHIFT 0 + +/* HDMI_CTRL */ +#define HDMI_CTRL_MUTE_MASK 0x0c +#define HDMI_CTRL_MUTE_SHIFT 2 +#define HDMI_CTRL_MUTE_AUTO 0L +#define HDMI_CTRL_MUTE_OFF 1L +#define HDMI_CTRL_MUTE_ON 2L +#define HDMI_CTRL_HDCP_MASK 0x03 +#define HDMI_CTRL_HDCP_SHIFT 0 +#define HDMI_CTRL_HDCP_EESS 2L +#define HDMI_CTRL_HDCP_OESS 1L +#define HDMI_CTRL_HDCP_AUTO 0L + +/* CGU_DBG_SEL bits */ +#define CGU_DBG_CLK_SEL_MASK 0x18 +#define CGU_DBG_CLK_SEL_SHIFT 3 +#define CGU_DBG_XO_FRO_SEL BIT(2) +#define CGU_DBG_VDP_CLK_SEL BIT(1) +#define CGU_DBG_PIX_CLK_SEL BIT(0) + +/* REG_MAN_SUS_HDMI_SEL / REG_MAN_HDMI_SET bits */ +#define MAN_DIS_OUT_BUF BIT(7) +#define MAN_DIS_ANA_PATH BIT(6) +#define MAN_DIS_HDCP BIT(5) +#define MAN_DIS_TMDS_ENC BIT(4) +#define MAN_DIS_TMDS_FLOW BIT(3) +#define MAN_RST_HDCP BIT(2) +#define MAN_RST_TMDS_ENC BIT(1) +#define MAN_RST_TMDS_FLOW BIT(0) + +/* Page 0x14 - Audio Extra control and debug */ +#define REG_FIFO_LATENCY_VAL 0x1403 +#define REG_AUDIO_CLOCK 0x1411 +#define REG_TEST_NCTS_CTRL 0x1415 +#define REG_TEST_AUDIO_FREQ 0x1426 +#define REG_TEST_MODE 0x1437 + +/* Audio Clock Configuration */ +#define AUDIO_CLOCK_PLL_PD BIT(7) /* powerdown PLL */ +#define AUDIO_CLOCK_SEL_MASK 0x7f +#define AUDIO_CLOCK_SEL_16FS 0L /* 16*fs */ +#define AUDIO_CLOCK_SEL_32FS 1L /* 32*fs */ +#define AUDIO_CLOCK_SEL_64FS 2L /* 64*fs */ +#define AUDIO_CLOCK_SEL_128FS 3L /* 128*fs */ +#define AUDIO_CLOCK_SEL_256FS 4L /* 256*fs */ +#define AUDIO_CLOCK_SEL_512FS 5L /* 512*fs */ + +/* Page 0x20: EDID and Hotplug Detect */ +#define REG_EDID_IN_BYTE0 0x2000 /* EDID base */ +#define REG_EDID_IN_VERSION 0x2080 +#define REG_EDID_ENABLE 0x2081 +#define REG_HPD_POWER 0x2084 +#define REG_HPD_AUTO_CTRL 0x2085 +#define REG_HPD_DURATION 0x2086 +#define REG_RX_HPD_HEAC 0x2087 + +/* EDID_ENABLE */ +#define EDID_ENABLE_NACK_OFF BIT(7) +#define EDID_ENABLE_EDID_ONLY BIT(6) +#define EDID_ENABLE_B_EN BIT(1) +#define EDID_ENABLE_A_EN BIT(0) + +/* HPD Power */ +#define HPD_POWER_BP_MASK 0x0c +#define HPD_POWER_BP_SHIFT 2 +#define HPD_POWER_BP_LOW 0L +#define HPD_POWER_BP_HIGH 1L +#define HPD_POWER_EDID_ONLY BIT(1) + +/* HPD Auto control */ +#define HPD_AUTO_READ_EDID BIT(7) +#define HPD_AUTO_HPD_F3TECH BIT(5) +#define HPD_AUTO_HP_OTHER BIT(4) +#define HPD_AUTO_HPD_UNSEL BIT(3) +#define HPD_AUTO_HPD_ALL_CH BIT(2) +#define HPD_AUTO_HPD_PRV_CH BIT(1) +#define HPD_AUTO_HPD_NEW_CH BIT(0) + +/* Page 0x21 - EDID content */ +#define REG_EDID_IN_BYTE128 0x2100 /* CEA Extension block */ +#define REG_EDID_IN_SPA_SUB 0x2180 +#define REG_EDID_IN_SPA_AB_A 0x2181 +#define REG_EDID_IN_SPA_CD_A 0x2182 +#define REG_EDID_IN_CKSUM_A 0x2183 +#define REG_EDID_IN_SPA_AB_B 0x2184 +#define REG_EDID_IN_SPA_CD_B 0x2185 +#define REG_EDID_IN_CKSUM_B 0x2186 + +/* Page 0x30 - NV Configuration */ +#define REG_RT_AUTO_CTRL 0x3000 +#define REG_EQ_MAN_CTRL0 0x3001 +#define REG_EQ_MAN_CTRL1 0x3002 +#define REG_OUTPUT_CFG 0x3003 +#define REG_MUTE_CTRL 0x3004 +#define REG_SLAVE_ADDR 0x3005 +#define REG_CMTP_REG6 0x3006 +#define REG_CMTP_REG7 0x3007 +#define REG_CMTP_REG8 0x3008 +#define REG_CMTP_REG9 0x3009 +#define REG_CMTP_REGA 0x300A +#define REG_CMTP_REGB 0x300B +#define REG_CMTP_REGC 0x300C +#define REG_CMTP_REGD 0x300D +#define REG_CMTP_REGE 0x300E +#define REG_CMTP_REGF 0x300F +#define REG_CMTP_REG10 0x3010 +#define REG_CMTP_REG11 0x3011 + +/* Page 0x80 - CEC */ +#define REG_PWR_CONTROL 0x80F4 +#define REG_OSC_DIVIDER 0x80F5 +#define REG_EN_OSC_PERIOD_LSB 0x80F8 +#define REG_CONTROL 0x80FF + +/* global interrupt flags (INT_FLG_CRL_TOP) */ +#define INTERRUPT_AFE BIT(7) /* AFE module */ +#define INTERRUPT_HDCP BIT(6) /* HDCP module */ +#define INTERRUPT_AUDIO BIT(5) /* Audio module */ +#define INTERRUPT_INFO BIT(4) /* Infoframe module */ +#define INTERRUPT_MODE BIT(3) /* HDMI mode module */ +#define INTERRUPT_RATE BIT(2) /* rate module */ +#define INTERRUPT_DDC BIT(1) /* DDC module */ +#define INTERRUPT_SUS BIT(0) /* SUS module */ + +/* INT_FLG_CLR_HDCP bits */ +#define MASK_HDCP_MTP BIT(7) /* HDCP MTP busy */ +#define MASK_HDCP_DLMTP BIT(4) /* HDCP end download MTP to SRAM */ +#define MASK_HDCP_DLRAM BIT(3) /* HDCP end download keys from SRAM */ +#define MASK_HDCP_ENC BIT(2) /* HDCP ENC */ +#define MASK_STATE_C5 BIT(1) /* HDCP State C5 reached */ +#define MASK_AKSV BIT(0) /* AKSV received (start of auth) */ + +/* INT_FLG_CLR_RATE bits */ +#define MASK_RATE_B_DRIFT BIT(7) /* Rate measurement drifted */ +#define MASK_RATE_B_ST BIT(6) /* Rate measurement stability change */ +#define MASK_RATE_B_ACT BIT(5) /* Rate measurement activity change */ +#define MASK_RATE_B_PST BIT(4) /* Rate measreument presence change */ +#define MASK_RATE_A_DRIFT BIT(3) /* Rate measurement drifted */ +#define MASK_RATE_A_ST BIT(2) /* Rate measurement stability change */ +#define MASK_RATE_A_ACT BIT(1) /* Rate measurement presence change */ +#define MASK_RATE_A_PST BIT(0) /* Rate measreument presence change */ + +/* INT_FLG_CLR_SUS (Start Up Sequencer) bits */ +#define MASK_MPT BIT(7) /* Config MTP end of process */ +#define MASK_FMT BIT(5) /* Video format changed */ +#define MASK_RT_PULSE BIT(4) /* End of termination resistance pulse */ +#define MASK_SUS_END BIT(3) /* SUS last state reached */ +#define MASK_SUS_ACT BIT(2) /* Activity of selected input changed */ +#define MASK_SUS_CH BIT(1) /* Selected input changed */ +#define MASK_SUS_ST BIT(0) /* SUS state changed */ + +/* INT_FLG_CLR_DDC bits */ +#define MASK_EDID_MTP BIT(7) /* EDID MTP end of process */ +#define MASK_DDC_ERR BIT(6) /* master DDC error */ +#define MASK_DDC_CMD_DONE BIT(5) /* master DDC cmd send correct */ +#define MASK_READ_DONE BIT(4) /* End of down EDID read */ +#define MASK_RX_DDC_SW BIT(3) /* Output DDC switching finished */ +#define MASK_HDCP_DDC_SW BIT(2) /* HDCP DDC switching finished */ +#define MASK_HDP_PULSE_END BIT(1) /* End of Hot Plug Detect pulse */ +#define MASK_DET_5V BIT(0) /* Detection of +5V */ + +/* INT_FLG_CLR_MODE bits */ +#define MASK_HDMI_FLG BIT(7) /* HDMI mode/avmute/encrypt/FIFO fail */ +#define MASK_GAMUT BIT(6) /* Gamut packet */ +#define MASK_ISRC2 BIT(5) /* ISRC2 packet */ +#define MASK_ISRC1 BIT(4) /* ISRC1 packet */ +#define MASK_ACP BIT(3) /* Audio Content Protection packet */ +#define MASK_DC_NO_GCP BIT(2) /* GCP not received in 5 frames */ +#define MASK_DC_PHASE BIT(1) /* deepcolor pixel phase needs update */ +#define MASK_DC_MODE BIT(0) /* deepcolor color depth changed */ + +/* INT_FLG_CLR_INFO bits (Infoframe Change Status) */ +#define MASK_MPS_IF BIT(6) /* MPEG Source Product */ +#define MASK_AUD_IF BIT(5) /* Audio */ +#define MASK_SPD_IF BIT(4) /* Source Product Descriptor */ +#define MASK_AVI_IF BIT(3) /* Auxiliary Video IF */ +#define MASK_VS_IF_OTHER_BK2 BIT(2) /* Vendor Specific (bank2) */ +#define MASK_VS_IF_OTHER_BK1 BIT(1) /* Vendor Specific (bank1) */ +#define MASK_VS_IF_HDMI BIT(0) /* Vendor Specific (w/ HDMI LLC code) */ + +/* INT_FLG_CLR_AUDIO bits */ +#define MASK_AUDIO_FREQ_FLG BIT(5) /* Audio freq change */ +#define MASK_AUDIO_FLG BIT(4) /* DST, OBA, HBR, ASP change */ +#define MASK_MUTE_FLG BIT(3) /* Audio Mute */ +#define MASK_CH_STATE BIT(2) /* Channel status */ +#define MASK_UNMUTE_FIFO BIT(1) /* Audio Unmute */ +#define MASK_ERROR_FIFO_PT BIT(0) /* Audio FIFO pointer error */ + +/* INT_FLG_CLR_AFE bits */ +#define MASK_AFE_WDL_UNLOCKED BIT(7) /* Wordlocker was unlocked */ +#define MASK_AFE_GAIN_DONE BIT(6) /* Gain calibration done */ +#define MASK_AFE_OFFSET_DONE BIT(5) /* Offset calibration done */ +#define MASK_AFE_ACTIVITY_DET BIT(4) /* Activity detected on data */ +#define MASK_AFE_PLL_LOCK BIT(3) /* TMDS PLL is locked */ +#define MASK_AFE_TRMCAL_DONE BIT(2) /* Termination calibration done */ +#define MASK_AFE_ASU_STATE BIT(1) /* ASU state is reached */ +#define MASK_AFE_ASU_READY BIT(0) /* AFE calibration done: TMDS ready */ + +/* Audio Output */ +#define AUDCFG_CLK_INVERT BIT(7) /* invert A_CLK polarity */ +#define AUDCFG_TEST_TONE BIT(6) /* enable test tone generator */ +#define AUDCFG_BUS_SHIFT 5 +#define AUDCFG_BUS_I2S 0L +#define AUDCFG_BUS_SPDIF 1L +#define AUDCFG_I2SW_SHIFT 4 +#define AUDCFG_I2SW_16 0L +#define AUDCFG_I2SW_32 1L +#define AUDCFG_AUTO_MUTE_EN BIT(3) /* Enable Automatic audio mute */ +#define AUDCFG_HBR_SHIFT 2 +#define AUDCFG_HBR_STRAIGHT 0L /* straight via AP0 */ +#define AUDCFG_HBR_DEMUX 1L /* demuxed via AP0:AP3 */ +#define AUDCFG_TYPE_MASK 0x03 +#define AUDCFG_TYPE_SHIFT 0 +#define AUDCFG_TYPE_DST 3L /* Direct Stream Transfer (DST) */ +#define AUDCFG_TYPE_OBA 2L /* One Bit Audio (OBA) */ +#define AUDCFG_TYPE_HBR 1L /* High Bit Rate (HBR) */ +#define AUDCFG_TYPE_PCM 0L /* Audio samples */ + +/* Video Formatter */ +#define OF_VP_ENABLE BIT(7) /* VP[35:0]/HS/VS/DE/CLK */ +#define OF_BLK BIT(4) /* blanking codes */ +#define OF_TRC BIT(3) /* timing codes (SAV/EAV) */ +#define OF_FMT_MASK 0x3 +#define OF_FMT_444 0L /* RGB444/YUV444 */ +#define OF_FMT_422_SMPT 1L /* YUV422 semi-planar */ +#define OF_FMT_422_CCIR 2L /* YUV422 CCIR656 */ + +/* HS/HREF output control */ +#define HS_HREF_DELAY_MASK 0xf0 +#define HS_HREF_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ +#define HS_HREF_PXQ_SHIFT 3 /* Timing codes from HREF */ +#define HS_HREF_INV_SHIFT 2 /* polarity (1=invert) */ +#define HS_HREF_SEL_MASK 0x03 +#define HS_HREF_SEL_SHIFT 0 +#define HS_HREF_SEL_HS_VHREF 0L /* HS from VHREF */ +#define HS_HREF_SEL_HREF_VHREF 1L /* HREF from VHREF */ +#define HS_HREF_SEL_HREF_HDMI 2L /* HREF from HDMI */ +#define HS_HREF_SEL_NONE 3L /* not generated */ + +/* VS output control */ +#define VS_VREF_DELAY_MASK 0xf0 +#define VS_VREF_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ +#define VS_VREF_INV_SHIFT 2 /* polarity (1=invert) */ +#define VS_VREF_SEL_MASK 0x03 +#define VS_VREF_SEL_SHIFT 0 +#define VS_VREF_SEL_VS_VHREF 0L /* VS from VHREF */ +#define VS_VREF_SEL_VREF_VHREF 1L /* VREF from VHREF */ +#define VS_VREF_SEL_VREF_HDMI 2L /* VREF from HDMI */ +#define VS_VREF_SEL_NONE 3L /* not generated */ + +/* DE/FREF output control */ +#define DE_FREF_DELAY_MASK 0xf0 +#define DE_FREF_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ +#define DE_FREF_DE_PXQ_SHIFT 3 /* Timing codes from DE */ +#define DE_FREF_INV_SHIFT 2 /* polarity (1=invert) */ +#define DE_FREF_SEL_MASK 0x03 +#define DE_FREF_SEL_SHIFT 0 +#define DE_FREF_SEL_DE_VHREF 0L /* DE from VHREF (HREF and not(VREF) */ +#define DE_FREF_SEL_FREF_VHREF 1L /* FREF from VHREF */ +#define DE_FREF_SEL_FREF_HDMI 2L /* FREF from HDMI */ +#define DE_FREF_SEL_NONE 3L /* not generated */ + +/* HDMI_SOFT_RST bits */ +#define RESET_DC BIT(7) /* Reset deep color module */ +#define RESET_HDCP BIT(6) /* Reset HDCP module */ +#define RESET_KSV BIT(5) /* Reset KSV-FIFO */ +#define RESET_SCFG BIT(4) /* Reset HDCP and repeater function */ +#define RESET_HCFG BIT(3) /* Reset HDCP DDC part */ +#define RESET_PA BIT(2) /* Reset polarity adjust */ +#define RESET_EP BIT(1) /* Reset Error protection */ +#define RESET_TMDS BIT(0) /* Reset TMDS (calib, encoding, flow) */ + +/* HDMI_INFO_RST bits */ +#define NACK_HDCP BIT(7) /* No ACK on HDCP request */ +#define RESET_FIFO BIT(4) /* Reset Audio FIFO control */ +#define RESET_GAMUT BIT(3) /* Clear Gamut packet */ +#define RESET_AI BIT(2) /* Clear ACP and ISRC packets */ +#define RESET_IF BIT(1) /* Clear all Audio infoframe packets */ +#define RESET_AUDIO BIT(0) /* Reset Audio FIFO control */ + +/* HDCP_BCAPS bits */ +#define HDCP_HDMI BIT(7) /* HDCP supports HDMI (vs DVI only) */ +#define HDCP_REPEATER BIT(6) /* HDCP supports repeater function */ +#define HDCP_READY BIT(5) /* set by repeater function */ +#define HDCP_FAST BIT(4) /* Up to 400kHz */ +#define HDCP_11 BIT(1) /* HDCP 1.1 supported */ +#define HDCP_FAST_REAUTH BIT(0) /* fast reauthentication supported */ + +/* Audio output formatter */ +#define AUDIO_LAYOUT_SP_FLAG BIT(2) /* sp flag used by FIFO */ +#define AUDIO_LAYOUT_MANUAL BIT(1) /* manual layout (vs per pkt) */ +#define AUDIO_LAYOUT_LAYOUT1 BIT(0) /* Layout1: AP0-3 vs Layout0:AP0 */ + +/* masks for interrupt status registers */ +#define MASK_SUS_STATUS 0x1F +#define LAST_STATE_REACHED 0x1B +#define MASK_CLK_STABLE 0x04 +#define MASK_CLK_ACTIVE 0x02 +#define MASK_SUS_STATE 0x10 +#define MASK_SR_FIFO_FIFO_CTRL 0x30 +#define MASK_AUDIO_FLAG 0x10 + +/* Rate measurement */ +#define RATE_REFTIM_ENABLE 0x01 +#define CLK_MIN_RATE 0x0057e4 +#define CLK_MAX_RATE 0x0395f8 +#define WDL_CFG_VAL 0x82 +#define DC_FILTER_VAL 0x31 + +/* Infoframe */ +#define VS_HDMI_IF_UPDATE 0x0200 +#define VS_HDMI_IF 0x0201 +#define VS_BK1_IF_UPDATE 0x0220 +#define VS_BK1_IF 0x0221 +#define VS_BK2_IF_UPDATE 0x0240 +#define VS_BK2_IF 0x0241 +#define AVI_IF_UPDATE 0x0260 +#define AVI_IF 0x0261 +#define SPD_IF_UPDATE 0x0280 +#define SPD_IF 0x0281 +#define AUD_IF_UPDATE 0x02a0 +#define AUD_IF 0x02a1 +#define MPS_IF_UPDATE 0x02c0 +#define MPS_IF 0x02c1 diff --git a/drivers/media/i2c/tda7432.c b/drivers/media/i2c/tda7432.c new file mode 100644 index 000000000..cbdc9be0a --- /dev/null +++ b/drivers/media/i2c/tda7432.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * For the STS-Thompson TDA7432 audio processor chip + * + * Handles audio functions: volume, balance, tone, loudness + * This driver will not complain if used with any + * other i2c device with the same address. + * + * Muting and tone control by Jonathan Isom <jisom@ematic.com> + * + * Copyright (c) 2000 Eric Sandeen <eric_sandeen@bigfoot.com> + * Copyright (c) 2006 Mauro Carvalho Chehab <mchehab@kernel.org> + * + * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Which was based on tda8425.c by Greg Alexander (c) 1998 + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * set to 2 if you'd like to be inundated with debug messages + * + * loudness - set between 0 and 15 for varying degrees of loudness effect + * + * maxvol - set maximum volume to +20db (1), default is 0db(0) + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/i2c.h> + +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> + +#ifndef VIDEO_AUDIO_BALANCE +# define VIDEO_AUDIO_BALANCE 32 +#endif + +MODULE_AUTHOR("Eric Sandeen <eric_sandeen@bigfoot.com>"); +MODULE_DESCRIPTION("bttv driver for the tda7432 audio processor chip"); +MODULE_LICENSE("GPL"); + +static int maxvol; +static int loudness; /* disable loudness by default */ +static int debug; /* insmod parameter */ +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Set debugging level from 0 to 3. Default is off(0)."); +module_param(loudness, int, S_IRUGO); +MODULE_PARM_DESC(loudness, "Turn loudness on(1) else off(0). Default is off(0)."); +module_param(maxvol, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(maxvol, "Set maximum volume to +20dB(0) else +0dB(1). Default is +20dB(0)."); + + +/* Structure of address and subaddresses for the tda7432 */ + +struct tda7432 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct { + /* bass/treble cluster */ + struct v4l2_ctrl *bass; + struct v4l2_ctrl *treble; + }; + struct { + /* mute/balance cluster */ + struct v4l2_ctrl *mute; + struct v4l2_ctrl *balance; + }; +}; + +static inline struct tda7432 *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tda7432, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tda7432, hdl)->sd; +} + +/* The TDA7432 is made by STS-Thompson + * http://www.st.com + * http://us.st.com/stonline/books/pdf/docs/4056.pdf + * + * TDA7432: I2C-bus controlled basic audio processor + * + * The TDA7432 controls basic audio functions like volume, balance, + * and tone control (including loudness). It also has four channel + * output (for front and rear). Since most vidcap cards probably + * don't have 4 channel output, this driver will set front & rear + * together (no independent control). + */ + + /* Subaddresses for TDA7432 */ + +#define TDA7432_IN 0x00 /* Input select */ +#define TDA7432_VL 0x01 /* Volume */ +#define TDA7432_TN 0x02 /* Bass, Treble (Tone) */ +#define TDA7432_LF 0x03 /* Attenuation LF (Left Front) */ +#define TDA7432_LR 0x04 /* Attenuation LR (Left Rear) */ +#define TDA7432_RF 0x05 /* Attenuation RF (Right Front) */ +#define TDA7432_RR 0x06 /* Attenuation RR (Right Rear) */ +#define TDA7432_LD 0x07 /* Loudness */ + + + /* Masks for bits in TDA7432 subaddresses */ + +/* Many of these not used - just for documentation */ + +/* Subaddress 0x00 - Input selection and bass control */ + +/* Bits 0,1,2 control input: + * 0x00 - Stereo input + * 0x02 - Mono input + * 0x03 - Mute (Using Attenuators Plays better with modules) + * Mono probably isn't used - I'm guessing only the stereo + * input is connected on most cards, so we'll set it to stereo. + * + * Bit 3 controls bass cut: 0/1 is non-symmetric/symmetric bass cut + * Bit 4 controls bass range: 0/1 is extended/standard bass range + * + * Highest 3 bits not used + */ + +#define TDA7432_STEREO_IN 0 +#define TDA7432_MONO_IN 2 /* Probably won't be used */ +#define TDA7432_BASS_SYM 1 << 3 +#define TDA7432_BASS_NORM 1 << 4 + +/* Subaddress 0x01 - Volume */ + +/* Lower 7 bits control volume from -79dB to +32dB in 1dB steps + * Recommended maximum is +20 dB + * + * +32dB: 0x00 + * +20dB: 0x0c + * 0dB: 0x20 + * -79dB: 0x6f + * + * MSB (bit 7) controls loudness: 1/0 is loudness on/off + */ + +#define TDA7432_VOL_0DB 0x20 +#define TDA7432_LD_ON 1 << 7 + + +/* Subaddress 0x02 - Tone control */ + +/* Bits 0,1,2 control absolute treble gain from 0dB to 14dB + * 0x0 is 14dB, 0x7 is 0dB + * + * Bit 3 controls treble attenuation/gain (sign) + * 1 = gain (+) + * 0 = attenuation (-) + * + * Bits 4,5,6 control absolute bass gain from 0dB to 14dB + * (This is only true for normal base range, set in 0x00) + * 0x0 << 4 is 14dB, 0x7 is 0dB + * + * Bit 7 controls bass attenuation/gain (sign) + * 1 << 7 = gain (+) + * 0 << 7 = attenuation (-) + * + * Example: + * 1 1 0 1 0 1 0 1 is +4dB bass, -4dB treble + */ + +#define TDA7432_TREBLE_0DB 0xf +#define TDA7432_TREBLE 7 +#define TDA7432_TREBLE_GAIN 1 << 3 +#define TDA7432_BASS_0DB 0xf +#define TDA7432_BASS 7 << 4 +#define TDA7432_BASS_GAIN 1 << 7 + + +/* Subaddress 0x03 - Left Front attenuation */ +/* Subaddress 0x04 - Left Rear attenuation */ +/* Subaddress 0x05 - Right Front attenuation */ +/* Subaddress 0x06 - Right Rear attenuation */ + +/* Bits 0,1,2,3,4 control attenuation from 0dB to -37.5dB + * in 1.5dB steps. + * + * 0x00 is 0dB + * 0x1f is -37.5dB + * + * Bit 5 mutes that channel when set (1 = mute, 0 = unmute) + * We'll use the mute on the input, though (above) + * Bits 6,7 unused + */ + +#define TDA7432_ATTEN_0DB 0x00 +#define TDA7432_MUTE 0x1 << 5 + + +/* Subaddress 0x07 - Loudness Control */ + +/* Bits 0,1,2,3 control loudness from 0dB to -15dB in 1dB steps + * when bit 4 is NOT set + * + * 0x0 is 0dB + * 0xf is -15dB + * + * If bit 4 is set, then there is a flat attenuation according to + * the lower 4 bits, as above. + * + * Bits 5,6,7 unused + */ + + + +/* Begin code */ + +static int tda7432_write(struct v4l2_subdev *sd, int subaddr, int val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned char buffer[2]; + + v4l2_dbg(2, debug, sd, "In tda7432_write\n"); + v4l2_dbg(1, debug, sd, "Writing %d 0x%x\n", subaddr, val); + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(client, buffer, 2)) { + v4l2_err(sd, "I/O error, trying (write %d 0x%x)\n", + subaddr, val); + return -1; + } + return 0; +} + +static int tda7432_set(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned char buf[16]; + + buf[0] = TDA7432_IN; + buf[1] = TDA7432_STEREO_IN | /* Main (stereo) input */ + TDA7432_BASS_SYM | /* Symmetric bass cut */ + TDA7432_BASS_NORM; /* Normal bass range */ + buf[2] = 0x3b; + if (loudness) /* Turn loudness on? */ + buf[2] |= TDA7432_LD_ON; + buf[3] = TDA7432_TREBLE_0DB | (TDA7432_BASS_0DB << 4); + buf[4] = TDA7432_ATTEN_0DB; + buf[5] = TDA7432_ATTEN_0DB; + buf[6] = TDA7432_ATTEN_0DB; + buf[7] = TDA7432_ATTEN_0DB; + buf[8] = loudness; + if (9 != i2c_master_send(client, buf, 9)) { + v4l2_err(sd, "I/O error, trying tda7432_set\n"); + return -1; + } + + return 0; +} + +static int tda7432_log_status(struct v4l2_subdev *sd) +{ + struct tda7432 *state = to_state(sd); + + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +static int tda7432_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct tda7432 *t = to_state(sd); + u8 bass, treble, volume; + u8 lf, lr, rf, rr; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + if (t->balance->val < 0) { + /* shifted to left, attenuate right */ + rr = rf = -t->balance->val; + lr = lf = TDA7432_ATTEN_0DB; + } else if (t->balance->val > 0) { + /* shifted to right, attenuate left */ + rr = rf = TDA7432_ATTEN_0DB; + lr = lf = t->balance->val; + } else { + /* centered */ + rr = rf = TDA7432_ATTEN_0DB; + lr = lf = TDA7432_ATTEN_0DB; + } + if (t->mute->val) { + lf |= TDA7432_MUTE; + lr |= TDA7432_MUTE; + rf |= TDA7432_MUTE; + rr |= TDA7432_MUTE; + } + /* Mute & update balance*/ + tda7432_write(sd, TDA7432_LF, lf); + tda7432_write(sd, TDA7432_LR, lr); + tda7432_write(sd, TDA7432_RF, rf); + tda7432_write(sd, TDA7432_RR, rr); + return 0; + case V4L2_CID_AUDIO_VOLUME: + volume = 0x6f - ctrl->val; + if (loudness) /* Turn on the loudness bit */ + volume |= TDA7432_LD_ON; + + tda7432_write(sd, TDA7432_VL, volume); + return 0; + case V4L2_CID_AUDIO_BASS: + bass = t->bass->val; + treble = t->treble->val; + if (bass >= 0x8) + bass = 14 - (bass - 8); + if (treble >= 0x8) + treble = 14 - (treble - 8); + + tda7432_write(sd, TDA7432_TN, 0x10 | (bass << 4) | treble); + return 0; + } + return -EINVAL; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops tda7432_ctrl_ops = { + .s_ctrl = tda7432_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops tda7432_core_ops = { + .log_status = tda7432_log_status, +}; + +static const struct v4l2_subdev_ops tda7432_ops = { + .core = &tda7432_core_ops, +}; + +/* ----------------------------------------------------------------------- */ + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tda7432_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tda7432 *t; + struct v4l2_subdev *sd; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + t = devm_kzalloc(&client->dev, sizeof(*t), GFP_KERNEL); + if (!t) + return -ENOMEM; + sd = &t->sd; + v4l2_i2c_subdev_init(sd, client, &tda7432_ops); + v4l2_ctrl_handler_init(&t->hdl, 5); + v4l2_ctrl_new_std(&t->hdl, &tda7432_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, maxvol ? 0x68 : 0x4f, 1, maxvol ? 0x5d : 0x47); + t->mute = v4l2_ctrl_new_std(&t->hdl, &tda7432_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + t->balance = v4l2_ctrl_new_std(&t->hdl, &tda7432_ctrl_ops, + V4L2_CID_AUDIO_BALANCE, -31, 31, 1, 0); + t->bass = v4l2_ctrl_new_std(&t->hdl, &tda7432_ctrl_ops, + V4L2_CID_AUDIO_BASS, 0, 14, 1, 7); + t->treble = v4l2_ctrl_new_std(&t->hdl, &tda7432_ctrl_ops, + V4L2_CID_AUDIO_TREBLE, 0, 14, 1, 7); + sd->ctrl_handler = &t->hdl; + if (t->hdl.error) { + int err = t->hdl.error; + + v4l2_ctrl_handler_free(&t->hdl); + return err; + } + v4l2_ctrl_cluster(2, &t->bass); + v4l2_ctrl_cluster(2, &t->mute); + v4l2_ctrl_handler_setup(&t->hdl); + if (loudness < 0 || loudness > 15) { + v4l2_warn(sd, "loudness parameter must be between 0 and 15\n"); + if (loudness < 0) + loudness = 0; + if (loudness > 15) + loudness = 15; + } + + tda7432_set(sd); + return 0; +} + +static int tda7432_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tda7432 *t = to_state(sd); + + tda7432_set(sd); + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&t->hdl); + return 0; +} + +static const struct i2c_device_id tda7432_id[] = { + { "tda7432", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tda7432_id); + +static struct i2c_driver tda7432_driver = { + .driver = { + .name = "tda7432", + }, + .probe = tda7432_probe, + .remove = tda7432_remove, + .id_table = tda7432_id, +}; + +module_i2c_driver(tda7432_driver); diff --git a/drivers/media/i2c/tda9840.c b/drivers/media/i2c/tda9840.c new file mode 100644 index 000000000..8c6dfe746 --- /dev/null +++ b/drivers/media/i2c/tda9840.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + /* + tda9840 - i2c-driver for the tda9840 by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + Copyright (C) 2008 Hans Verkuil <hverkuil@xs4all.nl> + + The tda9840 is a stereo/dual sound processor with digital + identification. It can be found at address 0x84 on the i2c-bus. + + For detailed information download the specifications directly + from SGS Thomson at http://www.st.com + + */ + + +#include <linux/module.h> +#include <linux/ioctl.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <media/v4l2-device.h> + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tda9840 driver"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define SWITCH 0x00 +#define LEVEL_ADJUST 0x02 +#define STEREO_ADJUST 0x03 +#define TEST 0x04 + +#define TDA9840_SET_MUTE 0x00 +#define TDA9840_SET_MONO 0x10 +#define TDA9840_SET_STEREO 0x2a +#define TDA9840_SET_LANG1 0x12 +#define TDA9840_SET_LANG2 0x1e +#define TDA9840_SET_BOTH 0x1a +#define TDA9840_SET_BOTH_R 0x16 +#define TDA9840_SET_EXTERNAL 0x7a + + +static void tda9840_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (i2c_smbus_write_byte_data(client, reg, val)) + v4l2_dbg(1, debug, sd, "error writing %02x to %02x\n", + val, reg); +} + +static int tda9840_status(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int rc; + u8 byte; + + rc = i2c_master_recv(client, &byte, 1); + if (rc != 1) { + v4l2_dbg(1, debug, sd, + "i2c_master_recv() failed\n"); + if (rc < 0) + return rc; + return -EIO; + } + + if (byte & 0x80) { + v4l2_dbg(1, debug, sd, + "TDA9840_DETECT: register contents invalid\n"); + return -EINVAL; + } + + v4l2_dbg(1, debug, sd, "TDA9840_DETECT: byte: 0x%02x\n", byte); + return byte & 0x60; +} + +static int tda9840_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *t) +{ + int stat = tda9840_status(sd); + int byte; + + if (t->index) + return -EINVAL; + + stat = stat < 0 ? 0 : stat; + if (stat == 0 || stat == 0x60) /* mono input */ + byte = TDA9840_SET_MONO; + else if (stat == 0x40) /* stereo input */ + byte = (t->audmode == V4L2_TUNER_MODE_MONO) ? + TDA9840_SET_MONO : TDA9840_SET_STEREO; + else { /* bilingual */ + switch (t->audmode) { + case V4L2_TUNER_MODE_LANG1_LANG2: + byte = TDA9840_SET_BOTH; + break; + case V4L2_TUNER_MODE_LANG2: + byte = TDA9840_SET_LANG2; + break; + default: + byte = TDA9840_SET_LANG1; + break; + } + } + v4l2_dbg(1, debug, sd, "TDA9840_SWITCH: 0x%02x\n", byte); + tda9840_write(sd, SWITCH, byte); + return 0; +} + +static int tda9840_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *t) +{ + int stat = tda9840_status(sd); + + if (stat < 0) + return stat; + + t->rxsubchans = V4L2_TUNER_SUB_MONO; + + switch (stat & 0x60) { + case 0x00: + t->rxsubchans = V4L2_TUNER_SUB_MONO; + break; + case 0x20: + t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + break; + case 0x40: + t->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; + break; + default: /* Incorrect detect */ + t->rxsubchans = V4L2_TUNER_MODE_MONO; + break; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_tuner_ops tda9840_tuner_ops = { + .s_tuner = tda9840_s_tuner, + .g_tuner = tda9840_g_tuner, +}; + +static const struct v4l2_subdev_ops tda9840_ops = { + .tuner = &tda9840_tuner_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int tda9840_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct v4l2_subdev *sd; + + /* let's see whether this adapter can support what we need */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + sd = devm_kzalloc(&client->dev, sizeof(*sd), GFP_KERNEL); + if (sd == NULL) + return -ENOMEM; + v4l2_i2c_subdev_init(sd, client, &tda9840_ops); + + /* set initial values for level & stereo - adjustment, mode */ + tda9840_write(sd, LEVEL_ADJUST, 0); + tda9840_write(sd, STEREO_ADJUST, 0); + tda9840_write(sd, SWITCH, TDA9840_SET_STEREO); + return 0; +} + +static int tda9840_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +static const struct i2c_device_id tda9840_id[] = { + { "tda9840", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tda9840_id); + +static struct i2c_driver tda9840_driver = { + .driver = { + .name = "tda9840", + }, + .probe = tda9840_probe, + .remove = tda9840_remove, + .id_table = tda9840_id, +}; + +module_i2c_driver(tda9840_driver); diff --git a/drivers/media/i2c/tea6415c.c b/drivers/media/i2c/tea6415c.c new file mode 100644 index 000000000..67378dbcc --- /dev/null +++ b/drivers/media/i2c/tea6415c.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + /* + tea6415c - i2c-driver for the tea6415c by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + Copyright (C) 2008 Hans Verkuil <hverkuil@xs4all.nl> + + The tea6415c is a bus controlled video-matrix-switch + with 8 inputs and 6 outputs. + It is cascadable, i.e. it can be found at the addresses + 0x86 and 0x06 on the i2c-bus. + + For detailed information download the specifications directly + from SGS Thomson at http://www.st.com + + */ + + +#include <linux/module.h> +#include <linux/ioctl.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <media/v4l2-device.h> +#include "tea6415c.h" + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tea6415c driver"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* makes a connection between the input-pin 'i' and the output-pin 'o' */ +static int tea6415c_s_routing(struct v4l2_subdev *sd, + u32 i, u32 o, u32 config) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 byte = 0; + int ret; + + v4l2_dbg(1, debug, sd, "i=%d, o=%d\n", i, o); + + /* check if the pins are valid */ + if (0 == ((1 == i || 3 == i || 5 == i || 6 == i || 8 == i || 10 == i || 20 == i || 11 == i) + && (18 == o || 17 == o || 16 == o || 15 == o || 14 == o || 13 == o))) + return -EINVAL; + + /* to understand this, have a look at the tea6415c-specs (p.5) */ + switch (o) { + case 18: + byte = 0x00; + break; + case 14: + byte = 0x20; + break; + case 16: + byte = 0x10; + break; + case 17: + byte = 0x08; + break; + case 15: + byte = 0x18; + break; + case 13: + byte = 0x28; + break; + } + + switch (i) { + case 5: + byte |= 0x00; + break; + case 8: + byte |= 0x04; + break; + case 3: + byte |= 0x02; + break; + case 20: + byte |= 0x06; + break; + case 6: + byte |= 0x01; + break; + case 10: + byte |= 0x05; + break; + case 1: + byte |= 0x03; + break; + case 11: + byte |= 0x07; + break; + } + + ret = i2c_smbus_write_byte(client, byte); + if (ret) { + v4l2_dbg(1, debug, sd, + "i2c_smbus_write_byte() failed, ret:%d\n", ret); + return -EIO; + } + return ret; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_video_ops tea6415c_video_ops = { + .s_routing = tea6415c_s_routing, +}; + +static const struct v4l2_subdev_ops tea6415c_ops = { + .video = &tea6415c_video_ops, +}; + +static int tea6415c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct v4l2_subdev *sd; + + /* let's see whether this adapter can support what we need */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + sd = devm_kzalloc(&client->dev, sizeof(*sd), GFP_KERNEL); + if (sd == NULL) + return -ENOMEM; + v4l2_i2c_subdev_init(sd, client, &tea6415c_ops); + return 0; +} + +static int tea6415c_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +static const struct i2c_device_id tea6415c_id[] = { + { "tea6415c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tea6415c_id); + +static struct i2c_driver tea6415c_driver = { + .driver = { + .name = "tea6415c", + }, + .probe = tea6415c_probe, + .remove = tea6415c_remove, + .id_table = tea6415c_id, +}; + +module_i2c_driver(tea6415c_driver); diff --git a/drivers/media/i2c/tea6415c.h b/drivers/media/i2c/tea6415c.h new file mode 100644 index 000000000..f43228207 --- /dev/null +++ b/drivers/media/i2c/tea6415c.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __INCLUDED_TEA6415C__ +#define __INCLUDED_TEA6415C__ + +/* the tea6415c's design is quite brain-dead. although there are + 8 inputs and 6 outputs, these aren't enumerated in any way. because + I don't want to say "connect input pin 20 to output pin 17", I define + a "virtual" pin-order. */ + +/* input pins */ +#define TEA6415C_OUTPUT1 18 +#define TEA6415C_OUTPUT2 14 +#define TEA6415C_OUTPUT3 16 +#define TEA6415C_OUTPUT4 17 +#define TEA6415C_OUTPUT5 13 +#define TEA6415C_OUTPUT6 15 + +/* output pins */ +#define TEA6415C_INPUT1 5 +#define TEA6415C_INPUT2 8 +#define TEA6415C_INPUT3 3 +#define TEA6415C_INPUT4 20 +#define TEA6415C_INPUT5 6 +#define TEA6415C_INPUT6 10 +#define TEA6415C_INPUT7 1 +#define TEA6415C_INPUT8 11 + +#endif diff --git a/drivers/media/i2c/tea6420.c b/drivers/media/i2c/tea6420.c new file mode 100644 index 000000000..712141b26 --- /dev/null +++ b/drivers/media/i2c/tea6420.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + /* + tea6420 - i2c-driver for the tea6420 by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + Copyright (C) 2008 Hans Verkuil <hverkuil@xs4all.nl> + + The tea6420 is a bus controlled audio-matrix with 5 stereo inputs, + 4 stereo outputs and gain control for each output. + It is cascadable, i.e. it can be found at the addresses 0x98 + and 0x9a on the i2c-bus. + + For detailed information download the specifications directly + from SGS Thomson at http://www.st.com + + */ + + +#include <linux/module.h> +#include <linux/ioctl.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <media/v4l2-device.h> +#include "tea6420.h" + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tea6420 driver"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* make a connection between the input 'i' and the output 'o' + with gain 'g' (note: i = 6 means 'mute') */ +static int tea6420_s_routing(struct v4l2_subdev *sd, + u32 i, u32 o, u32 config) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int g = (o >> 4) & 0xf; + u8 byte; + int ret; + + o &= 0xf; + v4l2_dbg(1, debug, sd, "i=%d, o=%d, g=%d\n", i, o, g); + + /* check if the parameters are valid */ + if (i < 1 || i > 6 || o < 1 || o > 4 || g < 0 || g > 6 || g % 2 != 0) + return -EINVAL; + + byte = ((o - 1) << 5); + byte |= (i - 1); + + /* to understand this, have a look at the tea6420-specs (p.5) */ + switch (g) { + case 0: + byte |= (3 << 3); + break; + case 2: + byte |= (2 << 3); + break; + case 4: + byte |= (1 << 3); + break; + case 6: + break; + } + + ret = i2c_smbus_write_byte(client, byte); + if (ret) { + v4l2_dbg(1, debug, sd, + "i2c_smbus_write_byte() failed, ret:%d\n", ret); + return -EIO; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_audio_ops tea6420_audio_ops = { + .s_routing = tea6420_s_routing, +}; + +static const struct v4l2_subdev_ops tea6420_ops = { + .audio = &tea6420_audio_ops, +}; + +static int tea6420_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct v4l2_subdev *sd; + int err, i; + + /* let's see whether this adapter can support what we need */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + sd = devm_kzalloc(&client->dev, sizeof(*sd), GFP_KERNEL); + if (sd == NULL) + return -ENOMEM; + v4l2_i2c_subdev_init(sd, client, &tea6420_ops); + + /* set initial values: set "mute"-input to all outputs at gain 0 */ + err = 0; + for (i = 1; i < 5; i++) + err += tea6420_s_routing(sd, 6, i, 0); + if (err) { + v4l_dbg(1, debug, client, "could not initialize tea6420\n"); + return -ENODEV; + } + return 0; +} + +static int tea6420_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +static const struct i2c_device_id tea6420_id[] = { + { "tea6420", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tea6420_id); + +static struct i2c_driver tea6420_driver = { + .driver = { + .name = "tea6420", + }, + .probe = tea6420_probe, + .remove = tea6420_remove, + .id_table = tea6420_id, +}; + +module_i2c_driver(tea6420_driver); diff --git a/drivers/media/i2c/tea6420.h b/drivers/media/i2c/tea6420.h new file mode 100644 index 000000000..07f9d72a8 --- /dev/null +++ b/drivers/media/i2c/tea6420.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __INCLUDED_TEA6420__ +#define __INCLUDED_TEA6420__ + +/* input pins */ +#define TEA6420_OUTPUT1 1 +#define TEA6420_OUTPUT2 2 +#define TEA6420_OUTPUT3 3 +#define TEA6420_OUTPUT4 4 + +/* output pins */ +#define TEA6420_INPUT1 1 +#define TEA6420_INPUT2 2 +#define TEA6420_INPUT3 3 +#define TEA6420_INPUT4 4 +#define TEA6420_INPUT5 5 +#define TEA6420_INPUT6 6 + +/* gain on the output pins, ORed with the output pin */ +#define TEA6420_GAIN0 0x00 +#define TEA6420_GAIN2 0x20 +#define TEA6420_GAIN4 0x40 +#define TEA6420_GAIN6 0x60 + +#endif diff --git a/drivers/media/i2c/ths7303.c b/drivers/media/i2c/ths7303.c new file mode 100644 index 000000000..8206bf7a5 --- /dev/null +++ b/drivers/media/i2c/ths7303.c @@ -0,0 +1,387 @@ +/* + * ths7303/53- THS7303/53 Video Amplifier driver + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. + * + * Author: Chaithrika U S <chaithrika@ti.com> + * + * Contributors: + * Hans Verkuil <hans.verkuil@cisco.com> + * Lad, Prabhakar <prabhakar.lad@ti.com> + * Martin Bugge <marbugge@cisco.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <media/i2c/ths7303.h> +#include <media/v4l2-device.h> + +#define THS7303_CHANNEL_1 1 +#define THS7303_CHANNEL_2 2 +#define THS7303_CHANNEL_3 3 + +struct ths7303_state { + struct v4l2_subdev sd; + const struct ths7303_platform_data *pdata; + struct v4l2_bt_timings bt; + int std_id; + int stream_on; +}; + +enum ths7303_filter_mode { + THS7303_FILTER_MODE_480I_576I, + THS7303_FILTER_MODE_480P_576P, + THS7303_FILTER_MODE_720P_1080I, + THS7303_FILTER_MODE_1080P, + THS7303_FILTER_MODE_DISABLE +}; + +MODULE_DESCRIPTION("TI THS7303 video amplifier driver"); +MODULE_AUTHOR("Chaithrika U S"); +MODULE_LICENSE("GPL"); + +static inline struct ths7303_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ths7303_state, sd); +} + +static int ths7303_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int ths7303_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret == 0) + return 0; + } + return ret; +} + +/* following function is used to set ths7303 */ +static int ths7303_setval(struct v4l2_subdev *sd, + enum ths7303_filter_mode mode) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ths7303_state *state = to_state(sd); + const struct ths7303_platform_data *pdata = state->pdata; + u8 val, sel = 0; + int err, disable = 0; + + if (!client) + return -EINVAL; + + switch (mode) { + case THS7303_FILTER_MODE_1080P: + sel = 0x3; /*1080p and SXGA/UXGA */ + break; + case THS7303_FILTER_MODE_720P_1080I: + sel = 0x2; /*720p, 1080i and SVGA/XGA */ + break; + case THS7303_FILTER_MODE_480P_576P: + sel = 0x1; /* EDTV 480p/576p and VGA */ + break; + case THS7303_FILTER_MODE_480I_576I: + sel = 0x0; /* SDTV, S-Video, 480i/576i */ + break; + default: + /* disable all channels */ + disable = 1; + } + + val = (sel << 6) | (sel << 3); + if (!disable) + val |= (pdata->ch_1 & 0x27); + err = ths7303_write(sd, THS7303_CHANNEL_1, val); + if (err) + goto out; + + val = (sel << 6) | (sel << 3); + if (!disable) + val |= (pdata->ch_2 & 0x27); + err = ths7303_write(sd, THS7303_CHANNEL_2, val); + if (err) + goto out; + + val = (sel << 6) | (sel << 3); + if (!disable) + val |= (pdata->ch_3 & 0x27); + err = ths7303_write(sd, THS7303_CHANNEL_3, val); + if (err) + goto out; + + return 0; +out: + pr_info("write byte data failed\n"); + return err; +} + +static int ths7303_s_std_output(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct ths7303_state *state = to_state(sd); + + if (norm & (V4L2_STD_ALL & ~V4L2_STD_SECAM)) { + state->std_id = 1; + state->bt.pixelclock = 0; + return ths7303_setval(sd, THS7303_FILTER_MODE_480I_576I); + } + + return ths7303_setval(sd, THS7303_FILTER_MODE_DISABLE); +} + +static int ths7303_config(struct v4l2_subdev *sd) +{ + struct ths7303_state *state = to_state(sd); + int res; + + if (!state->stream_on) { + ths7303_write(sd, THS7303_CHANNEL_1, + (ths7303_read(sd, THS7303_CHANNEL_1) & 0xf8) | + 0x00); + ths7303_write(sd, THS7303_CHANNEL_2, + (ths7303_read(sd, THS7303_CHANNEL_2) & 0xf8) | + 0x00); + ths7303_write(sd, THS7303_CHANNEL_3, + (ths7303_read(sd, THS7303_CHANNEL_3) & 0xf8) | + 0x00); + return 0; + } + + if (state->bt.pixelclock > 120000000) + res = ths7303_setval(sd, THS7303_FILTER_MODE_1080P); + else if (state->bt.pixelclock > 70000000) + res = ths7303_setval(sd, THS7303_FILTER_MODE_720P_1080I); + else if (state->bt.pixelclock > 20000000) + res = ths7303_setval(sd, THS7303_FILTER_MODE_480P_576P); + else if (state->std_id) + res = ths7303_setval(sd, THS7303_FILTER_MODE_480I_576I); + else + /* disable all channels */ + res = ths7303_setval(sd, THS7303_FILTER_MODE_DISABLE); + + return res; + +} + +static int ths7303_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ths7303_state *state = to_state(sd); + + state->stream_on = enable; + + return ths7303_config(sd); +} + +/* for setting filter for HD output */ +static int ths7303_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *dv_timings) +{ + struct ths7303_state *state = to_state(sd); + + if (!dv_timings || dv_timings->type != V4L2_DV_BT_656_1120) + return -EINVAL; + + state->bt = dv_timings->bt; + state->std_id = 0; + + return ths7303_config(sd); +} + +static const struct v4l2_subdev_video_ops ths7303_video_ops = { + .s_stream = ths7303_s_stream, + .s_std_output = ths7303_s_std_output, + .s_dv_timings = ths7303_s_dv_timings, +}; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + +static int ths7303_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + reg->size = 1; + reg->val = ths7303_read(sd, reg->reg); + return 0; +} + +static int ths7303_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + ths7303_write(sd, reg->reg, reg->val); + return 0; +} +#endif + +static const char * const stc_lpf_sel_txt[4] = { + "500-kHz Filter", + "2.5-MHz Filter", + "5-MHz Filter", + "5-MHz Filter", +}; + +static const char * const in_mux_sel_txt[2] = { + "Input A Select", + "Input B Select", +}; + +static const char * const lpf_freq_sel_txt[4] = { + "9-MHz LPF", + "16-MHz LPF", + "35-MHz LPF", + "Bypass LPF", +}; + +static const char * const in_bias_sel_dis_cont_txt[8] = { + "Disable Channel", + "Mute Function - No Output", + "DC Bias Select", + "DC Bias + 250 mV Offset Select", + "AC Bias Select", + "Sync Tip Clamp with low bias", + "Sync Tip Clamp with mid bias", + "Sync Tip Clamp with high bias", +}; + +static void ths7303_log_channel_status(struct v4l2_subdev *sd, u8 reg) +{ + u8 val = ths7303_read(sd, reg); + + if ((val & 0x7) == 0) { + v4l2_info(sd, "Channel %d Off\n", reg); + return; + } + + v4l2_info(sd, "Channel %d On\n", reg); + v4l2_info(sd, " value 0x%x\n", val); + v4l2_info(sd, " %s\n", stc_lpf_sel_txt[(val >> 6) & 0x3]); + v4l2_info(sd, " %s\n", in_mux_sel_txt[(val >> 5) & 0x1]); + v4l2_info(sd, " %s\n", lpf_freq_sel_txt[(val >> 3) & 0x3]); + v4l2_info(sd, " %s\n", in_bias_sel_dis_cont_txt[(val >> 0) & 0x7]); +} + +static int ths7303_log_status(struct v4l2_subdev *sd) +{ + struct ths7303_state *state = to_state(sd); + + v4l2_info(sd, "stream %s\n", state->stream_on ? "On" : "Off"); + + if (state->bt.pixelclock) { + struct v4l2_bt_timings *bt = &state->bt; + u32 frame_width, frame_height; + + frame_width = V4L2_DV_BT_FRAME_WIDTH(bt); + frame_height = V4L2_DV_BT_FRAME_HEIGHT(bt); + v4l2_info(sd, + "timings: %dx%d%s%d (%dx%d). Pix freq. = %d Hz. Polarities = 0x%x\n", + bt->width, bt->height, bt->interlaced ? "i" : "p", + (frame_height * frame_width) > 0 ? + (int)bt->pixelclock / + (frame_height * frame_width) : 0, + frame_width, frame_height, + (int)bt->pixelclock, bt->polarities); + } else { + v4l2_info(sd, "no timings set\n"); + } + + ths7303_log_channel_status(sd, THS7303_CHANNEL_1); + ths7303_log_channel_status(sd, THS7303_CHANNEL_2); + ths7303_log_channel_status(sd, THS7303_CHANNEL_3); + + return 0; +} + +static const struct v4l2_subdev_core_ops ths7303_core_ops = { + .log_status = ths7303_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ths7303_g_register, + .s_register = ths7303_s_register, +#endif +}; + +static const struct v4l2_subdev_ops ths7303_ops = { + .core = &ths7303_core_ops, + .video = &ths7303_video_ops, +}; + +static int ths7303_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ths7303_platform_data *pdata = client->dev.platform_data; + struct ths7303_state *state; + struct v4l2_subdev *sd; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(struct ths7303_state), + GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->pdata = pdata; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &ths7303_ops); + + /* set to default 480I_576I filter mode */ + if (ths7303_setval(sd, THS7303_FILTER_MODE_480I_576I) < 0) { + v4l_err(client, "Setting to 480I_576I filter mode failed!\n"); + return -EINVAL; + } + + return 0; +} + +static int ths7303_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + + return 0; +} + +static const struct i2c_device_id ths7303_id[] = { + {"ths7303", 0}, + {"ths7353", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ths7303_id); + +static struct i2c_driver ths7303_driver = { + .driver = { + .name = "ths73x3", + }, + .probe = ths7303_probe, + .remove = ths7303_remove, + .id_table = ths7303_id, +}; + +module_i2c_driver(ths7303_driver); diff --git a/drivers/media/i2c/ths8200.c b/drivers/media/i2c/ths8200.c new file mode 100644 index 000000000..c52fe84cb --- /dev/null +++ b/drivers/media/i2c/ths8200.c @@ -0,0 +1,509 @@ +/* + * ths8200 - Texas Instruments THS8200 video encoder driver + * + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/v4l2-dv-timings.h> + +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-async.h> +#include <media/v4l2-device.h> + +#include "ths8200_regs.h" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Texas Instruments THS8200 video encoder driver"); +MODULE_AUTHOR("Mats Randgaard <mats.randgaard@cisco.com>"); +MODULE_AUTHOR("Martin Bugge <martin.bugge@cisco.com>"); +MODULE_LICENSE("GPL v2"); + +struct ths8200_state { + struct v4l2_subdev sd; + uint8_t chip_version; + /* Is the ths8200 powered on? */ + bool power_on; + struct v4l2_dv_timings dv_timings; +}; + +static const struct v4l2_dv_timings_cap ths8200_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1080, 25000000, 148500000, + V4L2_DV_BT_STD_CEA861, V4L2_DV_BT_CAP_PROGRESSIVE) +}; + +static inline struct ths8200_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ths8200_state, sd); +} + +static inline unsigned htotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_WIDTH(t); +} + +static inline unsigned vtotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_HEIGHT(t); +} + +static int ths8200_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int ths8200_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "I2C Write Problem\n"); + return ret; +} + +/* To set specific bits in the register, a clear-mask is given (to be AND-ed), + * and then the value-mask (to be OR-ed). + */ +static inline void +ths8200_write_and_or(struct v4l2_subdev *sd, u8 reg, + uint8_t clr_mask, uint8_t val_mask) +{ + ths8200_write(sd, reg, (ths8200_read(sd, reg) & clr_mask) | val_mask); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG + +static int ths8200_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + reg->val = ths8200_read(sd, reg->reg & 0xff); + reg->size = 1; + + return 0; +} + +static int ths8200_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + ths8200_write(sd, reg->reg & 0xff, reg->val & 0xff); + + return 0; +} +#endif + +static int ths8200_log_status(struct v4l2_subdev *sd) +{ + struct ths8200_state *state = to_state(sd); + uint8_t reg_03 = ths8200_read(sd, THS8200_CHIP_CTL); + + v4l2_info(sd, "----- Chip status -----\n"); + v4l2_info(sd, "version: %u\n", state->chip_version); + v4l2_info(sd, "power: %s\n", (reg_03 & 0x0c) ? "off" : "on"); + v4l2_info(sd, "reset: %s\n", (reg_03 & 0x01) ? "off" : "on"); + v4l2_info(sd, "test pattern: %s\n", + (reg_03 & 0x20) ? "enabled" : "disabled"); + v4l2_info(sd, "format: %ux%u\n", + ths8200_read(sd, THS8200_DTG2_PIXEL_CNT_MSB) * 256 + + ths8200_read(sd, THS8200_DTG2_PIXEL_CNT_LSB), + (ths8200_read(sd, THS8200_DTG2_LINE_CNT_MSB) & 0x07) * 256 + + ths8200_read(sd, THS8200_DTG2_LINE_CNT_LSB)); + v4l2_print_dv_timings(sd->name, "Configured format:", + &state->dv_timings, true); + return 0; +} + +/* Power up/down ths8200 */ +static int ths8200_s_power(struct v4l2_subdev *sd, int on) +{ + struct ths8200_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s: power %s\n", __func__, on ? "on" : "off"); + + state->power_on = on; + + /* Power up/down - leave in reset state until input video is present */ + ths8200_write_and_or(sd, THS8200_CHIP_CTL, 0xf2, (on ? 0x00 : 0x0c)); + + return 0; +} + +static const struct v4l2_subdev_core_ops ths8200_core_ops = { + .log_status = ths8200_log_status, + .s_power = ths8200_s_power, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ths8200_g_register, + .s_register = ths8200_s_register, +#endif +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev video operations + */ + +static int ths8200_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ths8200_state *state = to_state(sd); + + if (enable && !state->power_on) + ths8200_s_power(sd, true); + + ths8200_write_and_or(sd, THS8200_CHIP_CTL, 0xfe, + (enable ? 0x01 : 0x00)); + + v4l2_dbg(1, debug, sd, "%s: %sable\n", + __func__, (enable ? "en" : "dis")); + + return 0; +} + +static void ths8200_core_init(struct v4l2_subdev *sd) +{ + /* setup clocks */ + ths8200_write_and_or(sd, THS8200_CHIP_CTL, 0x3f, 0xc0); + + /**** Data path control (DATA) ****/ + /* Set FSADJ 700 mV, + * bypass 422-444 interpolation, + * input format 30 bit RGB444 + */ + ths8200_write(sd, THS8200_DATA_CNTL, 0x70); + + /* DTG Mode (Video blocked during blanking + * VESA slave + */ + ths8200_write(sd, THS8200_DTG1_MODE, 0x87); + + /**** Display Timing Generator Control, Part 1 (DTG1). ****/ + + /* Disable embedded syncs on the output by setting + * the amplitude to zero for all channels. + */ + ths8200_write(sd, THS8200_DTG1_Y_SYNC_MSB, 0x00); + ths8200_write(sd, THS8200_DTG1_CBCR_SYNC_MSB, 0x00); +} + +static void ths8200_setup(struct v4l2_subdev *sd, struct v4l2_bt_timings *bt) +{ + uint8_t polarity = 0; + uint16_t line_start_active_video = (bt->vsync + bt->vbackporch); + uint16_t line_start_front_porch = (vtotal(bt) - bt->vfrontporch); + + /*** System ****/ + /* Set chip in reset while it is configured */ + ths8200_s_stream(sd, false); + + /* configure video output timings */ + ths8200_write(sd, THS8200_DTG1_SPEC_A, bt->hsync); + ths8200_write(sd, THS8200_DTG1_SPEC_B, bt->hfrontporch); + + /* Zero for progressive scan formats.*/ + if (!bt->interlaced) + ths8200_write(sd, THS8200_DTG1_SPEC_C, 0x00); + + /* Distance from leading edge of h sync to start of active video. + * MSB in 0x2b + */ + ths8200_write(sd, THS8200_DTG1_SPEC_D_LSB, + (bt->hbackporch + bt->hsync) & 0xff); + /* Zero for SDTV-mode. MSB in 0x2b */ + ths8200_write(sd, THS8200_DTG1_SPEC_E_LSB, 0x00); + /* + * MSB for dtg1_spec(d/e/h). See comment for + * corresponding LSB registers. + */ + ths8200_write(sd, THS8200_DTG1_SPEC_DEH_MSB, + ((bt->hbackporch + bt->hsync) & 0x100) >> 1); + + /* h front porch */ + ths8200_write(sd, THS8200_DTG1_SPEC_K_LSB, (bt->hfrontporch) & 0xff); + ths8200_write(sd, THS8200_DTG1_SPEC_K_MSB, + ((bt->hfrontporch) & 0x700) >> 8); + + /* Half the line length. Used to calculate SDTV line types. */ + ths8200_write(sd, THS8200_DTG1_SPEC_G_LSB, (htotal(bt)/2) & 0xff); + ths8200_write(sd, THS8200_DTG1_SPEC_G_MSB, + ((htotal(bt)/2) >> 8) & 0x0f); + + /* Total pixels per line (ex. 720p: 1650) */ + ths8200_write(sd, THS8200_DTG1_TOT_PIXELS_MSB, htotal(bt) >> 8); + ths8200_write(sd, THS8200_DTG1_TOT_PIXELS_LSB, htotal(bt) & 0xff); + + /* Frame height and field height */ + /* Field height should be programmed higher than frame_size for + * progressive scan formats + */ + ths8200_write(sd, THS8200_DTG1_FRAME_FIELD_SZ_MSB, + ((vtotal(bt) >> 4) & 0xf0) + 0x7); + ths8200_write(sd, THS8200_DTG1_FRAME_SZ_LSB, vtotal(bt) & 0xff); + + /* Should be programmed higher than frame_size + * for progressive formats + */ + if (!bt->interlaced) + ths8200_write(sd, THS8200_DTG1_FIELD_SZ_LSB, 0xff); + + /**** Display Timing Generator Control, Part 2 (DTG2). ****/ + /* Set breakpoint line numbers and types + * THS8200 generates line types with different properties. A line type + * that sets all the RGB-outputs to zero is used in the blanking areas, + * while a line type that enable the RGB-outputs is used in active video + * area. The line numbers for start of active video, start of front + * porch and after the last line in the frame must be set with the + * corresponding line types. + * + * Line types: + * 0x9 - Full normal sync pulse: Blocks data when dtg1_pass is off. + * Used in blanking area. + * 0x0 - Active video: Video data is always passed. Used in active + * video area. + */ + ths8200_write_and_or(sd, THS8200_DTG2_BP1_2_MSB, 0x88, + ((line_start_active_video >> 4) & 0x70) + + ((line_start_front_porch >> 8) & 0x07)); + ths8200_write(sd, THS8200_DTG2_BP3_4_MSB, ((vtotal(bt)) >> 4) & 0x70); + ths8200_write(sd, THS8200_DTG2_BP1_LSB, line_start_active_video & 0xff); + ths8200_write(sd, THS8200_DTG2_BP2_LSB, line_start_front_porch & 0xff); + ths8200_write(sd, THS8200_DTG2_BP3_LSB, (vtotal(bt)) & 0xff); + + /* line types */ + ths8200_write(sd, THS8200_DTG2_LINETYPE1, 0x90); + ths8200_write(sd, THS8200_DTG2_LINETYPE2, 0x90); + + /* h sync width transmitted */ + ths8200_write(sd, THS8200_DTG2_HLENGTH_LSB, bt->hsync & 0xff); + ths8200_write_and_or(sd, THS8200_DTG2_HLENGTH_LSB_HDLY_MSB, 0x3f, + (bt->hsync >> 2) & 0xc0); + + /* The pixel value h sync is asserted on */ + ths8200_write_and_or(sd, THS8200_DTG2_HLENGTH_LSB_HDLY_MSB, 0xe0, + (htotal(bt) >> 8) & 0x1f); + ths8200_write(sd, THS8200_DTG2_HLENGTH_HDLY_LSB, htotal(bt)); + + /* v sync width transmitted (must add 1 to get correct output) */ + ths8200_write(sd, THS8200_DTG2_VLENGTH1_LSB, (bt->vsync + 1) & 0xff); + ths8200_write_and_or(sd, THS8200_DTG2_VLENGTH1_MSB_VDLY1_MSB, 0x3f, + ((bt->vsync + 1) >> 2) & 0xc0); + + /* The pixel value v sync is asserted on (must add 1 to get correct output) */ + ths8200_write_and_or(sd, THS8200_DTG2_VLENGTH1_MSB_VDLY1_MSB, 0xf8, + ((vtotal(bt) + 1) >> 8) & 0x7); + ths8200_write(sd, THS8200_DTG2_VDLY1_LSB, vtotal(bt) + 1); + + /* For progressive video vlength2 must be set to all 0 and vdly2 must + * be set to all 1. + */ + ths8200_write(sd, THS8200_DTG2_VLENGTH2_LSB, 0x00); + ths8200_write(sd, THS8200_DTG2_VLENGTH2_MSB_VDLY2_MSB, 0x07); + ths8200_write(sd, THS8200_DTG2_VDLY2_LSB, 0xff); + + /* Internal delay factors to synchronize the sync pulses and the data */ + /* Experimental values delays (hor 0, ver 0) */ + ths8200_write(sd, THS8200_DTG2_HS_IN_DLY_MSB, 0); + ths8200_write(sd, THS8200_DTG2_HS_IN_DLY_LSB, 0); + ths8200_write(sd, THS8200_DTG2_VS_IN_DLY_MSB, 0); + ths8200_write(sd, THS8200_DTG2_VS_IN_DLY_LSB, 0); + + /* Polarity of received and transmitted sync signals */ + if (bt->polarities & V4L2_DV_HSYNC_POS_POL) { + polarity |= 0x01; /* HS_IN */ + polarity |= 0x08; /* HS_OUT */ + } + if (bt->polarities & V4L2_DV_VSYNC_POS_POL) { + polarity |= 0x02; /* VS_IN */ + polarity |= 0x10; /* VS_OUT */ + } + + /* RGB mode, no embedded timings */ + /* Timing of video input bus is derived from HS, VS, and FID dedicated + * inputs + */ + ths8200_write(sd, THS8200_DTG2_CNTL, 0x44 | polarity); + + /* leave reset */ + ths8200_s_stream(sd, true); + + v4l2_dbg(1, debug, sd, "%s: frame %dx%d, polarity %d\n" + "horizontal: front porch %d, back porch %d, sync %d\n" + "vertical: sync %d\n", __func__, htotal(bt), vtotal(bt), + polarity, bt->hfrontporch, bt->hbackporch, + bt->hsync, bt->vsync); +} + +static int ths8200_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct ths8200_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (!v4l2_valid_dv_timings(timings, &ths8200_timings_cap, + NULL, NULL)) + return -EINVAL; + + if (!v4l2_find_dv_timings_cap(timings, &ths8200_timings_cap, 10, + NULL, NULL)) { + v4l2_dbg(1, debug, sd, "Unsupported format\n"); + return -EINVAL; + } + + timings->bt.flags &= ~V4L2_DV_FL_REDUCED_FPS; + + /* save timings */ + state->dv_timings = *timings; + + ths8200_setup(sd, &timings->bt); + + return 0; +} + +static int ths8200_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct ths8200_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + *timings = state->dv_timings; + + return 0; +} + +static int ths8200_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->pad != 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, &ths8200_timings_cap, + NULL, NULL); +} + +static int ths8200_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + if (cap->pad != 0) + return -EINVAL; + + *cap = ths8200_timings_cap; + return 0; +} + +/* Specific video subsystem operation handlers */ +static const struct v4l2_subdev_video_ops ths8200_video_ops = { + .s_stream = ths8200_s_stream, + .s_dv_timings = ths8200_s_dv_timings, + .g_dv_timings = ths8200_g_dv_timings, +}; + +static const struct v4l2_subdev_pad_ops ths8200_pad_ops = { + .enum_dv_timings = ths8200_enum_dv_timings, + .dv_timings_cap = ths8200_dv_timings_cap, +}; + +/* V4L2 top level operation handlers */ +static const struct v4l2_subdev_ops ths8200_ops = { + .core = &ths8200_core_ops, + .video = &ths8200_video_ops, + .pad = &ths8200_pad_ops, +}; + +static int ths8200_probe(struct i2c_client *client) +{ + struct ths8200_state *state; + struct v4l2_subdev *sd; + int error; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &ths8200_ops); + + state->chip_version = ths8200_read(sd, THS8200_VERSION); + v4l2_dbg(1, debug, sd, "chip version 0x%x\n", state->chip_version); + + ths8200_core_init(sd); + + error = v4l2_async_register_subdev(&state->sd); + if (error) + return error; + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + return 0; +} + +static int ths8200_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ths8200_state *decoder = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + ths8200_s_power(sd, false); + v4l2_async_unregister_subdev(&decoder->sd); + + return 0; +} + +static const struct i2c_device_id ths8200_id[] = { + { "ths8200", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ths8200_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id ths8200_of_match[] = { + { .compatible = "ti,ths8200", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ths8200_of_match); +#endif + +static struct i2c_driver ths8200_driver = { + .driver = { + .name = "ths8200", + .of_match_table = of_match_ptr(ths8200_of_match), + }, + .probe_new = ths8200_probe, + .remove = ths8200_remove, + .id_table = ths8200_id, +}; + +module_i2c_driver(ths8200_driver); diff --git a/drivers/media/i2c/ths8200_regs.h b/drivers/media/i2c/ths8200_regs.h new file mode 100644 index 000000000..6bc9fd111 --- /dev/null +++ b/drivers/media/i2c/ths8200_regs.h @@ -0,0 +1,161 @@ +/* + * ths8200 - Texas Instruments THS8200 video encoder driver + * + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef THS8200_REGS_H +#define THS8200_REGS_H + +/* Register offset macros */ +#define THS8200_VERSION 0x02 +#define THS8200_CHIP_CTL 0x03 +#define THS8200_CSC_R11 0x04 +#define THS8200_CSC_R12 0x05 +#define THS8200_CSC_R21 0x06 +#define THS8200_CSC_R22 0x07 +#define THS8200_CSC_R31 0x08 +#define THS8200_CSC_R32 0x09 +#define THS8200_CSC_G11 0x0a +#define THS8200_CSC_G12 0x0b +#define THS8200_CSC_G21 0x0c +#define THS8200_CSC_G22 0x0d +#define THS8200_CSC_G31 0x0e +#define THS8200_CSC_G32 0x0f +#define THS8200_CSC_B11 0x10 +#define THS8200_CSC_B12 0x11 +#define THS8200_CSC_B21 0x12 +#define THS8200_CSC_B22 0x13 +#define THS8200_CSC_B31 0x14 +#define THS8200_CSC_B32 0x15 +#define THS8200_CSC_OFFS1 0x16 +#define THS8200_CSC_OFFS12 0x17 +#define THS8200_CSC_OFFS23 0x18 +#define THS8200_CSC_OFFS3 0x19 +#define THS8200_TST_CNTL1 0x1a +#define THS8200_TST_CNTL2 0x1b +#define THS8200_DATA_CNTL 0x1c +#define THS8200_DTG1_Y_SYNC1_LSB 0x1d +#define THS8200_DTG1_Y_SYNC2_LSB 0x1e +#define THS8200_DTG1_Y_SYNC3_LSB 0x1f +#define THS8200_DTG1_CBCR_SYNC1_LSB 0x20 +#define THS8200_DTG1_CBCR_SYNC2_LSB 0x21 +#define THS8200_DTG1_CBCR_SYNC3_LSB 0x22 +#define THS8200_DTG1_Y_SYNC_MSB 0x23 +#define THS8200_DTG1_CBCR_SYNC_MSB 0x24 +#define THS8200_DTG1_SPEC_A 0x25 +#define THS8200_DTG1_SPEC_B 0x26 +#define THS8200_DTG1_SPEC_C 0x27 +#define THS8200_DTG1_SPEC_D_LSB 0x28 +#define THS8200_DTG1_SPEC_D1 0x29 +#define THS8200_DTG1_SPEC_E_LSB 0x2a +#define THS8200_DTG1_SPEC_DEH_MSB 0x2b +#define THS8200_DTG1_SPEC_H_LSB 0x2c +#define THS8200_DTG1_SPEC_I_MSB 0x2d +#define THS8200_DTG1_SPEC_I_LSB 0x2e +#define THS8200_DTG1_SPEC_K_LSB 0x2f +#define THS8200_DTG1_SPEC_K_MSB 0x30 +#define THS8200_DTG1_SPEC_K1 0x31 +#define THS8200_DTG1_SPEC_G_LSB 0x32 +#define THS8200_DTG1_SPEC_G_MSB 0x33 +#define THS8200_DTG1_TOT_PIXELS_MSB 0x34 +#define THS8200_DTG1_TOT_PIXELS_LSB 0x35 +#define THS8200_DTG1_FLD_FLIP_LINECNT_MSB 0x36 +#define THS8200_DTG1_LINECNT_LSB 0x37 +#define THS8200_DTG1_MODE 0x38 +#define THS8200_DTG1_FRAME_FIELD_SZ_MSB 0x39 +#define THS8200_DTG1_FRAME_SZ_LSB 0x3a +#define THS8200_DTG1_FIELD_SZ_LSB 0x3b +#define THS8200_DTG1_VESA_CBAR_SIZE 0x3c +#define THS8200_DAC_CNTL_MSB 0x3d +#define THS8200_DAC1_CNTL_LSB 0x3e +#define THS8200_DAC2_CNTL_LSB 0x3f +#define THS8200_DAC3_CNTL_LSB 0x40 +#define THS8200_CSM_CLIP_GY_LOW 0x41 +#define THS8200_CSM_CLIP_BCB_LOW 0x42 +#define THS8200_CSM_CLIP_RCR_LOW 0x43 +#define THS8200_CSM_CLIP_GY_HIGH 0x44 +#define THS8200_CSM_CLIP_BCB_HIGH 0x45 +#define THS8200_CSM_CLIP_RCR_HIGH 0x46 +#define THS8200_CSM_SHIFT_GY 0x47 +#define THS8200_CSM_SHIFT_BCB 0x48 +#define THS8200_CSM_SHIFT_RCR 0x49 +#define THS8200_CSM_GY_CNTL_MULT_MSB 0x4a +#define THS8200_CSM_MULT_BCB_RCR_MSB 0x4b +#define THS8200_CSM_MULT_GY_LSB 0x4c +#define THS8200_CSM_MULT_BCB_LSB 0x4d +#define THS8200_CSM_MULT_RCR_LSB 0x4e +#define THS8200_CSM_MULT_RCR_BCB_CNTL 0x4f +#define THS8200_CSM_MULT_RCR_LSB 0x4e +#define THS8200_DTG2_BP1_2_MSB 0x50 +#define THS8200_DTG2_BP3_4_MSB 0x51 +#define THS8200_DTG2_BP5_6_MSB 0x52 +#define THS8200_DTG2_BP7_8_MSB 0x53 +#define THS8200_DTG2_BP9_10_MSB 0x54 +#define THS8200_DTG2_BP11_12_MSB 0x55 +#define THS8200_DTG2_BP13_14_MSB 0x56 +#define THS8200_DTG2_BP15_16_MSB 0x57 +#define THS8200_DTG2_BP1_LSB 0x58 +#define THS8200_DTG2_BP2_LSB 0x59 +#define THS8200_DTG2_BP3_LSB 0x5a +#define THS8200_DTG2_BP4_LSB 0x5b +#define THS8200_DTG2_BP5_LSB 0x5c +#define THS8200_DTG2_BP6_LSB 0x5d +#define THS8200_DTG2_BP7_LSB 0x5e +#define THS8200_DTG2_BP8_LSB 0x5f +#define THS8200_DTG2_BP9_LSB 0x60 +#define THS8200_DTG2_BP10_LSB 0x61 +#define THS8200_DTG2_BP11_LSB 0x62 +#define THS8200_DTG2_BP12_LSB 0x63 +#define THS8200_DTG2_BP13_LSB 0x64 +#define THS8200_DTG2_BP14_LSB 0x65 +#define THS8200_DTG2_BP15_LSB 0x66 +#define THS8200_DTG2_BP16_LSB 0x67 +#define THS8200_DTG2_LINETYPE1 0x68 +#define THS8200_DTG2_LINETYPE2 0x69 +#define THS8200_DTG2_LINETYPE3 0x6a +#define THS8200_DTG2_LINETYPE4 0x6b +#define THS8200_DTG2_LINETYPE5 0x6c +#define THS8200_DTG2_LINETYPE6 0x6d +#define THS8200_DTG2_LINETYPE7 0x6e +#define THS8200_DTG2_LINETYPE8 0x6f +#define THS8200_DTG2_HLENGTH_LSB 0x70 +#define THS8200_DTG2_HLENGTH_LSB_HDLY_MSB 0x71 +#define THS8200_DTG2_HLENGTH_HDLY_LSB 0x72 +#define THS8200_DTG2_VLENGTH1_LSB 0x73 +#define THS8200_DTG2_VLENGTH1_MSB_VDLY1_MSB 0x74 +#define THS8200_DTG2_VDLY1_LSB 0x75 +#define THS8200_DTG2_VLENGTH2_LSB 0x76 +#define THS8200_DTG2_VLENGTH2_MSB_VDLY2_MSB 0x77 +#define THS8200_DTG2_VDLY2_LSB 0x78 +#define THS8200_DTG2_HS_IN_DLY_MSB 0x79 +#define THS8200_DTG2_HS_IN_DLY_LSB 0x7a +#define THS8200_DTG2_VS_IN_DLY_MSB 0x7b +#define THS8200_DTG2_VS_IN_DLY_LSB 0x7c +#define THS8200_DTG2_PIXEL_CNT_MSB 0x7d +#define THS8200_DTG2_PIXEL_CNT_LSB 0x7e +#define THS8200_DTG2_LINE_CNT_MSB 0x7f +#define THS8200_DTG2_LINE_CNT_LSB 0x80 +#define THS8200_DTG2_CNTL 0x82 +#define THS8200_CGMS_CNTL_HEADER 0x83 +#define THS8200_CGMS_PAYLOAD_MSB 0x84 +#define THS8200_CGMS_PAYLOAD_LSB 0x85 +#define THS8200_MISC_PPL_LSB 0x86 +#define THS8200_MISC_PPL_MSB 0x87 +#define THS8200_MISC_LPF_MSB 0x88 +#define THS8200_MISC_LPF_LSB 0x89 + +#endif /* THS8200_REGS_H */ diff --git a/drivers/media/i2c/tlv320aic23b.c b/drivers/media/i2c/tlv320aic23b.c new file mode 100644 index 000000000..e4c21990f --- /dev/null +++ b/drivers/media/i2c/tlv320aic23b.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * tlv320aic23b - driver version 0.0.1 + * + * Copyright (C) 2006 Scott Alfter <salfter@ssai.us> + * + * Based on wm8775 driver + * + * Copyright (C) 2004 Ulf Eklund <ivtv at eklund.to> + * Copyright (C) 2005 Hans Verkuil <hverkuil@xs4all.nl> + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +MODULE_DESCRIPTION("tlv320aic23b driver"); +MODULE_AUTHOR("Scott Alfter, Ulf Eklund, Hans Verkuil"); +MODULE_LICENSE("GPL"); + + +/* ----------------------------------------------------------------------- */ + +struct tlv320aic23b_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; +}; + +static inline struct tlv320aic23b_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tlv320aic23b_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tlv320aic23b_state, hdl)->sd; +} + +static int tlv320aic23b_write(struct v4l2_subdev *sd, int reg, u16 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int i; + + if ((reg < 0 || reg > 9) && (reg != 15)) { + v4l2_err(sd, "Invalid register R%d\n", reg); + return -1; + } + + for (i = 0; i < 3; i++) + if (i2c_smbus_write_byte_data(client, + (reg << 1) | (val >> 8), val & 0xff) == 0) + return 0; + v4l2_err(sd, "I2C: cannot write %03x to register R%d\n", val, reg); + return -1; +} + +static int tlv320aic23b_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + switch (freq) { + case 32000: /* set sample rate to 32 kHz */ + tlv320aic23b_write(sd, 8, 0x018); + break; + case 44100: /* set sample rate to 44.1 kHz */ + tlv320aic23b_write(sd, 8, 0x022); + break; + case 48000: /* set sample rate to 48 kHz */ + tlv320aic23b_write(sd, 8, 0x000); + break; + default: + return -EINVAL; + } + return 0; +} + +static int tlv320aic23b_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + tlv320aic23b_write(sd, 0, 0x180); /* mute both channels */ + /* set gain on both channels to +3.0 dB */ + if (!ctrl->val) + tlv320aic23b_write(sd, 0, 0x119); + return 0; + } + return -EINVAL; +} + +static int tlv320aic23b_log_status(struct v4l2_subdev *sd) +{ + struct tlv320aic23b_state *state = to_state(sd); + + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops tlv320aic23b_ctrl_ops = { + .s_ctrl = tlv320aic23b_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops tlv320aic23b_core_ops = { + .log_status = tlv320aic23b_log_status, +}; + +static const struct v4l2_subdev_audio_ops tlv320aic23b_audio_ops = { + .s_clock_freq = tlv320aic23b_s_clock_freq, +}; + +static const struct v4l2_subdev_ops tlv320aic23b_ops = { + .core = &tlv320aic23b_core_ops, + .audio = &tlv320aic23b_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ + +/* i2c implementation */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ + +static int tlv320aic23b_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tlv320aic23b_state *state; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &tlv320aic23b_ops); + + /* Initialize tlv320aic23b */ + + /* RESET */ + tlv320aic23b_write(sd, 15, 0x000); + /* turn off DAC & mic input */ + tlv320aic23b_write(sd, 6, 0x00A); + /* left-justified, 24-bit, master mode */ + tlv320aic23b_write(sd, 7, 0x049); + /* set gain on both channels to +3.0 dB */ + tlv320aic23b_write(sd, 0, 0x119); + /* set sample rate to 48 kHz */ + tlv320aic23b_write(sd, 8, 0x000); + /* activate digital interface */ + tlv320aic23b_write(sd, 9, 0x001); + + v4l2_ctrl_handler_init(&state->hdl, 1); + v4l2_ctrl_new_std(&state->hdl, &tlv320aic23b_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + v4l2_ctrl_handler_setup(&state->hdl); + return 0; +} + +static int tlv320aic23b_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tlv320aic23b_state *state = to_state(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id tlv320aic23b_id[] = { + { "tlv320aic23b", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tlv320aic23b_id); + +static struct i2c_driver tlv320aic23b_driver = { + .driver = { + .name = "tlv320aic23b", + }, + .probe = tlv320aic23b_probe, + .remove = tlv320aic23b_remove, + .id_table = tlv320aic23b_id, +}; + +module_i2c_driver(tlv320aic23b_driver); diff --git a/drivers/media/i2c/tvaudio.c b/drivers/media/i2c/tvaudio.c new file mode 100644 index 000000000..e6796e94d --- /dev/null +++ b/drivers/media/i2c/tvaudio.c @@ -0,0 +1,2103 @@ +/* + * Driver for simple i2c audio chips. + * + * Copyright (c) 2000 Gerd Knorr + * based on code by: + * Eric Sandeen (eric_sandeen@bigfoot.com) + * Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Greg Alexander (galexand@acm.org) + * + * For the TDA9875 part: + * Copyright (c) 2000 Guillaume Delvit based on Gerd Knorr source + * and Eric Sandeen + * + * Copyright(c) 2005-2008 Mauro Carvalho Chehab + * - Some cleanups, code fixes, etc + * - Convert it to V4L2 API + * + * This code is placed under the terms of the GNU General Public License + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kthread.h> +#include <linux/freezer.h> + +#include <media/i2c/tvaudio.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +/* ---------------------------------------------------------------------- */ +/* insmod args */ + +static int debug; /* insmod parameter */ +module_param(debug, int, 0644); + +MODULE_DESCRIPTION("device driver for various i2c TV sound decoder / audiomux chips"); +MODULE_AUTHOR("Eric Sandeen, Steve VanDeBogart, Greg Alexander, Gerd Knorr"); +MODULE_LICENSE("GPL"); + +#define UNSET (-1U) + +/* ---------------------------------------------------------------------- */ +/* our structs */ + +#define MAXREGS 256 + +struct CHIPSTATE; +typedef int (*getvalue)(int); +typedef int (*checkit)(struct CHIPSTATE*); +typedef int (*initialize)(struct CHIPSTATE*); +typedef int (*getrxsubchans)(struct CHIPSTATE *); +typedef void (*setaudmode)(struct CHIPSTATE*, int mode); + +/* i2c command */ +typedef struct AUDIOCMD { + int count; /* # of bytes to send */ + unsigned char bytes[MAXREGS+1]; /* addr, data, data, ... */ +} audiocmd; + +/* chip description */ +struct CHIPDESC { + char *name; /* chip name */ + int addr_lo, addr_hi; /* i2c address range */ + int registers; /* # of registers */ + + int *insmodopt; + checkit checkit; + initialize initialize; + int flags; +#define CHIP_HAS_VOLUME 1 +#define CHIP_HAS_BASSTREBLE 2 +#define CHIP_HAS_INPUTSEL 4 +#define CHIP_NEED_CHECKMODE 8 + + /* various i2c command sequences */ + audiocmd init; + + /* which register has which value */ + int leftreg, rightreg, treblereg, bassreg; + + /* initialize with (defaults to 65535/32768/32768 */ + int volinit, trebleinit, bassinit; + + /* functions to convert the values (v4l -> chip) */ + getvalue volfunc, treblefunc, bassfunc; + + /* get/set mode */ + getrxsubchans getrxsubchans; + setaudmode setaudmode; + + /* input switch register + values for v4l inputs */ + int inputreg; + int inputmap[4]; + int inputmute; + int inputmask; +}; + +/* current state of the chip */ +struct CHIPSTATE { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct { + /* volume/balance cluster */ + struct v4l2_ctrl *volume; + struct v4l2_ctrl *balance; + }; + + /* chip-specific description - should point to + an entry at CHIPDESC table */ + struct CHIPDESC *desc; + + /* shadow register set */ + audiocmd shadow; + + /* current settings */ + u16 muted; + int prevmode; + int radio; + int input; + + /* thread */ + struct task_struct *thread; + struct timer_list wt; + int audmode; +}; + +static inline struct CHIPSTATE *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct CHIPSTATE, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct CHIPSTATE, hdl)->sd; +} + + +/* ---------------------------------------------------------------------- */ +/* i2c I/O functions */ + +static int chip_write(struct CHIPSTATE *chip, int subaddr, int val) +{ + struct v4l2_subdev *sd = &chip->sd; + struct i2c_client *c = v4l2_get_subdevdata(sd); + unsigned char buffer[2]; + int rc; + + if (subaddr < 0) { + v4l2_dbg(1, debug, sd, "chip_write: 0x%x\n", val); + chip->shadow.bytes[1] = val; + buffer[0] = val; + rc = i2c_master_send(c, buffer, 1); + if (rc != 1) { + v4l2_warn(sd, "I/O error (write 0x%x)\n", val); + if (rc < 0) + return rc; + return -EIO; + } + } else { + if (subaddr + 1 >= ARRAY_SIZE(chip->shadow.bytes)) { + v4l2_info(sd, + "Tried to access a non-existent register: %d\n", + subaddr); + return -EINVAL; + } + + v4l2_dbg(1, debug, sd, "chip_write: reg%d=0x%x\n", + subaddr, val); + chip->shadow.bytes[subaddr+1] = val; + buffer[0] = subaddr; + buffer[1] = val; + rc = i2c_master_send(c, buffer, 2); + if (rc != 2) { + v4l2_warn(sd, "I/O error (write reg%d=0x%x)\n", + subaddr, val); + if (rc < 0) + return rc; + return -EIO; + } + } + return 0; +} + +static int chip_write_masked(struct CHIPSTATE *chip, + int subaddr, int val, int mask) +{ + struct v4l2_subdev *sd = &chip->sd; + + if (mask != 0) { + if (subaddr < 0) { + val = (chip->shadow.bytes[1] & ~mask) | (val & mask); + } else { + if (subaddr + 1 >= ARRAY_SIZE(chip->shadow.bytes)) { + v4l2_info(sd, + "Tried to access a non-existent register: %d\n", + subaddr); + return -EINVAL; + } + + val = (chip->shadow.bytes[subaddr+1] & ~mask) | (val & mask); + } + } + return chip_write(chip, subaddr, val); +} + +static int chip_read(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + struct i2c_client *c = v4l2_get_subdevdata(sd); + unsigned char buffer; + int rc; + + rc = i2c_master_recv(c, &buffer, 1); + if (rc != 1) { + v4l2_warn(sd, "I/O error (read)\n"); + if (rc < 0) + return rc; + return -EIO; + } + v4l2_dbg(1, debug, sd, "chip_read: 0x%x\n", buffer); + return buffer; +} + +static int chip_read2(struct CHIPSTATE *chip, int subaddr) +{ + struct v4l2_subdev *sd = &chip->sd; + struct i2c_client *c = v4l2_get_subdevdata(sd); + int rc; + unsigned char write[1]; + unsigned char read[1]; + struct i2c_msg msgs[2] = { + { + .addr = c->addr, + .len = 1, + .buf = write + }, + { + .addr = c->addr, + .flags = I2C_M_RD, + .len = 1, + .buf = read + } + }; + + write[0] = subaddr; + + rc = i2c_transfer(c->adapter, msgs, 2); + if (rc != 2) { + v4l2_warn(sd, "I/O error (read2)\n"); + if (rc < 0) + return rc; + return -EIO; + } + v4l2_dbg(1, debug, sd, "chip_read2: reg%d=0x%x\n", + subaddr, read[0]); + return read[0]; +} + +static int chip_cmd(struct CHIPSTATE *chip, char *name, audiocmd *cmd) +{ + struct v4l2_subdev *sd = &chip->sd; + struct i2c_client *c = v4l2_get_subdevdata(sd); + int i, rc; + + if (0 == cmd->count) + return 0; + + if (cmd->count + cmd->bytes[0] - 1 >= ARRAY_SIZE(chip->shadow.bytes)) { + v4l2_info(sd, + "Tried to access a non-existent register range: %d to %d\n", + cmd->bytes[0] + 1, cmd->bytes[0] + cmd->count - 1); + return -EINVAL; + } + + /* FIXME: it seems that the shadow bytes are wrong below !*/ + + /* update our shadow register set; print bytes if (debug > 0) */ + v4l2_dbg(1, debug, sd, "chip_cmd(%s): reg=%d, data:", + name, cmd->bytes[0]); + for (i = 1; i < cmd->count; i++) { + if (debug) + printk(KERN_CONT " 0x%x", cmd->bytes[i]); + chip->shadow.bytes[i+cmd->bytes[0]] = cmd->bytes[i]; + } + if (debug) + printk(KERN_CONT "\n"); + + /* send data to the chip */ + rc = i2c_master_send(c, cmd->bytes, cmd->count); + if (rc != cmd->count) { + v4l2_warn(sd, "I/O error (%s)\n", name); + if (rc < 0) + return rc; + return -EIO; + } + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* kernel thread for doing i2c stuff asyncronly + * right now it is used only to check the audio mode (mono/stereo/whatever) + * some time after switching to another TV channel, then turn on stereo + * if available, ... + */ + +static void chip_thread_wake(struct timer_list *t) +{ + struct CHIPSTATE *chip = from_timer(chip, t, wt); + wake_up_process(chip->thread); +} + +static int chip_thread(void *data) +{ + struct CHIPSTATE *chip = data; + struct CHIPDESC *desc = chip->desc; + struct v4l2_subdev *sd = &chip->sd; + int mode, selected; + + v4l2_dbg(1, debug, sd, "thread started\n"); + set_freezable(); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (!kthread_should_stop()) + schedule(); + set_current_state(TASK_RUNNING); + try_to_freeze(); + if (kthread_should_stop()) + break; + v4l2_dbg(1, debug, sd, "thread wakeup\n"); + + /* don't do anything for radio */ + if (chip->radio) + continue; + + /* have a look what's going on */ + mode = desc->getrxsubchans(chip); + if (mode == chip->prevmode) + continue; + + /* chip detected a new audio mode - set it */ + v4l2_dbg(1, debug, sd, "thread checkmode\n"); + + chip->prevmode = mode; + + selected = V4L2_TUNER_MODE_MONO; + switch (chip->audmode) { + case V4L2_TUNER_MODE_MONO: + if (mode & V4L2_TUNER_SUB_LANG1) + selected = V4L2_TUNER_MODE_LANG1; + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1: + if (mode & V4L2_TUNER_SUB_LANG1) + selected = V4L2_TUNER_MODE_LANG1; + else if (mode & V4L2_TUNER_SUB_STEREO) + selected = V4L2_TUNER_MODE_STEREO; + break; + case V4L2_TUNER_MODE_LANG2: + if (mode & V4L2_TUNER_SUB_LANG2) + selected = V4L2_TUNER_MODE_LANG2; + else if (mode & V4L2_TUNER_SUB_STEREO) + selected = V4L2_TUNER_MODE_STEREO; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + if (mode & V4L2_TUNER_SUB_LANG2) + selected = V4L2_TUNER_MODE_LANG1_LANG2; + else if (mode & V4L2_TUNER_SUB_STEREO) + selected = V4L2_TUNER_MODE_STEREO; + } + desc->setaudmode(chip, selected); + + /* schedule next check */ + mod_timer(&chip->wt, jiffies+msecs_to_jiffies(2000)); + } + + v4l2_dbg(1, debug, sd, "thread exiting\n"); + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda9840 */ + +#define TDA9840_SW 0x00 +#define TDA9840_LVADJ 0x02 +#define TDA9840_STADJ 0x03 +#define TDA9840_TEST 0x04 + +#define TDA9840_MONO 0x10 +#define TDA9840_STEREO 0x2a +#define TDA9840_DUALA 0x12 +#define TDA9840_DUALB 0x1e +#define TDA9840_DUALAB 0x1a +#define TDA9840_DUALBA 0x16 +#define TDA9840_EXTERNAL 0x7a + +#define TDA9840_DS_DUAL 0x20 /* Dual sound identified */ +#define TDA9840_ST_STEREO 0x40 /* Stereo sound identified */ +#define TDA9840_PONRES 0x80 /* Power-on reset detected if = 1 */ + +#define TDA9840_TEST_INT1SN 0x1 /* Integration time 0.5s when set */ +#define TDA9840_TEST_INTFU 0x02 /* Disables integrator function */ + +static int tda9840_getrxsubchans(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + int val, mode; + + mode = V4L2_TUNER_SUB_MONO; + + val = chip_read(chip); + if (val < 0) + return mode; + + if (val & TDA9840_DS_DUAL) + mode |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + if (val & TDA9840_ST_STEREO) + mode = V4L2_TUNER_SUB_STEREO; + + v4l2_dbg(1, debug, sd, + "tda9840_getrxsubchans(): raw chip read: %d, return: %d\n", + val, mode); + return mode; +} + +static void tda9840_setaudmode(struct CHIPSTATE *chip, int mode) +{ + int update = 1; + int t = chip->shadow.bytes[TDA9840_SW + 1] & ~0x7e; + + switch (mode) { + case V4L2_TUNER_MODE_MONO: + t |= TDA9840_MONO; + break; + case V4L2_TUNER_MODE_STEREO: + t |= TDA9840_STEREO; + break; + case V4L2_TUNER_MODE_LANG1: + t |= TDA9840_DUALA; + break; + case V4L2_TUNER_MODE_LANG2: + t |= TDA9840_DUALB; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + t |= TDA9840_DUALAB; + break; + default: + update = 0; + } + + if (update) + chip_write(chip, TDA9840_SW, t); +} + +static int tda9840_checkit(struct CHIPSTATE *chip) +{ + int rc; + + rc = chip_read(chip); + if (rc < 0) + return 0; + + + /* lower 5 bits should be 0 */ + return ((rc & 0x1f) == 0) ? 1 : 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda985x */ + +/* subaddresses for TDA9855 */ +#define TDA9855_VR 0x00 /* Volume, right */ +#define TDA9855_VL 0x01 /* Volume, left */ +#define TDA9855_BA 0x02 /* Bass */ +#define TDA9855_TR 0x03 /* Treble */ +#define TDA9855_SW 0x04 /* Subwoofer - not connected on DTV2000 */ + +/* subaddresses for TDA9850 */ +#define TDA9850_C4 0x04 /* Control 1 for TDA9850 */ + +/* subaddesses for both chips */ +#define TDA985x_C5 0x05 /* Control 2 for TDA9850, Control 1 for TDA9855 */ +#define TDA985x_C6 0x06 /* Control 3 for TDA9850, Control 2 for TDA9855 */ +#define TDA985x_C7 0x07 /* Control 4 for TDA9850, Control 3 for TDA9855 */ +#define TDA985x_A1 0x08 /* Alignment 1 for both chips */ +#define TDA985x_A2 0x09 /* Alignment 2 for both chips */ +#define TDA985x_A3 0x0a /* Alignment 3 for both chips */ + +/* Masks for bits in TDA9855 subaddresses */ +/* 0x00 - VR in TDA9855 */ +/* 0x01 - VL in TDA9855 */ +/* lower 7 bits control gain from -71dB (0x28) to 16dB (0x7f) + * in 1dB steps - mute is 0x27 */ + + +/* 0x02 - BA in TDA9855 */ +/* lower 5 bits control bass gain from -12dB (0x06) to 16.5dB (0x19) + * in .5dB steps - 0 is 0x0E */ + + +/* 0x03 - TR in TDA9855 */ +/* 4 bits << 1 control treble gain from -12dB (0x3) to 12dB (0xb) + * in 3dB steps - 0 is 0x7 */ + +/* Masks for bits in both chips' subaddresses */ +/* 0x04 - SW in TDA9855, C4/Control 1 in TDA9850 */ +/* Unique to TDA9855: */ +/* 4 bits << 2 control subwoofer/surround gain from -14db (0x1) to 14db (0xf) + * in 3dB steps - mute is 0x0 */ + +/* Unique to TDA9850: */ +/* lower 4 bits control stereo noise threshold, over which stereo turns off + * set to values of 0x00 through 0x0f for Ster1 through Ster16 */ + + +/* 0x05 - C5 - Control 1 in TDA9855 , Control 2 in TDA9850*/ +/* Unique to TDA9855: */ +#define TDA9855_MUTE 1<<7 /* GMU, Mute at outputs */ +#define TDA9855_AVL 1<<6 /* AVL, Automatic Volume Level */ +#define TDA9855_LOUD 1<<5 /* Loudness, 1==off */ +#define TDA9855_SUR 1<<3 /* Surround / Subwoofer 1==.5(L-R) 0==.5(L+R) */ + /* Bits 0 to 3 select various combinations + * of line in and line out, only the + * interesting ones are defined */ +#define TDA9855_EXT 1<<2 /* Selects inputs LIR and LIL. Pins 41 & 12 */ +#define TDA9855_INT 0 /* Selects inputs LOR and LOL. (internal) */ + +/* Unique to TDA9850: */ +/* lower 4 bits control SAP noise threshold, over which SAP turns off + * set to values of 0x00 through 0x0f for SAP1 through SAP16 */ + + +/* 0x06 - C6 - Control 2 in TDA9855, Control 3 in TDA9850 */ +/* Common to TDA9855 and TDA9850: */ +#define TDA985x_SAP 3<<6 /* Selects SAP output, mute if not received */ +#define TDA985x_MONOSAP 2<<6 /* Selects Mono on left, SAP on right */ +#define TDA985x_STEREO 1<<6 /* Selects Stereo output, mono if not received */ +#define TDA985x_MONO 0 /* Forces Mono output */ +#define TDA985x_LMU 1<<3 /* Mute (LOR/LOL for 9855, OUTL/OUTR for 9850) */ + +/* Unique to TDA9855: */ +#define TDA9855_TZCM 1<<5 /* If set, don't mute till zero crossing */ +#define TDA9855_VZCM 1<<4 /* If set, don't change volume till zero crossing*/ +#define TDA9855_LINEAR 0 /* Linear Stereo */ +#define TDA9855_PSEUDO 1 /* Pseudo Stereo */ +#define TDA9855_SPAT_30 2 /* Spatial Stereo, 30% anti-phase crosstalk */ +#define TDA9855_SPAT_50 3 /* Spatial Stereo, 52% anti-phase crosstalk */ +#define TDA9855_E_MONO 7 /* Forced mono - mono select elseware, so useless*/ + +/* 0x07 - C7 - Control 3 in TDA9855, Control 4 in TDA9850 */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 4 bits control input gain from -3.5dB (0x0) to 4dB (0xF) + * in .5dB steps - 0dB is 0x7 */ + +/* 0x08, 0x09 - A1 and A2 (read/write) */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 5 bites are wideband and spectral expander alignment + * from 0x00 to 0x1f - nominal at 0x0f and 0x10 (read/write) */ +#define TDA985x_STP 1<<5 /* Stereo Pilot/detect (read-only) */ +#define TDA985x_SAPP 1<<6 /* SAP Pilot/detect (read-only) */ +#define TDA985x_STS 1<<7 /* Stereo trigger 1= <35mV 0= <30mV (write-only)*/ + +/* 0x0a - A3 */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 3 bits control timing current for alignment: -30% (0x0), -20% (0x1), + * -10% (0x2), nominal (0x3), +10% (0x6), +20% (0x5), +30% (0x4) */ +#define TDA985x_ADJ 1<<7 /* Stereo adjust on/off (wideband and spectral */ + +static int tda9855_volume(int val) { return val/0x2e8+0x27; } +static int tda9855_bass(int val) { return val/0xccc+0x06; } +static int tda9855_treble(int val) { return (val/0x1c71+0x3)<<1; } + +static int tda985x_getrxsubchans(struct CHIPSTATE *chip) +{ + int mode, val; + + /* Add mono mode regardless of SAP and stereo */ + /* Allows forced mono */ + mode = V4L2_TUNER_SUB_MONO; + val = chip_read(chip); + if (val < 0) + return mode; + + if (val & TDA985x_STP) + mode = V4L2_TUNER_SUB_STEREO; + if (val & TDA985x_SAPP) + mode |= V4L2_TUNER_SUB_SAP; + return mode; +} + +static void tda985x_setaudmode(struct CHIPSTATE *chip, int mode) +{ + int update = 1; + int c6 = chip->shadow.bytes[TDA985x_C6+1] & 0x3f; + + switch (mode) { + case V4L2_TUNER_MODE_MONO: + c6 |= TDA985x_MONO; + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1: + c6 |= TDA985x_STEREO; + break; + case V4L2_TUNER_MODE_SAP: + c6 |= TDA985x_SAP; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + c6 |= TDA985x_MONOSAP; + break; + default: + update = 0; + } + if (update) + chip_write(chip,TDA985x_C6,c6); +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda9873h */ + +/* Subaddresses for TDA9873H */ + +#define TDA9873_SW 0x00 /* Switching */ +#define TDA9873_AD 0x01 /* Adjust */ +#define TDA9873_PT 0x02 /* Port */ + +/* Subaddress 0x00: Switching Data + * B7..B0: + * + * B1, B0: Input source selection + * 0, 0 internal + * 1, 0 external stereo + * 0, 1 external mono + */ +#define TDA9873_INP_MASK 3 +#define TDA9873_INTERNAL 0 +#define TDA9873_EXT_STEREO 2 +#define TDA9873_EXT_MONO 1 + +/* B3, B2: output signal select + * B4 : transmission mode + * 0, 0, 1 Mono + * 1, 0, 0 Stereo + * 1, 1, 1 Stereo (reversed channel) + * 0, 0, 0 Dual AB + * 0, 0, 1 Dual AA + * 0, 1, 0 Dual BB + * 0, 1, 1 Dual BA + */ + +#define TDA9873_TR_MASK (7 << 2) +#define TDA9873_TR_MONO 4 +#define TDA9873_TR_STEREO 1 << 4 +#define TDA9873_TR_REVERSE ((1 << 3) | (1 << 2)) +#define TDA9873_TR_DUALA 1 << 2 +#define TDA9873_TR_DUALB 1 << 3 +#define TDA9873_TR_DUALAB 0 + +/* output level controls + * B5: output level switch (0 = reduced gain, 1 = normal gain) + * B6: mute (1 = muted) + * B7: auto-mute (1 = auto-mute enabled) + */ + +#define TDA9873_GAIN_NORMAL 1 << 5 +#define TDA9873_MUTE 1 << 6 +#define TDA9873_AUTOMUTE 1 << 7 + +/* Subaddress 0x01: Adjust/standard */ + +/* Lower 4 bits (C3..C0) control stereo adjustment on R channel (-0.6 - +0.7 dB) + * Recommended value is +0 dB + */ + +#define TDA9873_STEREO_ADJ 0x06 /* 0dB gain */ + +/* Bits C6..C4 control FM stantard + * C6, C5, C4 + * 0, 0, 0 B/G (PAL FM) + * 0, 0, 1 M + * 0, 1, 0 D/K(1) + * 0, 1, 1 D/K(2) + * 1, 0, 0 D/K(3) + * 1, 0, 1 I + */ +#define TDA9873_BG 0 +#define TDA9873_M 1 +#define TDA9873_DK1 2 +#define TDA9873_DK2 3 +#define TDA9873_DK3 4 +#define TDA9873_I 5 + +/* C7 controls identification response time (1=fast/0=normal) + */ +#define TDA9873_IDR_NORM 0 +#define TDA9873_IDR_FAST 1 << 7 + + +/* Subaddress 0x02: Port data */ + +/* E1, E0 free programmable ports P1/P2 + 0, 0 both ports low + 0, 1 P1 high + 1, 0 P2 high + 1, 1 both ports high +*/ + +#define TDA9873_PORTS 3 + +/* E2: test port */ +#define TDA9873_TST_PORT 1 << 2 + +/* E5..E3 control mono output channel (together with transmission mode bit B4) + * + * E5 E4 E3 B4 OUTM + * 0 0 0 0 mono + * 0 0 1 0 DUAL B + * 0 1 0 1 mono (from stereo decoder) + */ +#define TDA9873_MOUT_MONO 0 +#define TDA9873_MOUT_FMONO 0 +#define TDA9873_MOUT_DUALA 0 +#define TDA9873_MOUT_DUALB 1 << 3 +#define TDA9873_MOUT_ST 1 << 4 +#define TDA9873_MOUT_EXTM ((1 << 4) | (1 << 3)) +#define TDA9873_MOUT_EXTL 1 << 5 +#define TDA9873_MOUT_EXTR ((1 << 5) | (1 << 3)) +#define TDA9873_MOUT_EXTLR ((1 << 5) | (1 << 4)) +#define TDA9873_MOUT_MUTE ((1 << 5) | (1 << 4) | (1 << 3)) + +/* Status bits: (chip read) */ +#define TDA9873_PONR 0 /* Power-on reset detected if = 1 */ +#define TDA9873_STEREO 2 /* Stereo sound is identified */ +#define TDA9873_DUAL 4 /* Dual sound is identified */ + +static int tda9873_getrxsubchans(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + int val,mode; + + mode = V4L2_TUNER_SUB_MONO; + + val = chip_read(chip); + if (val < 0) + return mode; + + if (val & TDA9873_STEREO) + mode = V4L2_TUNER_SUB_STEREO; + if (val & TDA9873_DUAL) + mode |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + v4l2_dbg(1, debug, sd, + "tda9873_getrxsubchans(): raw chip read: %d, return: %d\n", + val, mode); + return mode; +} + +static void tda9873_setaudmode(struct CHIPSTATE *chip, int mode) +{ + struct v4l2_subdev *sd = &chip->sd; + int sw_data = chip->shadow.bytes[TDA9873_SW+1] & ~ TDA9873_TR_MASK; + /* int adj_data = chip->shadow.bytes[TDA9873_AD+1] ; */ + + if ((sw_data & TDA9873_INP_MASK) != TDA9873_INTERNAL) { + v4l2_dbg(1, debug, sd, + "tda9873_setaudmode(): external input\n"); + return; + } + + v4l2_dbg(1, debug, sd, + "tda9873_setaudmode(): chip->shadow.bytes[%d] = %d\n", + TDA9873_SW+1, chip->shadow.bytes[TDA9873_SW+1]); + v4l2_dbg(1, debug, sd, "tda9873_setaudmode(): sw_data = %d\n", + sw_data); + + switch (mode) { + case V4L2_TUNER_MODE_MONO: + sw_data |= TDA9873_TR_MONO; + break; + case V4L2_TUNER_MODE_STEREO: + sw_data |= TDA9873_TR_STEREO; + break; + case V4L2_TUNER_MODE_LANG1: + sw_data |= TDA9873_TR_DUALA; + break; + case V4L2_TUNER_MODE_LANG2: + sw_data |= TDA9873_TR_DUALB; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + sw_data |= TDA9873_TR_DUALAB; + break; + default: + return; + } + + chip_write(chip, TDA9873_SW, sw_data); + v4l2_dbg(1, debug, sd, + "tda9873_setaudmode(): req. mode %d; chip_write: %d\n", + mode, sw_data); +} + +static int tda9873_checkit(struct CHIPSTATE *chip) +{ + int rc; + + rc = chip_read2(chip, 254); + if (rc < 0) + return 0; + return (rc & ~0x1f) == 0x80; +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip description - defines+functions for tda9874h and tda9874a */ +/* Dariusz Kowalewski <darekk@automex.pl> */ + +/* Subaddresses for TDA9874H and TDA9874A (slave rx) */ +#define TDA9874A_AGCGR 0x00 /* AGC gain */ +#define TDA9874A_GCONR 0x01 /* general config */ +#define TDA9874A_MSR 0x02 /* monitor select */ +#define TDA9874A_C1FRA 0x03 /* carrier 1 freq. */ +#define TDA9874A_C1FRB 0x04 /* carrier 1 freq. */ +#define TDA9874A_C1FRC 0x05 /* carrier 1 freq. */ +#define TDA9874A_C2FRA 0x06 /* carrier 2 freq. */ +#define TDA9874A_C2FRB 0x07 /* carrier 2 freq. */ +#define TDA9874A_C2FRC 0x08 /* carrier 2 freq. */ +#define TDA9874A_DCR 0x09 /* demodulator config */ +#define TDA9874A_FMER 0x0a /* FM de-emphasis */ +#define TDA9874A_FMMR 0x0b /* FM dematrix */ +#define TDA9874A_C1OLAR 0x0c /* ch.1 output level adj. */ +#define TDA9874A_C2OLAR 0x0d /* ch.2 output level adj. */ +#define TDA9874A_NCONR 0x0e /* NICAM config */ +#define TDA9874A_NOLAR 0x0f /* NICAM output level adj. */ +#define TDA9874A_NLELR 0x10 /* NICAM lower error limit */ +#define TDA9874A_NUELR 0x11 /* NICAM upper error limit */ +#define TDA9874A_AMCONR 0x12 /* audio mute control */ +#define TDA9874A_SDACOSR 0x13 /* stereo DAC output select */ +#define TDA9874A_AOSR 0x14 /* analog output select */ +#define TDA9874A_DAICONR 0x15 /* digital audio interface config */ +#define TDA9874A_I2SOSR 0x16 /* I2S-bus output select */ +#define TDA9874A_I2SOLAR 0x17 /* I2S-bus output level adj. */ +#define TDA9874A_MDACOSR 0x18 /* mono DAC output select (tda9874a) */ +#define TDA9874A_ESP 0xFF /* easy standard progr. (tda9874a) */ + +/* Subaddresses for TDA9874H and TDA9874A (slave tx) */ +#define TDA9874A_DSR 0x00 /* device status */ +#define TDA9874A_NSR 0x01 /* NICAM status */ +#define TDA9874A_NECR 0x02 /* NICAM error count */ +#define TDA9874A_DR1 0x03 /* add. data LSB */ +#define TDA9874A_DR2 0x04 /* add. data MSB */ +#define TDA9874A_LLRA 0x05 /* monitor level read-out LSB */ +#define TDA9874A_LLRB 0x06 /* monitor level read-out MSB */ +#define TDA9874A_SIFLR 0x07 /* SIF level */ +#define TDA9874A_TR2 252 /* test reg. 2 */ +#define TDA9874A_TR1 253 /* test reg. 1 */ +#define TDA9874A_DIC 254 /* device id. code */ +#define TDA9874A_SIC 255 /* software id. code */ + + +static int tda9874a_mode = 1; /* 0: A2, 1: NICAM */ +static int tda9874a_GCONR = 0xc0; /* default config. input pin: SIFSEL=0 */ +static int tda9874a_NCONR = 0x01; /* default NICAM config.: AMSEL=0,AMUTE=1 */ +static int tda9874a_ESP = 0x07; /* default standard: NICAM D/K */ +static int tda9874a_dic = -1; /* device id. code */ + +/* insmod options for tda9874a */ +static unsigned int tda9874a_SIF = UNSET; +static unsigned int tda9874a_AMSEL = UNSET; +static unsigned int tda9874a_STD = UNSET; +module_param(tda9874a_SIF, int, 0444); +module_param(tda9874a_AMSEL, int, 0444); +module_param(tda9874a_STD, int, 0444); + +/* + * initialization table for tda9874 decoder: + * - carrier 1 freq. registers (3 bytes) + * - carrier 2 freq. registers (3 bytes) + * - demudulator config register + * - FM de-emphasis register (slow identification mode) + * Note: frequency registers must be written in single i2c transfer. + */ +static struct tda9874a_MODES { + char *name; + audiocmd cmd; +} tda9874a_modelist[9] = { + { "A2, B/G", /* default */ + { 9, { TDA9874A_C1FRA, 0x72,0x95,0x55, 0x77,0xA0,0x00, 0x00,0x00 }} }, + { "A2, M (Korea)", + { 9, { TDA9874A_C1FRA, 0x5D,0xC0,0x00, 0x62,0x6A,0xAA, 0x20,0x22 }} }, + { "A2, D/K (1)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x82,0x60,0x00, 0x00,0x00 }} }, + { "A2, D/K (2)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x8C,0x75,0x55, 0x00,0x00 }} }, + { "A2, D/K (3)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x77,0xA0,0x00, 0x00,0x00 }} }, + { "NICAM, I", + { 9, { TDA9874A_C1FRA, 0x7D,0x00,0x00, 0x88,0x8A,0xAA, 0x08,0x33 }} }, + { "NICAM, B/G", + { 9, { TDA9874A_C1FRA, 0x72,0x95,0x55, 0x79,0xEA,0xAA, 0x08,0x33 }} }, + { "NICAM, D/K", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x79,0xEA,0xAA, 0x08,0x33 }} }, + { "NICAM, L", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x79,0xEA,0xAA, 0x09,0x33 }} } +}; + +static int tda9874a_setup(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + + chip_write(chip, TDA9874A_AGCGR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_GCONR, tda9874a_GCONR); + chip_write(chip, TDA9874A_MSR, (tda9874a_mode) ? 0x03:0x02); + if(tda9874a_dic == 0x11) { + chip_write(chip, TDA9874A_FMMR, 0x80); + } else { /* dic == 0x07 */ + chip_cmd(chip,"tda9874_modelist",&tda9874a_modelist[tda9874a_STD].cmd); + chip_write(chip, TDA9874A_FMMR, 0x00); + } + chip_write(chip, TDA9874A_C1OLAR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_C2OLAR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_NCONR, tda9874a_NCONR); + chip_write(chip, TDA9874A_NOLAR, 0x00); /* 0 dB */ + /* Note: If signal quality is poor you may want to change NICAM */ + /* error limit registers (NLELR and NUELR) to some greater values. */ + /* Then the sound would remain stereo, but won't be so clear. */ + chip_write(chip, TDA9874A_NLELR, 0x14); /* default */ + chip_write(chip, TDA9874A_NUELR, 0x50); /* default */ + + if(tda9874a_dic == 0x11) { + chip_write(chip, TDA9874A_AMCONR, 0xf9); + chip_write(chip, TDA9874A_SDACOSR, (tda9874a_mode) ? 0x81:0x80); + chip_write(chip, TDA9874A_AOSR, 0x80); + chip_write(chip, TDA9874A_MDACOSR, (tda9874a_mode) ? 0x82:0x80); + chip_write(chip, TDA9874A_ESP, tda9874a_ESP); + } else { /* dic == 0x07 */ + chip_write(chip, TDA9874A_AMCONR, 0xfb); + chip_write(chip, TDA9874A_SDACOSR, (tda9874a_mode) ? 0x81:0x80); + chip_write(chip, TDA9874A_AOSR, 0x00); /* or 0x10 */ + } + v4l2_dbg(1, debug, sd, "tda9874a_setup(): %s [0x%02X].\n", + tda9874a_modelist[tda9874a_STD].name,tda9874a_STD); + return 1; +} + +static int tda9874a_getrxsubchans(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + int dsr,nsr,mode; + int necr; /* just for debugging */ + + mode = V4L2_TUNER_SUB_MONO; + + dsr = chip_read2(chip, TDA9874A_DSR); + if (dsr < 0) + return mode; + nsr = chip_read2(chip, TDA9874A_NSR); + if (nsr < 0) + return mode; + necr = chip_read2(chip, TDA9874A_NECR); + if (necr < 0) + return mode; + + /* need to store dsr/nsr somewhere */ + chip->shadow.bytes[MAXREGS-2] = dsr; + chip->shadow.bytes[MAXREGS-1] = nsr; + + if(tda9874a_mode) { + /* Note: DSR.RSSF and DSR.AMSTAT bits are also checked. + * If NICAM auto-muting is enabled, DSR.AMSTAT=1 indicates + * that sound has (temporarily) switched from NICAM to + * mono FM (or AM) on 1st sound carrier due to high NICAM bit + * error count. So in fact there is no stereo in this case :-( + * But changing the mode to V4L2_TUNER_MODE_MONO would switch + * external 4052 multiplexer in audio_hook(). + */ + if(nsr & 0x02) /* NSR.S/MB=1 */ + mode = V4L2_TUNER_SUB_STEREO; + if(nsr & 0x01) /* NSR.D/SB=1 */ + mode |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + } else { + if(dsr & 0x02) /* DSR.IDSTE=1 */ + mode = V4L2_TUNER_SUB_STEREO; + if(dsr & 0x04) /* DSR.IDDUA=1 */ + mode |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + } + + v4l2_dbg(1, debug, sd, + "tda9874a_getrxsubchans(): DSR=0x%X, NSR=0x%X, NECR=0x%X, return: %d.\n", + dsr, nsr, necr, mode); + return mode; +} + +static void tda9874a_setaudmode(struct CHIPSTATE *chip, int mode) +{ + struct v4l2_subdev *sd = &chip->sd; + + /* Disable/enable NICAM auto-muting (based on DSR.RSSF status bit). */ + /* If auto-muting is disabled, we can hear a signal of degrading quality. */ + if (tda9874a_mode) { + if(chip->shadow.bytes[MAXREGS-2] & 0x20) /* DSR.RSSF=1 */ + tda9874a_NCONR &= 0xfe; /* enable */ + else + tda9874a_NCONR |= 0x01; /* disable */ + chip_write(chip, TDA9874A_NCONR, tda9874a_NCONR); + } + + /* Note: TDA9874A supports automatic FM dematrixing (FMMR register) + * and has auto-select function for audio output (AOSR register). + * Old TDA9874H doesn't support these features. + * TDA9874A also has additional mono output pin (OUTM), which + * on same (all?) tv-cards is not used, anyway (as well as MONOIN). + */ + if(tda9874a_dic == 0x11) { + int aosr = 0x80; + int mdacosr = (tda9874a_mode) ? 0x82:0x80; + + switch(mode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_STEREO: + break; + case V4L2_TUNER_MODE_LANG1: + aosr = 0x80; /* auto-select, dual A/A */ + mdacosr = (tda9874a_mode) ? 0x82:0x80; + break; + case V4L2_TUNER_MODE_LANG2: + aosr = 0xa0; /* auto-select, dual B/B */ + mdacosr = (tda9874a_mode) ? 0x83:0x81; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + aosr = 0x00; /* always route L to L and R to R */ + mdacosr = (tda9874a_mode) ? 0x82:0x80; + break; + default: + return; + } + chip_write(chip, TDA9874A_AOSR, aosr); + chip_write(chip, TDA9874A_MDACOSR, mdacosr); + + v4l2_dbg(1, debug, sd, + "tda9874a_setaudmode(): req. mode %d; AOSR=0x%X, MDACOSR=0x%X.\n", + mode, aosr, mdacosr); + + } else { /* dic == 0x07 */ + int fmmr,aosr; + + switch(mode) { + case V4L2_TUNER_MODE_MONO: + fmmr = 0x00; /* mono */ + aosr = 0x10; /* A/A */ + break; + case V4L2_TUNER_MODE_STEREO: + if(tda9874a_mode) { + fmmr = 0x00; + aosr = 0x00; /* handled by NICAM auto-mute */ + } else { + fmmr = (tda9874a_ESP == 1) ? 0x05 : 0x04; /* stereo */ + aosr = 0x00; + } + break; + case V4L2_TUNER_MODE_LANG1: + fmmr = 0x02; /* dual */ + aosr = 0x10; /* dual A/A */ + break; + case V4L2_TUNER_MODE_LANG2: + fmmr = 0x02; /* dual */ + aosr = 0x20; /* dual B/B */ + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + fmmr = 0x02; /* dual */ + aosr = 0x00; /* dual A/B */ + break; + default: + return; + } + chip_write(chip, TDA9874A_FMMR, fmmr); + chip_write(chip, TDA9874A_AOSR, aosr); + + v4l2_dbg(1, debug, sd, + "tda9874a_setaudmode(): req. mode %d; FMMR=0x%X, AOSR=0x%X.\n", + mode, fmmr, aosr); + } +} + +static int tda9874a_checkit(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + int dic,sic; /* device id. and software id. codes */ + + dic = chip_read2(chip, TDA9874A_DIC); + if (dic < 0) + return 0; + sic = chip_read2(chip, TDA9874A_SIC); + if (sic < 0) + return 0; + + v4l2_dbg(1, debug, sd, "tda9874a_checkit(): DIC=0x%X, SIC=0x%X.\n", dic, sic); + + if((dic == 0x11)||(dic == 0x07)) { + v4l2_info(sd, "found tda9874%s.\n", (dic == 0x11) ? "a" : "h"); + tda9874a_dic = dic; /* remember device id. */ + return 1; + } + return 0; /* not found */ +} + +static int tda9874a_initialize(struct CHIPSTATE *chip) +{ + if (tda9874a_SIF > 2) + tda9874a_SIF = 1; + if (tda9874a_STD >= ARRAY_SIZE(tda9874a_modelist)) + tda9874a_STD = 0; + if(tda9874a_AMSEL > 1) + tda9874a_AMSEL = 0; + + if(tda9874a_SIF == 1) + tda9874a_GCONR = 0xc0; /* sound IF input 1 */ + else + tda9874a_GCONR = 0xc1; /* sound IF input 2 */ + + tda9874a_ESP = tda9874a_STD; + tda9874a_mode = (tda9874a_STD < 5) ? 0 : 1; + + if(tda9874a_AMSEL == 0) + tda9874a_NCONR = 0x01; /* auto-mute: analog mono input */ + else + tda9874a_NCONR = 0x05; /* auto-mute: 1st carrier FM or AM */ + + tda9874a_setup(chip); + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip description - defines+functions for tda9875 */ +/* The TDA9875 is made by Philips Semiconductor + * http://www.semiconductors.philips.com + * TDA9875: I2C-bus controlled DSP audio processor, FM demodulator + * + */ + +/* subaddresses for TDA9875 */ +#define TDA9875_MUT 0x12 /*General mute (value --> 0b11001100*/ +#define TDA9875_CFG 0x01 /* Config register (value --> 0b00000000 */ +#define TDA9875_DACOS 0x13 /*DAC i/o select (ADC) 0b0000100*/ +#define TDA9875_LOSR 0x16 /*Line output select regirter 0b0100 0001*/ + +#define TDA9875_CH1V 0x0c /*Channel 1 volume (mute)*/ +#define TDA9875_CH2V 0x0d /*Channel 2 volume (mute)*/ +#define TDA9875_SC1 0x14 /*SCART 1 in (mono)*/ +#define TDA9875_SC2 0x15 /*SCART 2 in (mono)*/ + +#define TDA9875_ADCIS 0x17 /*ADC input select (mono) 0b0110 000*/ +#define TDA9875_AER 0x19 /*Audio effect (AVL+Pseudo) 0b0000 0110*/ +#define TDA9875_MCS 0x18 /*Main channel select (DAC) 0b0000100*/ +#define TDA9875_MVL 0x1a /* Main volume gauche */ +#define TDA9875_MVR 0x1b /* Main volume droite */ +#define TDA9875_MBA 0x1d /* Main Basse */ +#define TDA9875_MTR 0x1e /* Main treble */ +#define TDA9875_ACS 0x1f /* Auxiliary channel select (FM) 0b0000000*/ +#define TDA9875_AVL 0x20 /* Auxiliary volume gauche */ +#define TDA9875_AVR 0x21 /* Auxiliary volume droite */ +#define TDA9875_ABA 0x22 /* Auxiliary Basse */ +#define TDA9875_ATR 0x23 /* Auxiliary treble */ + +#define TDA9875_MSR 0x02 /* Monitor select register */ +#define TDA9875_C1MSB 0x03 /* Carrier 1 (FM) frequency register MSB */ +#define TDA9875_C1MIB 0x04 /* Carrier 1 (FM) frequency register (16-8]b */ +#define TDA9875_C1LSB 0x05 /* Carrier 1 (FM) frequency register LSB */ +#define TDA9875_C2MSB 0x06 /* Carrier 2 (nicam) frequency register MSB */ +#define TDA9875_C2MIB 0x07 /* Carrier 2 (nicam) frequency register (16-8]b */ +#define TDA9875_C2LSB 0x08 /* Carrier 2 (nicam) frequency register LSB */ +#define TDA9875_DCR 0x09 /* Demodulateur configuration regirter*/ +#define TDA9875_DEEM 0x0a /* FM de-emphasis regirter*/ +#define TDA9875_FMAT 0x0b /* FM Matrix regirter*/ + +/* values */ +#define TDA9875_MUTE_ON 0xff /* general mute */ +#define TDA9875_MUTE_OFF 0xcc /* general no mute */ + +static int tda9875_initialize(struct CHIPSTATE *chip) +{ + chip_write(chip, TDA9875_CFG, 0xd0); /*reg de config 0 (reset)*/ + chip_write(chip, TDA9875_MSR, 0x03); /* Monitor 0b00000XXX*/ + chip_write(chip, TDA9875_C1MSB, 0x00); /*Car1(FM) MSB XMHz*/ + chip_write(chip, TDA9875_C1MIB, 0x00); /*Car1(FM) MIB XMHz*/ + chip_write(chip, TDA9875_C1LSB, 0x00); /*Car1(FM) LSB XMHz*/ + chip_write(chip, TDA9875_C2MSB, 0x00); /*Car2(NICAM) MSB XMHz*/ + chip_write(chip, TDA9875_C2MIB, 0x00); /*Car2(NICAM) MIB XMHz*/ + chip_write(chip, TDA9875_C2LSB, 0x00); /*Car2(NICAM) LSB XMHz*/ + chip_write(chip, TDA9875_DCR, 0x00); /*Demod config 0x00*/ + chip_write(chip, TDA9875_DEEM, 0x44); /*DE-Emph 0b0100 0100*/ + chip_write(chip, TDA9875_FMAT, 0x00); /*FM Matrix reg 0x00*/ + chip_write(chip, TDA9875_SC1, 0x00); /* SCART 1 (SC1)*/ + chip_write(chip, TDA9875_SC2, 0x01); /* SCART 2 (sc2)*/ + + chip_write(chip, TDA9875_CH1V, 0x10); /* Channel volume 1 mute*/ + chip_write(chip, TDA9875_CH2V, 0x10); /* Channel volume 2 mute */ + chip_write(chip, TDA9875_DACOS, 0x02); /* sig DAC i/o(in:nicam)*/ + chip_write(chip, TDA9875_ADCIS, 0x6f); /* sig ADC input(in:mono)*/ + chip_write(chip, TDA9875_LOSR, 0x00); /* line out (in:mono)*/ + chip_write(chip, TDA9875_AER, 0x00); /*06 Effect (AVL+PSEUDO) */ + chip_write(chip, TDA9875_MCS, 0x44); /* Main ch select (DAC) */ + chip_write(chip, TDA9875_MVL, 0x03); /* Vol Main left 10dB */ + chip_write(chip, TDA9875_MVR, 0x03); /* Vol Main right 10dB*/ + chip_write(chip, TDA9875_MBA, 0x00); /* Main Bass Main 0dB*/ + chip_write(chip, TDA9875_MTR, 0x00); /* Main Treble Main 0dB*/ + chip_write(chip, TDA9875_ACS, 0x44); /* Aux chan select (dac)*/ + chip_write(chip, TDA9875_AVL, 0x00); /* Vol Aux left 0dB*/ + chip_write(chip, TDA9875_AVR, 0x00); /* Vol Aux right 0dB*/ + chip_write(chip, TDA9875_ABA, 0x00); /* Aux Bass Main 0dB*/ + chip_write(chip, TDA9875_ATR, 0x00); /* Aux Aigus Main 0dB*/ + + chip_write(chip, TDA9875_MUT, 0xcc); /* General mute */ + return 0; +} + +static int tda9875_volume(int val) { return (unsigned char)(val / 602 - 84); } +static int tda9875_bass(int val) { return (unsigned char)(max(-12, val / 2115 - 15)); } +static int tda9875_treble(int val) { return (unsigned char)(val / 2622 - 12); } + +/* ----------------------------------------------------------------------- */ + + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tda9875_checkit(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + int dic, rev; + + dic = chip_read2(chip, 254); + if (dic < 0) + return 0; + rev = chip_read2(chip, 255); + if (rev < 0) + return 0; + + if (dic == 0 || dic == 2) { /* tda9875 and tda9875A */ + v4l2_info(sd, "found tda9875%s rev. %d.\n", + dic == 0 ? "" : "A", rev); + return 1; + } + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tea6420 */ + +#define TEA6300_VL 0x00 /* volume left */ +#define TEA6300_VR 0x01 /* volume right */ +#define TEA6300_BA 0x02 /* bass */ +#define TEA6300_TR 0x03 /* treble */ +#define TEA6300_FA 0x04 /* fader control */ +#define TEA6300_S 0x05 /* switch register */ + /* values for those registers: */ +#define TEA6300_S_SA 0x01 /* stereo A input */ +#define TEA6300_S_SB 0x02 /* stereo B */ +#define TEA6300_S_SC 0x04 /* stereo C */ +#define TEA6300_S_GMU 0x80 /* general mute */ + +#define TEA6320_V 0x00 /* volume (0-5)/loudness off (6)/zero crossing mute(7) */ +#define TEA6320_FFR 0x01 /* fader front right (0-5) */ +#define TEA6320_FFL 0x02 /* fader front left (0-5) */ +#define TEA6320_FRR 0x03 /* fader rear right (0-5) */ +#define TEA6320_FRL 0x04 /* fader rear left (0-5) */ +#define TEA6320_BA 0x05 /* bass (0-4) */ +#define TEA6320_TR 0x06 /* treble (0-4) */ +#define TEA6320_S 0x07 /* switch register */ + /* values for those registers: */ +#define TEA6320_S_SA 0x07 /* stereo A input */ +#define TEA6320_S_SB 0x06 /* stereo B */ +#define TEA6320_S_SC 0x05 /* stereo C */ +#define TEA6320_S_SD 0x04 /* stereo D */ +#define TEA6320_S_GMU 0x80 /* general mute */ + +#define TEA6420_S_SA 0x00 /* stereo A input */ +#define TEA6420_S_SB 0x01 /* stereo B */ +#define TEA6420_S_SC 0x02 /* stereo C */ +#define TEA6420_S_SD 0x03 /* stereo D */ +#define TEA6420_S_SE 0x04 /* stereo E */ +#define TEA6420_S_GMU 0x05 /* general mute */ + +static int tea6300_shift10(int val) { return val >> 10; } +static int tea6300_shift12(int val) { return val >> 12; } + +/* Assumes 16bit input (values 0x3f to 0x0c are unique, values less than */ +/* 0x0c mirror those immediately higher) */ +static int tea6320_volume(int val) { return (val / (65535/(63-12)) + 12) & 0x3f; } +static int tea6320_shift11(int val) { return val >> 11; } +static int tea6320_initialize(struct CHIPSTATE * chip) +{ + chip_write(chip, TEA6320_FFR, 0x3f); + chip_write(chip, TEA6320_FFL, 0x3f); + chip_write(chip, TEA6320_FRR, 0x3f); + chip_write(chip, TEA6320_FRL, 0x3f); + + return 0; +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda8425 */ + +#define TDA8425_VL 0x00 /* volume left */ +#define TDA8425_VR 0x01 /* volume right */ +#define TDA8425_BA 0x02 /* bass */ +#define TDA8425_TR 0x03 /* treble */ +#define TDA8425_S1 0x08 /* switch functions */ + /* values for those registers: */ +#define TDA8425_S1_OFF 0xEE /* audio off (mute on) */ +#define TDA8425_S1_CH1 0xCE /* audio channel 1 (mute off) - "linear stereo" mode */ +#define TDA8425_S1_CH2 0xCF /* audio channel 2 (mute off) - "linear stereo" mode */ +#define TDA8425_S1_MU 0x20 /* mute bit */ +#define TDA8425_S1_STEREO 0x18 /* stereo bits */ +#define TDA8425_S1_STEREO_SPATIAL 0x18 /* spatial stereo */ +#define TDA8425_S1_STEREO_LINEAR 0x08 /* linear stereo */ +#define TDA8425_S1_STEREO_PSEUDO 0x10 /* pseudo stereo */ +#define TDA8425_S1_STEREO_MONO 0x00 /* forced mono */ +#define TDA8425_S1_ML 0x06 /* language selector */ +#define TDA8425_S1_ML_SOUND_A 0x02 /* sound a */ +#define TDA8425_S1_ML_SOUND_B 0x04 /* sound b */ +#define TDA8425_S1_ML_STEREO 0x06 /* stereo */ +#define TDA8425_S1_IS 0x01 /* channel selector */ + + +static int tda8425_shift10(int val) { return (val >> 10) | 0xc0; } +static int tda8425_shift12(int val) { return (val >> 12) | 0xf0; } + +static void tda8425_setaudmode(struct CHIPSTATE *chip, int mode) +{ + int s1 = chip->shadow.bytes[TDA8425_S1+1] & 0xe1; + + switch (mode) { + case V4L2_TUNER_MODE_LANG1: + s1 |= TDA8425_S1_ML_SOUND_A; + s1 |= TDA8425_S1_STEREO_PSEUDO; + break; + case V4L2_TUNER_MODE_LANG2: + s1 |= TDA8425_S1_ML_SOUND_B; + s1 |= TDA8425_S1_STEREO_PSEUDO; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + s1 |= TDA8425_S1_ML_STEREO; + s1 |= TDA8425_S1_STEREO_LINEAR; + break; + case V4L2_TUNER_MODE_MONO: + s1 |= TDA8425_S1_ML_STEREO; + s1 |= TDA8425_S1_STEREO_MONO; + break; + case V4L2_TUNER_MODE_STEREO: + s1 |= TDA8425_S1_ML_STEREO; + s1 |= TDA8425_S1_STEREO_SPATIAL; + break; + default: + return; + } + chip_write(chip,TDA8425_S1,s1); +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for pic16c54 (PV951) */ + +/* the registers of 16C54, I2C sub address. */ +#define PIC16C54_REG_KEY_CODE 0x01 /* Not use. */ +#define PIC16C54_REG_MISC 0x02 + +/* bit definition of the RESET register, I2C data. */ +#define PIC16C54_MISC_RESET_REMOTE_CTL 0x01 /* bit 0, Reset to receive the key */ + /* code of remote controller */ +#define PIC16C54_MISC_MTS_MAIN 0x02 /* bit 1 */ +#define PIC16C54_MISC_MTS_SAP 0x04 /* bit 2 */ +#define PIC16C54_MISC_MTS_BOTH 0x08 /* bit 3 */ +#define PIC16C54_MISC_SND_MUTE 0x10 /* bit 4, Mute Audio(Line-in and Tuner) */ +#define PIC16C54_MISC_SND_NOTMUTE 0x20 /* bit 5 */ +#define PIC16C54_MISC_SWITCH_TUNER 0x40 /* bit 6 , Switch to Line-in */ +#define PIC16C54_MISC_SWITCH_LINE 0x80 /* bit 7 , Switch to Tuner */ + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for TA8874Z */ + +/* write 1st byte */ +#define TA8874Z_LED_STE 0x80 +#define TA8874Z_LED_BIL 0x40 +#define TA8874Z_LED_EXT 0x20 +#define TA8874Z_MONO_SET 0x10 +#define TA8874Z_MUTE 0x08 +#define TA8874Z_F_MONO 0x04 +#define TA8874Z_MODE_SUB 0x02 +#define TA8874Z_MODE_MAIN 0x01 + +/* write 2nd byte */ +/*#define TA8874Z_TI 0x80 */ /* test mode */ +#define TA8874Z_SEPARATION 0x3f +#define TA8874Z_SEPARATION_DEFAULT 0x10 + +/* read */ +#define TA8874Z_B1 0x80 +#define TA8874Z_B0 0x40 +#define TA8874Z_CHAG_FLAG 0x20 + +/* + * B1 B0 + * mono L H + * stereo L L + * BIL H L + */ +static int ta8874z_getrxsubchans(struct CHIPSTATE *chip) +{ + int val, mode; + + mode = V4L2_TUNER_SUB_MONO; + + val = chip_read(chip); + if (val < 0) + return mode; + + if (val & TA8874Z_B1){ + mode |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + }else if (!(val & TA8874Z_B0)){ + mode = V4L2_TUNER_SUB_STEREO; + } + /* v4l2_dbg(1, debug, &chip->sd, + "ta8874z_getrxsubchans(): raw chip read: 0x%02x, return: 0x%02x\n", + val, mode); */ + return mode; +} + +static audiocmd ta8874z_stereo = { 2, {0, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_mono = {2, { TA8874Z_MONO_SET, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_main = {2, { 0, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_sub = {2, { TA8874Z_MODE_SUB, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_both = {2, { TA8874Z_MODE_MAIN | TA8874Z_MODE_SUB, TA8874Z_SEPARATION_DEFAULT}}; + +static void ta8874z_setaudmode(struct CHIPSTATE *chip, int mode) +{ + struct v4l2_subdev *sd = &chip->sd; + int update = 1; + audiocmd *t = NULL; + + v4l2_dbg(1, debug, sd, "ta8874z_setaudmode(): mode: 0x%02x\n", mode); + + switch(mode){ + case V4L2_TUNER_MODE_MONO: + t = &ta8874z_mono; + break; + case V4L2_TUNER_MODE_STEREO: + t = &ta8874z_stereo; + break; + case V4L2_TUNER_MODE_LANG1: + t = &ta8874z_main; + break; + case V4L2_TUNER_MODE_LANG2: + t = &ta8874z_sub; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + t = &ta8874z_both; + break; + default: + update = 0; + } + + if(update) + chip_cmd(chip, "TA8874Z", t); +} + +static int ta8874z_checkit(struct CHIPSTATE *chip) +{ + int rc; + + rc = chip_read(chip); + if (rc < 0) + return rc; + + return ((rc & 0x1f) == 0x1f) ? 1 : 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - struct CHIPDESC */ + +/* insmod options to enable/disable individual audio chips */ +static int tda8425 = 1; +static int tda9840 = 1; +static int tda9850 = 1; +static int tda9855 = 1; +static int tda9873 = 1; +static int tda9874a = 1; +static int tda9875 = 1; +static int tea6300; /* default 0 - address clash with msp34xx */ +static int tea6320; /* default 0 - address clash with msp34xx */ +static int tea6420 = 1; +static int pic16c54 = 1; +static int ta8874z; /* default 0 - address clash with tda9840 */ + +module_param(tda8425, int, 0444); +module_param(tda9840, int, 0444); +module_param(tda9850, int, 0444); +module_param(tda9855, int, 0444); +module_param(tda9873, int, 0444); +module_param(tda9874a, int, 0444); +module_param(tda9875, int, 0444); +module_param(tea6300, int, 0444); +module_param(tea6320, int, 0444); +module_param(tea6420, int, 0444); +module_param(pic16c54, int, 0444); +module_param(ta8874z, int, 0444); + +static struct CHIPDESC chiplist[] = { + { + .name = "tda9840", + .insmodopt = &tda9840, + .addr_lo = I2C_ADDR_TDA9840 >> 1, + .addr_hi = I2C_ADDR_TDA9840 >> 1, + .registers = 5, + .flags = CHIP_NEED_CHECKMODE, + + /* callbacks */ + .checkit = tda9840_checkit, + .getrxsubchans = tda9840_getrxsubchans, + .setaudmode = tda9840_setaudmode, + + .init = { 2, { TDA9840_TEST, TDA9840_TEST_INT1SN + /* ,TDA9840_SW, TDA9840_MONO */} } + }, + { + .name = "tda9873h", + .insmodopt = &tda9873, + .addr_lo = I2C_ADDR_TDA985x_L >> 1, + .addr_hi = I2C_ADDR_TDA985x_H >> 1, + .registers = 3, + .flags = CHIP_HAS_INPUTSEL | CHIP_NEED_CHECKMODE, + + /* callbacks */ + .checkit = tda9873_checkit, + .getrxsubchans = tda9873_getrxsubchans, + .setaudmode = tda9873_setaudmode, + + .init = { 4, { TDA9873_SW, 0xa4, 0x06, 0x03 } }, + .inputreg = TDA9873_SW, + .inputmute = TDA9873_MUTE | TDA9873_AUTOMUTE, + .inputmap = {0xa0, 0xa2, 0xa0, 0xa0}, + .inputmask = TDA9873_INP_MASK|TDA9873_MUTE|TDA9873_AUTOMUTE, + + }, + { + .name = "tda9874h/a", + .insmodopt = &tda9874a, + .addr_lo = I2C_ADDR_TDA9874 >> 1, + .addr_hi = I2C_ADDR_TDA9874 >> 1, + .flags = CHIP_NEED_CHECKMODE, + + /* callbacks */ + .initialize = tda9874a_initialize, + .checkit = tda9874a_checkit, + .getrxsubchans = tda9874a_getrxsubchans, + .setaudmode = tda9874a_setaudmode, + }, + { + .name = "tda9875", + .insmodopt = &tda9875, + .addr_lo = I2C_ADDR_TDA9875 >> 1, + .addr_hi = I2C_ADDR_TDA9875 >> 1, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE, + + /* callbacks */ + .initialize = tda9875_initialize, + .checkit = tda9875_checkit, + .volfunc = tda9875_volume, + .bassfunc = tda9875_bass, + .treblefunc = tda9875_treble, + .leftreg = TDA9875_MVL, + .rightreg = TDA9875_MVR, + .bassreg = TDA9875_MBA, + .treblereg = TDA9875_MTR, + .volinit = 58880, + }, + { + .name = "tda9850", + .insmodopt = &tda9850, + .addr_lo = I2C_ADDR_TDA985x_L >> 1, + .addr_hi = I2C_ADDR_TDA985x_H >> 1, + .registers = 11, + + .getrxsubchans = tda985x_getrxsubchans, + .setaudmode = tda985x_setaudmode, + + .init = { 8, { TDA9850_C4, 0x08, 0x08, TDA985x_STEREO, 0x07, 0x10, 0x10, 0x03 } } + }, + { + .name = "tda9855", + .insmodopt = &tda9855, + .addr_lo = I2C_ADDR_TDA985x_L >> 1, + .addr_hi = I2C_ADDR_TDA985x_H >> 1, + .registers = 11, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE, + + .leftreg = TDA9855_VL, + .rightreg = TDA9855_VR, + .bassreg = TDA9855_BA, + .treblereg = TDA9855_TR, + + /* callbacks */ + .volfunc = tda9855_volume, + .bassfunc = tda9855_bass, + .treblefunc = tda9855_treble, + .getrxsubchans = tda985x_getrxsubchans, + .setaudmode = tda985x_setaudmode, + + .init = { 12, { 0, 0x6f, 0x6f, 0x0e, 0x07<<1, 0x8<<2, + TDA9855_MUTE | TDA9855_AVL | TDA9855_LOUD | TDA9855_INT, + TDA985x_STEREO | TDA9855_LINEAR | TDA9855_TZCM | TDA9855_VZCM, + 0x07, 0x10, 0x10, 0x03 }} + }, + { + .name = "tea6300", + .insmodopt = &tea6300, + .addr_lo = I2C_ADDR_TEA6300 >> 1, + .addr_hi = I2C_ADDR_TEA6300 >> 1, + .registers = 6, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TEA6300_VR, + .rightreg = TEA6300_VL, + .bassreg = TEA6300_BA, + .treblereg = TEA6300_TR, + + /* callbacks */ + .volfunc = tea6300_shift10, + .bassfunc = tea6300_shift12, + .treblefunc = tea6300_shift12, + + .inputreg = TEA6300_S, + .inputmap = { TEA6300_S_SA, TEA6300_S_SB, TEA6300_S_SC }, + .inputmute = TEA6300_S_GMU, + }, + { + .name = "tea6320", + .insmodopt = &tea6320, + .addr_lo = I2C_ADDR_TEA6300 >> 1, + .addr_hi = I2C_ADDR_TEA6300 >> 1, + .registers = 8, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TEA6320_V, + .rightreg = TEA6320_V, + .bassreg = TEA6320_BA, + .treblereg = TEA6320_TR, + + /* callbacks */ + .initialize = tea6320_initialize, + .volfunc = tea6320_volume, + .bassfunc = tea6320_shift11, + .treblefunc = tea6320_shift11, + + .inputreg = TEA6320_S, + .inputmap = { TEA6320_S_SA, TEA6420_S_SB, TEA6300_S_SC, TEA6320_S_SD }, + .inputmute = TEA6300_S_GMU, + }, + { + .name = "tea6420", + .insmodopt = &tea6420, + .addr_lo = I2C_ADDR_TEA6420 >> 1, + .addr_hi = I2C_ADDR_TEA6420 >> 1, + .registers = 1, + .flags = CHIP_HAS_INPUTSEL, + + .inputreg = -1, + .inputmap = { TEA6420_S_SA, TEA6420_S_SB, TEA6420_S_SC }, + .inputmute = TEA6420_S_GMU, + .inputmask = 0x07, + }, + { + .name = "tda8425", + .insmodopt = &tda8425, + .addr_lo = I2C_ADDR_TDA8425 >> 1, + .addr_hi = I2C_ADDR_TDA8425 >> 1, + .registers = 9, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TDA8425_VL, + .rightreg = TDA8425_VR, + .bassreg = TDA8425_BA, + .treblereg = TDA8425_TR, + + /* callbacks */ + .volfunc = tda8425_shift10, + .bassfunc = tda8425_shift12, + .treblefunc = tda8425_shift12, + .setaudmode = tda8425_setaudmode, + + .inputreg = TDA8425_S1, + .inputmap = { TDA8425_S1_CH1, TDA8425_S1_CH1, TDA8425_S1_CH1 }, + .inputmute = TDA8425_S1_OFF, + + }, + { + .name = "pic16c54 (PV951)", + .insmodopt = &pic16c54, + .addr_lo = I2C_ADDR_PIC16C54 >> 1, + .addr_hi = I2C_ADDR_PIC16C54>> 1, + .registers = 2, + .flags = CHIP_HAS_INPUTSEL, + + .inputreg = PIC16C54_REG_MISC, + .inputmap = {PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_TUNER, + PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_LINE, + PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_LINE, + PIC16C54_MISC_SND_MUTE}, + .inputmute = PIC16C54_MISC_SND_MUTE, + }, + { + .name = "ta8874z", + .checkit = ta8874z_checkit, + .insmodopt = &ta8874z, + .addr_lo = I2C_ADDR_TDA9840 >> 1, + .addr_hi = I2C_ADDR_TDA9840 >> 1, + .registers = 2, + + /* callbacks */ + .getrxsubchans = ta8874z_getrxsubchans, + .setaudmode = ta8874z_setaudmode, + + .init = {2, { TA8874Z_MONO_SET, TA8874Z_SEPARATION_DEFAULT}}, + }, + { .name = NULL } /* EOF */ +}; + + +/* ---------------------------------------------------------------------- */ + +static int tvaudio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + chip->muted = ctrl->val; + if (chip->muted) + chip_write_masked(chip,desc->inputreg,desc->inputmute,desc->inputmask); + else + chip_write_masked(chip,desc->inputreg, + desc->inputmap[chip->input],desc->inputmask); + return 0; + case V4L2_CID_AUDIO_VOLUME: { + u32 volume, balance; + u32 left, right; + + volume = chip->volume->val; + balance = chip->balance->val; + left = (min(65536U - balance, 32768U) * volume) / 32768U; + right = (min(balance, 32768U) * volume) / 32768U; + + chip_write(chip, desc->leftreg, desc->volfunc(left)); + chip_write(chip, desc->rightreg, desc->volfunc(right)); + return 0; + } + case V4L2_CID_AUDIO_BASS: + chip_write(chip, desc->bassreg, desc->bassfunc(ctrl->val)); + return 0; + case V4L2_CID_AUDIO_TREBLE: + chip_write(chip, desc->treblereg, desc->treblefunc(ctrl->val)); + return 0; + } + return -EINVAL; +} + + +/* ---------------------------------------------------------------------- */ +/* video4linux interface */ + +static int tvaudio_s_radio(struct v4l2_subdev *sd) +{ + struct CHIPSTATE *chip = to_state(sd); + + chip->radio = 1; + /* del_timer(&chip->wt); */ + return 0; +} + +static int tvaudio_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + if (!(desc->flags & CHIP_HAS_INPUTSEL)) + return 0; + if (input >= 4) + return -EINVAL; + /* There are four inputs: tuner, radio, extern and intern. */ + chip->input = input; + if (chip->muted) + return 0; + chip_write_masked(chip, desc->inputreg, + desc->inputmap[chip->input], desc->inputmask); + return 0; +} + +static int tvaudio_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + if (!desc->setaudmode) + return 0; + if (chip->radio) + return 0; + + switch (vt->audmode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1: + case V4L2_TUNER_MODE_LANG2: + case V4L2_TUNER_MODE_LANG1_LANG2: + break; + default: + return -EINVAL; + } + chip->audmode = vt->audmode; + + if (chip->thread) + wake_up_process(chip->thread); + else + desc->setaudmode(chip, vt->audmode); + + return 0; +} + +static int tvaudio_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + if (!desc->getrxsubchans) + return 0; + if (chip->radio) + return 0; + + vt->audmode = chip->audmode; + vt->rxsubchans = desc->getrxsubchans(chip); + vt->capability |= V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; + + return 0; +} + +static int tvaudio_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct CHIPSTATE *chip = to_state(sd); + + chip->radio = 0; + return 0; +} + +static int tvaudio_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *freq) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + /* For chips that provide getrxsubchans and setaudmode, and doesn't + automatically follows the stereo carrier, a kthread is + created to set the audio standard. In this case, when then + the video channel is changed, tvaudio starts on MONO mode. + After waiting for 2 seconds, the kernel thread is called, + to follow whatever audio standard is pointed by the + audio carrier. + */ + if (chip->thread) { + desc->setaudmode(chip, V4L2_TUNER_MODE_MONO); + chip->prevmode = -1; /* reset previous mode */ + mod_timer(&chip->wt, jiffies+msecs_to_jiffies(2000)); + } + return 0; +} + +static int tvaudio_log_status(struct v4l2_subdev *sd) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + v4l2_info(sd, "Chip: %s\n", desc->name); + v4l2_ctrl_handler_log_status(&chip->hdl, sd->name); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops tvaudio_ctrl_ops = { + .s_ctrl = tvaudio_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops tvaudio_core_ops = { + .log_status = tvaudio_log_status, +}; + +static const struct v4l2_subdev_tuner_ops tvaudio_tuner_ops = { + .s_radio = tvaudio_s_radio, + .s_frequency = tvaudio_s_frequency, + .s_tuner = tvaudio_s_tuner, + .g_tuner = tvaudio_g_tuner, +}; + +static const struct v4l2_subdev_audio_ops tvaudio_audio_ops = { + .s_routing = tvaudio_s_routing, +}; + +static const struct v4l2_subdev_video_ops tvaudio_video_ops = { + .s_std = tvaudio_s_std, +}; + +static const struct v4l2_subdev_ops tvaudio_ops = { + .core = &tvaudio_core_ops, + .tuner = &tvaudio_tuner_ops, + .audio = &tvaudio_audio_ops, + .video = &tvaudio_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + + +/* i2c registration */ + +static int tvaudio_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct CHIPSTATE *chip; + struct CHIPDESC *desc; + struct v4l2_subdev *sd; + + if (debug) { + printk(KERN_INFO "tvaudio: TV audio decoder + audio/video mux driver\n"); + printk(KERN_INFO "tvaudio: known chips: "); + for (desc = chiplist; desc->name != NULL; desc++) + printk(KERN_CONT "%s%s", + (desc == chiplist) ? "" : ", ", desc->name); + printk(KERN_CONT "\n"); + } + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + sd = &chip->sd; + v4l2_i2c_subdev_init(sd, client, &tvaudio_ops); + + /* find description for the chip */ + v4l2_dbg(1, debug, sd, "chip found @ 0x%x\n", client->addr<<1); + for (desc = chiplist; desc->name != NULL; desc++) { + if (0 == *(desc->insmodopt)) + continue; + if (client->addr < desc->addr_lo || + client->addr > desc->addr_hi) + continue; + if (desc->checkit && !desc->checkit(chip)) + continue; + break; + } + if (desc->name == NULL) { + v4l2_dbg(1, debug, sd, "no matching chip description found\n"); + return -EIO; + } + v4l2_info(sd, "%s found @ 0x%x (%s)\n", desc->name, client->addr<<1, client->adapter->name); + if (desc->flags) { + v4l2_dbg(1, debug, sd, "matches:%s%s%s.\n", + (desc->flags & CHIP_HAS_VOLUME) ? " volume" : "", + (desc->flags & CHIP_HAS_BASSTREBLE) ? " bass/treble" : "", + (desc->flags & CHIP_HAS_INPUTSEL) ? " audiomux" : ""); + } + + /* fill required data structures */ + if (!id) + strscpy(client->name, desc->name, I2C_NAME_SIZE); + chip->desc = desc; + chip->shadow.count = desc->registers+1; + chip->prevmode = -1; + chip->audmode = V4L2_TUNER_MODE_LANG1; + + /* initialization */ + if (desc->initialize != NULL) + desc->initialize(chip); + else + chip_cmd(chip, "init", &desc->init); + + v4l2_ctrl_handler_init(&chip->hdl, 5); + if (desc->flags & CHIP_HAS_INPUTSEL) + v4l2_ctrl_new_std(&chip->hdl, &tvaudio_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + if (desc->flags & CHIP_HAS_VOLUME) { + if (!desc->volfunc) { + /* This shouldn't be happen. Warn user, but keep working + without volume controls + */ + v4l2_info(sd, "volume callback undefined!\n"); + desc->flags &= ~CHIP_HAS_VOLUME; + } else { + chip->volume = v4l2_ctrl_new_std(&chip->hdl, + &tvaudio_ctrl_ops, V4L2_CID_AUDIO_VOLUME, + 0, 65535, 65535 / 100, + desc->volinit ? desc->volinit : 65535); + chip->balance = v4l2_ctrl_new_std(&chip->hdl, + &tvaudio_ctrl_ops, V4L2_CID_AUDIO_BALANCE, + 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_cluster(2, &chip->volume); + } + } + if (desc->flags & CHIP_HAS_BASSTREBLE) { + if (!desc->bassfunc || !desc->treblefunc) { + /* This shouldn't be happen. Warn user, but keep working + without bass/treble controls + */ + v4l2_info(sd, "bass/treble callbacks undefined!\n"); + desc->flags &= ~CHIP_HAS_BASSTREBLE; + } else { + v4l2_ctrl_new_std(&chip->hdl, + &tvaudio_ctrl_ops, V4L2_CID_AUDIO_BASS, + 0, 65535, 65535 / 100, + desc->bassinit ? desc->bassinit : 32768); + v4l2_ctrl_new_std(&chip->hdl, + &tvaudio_ctrl_ops, V4L2_CID_AUDIO_TREBLE, + 0, 65535, 65535 / 100, + desc->trebleinit ? desc->trebleinit : 32768); + } + } + + sd->ctrl_handler = &chip->hdl; + if (chip->hdl.error) { + int err = chip->hdl.error; + + v4l2_ctrl_handler_free(&chip->hdl); + return err; + } + /* set controls to the default values */ + v4l2_ctrl_handler_setup(&chip->hdl); + + chip->thread = NULL; + timer_setup(&chip->wt, chip_thread_wake, 0); + if (desc->flags & CHIP_NEED_CHECKMODE) { + if (!desc->getrxsubchans || !desc->setaudmode) { + /* This shouldn't be happen. Warn user, but keep working + without kthread + */ + v4l2_info(sd, "set/get mode callbacks undefined!\n"); + return 0; + } + /* start async thread */ + chip->thread = kthread_run(chip_thread, chip, "%s", + client->name); + if (IS_ERR(chip->thread)) { + v4l2_warn(sd, "failed to create kthread\n"); + chip->thread = NULL; + } + } + return 0; +} + +static int tvaudio_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct CHIPSTATE *chip = to_state(sd); + + del_timer_sync(&chip->wt); + if (chip->thread) { + /* shutdown async thread */ + kthread_stop(chip->thread); + chip->thread = NULL; + } + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&chip->hdl); + return 0; +} + +/* This driver supports many devices and the idea is to let the driver + detect which device is present. So rather than listing all supported + devices here, we pretend to support a single, fake device type. */ +static const struct i2c_device_id tvaudio_id[] = { + { "tvaudio", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tvaudio_id); + +static struct i2c_driver tvaudio_driver = { + .driver = { + .name = "tvaudio", + }, + .probe = tvaudio_probe, + .remove = tvaudio_remove, + .id_table = tvaudio_id, +}; + +module_i2c_driver(tvaudio_driver); diff --git a/drivers/media/i2c/tvp514x.c b/drivers/media/i2c/tvp514x.c new file mode 100644 index 000000000..a7fbe5b40 --- /dev/null +++ b/drivers/media/i2c/tvp514x.c @@ -0,0 +1,1217 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/tvp514x.c + * + * TI TVP5146/47 decoder driver + * + * Copyright (C) 2008 Texas Instruments Inc + * Author: Vaibhav Hiremath <hvaibhav@ti.com> + * + * Contributors: + * Sivaraj R <sivaraj@ti.com> + * Brijesh R Jadav <brijesh.j@ti.com> + * Hardik Shah <hardik.shah@ti.com> + * Manjunath Hadli <mrh@ti.com> + * Karicheri Muralidharan <m-karicheri2@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <linux/module.h> +#include <linux/v4l2-mediabus.h> +#include <linux/of.h> +#include <linux/of_graph.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-device.h> +#include <media/v4l2-common.h> +#include <media/v4l2-mediabus.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-ctrls.h> +#include <media/i2c/tvp514x.h> +#include <media/media-entity.h> + +#include "tvp514x_regs.h" + +/* Private macros for TVP */ +#define I2C_RETRY_COUNT (5) +#define LOCK_RETRY_COUNT (5) +#define LOCK_RETRY_DELAY (200) + +/* Debug functions */ +static bool debug; +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("TVP514X linux decoder driver"); +MODULE_LICENSE("GPL"); + +/* enum tvp514x_std - enum for supported standards */ +enum tvp514x_std { + STD_NTSC_MJ = 0, + STD_PAL_BDGHIN, + STD_INVALID +}; + +/** + * struct tvp514x_std_info - Structure to store standard information + * @width: Line width in pixels + * @height:Number of active lines + * @video_std: Value to write in REG_VIDEO_STD register + * @standard: v4l2 standard structure information + */ +struct tvp514x_std_info { + unsigned long width; + unsigned long height; + u8 video_std; + struct v4l2_standard standard; +}; + +static struct tvp514x_reg tvp514x_reg_list_default[0x40]; + +static int tvp514x_s_stream(struct v4l2_subdev *sd, int enable); +/** + * struct tvp514x_decoder - TVP5146/47 decoder object + * @sd: Subdevice Slave handle + * @hdl: embedded &struct v4l2_ctrl_handler + * @tvp514x_regs: copy of hw's regs with preset values. + * @pdata: Board specific + * @ver: Chip version + * @streaming: TVP5146/47 decoder streaming - enabled or disabled. + * @pix: Current pixel format + * @num_fmts: Number of formats + * @fmt_list: Format list + * @current_std: Current standard + * @num_stds: Number of standards + * @std_list: Standards list + * @input: Input routing at chip level + * @output: Output routing at chip level + * @pad: subdev media pad associated with the decoder + * @format: media bus frame format + * @int_seq: driver's register init sequence + */ +struct tvp514x_decoder { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct tvp514x_reg tvp514x_regs[ARRAY_SIZE(tvp514x_reg_list_default)]; + const struct tvp514x_platform_data *pdata; + + int ver; + int streaming; + + struct v4l2_pix_format pix; + int num_fmts; + const struct v4l2_fmtdesc *fmt_list; + + enum tvp514x_std current_std; + int num_stds; + const struct tvp514x_std_info *std_list; + /* Input and Output Routing parameters */ + u32 input; + u32 output; + + /* mc related members */ + struct media_pad pad; + struct v4l2_mbus_framefmt format; + + struct tvp514x_reg *int_seq; +}; + +/* TVP514x default register values */ +static struct tvp514x_reg tvp514x_reg_list_default[] = { + /* Composite selected */ + {TOK_WRITE, REG_INPUT_SEL, 0x05}, + {TOK_WRITE, REG_AFE_GAIN_CTRL, 0x0F}, + /* Auto mode */ + {TOK_WRITE, REG_VIDEO_STD, 0x00}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, + {TOK_SKIP, REG_AUTOSWITCH_MASK, 0x3F}, + {TOK_WRITE, REG_COLOR_KILLER, 0x10}, + {TOK_WRITE, REG_LUMA_CONTROL1, 0x00}, + {TOK_WRITE, REG_LUMA_CONTROL2, 0x00}, + {TOK_WRITE, REG_LUMA_CONTROL3, 0x02}, + {TOK_WRITE, REG_BRIGHTNESS, 0x80}, + {TOK_WRITE, REG_CONTRAST, 0x80}, + {TOK_WRITE, REG_SATURATION, 0x80}, + {TOK_WRITE, REG_HUE, 0x00}, + {TOK_WRITE, REG_CHROMA_CONTROL1, 0x00}, + {TOK_WRITE, REG_CHROMA_CONTROL2, 0x0E}, + /* Reserved */ + {TOK_SKIP, 0x0F, 0x00}, + {TOK_WRITE, REG_COMP_PR_SATURATION, 0x80}, + {TOK_WRITE, REG_COMP_Y_CONTRAST, 0x80}, + {TOK_WRITE, REG_COMP_PB_SATURATION, 0x80}, + /* Reserved */ + {TOK_SKIP, 0x13, 0x00}, + {TOK_WRITE, REG_COMP_Y_BRIGHTNESS, 0x80}, + /* Reserved */ + {TOK_SKIP, 0x15, 0x00}, + /* NTSC timing */ + {TOK_SKIP, REG_AVID_START_PIXEL_LSB, 0x55}, + {TOK_SKIP, REG_AVID_START_PIXEL_MSB, 0x00}, + {TOK_SKIP, REG_AVID_STOP_PIXEL_LSB, 0x25}, + {TOK_SKIP, REG_AVID_STOP_PIXEL_MSB, 0x03}, + /* NTSC timing */ + {TOK_SKIP, REG_HSYNC_START_PIXEL_LSB, 0x00}, + {TOK_SKIP, REG_HSYNC_START_PIXEL_MSB, 0x00}, + {TOK_SKIP, REG_HSYNC_STOP_PIXEL_LSB, 0x40}, + {TOK_SKIP, REG_HSYNC_STOP_PIXEL_MSB, 0x00}, + /* NTSC timing */ + {TOK_SKIP, REG_VSYNC_START_LINE_LSB, 0x04}, + {TOK_SKIP, REG_VSYNC_START_LINE_MSB, 0x00}, + {TOK_SKIP, REG_VSYNC_STOP_LINE_LSB, 0x07}, + {TOK_SKIP, REG_VSYNC_STOP_LINE_MSB, 0x00}, + /* NTSC timing */ + {TOK_SKIP, REG_VBLK_START_LINE_LSB, 0x01}, + {TOK_SKIP, REG_VBLK_START_LINE_MSB, 0x00}, + {TOK_SKIP, REG_VBLK_STOP_LINE_LSB, 0x15}, + {TOK_SKIP, REG_VBLK_STOP_LINE_MSB, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x26, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x27, 0x00}, + {TOK_SKIP, REG_FAST_SWTICH_CONTROL, 0xCC}, + /* Reserved */ + {TOK_SKIP, 0x29, 0x00}, + {TOK_SKIP, REG_FAST_SWTICH_SCART_DELAY, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x2B, 0x00}, + {TOK_SKIP, REG_SCART_DELAY, 0x00}, + {TOK_SKIP, REG_CTI_DELAY, 0x00}, + {TOK_SKIP, REG_CTI_CONTROL, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x2F, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x30, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x31, 0x00}, + /* HS, VS active high */ + {TOK_WRITE, REG_SYNC_CONTROL, 0x00}, + /* 10-bit BT.656 */ + {TOK_WRITE, REG_OUTPUT_FORMATTER1, 0x00}, + /* Enable clk & data */ + {TOK_WRITE, REG_OUTPUT_FORMATTER2, 0x11}, + /* Enable AVID & FLD */ + {TOK_WRITE, REG_OUTPUT_FORMATTER3, 0xEE}, + /* Enable VS & HS */ + {TOK_WRITE, REG_OUTPUT_FORMATTER4, 0xAF}, + {TOK_WRITE, REG_OUTPUT_FORMATTER5, 0xFF}, + {TOK_WRITE, REG_OUTPUT_FORMATTER6, 0xFF}, + /* Clear status */ + {TOK_WRITE, REG_CLEAR_LOST_LOCK, 0x01}, + {TOK_TERM, 0, 0}, +}; + +/* + * List of image formats supported by TVP5146/47 decoder + * Currently we are using 8 bit mode only, but can be + * extended to 10/20 bit mode. + */ +static const struct v4l2_fmtdesc tvp514x_fmt_list[] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = 0, + .description = "8-bit UYVY 4:2:2 Format", + .pixelformat = V4L2_PIX_FMT_UYVY, + }, +}; + +/* + * Supported standards - + * + * Currently supports two standards only, need to add support for rest of the + * modes, like SECAM, etc... + */ +static const struct tvp514x_std_info tvp514x_std_list[] = { + /* Standard: STD_NTSC_MJ */ + [STD_NTSC_MJ] = { + .width = NTSC_NUM_ACTIVE_PIXELS, + .height = NTSC_NUM_ACTIVE_LINES, + .video_std = VIDEO_STD_NTSC_MJ_BIT, + .standard = { + .index = 0, + .id = V4L2_STD_NTSC, + .name = "NTSC", + .frameperiod = {1001, 30000}, + .framelines = 525 + }, + /* Standard: STD_PAL_BDGHIN */ + }, + [STD_PAL_BDGHIN] = { + .width = PAL_NUM_ACTIVE_PIXELS, + .height = PAL_NUM_ACTIVE_LINES, + .video_std = VIDEO_STD_PAL_BDGHIN_BIT, + .standard = { + .index = 1, + .id = V4L2_STD_PAL, + .name = "PAL", + .frameperiod = {1, 25}, + .framelines = 625 + }, + }, + /* Standard: need to add for additional standard */ +}; + + +static inline struct tvp514x_decoder *to_decoder(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tvp514x_decoder, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tvp514x_decoder, hdl)->sd; +} + + +/** + * tvp514x_read_reg() - Read a value from a register in an TVP5146/47. + * @sd: ptr to v4l2_subdev struct + * @reg: TVP5146/47 register address + * + * Returns value read if successful, or non-zero (-1) otherwise. + */ +static int tvp514x_read_reg(struct v4l2_subdev *sd, u8 reg) +{ + int err, retry = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + +read_again: + + err = i2c_smbus_read_byte_data(client, reg); + if (err < 0) { + if (retry <= I2C_RETRY_COUNT) { + v4l2_warn(sd, "Read: retry ... %d\n", retry); + retry++; + msleep_interruptible(10); + goto read_again; + } + } + + return err; +} + +/** + * dump_reg() - dump the register content of TVP5146/47. + * @sd: ptr to v4l2_subdev struct + * @reg: TVP5146/47 register address + */ +static void dump_reg(struct v4l2_subdev *sd, u8 reg) +{ + u32 val; + + val = tvp514x_read_reg(sd, reg); + v4l2_info(sd, "Reg(0x%.2X): 0x%.2X\n", reg, val); +} + +/** + * tvp514x_write_reg() - Write a value to a register in TVP5146/47 + * @sd: ptr to v4l2_subdev struct + * @reg: TVP5146/47 register address + * @val: value to be written to the register + * + * Write a value to a register in an TVP5146/47 decoder device. + * Returns zero if successful, or non-zero otherwise. + */ +static int tvp514x_write_reg(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + int err, retry = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + +write_again: + + err = i2c_smbus_write_byte_data(client, reg, val); + if (err) { + if (retry <= I2C_RETRY_COUNT) { + v4l2_warn(sd, "Write: retry ... %d\n", retry); + retry++; + msleep_interruptible(10); + goto write_again; + } + } + + return err; +} + +/** + * tvp514x_write_regs() : Initializes a list of TVP5146/47 registers + * @sd: ptr to v4l2_subdev struct + * @reglist: list of TVP5146/47 registers and values + * + * Initializes a list of TVP5146/47 registers:- + * if token is TOK_TERM, then entire write operation terminates + * if token is TOK_DELAY, then a delay of 'val' msec is introduced + * if token is TOK_SKIP, then the register write is skipped + * if token is TOK_WRITE, then the register write is performed + * Returns zero if successful, or non-zero otherwise. + */ +static int tvp514x_write_regs(struct v4l2_subdev *sd, + const struct tvp514x_reg reglist[]) +{ + int err; + const struct tvp514x_reg *next = reglist; + + for (; next->token != TOK_TERM; next++) { + if (next->token == TOK_DELAY) { + msleep(next->val); + continue; + } + + if (next->token == TOK_SKIP) + continue; + + err = tvp514x_write_reg(sd, next->reg, (u8) next->val); + if (err) { + v4l2_err(sd, "Write failed. Err[%d]\n", err); + return err; + } + } + return 0; +} + +/** + * tvp514x_query_current_std() : Query the current standard detected by TVP5146/47 + * @sd: ptr to v4l2_subdev struct + * + * Returns the current standard detected by TVP5146/47, STD_INVALID if there is no + * standard detected. + */ +static enum tvp514x_std tvp514x_query_current_std(struct v4l2_subdev *sd) +{ + u8 std, std_status; + + std = tvp514x_read_reg(sd, REG_VIDEO_STD); + if ((std & VIDEO_STD_MASK) == VIDEO_STD_AUTO_SWITCH_BIT) + /* use the standard status register */ + std_status = tvp514x_read_reg(sd, REG_VIDEO_STD_STATUS); + else + /* use the standard register itself */ + std_status = std; + + switch (std_status & VIDEO_STD_MASK) { + case VIDEO_STD_NTSC_MJ_BIT: + return STD_NTSC_MJ; + + case VIDEO_STD_PAL_BDGHIN_BIT: + return STD_PAL_BDGHIN; + + default: + return STD_INVALID; + } + + return STD_INVALID; +} + +/* TVP5146/47 register dump function */ +static void tvp514x_reg_dump(struct v4l2_subdev *sd) +{ + dump_reg(sd, REG_INPUT_SEL); + dump_reg(sd, REG_AFE_GAIN_CTRL); + dump_reg(sd, REG_VIDEO_STD); + dump_reg(sd, REG_OPERATION_MODE); + dump_reg(sd, REG_COLOR_KILLER); + dump_reg(sd, REG_LUMA_CONTROL1); + dump_reg(sd, REG_LUMA_CONTROL2); + dump_reg(sd, REG_LUMA_CONTROL3); + dump_reg(sd, REG_BRIGHTNESS); + dump_reg(sd, REG_CONTRAST); + dump_reg(sd, REG_SATURATION); + dump_reg(sd, REG_HUE); + dump_reg(sd, REG_CHROMA_CONTROL1); + dump_reg(sd, REG_CHROMA_CONTROL2); + dump_reg(sd, REG_COMP_PR_SATURATION); + dump_reg(sd, REG_COMP_Y_CONTRAST); + dump_reg(sd, REG_COMP_PB_SATURATION); + dump_reg(sd, REG_COMP_Y_BRIGHTNESS); + dump_reg(sd, REG_AVID_START_PIXEL_LSB); + dump_reg(sd, REG_AVID_START_PIXEL_MSB); + dump_reg(sd, REG_AVID_STOP_PIXEL_LSB); + dump_reg(sd, REG_AVID_STOP_PIXEL_MSB); + dump_reg(sd, REG_HSYNC_START_PIXEL_LSB); + dump_reg(sd, REG_HSYNC_START_PIXEL_MSB); + dump_reg(sd, REG_HSYNC_STOP_PIXEL_LSB); + dump_reg(sd, REG_HSYNC_STOP_PIXEL_MSB); + dump_reg(sd, REG_VSYNC_START_LINE_LSB); + dump_reg(sd, REG_VSYNC_START_LINE_MSB); + dump_reg(sd, REG_VSYNC_STOP_LINE_LSB); + dump_reg(sd, REG_VSYNC_STOP_LINE_MSB); + dump_reg(sd, REG_VBLK_START_LINE_LSB); + dump_reg(sd, REG_VBLK_START_LINE_MSB); + dump_reg(sd, REG_VBLK_STOP_LINE_LSB); + dump_reg(sd, REG_VBLK_STOP_LINE_MSB); + dump_reg(sd, REG_SYNC_CONTROL); + dump_reg(sd, REG_OUTPUT_FORMATTER1); + dump_reg(sd, REG_OUTPUT_FORMATTER2); + dump_reg(sd, REG_OUTPUT_FORMATTER3); + dump_reg(sd, REG_OUTPUT_FORMATTER4); + dump_reg(sd, REG_OUTPUT_FORMATTER5); + dump_reg(sd, REG_OUTPUT_FORMATTER6); + dump_reg(sd, REG_CLEAR_LOST_LOCK); +} + +/** + * tvp514x_configure() - Configure the TVP5146/47 registers + * @sd: ptr to v4l2_subdev struct + * @decoder: ptr to tvp514x_decoder structure + * + * Returns zero if successful, or non-zero otherwise. + */ +static int tvp514x_configure(struct v4l2_subdev *sd, + struct tvp514x_decoder *decoder) +{ + int err; + + /* common register initialization */ + err = + tvp514x_write_regs(sd, decoder->tvp514x_regs); + if (err) + return err; + + if (debug) + tvp514x_reg_dump(sd); + + return 0; +} + +/** + * tvp514x_detect() - Detect if an tvp514x is present, and if so which revision. + * @sd: pointer to standard V4L2 sub-device structure + * @decoder: pointer to tvp514x_decoder structure + * + * A device is considered to be detected if the chip ID (LSB and MSB) + * registers match the expected values. + * Any value of the rom version register is accepted. + * Returns ENODEV error number if no device is detected, or zero + * if a device is detected. + */ +static int tvp514x_detect(struct v4l2_subdev *sd, + struct tvp514x_decoder *decoder) +{ + u8 chip_id_msb, chip_id_lsb, rom_ver; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + chip_id_msb = tvp514x_read_reg(sd, REG_CHIP_ID_MSB); + chip_id_lsb = tvp514x_read_reg(sd, REG_CHIP_ID_LSB); + rom_ver = tvp514x_read_reg(sd, REG_ROM_VERSION); + + v4l2_dbg(1, debug, sd, + "chip id detected msb:0x%x lsb:0x%x rom version:0x%x\n", + chip_id_msb, chip_id_lsb, rom_ver); + if ((chip_id_msb != TVP514X_CHIP_ID_MSB) + || ((chip_id_lsb != TVP5146_CHIP_ID_LSB) + && (chip_id_lsb != TVP5147_CHIP_ID_LSB))) { + /* We didn't read the values we expected, so this must not be + * an TVP5146/47. + */ + v4l2_err(sd, "chip id mismatch msb:0x%x lsb:0x%x\n", + chip_id_msb, chip_id_lsb); + return -ENODEV; + } + + decoder->ver = rom_ver; + + v4l2_info(sd, "%s (Version - 0x%.2x) found at 0x%x (%s)\n", + client->name, decoder->ver, + client->addr << 1, client->adapter->name); + return 0; +} + +/** + * tvp514x_querystd() - V4L2 decoder interface handler for querystd + * @sd: pointer to standard V4L2 sub-device structure + * @std_id: standard V4L2 std_id ioctl enum + * + * Returns the current standard detected by TVP5146/47. If no active input is + * detected then *std_id is set to 0 and the function returns 0. + */ +static int tvp514x_querystd(struct v4l2_subdev *sd, v4l2_std_id *std_id) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + enum tvp514x_std current_std; + enum tvp514x_input input_sel; + u8 sync_lock_status, lock_mask; + + if (std_id == NULL) + return -EINVAL; + + /* To query the standard the TVP514x must power on the ADCs. */ + if (!decoder->streaming) { + tvp514x_s_stream(sd, 1); + msleep(LOCK_RETRY_DELAY); + } + + /* query the current standard */ + current_std = tvp514x_query_current_std(sd); + if (current_std == STD_INVALID) { + *std_id = V4L2_STD_UNKNOWN; + return 0; + } + + input_sel = decoder->input; + + switch (input_sel) { + case INPUT_CVBS_VI1A: + case INPUT_CVBS_VI1B: + case INPUT_CVBS_VI1C: + case INPUT_CVBS_VI2A: + case INPUT_CVBS_VI2B: + case INPUT_CVBS_VI2C: + case INPUT_CVBS_VI3A: + case INPUT_CVBS_VI3B: + case INPUT_CVBS_VI3C: + case INPUT_CVBS_VI4A: + lock_mask = STATUS_CLR_SUBCAR_LOCK_BIT | + STATUS_HORZ_SYNC_LOCK_BIT | + STATUS_VIRT_SYNC_LOCK_BIT; + break; + + case INPUT_SVIDEO_VI2A_VI1A: + case INPUT_SVIDEO_VI2B_VI1B: + case INPUT_SVIDEO_VI2C_VI1C: + case INPUT_SVIDEO_VI2A_VI3A: + case INPUT_SVIDEO_VI2B_VI3B: + case INPUT_SVIDEO_VI2C_VI3C: + case INPUT_SVIDEO_VI4A_VI1A: + case INPUT_SVIDEO_VI4A_VI1B: + case INPUT_SVIDEO_VI4A_VI1C: + case INPUT_SVIDEO_VI4A_VI3A: + case INPUT_SVIDEO_VI4A_VI3B: + case INPUT_SVIDEO_VI4A_VI3C: + lock_mask = STATUS_HORZ_SYNC_LOCK_BIT | + STATUS_VIRT_SYNC_LOCK_BIT; + break; + /*Need to add other interfaces*/ + default: + return -EINVAL; + } + /* check whether signal is locked */ + sync_lock_status = tvp514x_read_reg(sd, REG_STATUS1); + if (lock_mask != (sync_lock_status & lock_mask)) { + *std_id = V4L2_STD_UNKNOWN; + return 0; /* No input detected */ + } + + *std_id &= decoder->std_list[current_std].standard.id; + + v4l2_dbg(1, debug, sd, "Current STD: %s\n", + decoder->std_list[current_std].standard.name); + return 0; +} + +/** + * tvp514x_s_std() - V4L2 decoder interface handler for s_std + * @sd: pointer to standard V4L2 sub-device structure + * @std_id: standard V4L2 v4l2_std_id ioctl enum + * + * If std_id is supported, sets the requested standard. Otherwise, returns + * -EINVAL + */ +static int tvp514x_s_std(struct v4l2_subdev *sd, v4l2_std_id std_id) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + int err, i; + + for (i = 0; i < decoder->num_stds; i++) + if (std_id & decoder->std_list[i].standard.id) + break; + + if ((i == decoder->num_stds) || (i == STD_INVALID)) + return -EINVAL; + + err = tvp514x_write_reg(sd, REG_VIDEO_STD, + decoder->std_list[i].video_std); + if (err) + return err; + + decoder->current_std = i; + decoder->tvp514x_regs[REG_VIDEO_STD].val = + decoder->std_list[i].video_std; + + v4l2_dbg(1, debug, sd, "Standard set to: %s\n", + decoder->std_list[i].standard.name); + return 0; +} + +/** + * tvp514x_s_routing() - V4L2 decoder interface handler for s_routing + * @sd: pointer to standard V4L2 sub-device structure + * @input: input selector for routing the signal + * @output: output selector for routing the signal + * @config: config value. Not used + * + * If index is valid, selects the requested input. Otherwise, returns -EINVAL if + * the input is not supported or there is no active signal present in the + * selected input. + */ +static int tvp514x_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + int err; + enum tvp514x_input input_sel; + enum tvp514x_output output_sel; + + if ((input >= INPUT_INVALID) || + (output >= OUTPUT_INVALID)) + /* Index out of bound */ + return -EINVAL; + + input_sel = input; + output_sel = output; + + err = tvp514x_write_reg(sd, REG_INPUT_SEL, input_sel); + if (err) + return err; + + output_sel |= tvp514x_read_reg(sd, + REG_OUTPUT_FORMATTER1) & 0x7; + err = tvp514x_write_reg(sd, REG_OUTPUT_FORMATTER1, + output_sel); + if (err) + return err; + + decoder->tvp514x_regs[REG_INPUT_SEL].val = input_sel; + decoder->tvp514x_regs[REG_OUTPUT_FORMATTER1].val = output_sel; + decoder->input = input; + decoder->output = output; + + v4l2_dbg(1, debug, sd, "Input set to: %d\n", input_sel); + + return 0; +} + +/** + * tvp514x_s_ctrl() - V4L2 decoder interface handler for s_ctrl + * @ctrl: pointer to v4l2_ctrl structure + * + * If the requested control is supported, sets the control's current + * value in HW. Otherwise, returns -EINVAL if the control is not supported. + */ +static int tvp514x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct tvp514x_decoder *decoder = to_decoder(sd); + int err = -EINVAL, value; + + value = ctrl->val; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + err = tvp514x_write_reg(sd, REG_BRIGHTNESS, value); + if (!err) + decoder->tvp514x_regs[REG_BRIGHTNESS].val = value; + break; + case V4L2_CID_CONTRAST: + err = tvp514x_write_reg(sd, REG_CONTRAST, value); + if (!err) + decoder->tvp514x_regs[REG_CONTRAST].val = value; + break; + case V4L2_CID_SATURATION: + err = tvp514x_write_reg(sd, REG_SATURATION, value); + if (!err) + decoder->tvp514x_regs[REG_SATURATION].val = value; + break; + case V4L2_CID_HUE: + if (value == 180) + value = 0x7F; + else if (value == -180) + value = 0x80; + err = tvp514x_write_reg(sd, REG_HUE, value); + if (!err) + decoder->tvp514x_regs[REG_HUE].val = value; + break; + case V4L2_CID_AUTOGAIN: + err = tvp514x_write_reg(sd, REG_AFE_GAIN_CTRL, value ? 0x0f : 0x0c); + if (!err) + decoder->tvp514x_regs[REG_AFE_GAIN_CTRL].val = value; + break; + } + + v4l2_dbg(1, debug, sd, "Set Control: ID - %d - %d\n", + ctrl->id, ctrl->val); + return err; +} + +/** + * tvp514x_g_frame_interval() - V4L2 decoder interface handler + * @sd: pointer to standard V4L2 sub-device structure + * @ival: pointer to a v4l2_subdev_frame_interval structure + * + * Returns the decoder's video CAPTURE parameters. + */ +static int +tvp514x_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + enum tvp514x_std current_std; + + + /* get the current standard */ + current_std = decoder->current_std; + + ival->interval = + decoder->std_list[current_std].standard.frameperiod; + + return 0; +} + +/** + * tvp514x_s_frame_interval() - V4L2 decoder interface handler + * @sd: pointer to standard V4L2 sub-device structure + * @ival: pointer to a v4l2_subdev_frame_interval structure + * + * Configures the decoder to use the input parameters, if possible. If + * not possible, returns the appropriate error code. + */ +static int +tvp514x_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + struct v4l2_fract *timeperframe; + enum tvp514x_std current_std; + + + timeperframe = &ival->interval; + + /* get the current standard */ + current_std = decoder->current_std; + + *timeperframe = + decoder->std_list[current_std].standard.frameperiod; + + return 0; +} + +/** + * tvp514x_s_stream() - V4L2 decoder i/f handler for s_stream + * @sd: pointer to standard V4L2 sub-device structure + * @enable: streaming enable or disable + * + * Sets streaming to enable or disable, if possible. + */ +static int tvp514x_s_stream(struct v4l2_subdev *sd, int enable) +{ + int err = 0; + struct tvp514x_decoder *decoder = to_decoder(sd); + + if (decoder->streaming == enable) + return 0; + + switch (enable) { + case 0: + { + /* Power Down Sequence */ + err = tvp514x_write_reg(sd, REG_OPERATION_MODE, 0x01); + if (err) { + v4l2_err(sd, "Unable to turn off decoder\n"); + return err; + } + decoder->streaming = enable; + break; + } + case 1: + { + /* Power Up Sequence */ + err = tvp514x_write_regs(sd, decoder->int_seq); + if (err) { + v4l2_err(sd, "Unable to turn on decoder\n"); + return err; + } + /* Detect if not already detected */ + err = tvp514x_detect(sd, decoder); + if (err) { + v4l2_err(sd, "Unable to detect decoder\n"); + return err; + } + err = tvp514x_configure(sd, decoder); + if (err) { + v4l2_err(sd, "Unable to configure decoder\n"); + return err; + } + decoder->streaming = enable; + break; + } + default: + err = -ENODEV; + break; + } + + return err; +} + +static const struct v4l2_ctrl_ops tvp514x_ctrl_ops = { + .s_ctrl = tvp514x_s_ctrl, +}; + +/** + * tvp514x_enum_mbus_code() - V4L2 decoder interface handler for enum_mbus_code + * @sd: pointer to standard V4L2 sub-device structure + * @cfg: pad configuration + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * + * Enumertaes mbus codes supported + */ +static int tvp514x_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + u32 pad = code->pad; + u32 index = code->index; + + memset(code, 0, sizeof(*code)); + code->index = index; + code->pad = pad; + + if (index != 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_2X8; + + return 0; +} + +/** + * tvp514x_get_pad_format() - V4L2 decoder interface handler for get pad format + * @sd: pointer to standard V4L2 sub-device structure + * @cfg: pad configuration + * @format: pointer to v4l2_subdev_format structure + * + * Retrieves pad format which is active or tried based on requirement + */ +static int tvp514x_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + __u32 which = format->which; + + if (format->pad) + return -EINVAL; + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) { + format->format = decoder->format; + return 0; + } + + format->format.code = MEDIA_BUS_FMT_UYVY8_2X8; + format->format.width = tvp514x_std_list[decoder->current_std].width; + format->format.height = tvp514x_std_list[decoder->current_std].height; + format->format.colorspace = V4L2_COLORSPACE_SMPTE170M; + format->format.field = V4L2_FIELD_INTERLACED; + + return 0; +} + +/** + * tvp514x_set_pad_format() - V4L2 decoder interface handler for set pad format + * @sd: pointer to standard V4L2 sub-device structure + * @cfg: pad configuration + * @fmt: pointer to v4l2_subdev_format structure + * + * Set pad format for the output pad + */ +static int tvp514x_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + + if (fmt->format.field != V4L2_FIELD_INTERLACED || + fmt->format.code != MEDIA_BUS_FMT_UYVY8_2X8 || + fmt->format.colorspace != V4L2_COLORSPACE_SMPTE170M || + fmt->format.width != tvp514x_std_list[decoder->current_std].width || + fmt->format.height != tvp514x_std_list[decoder->current_std].height) + return -EINVAL; + + decoder->format = fmt->format; + + return 0; +} + +static const struct v4l2_subdev_video_ops tvp514x_video_ops = { + .s_std = tvp514x_s_std, + .s_routing = tvp514x_s_routing, + .querystd = tvp514x_querystd, + .g_frame_interval = tvp514x_g_frame_interval, + .s_frame_interval = tvp514x_s_frame_interval, + .s_stream = tvp514x_s_stream, +}; + +static const struct v4l2_subdev_pad_ops tvp514x_pad_ops = { + .enum_mbus_code = tvp514x_enum_mbus_code, + .get_fmt = tvp514x_get_pad_format, + .set_fmt = tvp514x_set_pad_format, +}; + +static const struct v4l2_subdev_ops tvp514x_ops = { + .video = &tvp514x_video_ops, + .pad = &tvp514x_pad_ops, +}; + +static const struct tvp514x_decoder tvp514x_dev = { + .streaming = 0, + .fmt_list = tvp514x_fmt_list, + .num_fmts = ARRAY_SIZE(tvp514x_fmt_list), + .pix = { + /* Default to NTSC 8-bit YUV 422 */ + .width = NTSC_NUM_ACTIVE_PIXELS, + .height = NTSC_NUM_ACTIVE_LINES, + .pixelformat = V4L2_PIX_FMT_UYVY, + .field = V4L2_FIELD_INTERLACED, + .bytesperline = NTSC_NUM_ACTIVE_PIXELS * 2, + .sizeimage = NTSC_NUM_ACTIVE_PIXELS * 2 * + NTSC_NUM_ACTIVE_LINES, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + }, + .current_std = STD_NTSC_MJ, + .std_list = tvp514x_std_list, + .num_stds = ARRAY_SIZE(tvp514x_std_list), + +}; + +static struct tvp514x_platform_data * +tvp514x_get_pdata(struct i2c_client *client) +{ + struct tvp514x_platform_data *pdata = NULL; + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + struct device_node *endpoint; + unsigned int flags; + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return client->dev.platform_data; + + endpoint = of_graph_get_next_endpoint(client->dev.of_node, NULL); + if (!endpoint) + return NULL; + + if (v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), &bus_cfg)) + goto done; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto done; + + flags = bus_cfg.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + pdata->hs_polarity = 1; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + pdata->vs_polarity = 1; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + pdata->clk_polarity = 1; + +done: + of_node_put(endpoint); + return pdata; +} + +/** + * tvp514x_probe() - decoder driver i2c probe handler + * @client: i2c driver client device structure + * @id: i2c driver id table + * + * Register decoder as an i2c client device and V4L2 + * device. + */ +static int +tvp514x_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct tvp514x_platform_data *pdata = tvp514x_get_pdata(client); + struct tvp514x_decoder *decoder; + struct v4l2_subdev *sd; + int ret; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + decoder = devm_kzalloc(&client->dev, sizeof(*decoder), GFP_KERNEL); + if (!decoder) + return -ENOMEM; + + /* Initialize the tvp514x_decoder with default configuration */ + *decoder = tvp514x_dev; + /* Copy default register configuration */ + memcpy(decoder->tvp514x_regs, tvp514x_reg_list_default, + sizeof(tvp514x_reg_list_default)); + + decoder->int_seq = (struct tvp514x_reg *)id->driver_data; + + /* Copy board specific information here */ + decoder->pdata = pdata; + + /** + * Fetch platform specific data, and configure the + * tvp514x_reg_list[] accordingly. Since this is one + * time configuration, no need to preserve. + */ + decoder->tvp514x_regs[REG_OUTPUT_FORMATTER2].val |= + (decoder->pdata->clk_polarity << 1); + decoder->tvp514x_regs[REG_SYNC_CONTROL].val |= + ((decoder->pdata->hs_polarity << 2) | + (decoder->pdata->vs_polarity << 3)); + /* Set default standard to auto */ + decoder->tvp514x_regs[REG_VIDEO_STD].val = + VIDEO_STD_AUTO_SWITCH_BIT; + + /* Register with V4L2 layer as slave device */ + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &tvp514x_ops); + +#if defined(CONFIG_MEDIA_CONTROLLER) + decoder->pad.flags = MEDIA_PAD_FL_SOURCE; + decoder->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + decoder->sd.entity.function = MEDIA_ENT_F_ATV_DECODER; + + ret = media_entity_pads_init(&decoder->sd.entity, 1, &decoder->pad); + if (ret < 0) { + v4l2_err(sd, "%s decoder driver failed to register !!\n", + sd->name); + return ret; + } +#endif + v4l2_ctrl_handler_init(&decoder->hdl, 5); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_HUE, -180, 180, 180, 0); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + sd->ctrl_handler = &decoder->hdl; + if (decoder->hdl.error) { + ret = decoder->hdl.error; + goto done; + } + v4l2_ctrl_handler_setup(&decoder->hdl); + + ret = v4l2_async_register_subdev(&decoder->sd); + if (!ret) + v4l2_info(sd, "%s decoder driver registered !!\n", sd->name); + +done: + if (ret < 0) { + v4l2_ctrl_handler_free(&decoder->hdl); + media_entity_cleanup(&decoder->sd.entity); + } + return ret; +} + +/** + * tvp514x_remove() - decoder driver i2c remove handler + * @client: i2c driver client device structure + * + * Unregister decoder as an i2c client device and V4L2 + * device. Complement of tvp514x_probe(). + */ +static int tvp514x_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tvp514x_decoder *decoder = to_decoder(sd); + + v4l2_async_unregister_subdev(&decoder->sd); + media_entity_cleanup(&decoder->sd.entity); + v4l2_ctrl_handler_free(&decoder->hdl); + return 0; +} +/* TVP5146 Init/Power on Sequence */ +static const struct tvp514x_reg tvp5146_init_reg_seq[] = { + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x02}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0x80}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x60}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xB0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x00}, + {TOK_WRITE, REG_OPERATION_MODE, 0x01}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, + {TOK_TERM, 0, 0}, +}; + +/* TVP5147 Init/Power on Sequence */ +static const struct tvp514x_reg tvp5147_init_reg_seq[] = { + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x02}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0x80}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x60}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xB0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x16}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xA0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x16}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x60}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xB0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x00}, + {TOK_WRITE, REG_OPERATION_MODE, 0x01}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, + {TOK_TERM, 0, 0}, +}; + +/* TVP5146M2/TVP5147M1 Init/Power on Sequence */ +static const struct tvp514x_reg tvp514xm_init_reg_seq[] = { + {TOK_WRITE, REG_OPERATION_MODE, 0x01}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, + {TOK_TERM, 0, 0}, +}; + +/* + * I2C Device Table - + * + * name - Name of the actual device/chip. + * driver_data - Driver data + */ +static const struct i2c_device_id tvp514x_id[] = { + {"tvp5146", (unsigned long)tvp5146_init_reg_seq}, + {"tvp5146m2", (unsigned long)tvp514xm_init_reg_seq}, + {"tvp5147", (unsigned long)tvp5147_init_reg_seq}, + {"tvp5147m1", (unsigned long)tvp514xm_init_reg_seq}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, tvp514x_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tvp514x_of_match[] = { + { .compatible = "ti,tvp5146", }, + { .compatible = "ti,tvp5146m2", }, + { .compatible = "ti,tvp5147", }, + { .compatible = "ti,tvp5147m1", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, tvp514x_of_match); +#endif + +static struct i2c_driver tvp514x_driver = { + .driver = { + .of_match_table = of_match_ptr(tvp514x_of_match), + .name = TVP514X_MODULE_NAME, + }, + .probe = tvp514x_probe, + .remove = tvp514x_remove, + .id_table = tvp514x_id, +}; + +module_i2c_driver(tvp514x_driver); diff --git a/drivers/media/i2c/tvp514x_regs.h b/drivers/media/i2c/tvp514x_regs.h new file mode 100644 index 000000000..cc236c968 --- /dev/null +++ b/drivers/media/i2c/tvp514x_regs.h @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/tvp514x_regs.h + * + * Copyright (C) 2008 Texas Instruments Inc + * Author: Vaibhav Hiremath <hvaibhav@ti.com> + * + * Contributors: + * Sivaraj R <sivaraj@ti.com> + * Brijesh R Jadav <brijesh.j@ti.com> + * Hardik Shah <hardik.shah@ti.com> + * Manjunath Hadli <mrh@ti.com> + * Karicheri Muralidharan <m-karicheri2@ti.com> + */ + +#ifndef _TVP514X_REGS_H +#define _TVP514X_REGS_H + +/* + * TVP5146/47 registers + */ +#define REG_INPUT_SEL (0x00) +#define REG_AFE_GAIN_CTRL (0x01) +#define REG_VIDEO_STD (0x02) +#define REG_OPERATION_MODE (0x03) +#define REG_AUTOSWITCH_MASK (0x04) + +#define REG_COLOR_KILLER (0x05) +#define REG_LUMA_CONTROL1 (0x06) +#define REG_LUMA_CONTROL2 (0x07) +#define REG_LUMA_CONTROL3 (0x08) + +#define REG_BRIGHTNESS (0x09) +#define REG_CONTRAST (0x0A) +#define REG_SATURATION (0x0B) +#define REG_HUE (0x0C) + +#define REG_CHROMA_CONTROL1 (0x0D) +#define REG_CHROMA_CONTROL2 (0x0E) + +/* 0x0F Reserved */ + +#define REG_COMP_PR_SATURATION (0x10) +#define REG_COMP_Y_CONTRAST (0x11) +#define REG_COMP_PB_SATURATION (0x12) + +/* 0x13 Reserved */ + +#define REG_COMP_Y_BRIGHTNESS (0x14) + +/* 0x15 Reserved */ + +#define REG_AVID_START_PIXEL_LSB (0x16) +#define REG_AVID_START_PIXEL_MSB (0x17) +#define REG_AVID_STOP_PIXEL_LSB (0x18) +#define REG_AVID_STOP_PIXEL_MSB (0x19) + +#define REG_HSYNC_START_PIXEL_LSB (0x1A) +#define REG_HSYNC_START_PIXEL_MSB (0x1B) +#define REG_HSYNC_STOP_PIXEL_LSB (0x1C) +#define REG_HSYNC_STOP_PIXEL_MSB (0x1D) + +#define REG_VSYNC_START_LINE_LSB (0x1E) +#define REG_VSYNC_START_LINE_MSB (0x1F) +#define REG_VSYNC_STOP_LINE_LSB (0x20) +#define REG_VSYNC_STOP_LINE_MSB (0x21) + +#define REG_VBLK_START_LINE_LSB (0x22) +#define REG_VBLK_START_LINE_MSB (0x23) +#define REG_VBLK_STOP_LINE_LSB (0x24) +#define REG_VBLK_STOP_LINE_MSB (0x25) + +/* 0x26 - 0x27 Reserved */ + +#define REG_FAST_SWTICH_CONTROL (0x28) + +/* 0x29 Reserved */ + +#define REG_FAST_SWTICH_SCART_DELAY (0x2A) + +/* 0x2B Reserved */ + +#define REG_SCART_DELAY (0x2C) +#define REG_CTI_DELAY (0x2D) +#define REG_CTI_CONTROL (0x2E) + +/* 0x2F - 0x31 Reserved */ + +#define REG_SYNC_CONTROL (0x32) +#define REG_OUTPUT_FORMATTER1 (0x33) +#define REG_OUTPUT_FORMATTER2 (0x34) +#define REG_OUTPUT_FORMATTER3 (0x35) +#define REG_OUTPUT_FORMATTER4 (0x36) +#define REG_OUTPUT_FORMATTER5 (0x37) +#define REG_OUTPUT_FORMATTER6 (0x38) +#define REG_CLEAR_LOST_LOCK (0x39) + +#define REG_STATUS1 (0x3A) +#define REG_STATUS2 (0x3B) + +#define REG_AGC_GAIN_STATUS_LSB (0x3C) +#define REG_AGC_GAIN_STATUS_MSB (0x3D) + +/* 0x3E Reserved */ + +#define REG_VIDEO_STD_STATUS (0x3F) +#define REG_GPIO_INPUT1 (0x40) +#define REG_GPIO_INPUT2 (0x41) + +/* 0x42 - 0x45 Reserved */ + +#define REG_AFE_COARSE_GAIN_CH1 (0x46) +#define REG_AFE_COARSE_GAIN_CH2 (0x47) +#define REG_AFE_COARSE_GAIN_CH3 (0x48) +#define REG_AFE_COARSE_GAIN_CH4 (0x49) + +#define REG_AFE_FINE_GAIN_PB_B_LSB (0x4A) +#define REG_AFE_FINE_GAIN_PB_B_MSB (0x4B) +#define REG_AFE_FINE_GAIN_Y_G_CHROMA_LSB (0x4C) +#define REG_AFE_FINE_GAIN_Y_G_CHROMA_MSB (0x4D) +#define REG_AFE_FINE_GAIN_PR_R_LSB (0x4E) +#define REG_AFE_FINE_GAIN_PR_R_MSB (0x4F) +#define REG_AFE_FINE_GAIN_CVBS_LUMA_LSB (0x50) +#define REG_AFE_FINE_GAIN_CVBS_LUMA_MSB (0x51) + +/* 0x52 - 0x68 Reserved */ + +#define REG_FBIT_VBIT_CONTROL1 (0x69) + +/* 0x6A - 0x6B Reserved */ + +#define REG_BACKEND_AGC_CONTROL (0x6C) + +/* 0x6D - 0x6E Reserved */ + +#define REG_AGC_DECREMENT_SPEED_CONTROL (0x6F) +#define REG_ROM_VERSION (0x70) + +/* 0x71 - 0x73 Reserved */ + +#define REG_AGC_WHITE_PEAK_PROCESSING (0x74) +#define REG_FBIT_VBIT_CONTROL2 (0x75) +#define REG_VCR_TRICK_MODE_CONTROL (0x76) +#define REG_HORIZONTAL_SHAKE_INCREMENT (0x77) +#define REG_AGC_INCREMENT_SPEED (0x78) +#define REG_AGC_INCREMENT_DELAY (0x79) + +/* 0x7A - 0x7F Reserved */ + +#define REG_CHIP_ID_MSB (0x80) +#define REG_CHIP_ID_LSB (0x81) + +/* 0x82 Reserved */ + +#define REG_CPLL_SPEED_CONTROL (0x83) + +/* 0x84 - 0x96 Reserved */ + +#define REG_STATUS_REQUEST (0x97) + +/* 0x98 - 0x99 Reserved */ + +#define REG_VERTICAL_LINE_COUNT_LSB (0x9A) +#define REG_VERTICAL_LINE_COUNT_MSB (0x9B) + +/* 0x9C - 0x9D Reserved */ + +#define REG_AGC_DECREMENT_DELAY (0x9E) + +/* 0x9F - 0xB0 Reserved */ + +#define REG_VDP_TTX_FILTER_1_MASK1 (0xB1) +#define REG_VDP_TTX_FILTER_1_MASK2 (0xB2) +#define REG_VDP_TTX_FILTER_1_MASK3 (0xB3) +#define REG_VDP_TTX_FILTER_1_MASK4 (0xB4) +#define REG_VDP_TTX_FILTER_1_MASK5 (0xB5) +#define REG_VDP_TTX_FILTER_2_MASK1 (0xB6) +#define REG_VDP_TTX_FILTER_2_MASK2 (0xB7) +#define REG_VDP_TTX_FILTER_2_MASK3 (0xB8) +#define REG_VDP_TTX_FILTER_2_MASK4 (0xB9) +#define REG_VDP_TTX_FILTER_2_MASK5 (0xBA) +#define REG_VDP_TTX_FILTER_CONTROL (0xBB) +#define REG_VDP_FIFO_WORD_COUNT (0xBC) +#define REG_VDP_FIFO_INTERRUPT_THRLD (0xBD) + +/* 0xBE Reserved */ + +#define REG_VDP_FIFO_RESET (0xBF) +#define REG_VDP_FIFO_OUTPUT_CONTROL (0xC0) +#define REG_VDP_LINE_NUMBER_INTERRUPT (0xC1) +#define REG_VDP_PIXEL_ALIGNMENT_LSB (0xC2) +#define REG_VDP_PIXEL_ALIGNMENT_MSB (0xC3) + +/* 0xC4 - 0xD5 Reserved */ + +#define REG_VDP_LINE_START (0xD6) +#define REG_VDP_LINE_STOP (0xD7) +#define REG_VDP_GLOBAL_LINE_MODE (0xD8) +#define REG_VDP_FULL_FIELD_ENABLE (0xD9) +#define REG_VDP_FULL_FIELD_MODE (0xDA) + +/* 0xDB - 0xDF Reserved */ + +#define REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR (0xE0) +#define REG_VBUS_DATA_ACCESS_VBUS_ADDR_INCR (0xE1) +#define REG_FIFO_READ_DATA (0xE2) + +/* 0xE3 - 0xE7 Reserved */ + +#define REG_VBUS_ADDRESS_ACCESS1 (0xE8) +#define REG_VBUS_ADDRESS_ACCESS2 (0xE9) +#define REG_VBUS_ADDRESS_ACCESS3 (0xEA) + +/* 0xEB - 0xEF Reserved */ + +#define REG_INTERRUPT_RAW_STATUS0 (0xF0) +#define REG_INTERRUPT_RAW_STATUS1 (0xF1) +#define REG_INTERRUPT_STATUS0 (0xF2) +#define REG_INTERRUPT_STATUS1 (0xF3) +#define REG_INTERRUPT_MASK0 (0xF4) +#define REG_INTERRUPT_MASK1 (0xF5) +#define REG_INTERRUPT_CLEAR0 (0xF6) +#define REG_INTERRUPT_CLEAR1 (0xF7) + +/* 0xF8 - 0xFF Reserved */ + +/* + * Mask and bit definitions of TVP5146/47 registers + */ +/* The ID values we are looking for */ +#define TVP514X_CHIP_ID_MSB (0x51) +#define TVP5146_CHIP_ID_LSB (0x46) +#define TVP5147_CHIP_ID_LSB (0x47) + +#define VIDEO_STD_MASK (0x07) +#define VIDEO_STD_AUTO_SWITCH_BIT (0x00) +#define VIDEO_STD_NTSC_MJ_BIT (0x01) +#define VIDEO_STD_PAL_BDGHIN_BIT (0x02) +#define VIDEO_STD_PAL_M_BIT (0x03) +#define VIDEO_STD_PAL_COMBINATION_N_BIT (0x04) +#define VIDEO_STD_NTSC_4_43_BIT (0x05) +#define VIDEO_STD_SECAM_BIT (0x06) +#define VIDEO_STD_PAL_60_BIT (0x07) + +/* + * Status bit + */ +#define STATUS_TV_VCR_BIT (1<<0) +#define STATUS_HORZ_SYNC_LOCK_BIT (1<<1) +#define STATUS_VIRT_SYNC_LOCK_BIT (1<<2) +#define STATUS_CLR_SUBCAR_LOCK_BIT (1<<3) +#define STATUS_LOST_LOCK_DETECT_BIT (1<<4) +#define STATUS_FEILD_RATE_BIT (1<<5) +#define STATUS_LINE_ALTERNATING_BIT (1<<6) +#define STATUS_PEAK_WHITE_DETECT_BIT (1<<7) + +/* Tokens for register write */ +#define TOK_WRITE (0) /* token for write operation */ +#define TOK_TERM (1) /* terminating token */ +#define TOK_DELAY (2) /* delay token for reg list */ +#define TOK_SKIP (3) /* token to skip a register */ +/** + * struct tvp514x_reg - Structure for TVP5146/47 register initialization values + * @token - Token: TOK_WRITE, TOK_TERM etc.. + * @reg - Register offset + * @val - Register Value for TOK_WRITE or delay in ms for TOK_DELAY + */ +struct tvp514x_reg { + u8 token; + u8 reg; + u32 val; +}; + +#endif /* ifndef _TVP514X_REGS_H */ diff --git a/drivers/media/i2c/tvp5150.c b/drivers/media/i2c/tvp5150.c new file mode 100644 index 000000000..cf0570a67 --- /dev/null +++ b/drivers/media/i2c/tvp5150.c @@ -0,0 +1,2304 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// tvp5150 - Texas Instruments TVP5150A/AM1 and TVP5151 video decoder driver +// +// Copyright (c) 2005,2006 Mauro Carvalho Chehab <mchehab@kernel.org> + +#include <dt-bindings/media/tvp5150.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <media/v4l2-async.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-rect.h> + +#include "tvp5150_reg.h" + +#define TVP5150_H_MAX 720U +#define TVP5150_V_MAX_525_60 480U +#define TVP5150_V_MAX_OTHERS 576U +#define TVP5150_MAX_CROP_LEFT 511 +#define TVP5150_MAX_CROP_TOP 127 +#define TVP5150_CROP_SHIFT 2 +#define TVP5150_MBUS_FMT MEDIA_BUS_FMT_UYVY8_2X8 +#define TVP5150_FIELD V4L2_FIELD_ALTERNATE +#define TVP5150_COLORSPACE V4L2_COLORSPACE_SMPTE170M +#define TVP5150_STD_MASK (V4L2_STD_NTSC | \ + V4L2_STD_NTSC_443 | \ + V4L2_STD_PAL | \ + V4L2_STD_PAL_M | \ + V4L2_STD_PAL_N | \ + V4L2_STD_PAL_Nc | \ + V4L2_STD_SECAM) + +#define TVP5150_MAX_CONNECTORS 3 /* Check dt-bindings for more information */ + +MODULE_DESCRIPTION("Texas Instruments TVP5150A/TVP5150AM1/TVP5151 video decoder driver"); +MODULE_AUTHOR("Mauro Carvalho Chehab"); +MODULE_LICENSE("GPL v2"); + + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +#define dprintk0(__dev, __arg...) dev_dbg_lvl(__dev, 0, 0, __arg) + +enum tvp5150_pads { + TVP5150_PAD_AIP1A, + TVP5150_PAD_AIP1B, + TVP5150_PAD_VID_OUT, + TVP5150_NUM_PADS +}; + +struct tvp5150_connector { + struct v4l2_fwnode_connector base; + struct media_entity ent; + struct media_pad pad; +}; + +struct tvp5150 { + struct v4l2_subdev sd; + + struct media_pad pads[TVP5150_NUM_PADS]; + struct tvp5150_connector connectors[TVP5150_MAX_CONNECTORS]; + struct tvp5150_connector *cur_connector; + unsigned int connectors_num; + + struct v4l2_ctrl_handler hdl; + struct v4l2_rect rect; + struct regmap *regmap; + int irq; + + v4l2_std_id norm; /* Current set standard */ + v4l2_std_id detected_norm; + u32 input; + u32 output; + u32 oe; + int enable; + bool lock; + + u16 dev_id; + u16 rom_ver; + + enum v4l2_mbus_type mbus_type; +}; + +static inline struct tvp5150 *to_tvp5150(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tvp5150, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tvp5150, hdl)->sd; +} + +static int tvp5150_read(struct v4l2_subdev *sd, unsigned char addr) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + int ret, val; + + ret = regmap_read(decoder->regmap, addr, &val); + if (ret < 0) + return ret; + + return val; +} + +static void dump_reg_range(struct v4l2_subdev *sd, char *s, u8 init, + const u8 end, int max_line) +{ + u8 buf[16]; + int i = 0, j, len; + + if (max_line > 16) { + dprintk0(sd->dev, "too much data to dump\n"); + return; + } + + for (i = init; i < end; i += max_line) { + len = (end - i > max_line) ? max_line : end - i; + + for (j = 0; j < len; j++) + buf[j] = tvp5150_read(sd, i + j); + + dprintk0(sd->dev, "%s reg %02x = %*ph\n", s, i, len, buf); + } +} + +static int tvp5150_log_status(struct v4l2_subdev *sd) +{ + dprintk0(sd->dev, "tvp5150: Video input source selection #1 = 0x%02x\n", + tvp5150_read(sd, TVP5150_VD_IN_SRC_SEL_1)); + dprintk0(sd->dev, "tvp5150: Analog channel controls = 0x%02x\n", + tvp5150_read(sd, TVP5150_ANAL_CHL_CTL)); + dprintk0(sd->dev, "tvp5150: Operation mode controls = 0x%02x\n", + tvp5150_read(sd, TVP5150_OP_MODE_CTL)); + dprintk0(sd->dev, "tvp5150: Miscellaneous controls = 0x%02x\n", + tvp5150_read(sd, TVP5150_MISC_CTL)); + dprintk0(sd->dev, "tvp5150: Autoswitch mask= 0x%02x\n", + tvp5150_read(sd, TVP5150_AUTOSW_MSK)); + dprintk0(sd->dev, "tvp5150: Color killer threshold control = 0x%02x\n", + tvp5150_read(sd, TVP5150_COLOR_KIL_THSH_CTL)); + dprintk0(sd->dev, "tvp5150: Luminance processing controls #1 #2 and #3 = %02x %02x %02x\n", + tvp5150_read(sd, TVP5150_LUMA_PROC_CTL_1), + tvp5150_read(sd, TVP5150_LUMA_PROC_CTL_2), + tvp5150_read(sd, TVP5150_LUMA_PROC_CTL_3)); + dprintk0(sd->dev, "tvp5150: Brightness control = 0x%02x\n", + tvp5150_read(sd, TVP5150_BRIGHT_CTL)); + dprintk0(sd->dev, "tvp5150: Color saturation control = 0x%02x\n", + tvp5150_read(sd, TVP5150_SATURATION_CTL)); + dprintk0(sd->dev, "tvp5150: Hue control = 0x%02x\n", + tvp5150_read(sd, TVP5150_HUE_CTL)); + dprintk0(sd->dev, "tvp5150: Contrast control = 0x%02x\n", + tvp5150_read(sd, TVP5150_CONTRAST_CTL)); + dprintk0(sd->dev, "tvp5150: Outputs and data rates select = 0x%02x\n", + tvp5150_read(sd, TVP5150_DATA_RATE_SEL)); + dprintk0(sd->dev, "tvp5150: Configuration shared pins = 0x%02x\n", + tvp5150_read(sd, TVP5150_CONF_SHARED_PIN)); + dprintk0(sd->dev, "tvp5150: Active video cropping start = 0x%02x%02x\n", + tvp5150_read(sd, TVP5150_ACT_VD_CROP_ST_MSB), + tvp5150_read(sd, TVP5150_ACT_VD_CROP_ST_LSB)); + dprintk0(sd->dev, "tvp5150: Active video cropping stop = 0x%02x%02x\n", + tvp5150_read(sd, TVP5150_ACT_VD_CROP_STP_MSB), + tvp5150_read(sd, TVP5150_ACT_VD_CROP_STP_LSB)); + dprintk0(sd->dev, "tvp5150: Genlock/RTC = 0x%02x\n", + tvp5150_read(sd, TVP5150_GENLOCK)); + dprintk0(sd->dev, "tvp5150: Horizontal sync start = 0x%02x\n", + tvp5150_read(sd, TVP5150_HORIZ_SYNC_START)); + dprintk0(sd->dev, "tvp5150: Vertical blanking start = 0x%02x\n", + tvp5150_read(sd, TVP5150_VERT_BLANKING_START)); + dprintk0(sd->dev, "tvp5150: Vertical blanking stop = 0x%02x\n", + tvp5150_read(sd, TVP5150_VERT_BLANKING_STOP)); + dprintk0(sd->dev, "tvp5150: Chrominance processing control #1 and #2 = %02x %02x\n", + tvp5150_read(sd, TVP5150_CHROMA_PROC_CTL_1), + tvp5150_read(sd, TVP5150_CHROMA_PROC_CTL_2)); + dprintk0(sd->dev, "tvp5150: Interrupt reset register B = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_RESET_REG_B)); + dprintk0(sd->dev, "tvp5150: Interrupt enable register B = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_ENABLE_REG_B)); + dprintk0(sd->dev, "tvp5150: Interrupt configuration register B = 0x%02x\n", + tvp5150_read(sd, TVP5150_INTT_CONFIG_REG_B)); + dprintk0(sd->dev, "tvp5150: Video standard = 0x%02x\n", + tvp5150_read(sd, TVP5150_VIDEO_STD)); + dprintk0(sd->dev, "tvp5150: Chroma gain factor: Cb=0x%02x Cr=0x%02x\n", + tvp5150_read(sd, TVP5150_CB_GAIN_FACT), + tvp5150_read(sd, TVP5150_CR_GAIN_FACTOR)); + dprintk0(sd->dev, "tvp5150: Macrovision on counter = 0x%02x\n", + tvp5150_read(sd, TVP5150_MACROVISION_ON_CTR)); + dprintk0(sd->dev, "tvp5150: Macrovision off counter = 0x%02x\n", + tvp5150_read(sd, TVP5150_MACROVISION_OFF_CTR)); + dprintk0(sd->dev, "tvp5150: ITU-R BT.656.%d timing(TVP5150AM1 only)\n", + (tvp5150_read(sd, TVP5150_REV_SELECT) & 1) ? 3 : 4); + dprintk0(sd->dev, "tvp5150: Device ID = %02x%02x\n", + tvp5150_read(sd, TVP5150_MSB_DEV_ID), + tvp5150_read(sd, TVP5150_LSB_DEV_ID)); + dprintk0(sd->dev, "tvp5150: ROM version = (hex) %02x.%02x\n", + tvp5150_read(sd, TVP5150_ROM_MAJOR_VER), + tvp5150_read(sd, TVP5150_ROM_MINOR_VER)); + dprintk0(sd->dev, "tvp5150: Vertical line count = 0x%02x%02x\n", + tvp5150_read(sd, TVP5150_VERT_LN_COUNT_MSB), + tvp5150_read(sd, TVP5150_VERT_LN_COUNT_LSB)); + dprintk0(sd->dev, "tvp5150: Interrupt status register B = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_STATUS_REG_B)); + dprintk0(sd->dev, "tvp5150: Interrupt active register B = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_ACTIVE_REG_B)); + dprintk0(sd->dev, "tvp5150: Status regs #1 to #5 = %02x %02x %02x %02x %02x\n", + tvp5150_read(sd, TVP5150_STATUS_REG_1), + tvp5150_read(sd, TVP5150_STATUS_REG_2), + tvp5150_read(sd, TVP5150_STATUS_REG_3), + tvp5150_read(sd, TVP5150_STATUS_REG_4), + tvp5150_read(sd, TVP5150_STATUS_REG_5)); + + dump_reg_range(sd, "Teletext filter 1", TVP5150_TELETEXT_FIL1_INI, + TVP5150_TELETEXT_FIL1_END, 8); + dump_reg_range(sd, "Teletext filter 2", TVP5150_TELETEXT_FIL2_INI, + TVP5150_TELETEXT_FIL2_END, 8); + + dprintk0(sd->dev, "tvp5150: Teletext filter enable = 0x%02x\n", + tvp5150_read(sd, TVP5150_TELETEXT_FIL_ENA)); + dprintk0(sd->dev, "tvp5150: Interrupt status register A = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_STATUS_REG_A)); + dprintk0(sd->dev, "tvp5150: Interrupt enable register A = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_ENABLE_REG_A)); + dprintk0(sd->dev, "tvp5150: Interrupt configuration = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_CONF)); + dprintk0(sd->dev, "tvp5150: VDP status register = 0x%02x\n", + tvp5150_read(sd, TVP5150_VDP_STATUS_REG)); + dprintk0(sd->dev, "tvp5150: FIFO word count = 0x%02x\n", + tvp5150_read(sd, TVP5150_FIFO_WORD_COUNT)); + dprintk0(sd->dev, "tvp5150: FIFO interrupt threshold = 0x%02x\n", + tvp5150_read(sd, TVP5150_FIFO_INT_THRESHOLD)); + dprintk0(sd->dev, "tvp5150: FIFO reset = 0x%02x\n", + tvp5150_read(sd, TVP5150_FIFO_RESET)); + dprintk0(sd->dev, "tvp5150: Line number interrupt = 0x%02x\n", + tvp5150_read(sd, TVP5150_LINE_NUMBER_INT)); + dprintk0(sd->dev, "tvp5150: Pixel alignment register = 0x%02x%02x\n", + tvp5150_read(sd, TVP5150_PIX_ALIGN_REG_HIGH), + tvp5150_read(sd, TVP5150_PIX_ALIGN_REG_LOW)); + dprintk0(sd->dev, "tvp5150: FIFO output control = 0x%02x\n", + tvp5150_read(sd, TVP5150_FIFO_OUT_CTRL)); + dprintk0(sd->dev, "tvp5150: Full field enable = 0x%02x\n", + tvp5150_read(sd, TVP5150_FULL_FIELD_ENA)); + dprintk0(sd->dev, "tvp5150: Full field mode register = 0x%02x\n", + tvp5150_read(sd, TVP5150_FULL_FIELD_MODE_REG)); + + dump_reg_range(sd, "CC data", TVP5150_CC_DATA_INI, + TVP5150_CC_DATA_END, 8); + + dump_reg_range(sd, "WSS data", TVP5150_WSS_DATA_INI, + TVP5150_WSS_DATA_END, 8); + + dump_reg_range(sd, "VPS data", TVP5150_VPS_DATA_INI, + TVP5150_VPS_DATA_END, 8); + + dump_reg_range(sd, "VITC data", TVP5150_VITC_DATA_INI, + TVP5150_VITC_DATA_END, 10); + + dump_reg_range(sd, "Line mode", TVP5150_LINE_MODE_INI, + TVP5150_LINE_MODE_END, 8); + return 0; +} + +/**************************************************************************** + Basic functions + ****************************************************************************/ + +static void tvp5150_selmux(struct v4l2_subdev *sd) +{ + int opmode = 0; + struct tvp5150 *decoder = to_tvp5150(sd); + unsigned int mask, val; + int input = 0; + + /* Only tvp5150am1 and tvp5151 have signal generator support */ + if ((decoder->dev_id == 0x5150 && decoder->rom_ver == 0x0400) || + (decoder->dev_id == 0x5151 && decoder->rom_ver == 0x0100)) { + if (!decoder->enable) + input = 8; + } + + switch (decoder->input) { + case TVP5150_COMPOSITE1: + input |= 2; + fallthrough; + case TVP5150_COMPOSITE0: + break; + case TVP5150_SVIDEO: + default: + input |= 1; + break; + } + + dev_dbg_lvl(sd->dev, 1, debug, + "Selecting video route: route input=%s, output=%s => tvp5150 input=0x%02x, opmode=0x%02x\n", + decoder->input == 0 ? "aip1a" : + decoder->input == 2 ? "aip1b" : "svideo", + decoder->output == 0 ? "normal" : "black-frame-gen", + input, opmode); + + regmap_write(decoder->regmap, TVP5150_OP_MODE_CTL, opmode); + regmap_write(decoder->regmap, TVP5150_VD_IN_SRC_SEL_1, input); + + /* + * Setup the FID/GLCO/VLK/HVLK and INTREQ/GPCL/VBLK output signals. For + * S-Video we output the vertical lock (VLK) signal on FID/GLCO/VLK/HVLK + * and set INTREQ/GPCL/VBLK to logic 0. For composite we output the + * field indicator (FID) signal on FID/GLCO/VLK/HVLK and set + * INTREQ/GPCL/VBLK to logic 1. + */ + mask = TVP5150_MISC_CTL_GPCL | TVP5150_MISC_CTL_HVLK; + if (decoder->input == TVP5150_SVIDEO) + val = TVP5150_MISC_CTL_HVLK; + else + val = TVP5150_MISC_CTL_GPCL; + regmap_update_bits(decoder->regmap, TVP5150_MISC_CTL, mask, val); +}; + +struct i2c_reg_value { + unsigned char reg; + unsigned char value; +}; + +/* Default values as sugested at TVP5150AM1 datasheet */ +static const struct i2c_reg_value tvp5150_init_default[] = { + { /* 0x00 */ + TVP5150_VD_IN_SRC_SEL_1, 0x00 + }, + { /* 0x01 */ + TVP5150_ANAL_CHL_CTL, 0x15 + }, + { /* 0x02 */ + TVP5150_OP_MODE_CTL, 0x00 + }, + { /* 0x03 */ + TVP5150_MISC_CTL, 0x01 + }, + { /* 0x06 */ + TVP5150_COLOR_KIL_THSH_CTL, 0x10 + }, + { /* 0x07 */ + TVP5150_LUMA_PROC_CTL_1, 0x60 + }, + { /* 0x08 */ + TVP5150_LUMA_PROC_CTL_2, 0x00 + }, + { /* 0x09 */ + TVP5150_BRIGHT_CTL, 0x80 + }, + { /* 0x0a */ + TVP5150_SATURATION_CTL, 0x80 + }, + { /* 0x0b */ + TVP5150_HUE_CTL, 0x00 + }, + { /* 0x0c */ + TVP5150_CONTRAST_CTL, 0x80 + }, + { /* 0x0d */ + TVP5150_DATA_RATE_SEL, 0x47 + }, + { /* 0x0e */ + TVP5150_LUMA_PROC_CTL_3, 0x00 + }, + { /* 0x0f */ + TVP5150_CONF_SHARED_PIN, 0x08 + }, + { /* 0x11 */ + TVP5150_ACT_VD_CROP_ST_MSB, 0x00 + }, + { /* 0x12 */ + TVP5150_ACT_VD_CROP_ST_LSB, 0x00 + }, + { /* 0x13 */ + TVP5150_ACT_VD_CROP_STP_MSB, 0x00 + }, + { /* 0x14 */ + TVP5150_ACT_VD_CROP_STP_LSB, 0x00 + }, + { /* 0x15 */ + TVP5150_GENLOCK, 0x01 + }, + { /* 0x16 */ + TVP5150_HORIZ_SYNC_START, 0x80 + }, + { /* 0x18 */ + TVP5150_VERT_BLANKING_START, 0x00 + }, + { /* 0x19 */ + TVP5150_VERT_BLANKING_STOP, 0x00 + }, + { /* 0x1a */ + TVP5150_CHROMA_PROC_CTL_1, 0x0c + }, + { /* 0x1b */ + TVP5150_CHROMA_PROC_CTL_2, 0x14 + }, + { /* 0x1c */ + TVP5150_INT_RESET_REG_B, 0x00 + }, + { /* 0x1d */ + TVP5150_INT_ENABLE_REG_B, 0x00 + }, + { /* 0x1e */ + TVP5150_INTT_CONFIG_REG_B, 0x00 + }, + { /* 0x28 */ + TVP5150_VIDEO_STD, 0x00 + }, + { /* 0x2e */ + TVP5150_MACROVISION_ON_CTR, 0x0f + }, + { /* 0x2f */ + TVP5150_MACROVISION_OFF_CTR, 0x01 + }, + { /* 0xbb */ + TVP5150_TELETEXT_FIL_ENA, 0x00 + }, + { /* 0xc0 */ + TVP5150_INT_STATUS_REG_A, 0x00 + }, + { /* 0xc1 */ + TVP5150_INT_ENABLE_REG_A, 0x00 + }, + { /* 0xc2 */ + TVP5150_INT_CONF, 0x04 + }, + { /* 0xc8 */ + TVP5150_FIFO_INT_THRESHOLD, 0x80 + }, + { /* 0xc9 */ + TVP5150_FIFO_RESET, 0x00 + }, + { /* 0xca */ + TVP5150_LINE_NUMBER_INT, 0x00 + }, + { /* 0xcb */ + TVP5150_PIX_ALIGN_REG_LOW, 0x4e + }, + { /* 0xcc */ + TVP5150_PIX_ALIGN_REG_HIGH, 0x00 + }, + { /* 0xcd */ + TVP5150_FIFO_OUT_CTRL, 0x01 + }, + { /* 0xcf */ + TVP5150_FULL_FIELD_ENA, 0x00 + }, + { /* 0xd0 */ + TVP5150_LINE_MODE_INI, 0x00 + }, + { /* 0xfc */ + TVP5150_FULL_FIELD_MODE_REG, 0x7f + }, + { /* end of data */ + 0xff, 0xff + } +}; + +/* Default values as sugested at TVP5150AM1 datasheet */ +static const struct i2c_reg_value tvp5150_init_enable[] = { + { /* Automatic offset and AGC enabled */ + TVP5150_ANAL_CHL_CTL, 0x15 + }, { /* Activate YCrCb output 0x9 or 0xd ? */ + TVP5150_MISC_CTL, TVP5150_MISC_CTL_GPCL | + TVP5150_MISC_CTL_INTREQ_OE | + TVP5150_MISC_CTL_YCBCR_OE | + TVP5150_MISC_CTL_SYNC_OE | + TVP5150_MISC_CTL_VBLANK | + TVP5150_MISC_CTL_CLOCK_OE, + }, { /* Activates video std autodetection for all standards */ + TVP5150_AUTOSW_MSK, 0x0 + }, { /* Default format: 0x47. For 4:2:2: 0x40 */ + TVP5150_DATA_RATE_SEL, 0x47 + }, { + TVP5150_CHROMA_PROC_CTL_1, 0x0c + }, { + TVP5150_CHROMA_PROC_CTL_2, 0x54 + }, { /* Non documented, but initialized on WinTV USB2 */ + 0x27, 0x20 + }, { + 0xff, 0xff + } +}; + +struct tvp5150_vbi_type { + unsigned int vbi_type; + unsigned int ini_line; + unsigned int end_line; + unsigned int by_field :1; +}; + +struct i2c_vbi_ram_value { + u16 reg; + struct tvp5150_vbi_type type; + unsigned char values[16]; +}; + +/* This struct have the values for each supported VBI Standard + * by + tvp5150_vbi_types should follow the same order as vbi_ram_default + * value 0 means rom position 0x10, value 1 means rom position 0x30 + * and so on. There are 16 possible locations from 0 to 15. + */ + +static struct i2c_vbi_ram_value vbi_ram_default[] = { + + /* + * FIXME: Current api doesn't handle all VBI types, those not + * yet supported are placed under #if 0 + */ +#if 0 + [0] = {0x010, /* Teletext, SECAM, WST System A */ + {V4L2_SLICED_TELETEXT_SECAM, 6, 23, 1}, + { 0xaa, 0xaa, 0xff, 0xff, 0xe7, 0x2e, 0x20, 0x26, + 0xe6, 0xb4, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00 } + }, +#endif + [1] = {0x030, /* Teletext, PAL, WST System B */ + {V4L2_SLICED_TELETEXT_B, 6, 22, 1}, + { 0xaa, 0xaa, 0xff, 0xff, 0x27, 0x2e, 0x20, 0x2b, + 0xa6, 0x72, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00 } + }, +#if 0 + [2] = {0x050, /* Teletext, PAL, WST System C */ + {V4L2_SLICED_TELETEXT_PAL_C, 6, 22, 1}, + { 0xaa, 0xaa, 0xff, 0xff, 0xe7, 0x2e, 0x20, 0x22, + 0xa6, 0x98, 0x0d, 0x00, 0x00, 0x00, 0x10, 0x00 } + }, + [3] = {0x070, /* Teletext, NTSC, WST System B */ + {V4L2_SLICED_TELETEXT_NTSC_B, 10, 21, 1}, + { 0xaa, 0xaa, 0xff, 0xff, 0x27, 0x2e, 0x20, 0x23, + 0x69, 0x93, 0x0d, 0x00, 0x00, 0x00, 0x10, 0x00 } + }, + [4] = {0x090, /* Tetetext, NTSC NABTS System C */ + {V4L2_SLICED_TELETEXT_NTSC_C, 10, 21, 1}, + { 0xaa, 0xaa, 0xff, 0xff, 0xe7, 0x2e, 0x20, 0x22, + 0x69, 0x93, 0x0d, 0x00, 0x00, 0x00, 0x15, 0x00 } + }, + [5] = {0x0b0, /* Teletext, NTSC-J, NABTS System D */ + {V4L2_SLICED_TELETEXT_NTSC_D, 10, 21, 1}, + { 0xaa, 0xaa, 0xff, 0xff, 0xa7, 0x2e, 0x20, 0x23, + 0x69, 0x93, 0x0d, 0x00, 0x00, 0x00, 0x10, 0x00 } + }, + [6] = {0x0d0, /* Closed Caption, PAL/SECAM */ + {V4L2_SLICED_CAPTION_625, 22, 22, 1}, + { 0xaa, 0x2a, 0xff, 0x3f, 0x04, 0x51, 0x6e, 0x02, + 0xa6, 0x7b, 0x09, 0x00, 0x00, 0x00, 0x27, 0x00 } + }, +#endif + [7] = {0x0f0, /* Closed Caption, NTSC */ + {V4L2_SLICED_CAPTION_525, 21, 21, 1}, + { 0xaa, 0x2a, 0xff, 0x3f, 0x04, 0x51, 0x6e, 0x02, + 0x69, 0x8c, 0x09, 0x00, 0x00, 0x00, 0x27, 0x00 } + }, + [8] = {0x110, /* Wide Screen Signal, PAL/SECAM */ + {V4L2_SLICED_WSS_625, 23, 23, 1}, + { 0x5b, 0x55, 0xc5, 0xff, 0x00, 0x71, 0x6e, 0x42, + 0xa6, 0xcd, 0x0f, 0x00, 0x00, 0x00, 0x3a, 0x00 } + }, +#if 0 + [9] = {0x130, /* Wide Screen Signal, NTSC C */ + {V4L2_SLICED_WSS_525, 20, 20, 1}, + { 0x38, 0x00, 0x3f, 0x00, 0x00, 0x71, 0x6e, 0x43, + 0x69, 0x7c, 0x08, 0x00, 0x00, 0x00, 0x39, 0x00 } + }, + [10] = {0x150, /* Vertical Interval Timecode (VITC), PAL/SECAM */ + {V4l2_SLICED_VITC_625, 6, 22, 0}, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x6d, 0x49, + 0xa6, 0x85, 0x08, 0x00, 0x00, 0x00, 0x4c, 0x00 } + }, + [11] = {0x170, /* Vertical Interval Timecode (VITC), NTSC */ + {V4l2_SLICED_VITC_525, 10, 20, 0}, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x6d, 0x49, + 0x69, 0x94, 0x08, 0x00, 0x00, 0x00, 0x4c, 0x00 } + }, +#endif + [12] = {0x190, /* Video Program System (VPS), PAL */ + {V4L2_SLICED_VPS, 16, 16, 0}, + { 0xaa, 0xaa, 0xff, 0xff, 0xba, 0xce, 0x2b, 0x0d, + 0xa6, 0xda, 0x0b, 0x00, 0x00, 0x00, 0x60, 0x00 } + }, + /* 0x1d0 User programmable */ +}; + +static int tvp5150_write_inittab(struct v4l2_subdev *sd, + const struct i2c_reg_value *regs) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + while (regs->reg != 0xff) { + regmap_write(decoder->regmap, regs->reg, regs->value); + regs++; + } + return 0; +} + +static int tvp5150_vdp_init(struct v4l2_subdev *sd) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + struct regmap *map = decoder->regmap; + unsigned int i; + int j; + + /* Disable Full Field */ + regmap_write(map, TVP5150_FULL_FIELD_ENA, 0); + + /* Before programming, Line mode should be at 0xff */ + for (i = TVP5150_LINE_MODE_INI; i <= TVP5150_LINE_MODE_END; i++) + regmap_write(map, i, 0xff); + + /* Load Ram Table */ + for (j = 0; j < ARRAY_SIZE(vbi_ram_default); j++) { + const struct i2c_vbi_ram_value *regs = &vbi_ram_default[j]; + + if (!regs->type.vbi_type) + continue; + + regmap_write(map, TVP5150_CONF_RAM_ADDR_HIGH, regs->reg >> 8); + regmap_write(map, TVP5150_CONF_RAM_ADDR_LOW, regs->reg); + + for (i = 0; i < 16; i++) + regmap_write(map, TVP5150_VDP_CONF_RAM_DATA, + regs->values[i]); + } + return 0; +} + +/* Fills VBI capabilities based on i2c_vbi_ram_value struct */ +static int tvp5150_g_sliced_vbi_cap(struct v4l2_subdev *sd, + struct v4l2_sliced_vbi_cap *cap) +{ + int line, i; + + dev_dbg_lvl(sd->dev, 1, debug, "g_sliced_vbi_cap\n"); + memset(cap, 0, sizeof(*cap)); + + for (i = 0; i < ARRAY_SIZE(vbi_ram_default); i++) { + const struct i2c_vbi_ram_value *regs = &vbi_ram_default[i]; + + if (!regs->type.vbi_type) + continue; + + for (line = regs->type.ini_line; + line <= regs->type.end_line; + line++) { + cap->service_lines[0][line] |= regs->type.vbi_type; + } + cap->service_set |= regs->type.vbi_type; + } + return 0; +} + +/* Set vbi processing + * type - one of tvp5150_vbi_types + * line - line to gather data + * fields: bit 0 field1, bit 1, field2 + * flags (default=0xf0) is a bitmask, were set means: + * bit 7: enable filtering null bytes on CC + * bit 6: send data also to FIFO + * bit 5: don't allow data with errors on FIFO + * bit 4: enable ECC when possible + * pix_align = pix alignment: + * LSB = field1 + * MSB = field2 + */ +static int tvp5150_set_vbi(struct v4l2_subdev *sd, + unsigned int type, u8 flags, int line, + const int fields) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + v4l2_std_id std = decoder->norm; + u8 reg; + int i, pos = 0; + + if (std == V4L2_STD_ALL) { + dev_err(sd->dev, "VBI can't be configured without knowing number of lines\n"); + return 0; + } else if (std & V4L2_STD_625_50) { + /* Don't follow NTSC Line number convension */ + line += 3; + } + + if (line < 6 || line > 27) + return 0; + + for (i = 0; i < ARRAY_SIZE(vbi_ram_default); i++) { + const struct i2c_vbi_ram_value *regs = &vbi_ram_default[i]; + + if (!regs->type.vbi_type) + continue; + + if ((type & regs->type.vbi_type) && + (line >= regs->type.ini_line) && + (line <= regs->type.end_line)) + break; + pos++; + } + + type = pos | (flags & 0xf0); + reg = ((line - 6) << 1) + TVP5150_LINE_MODE_INI; + + if (fields & 1) + regmap_write(decoder->regmap, reg, type); + + if (fields & 2) + regmap_write(decoder->regmap, reg + 1, type); + + return type; +} + +static int tvp5150_get_vbi(struct v4l2_subdev *sd, int line) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + v4l2_std_id std = decoder->norm; + u8 reg; + int pos, type = 0; + int i, ret = 0; + + if (std == V4L2_STD_ALL) { + dev_err(sd->dev, "VBI can't be configured without knowing number of lines\n"); + return 0; + } else if (std & V4L2_STD_625_50) { + /* Don't follow NTSC Line number convension */ + line += 3; + } + + if (line < 6 || line > 27) + return 0; + + reg = ((line - 6) << 1) + TVP5150_LINE_MODE_INI; + + for (i = 0; i <= 1; i++) { + ret = tvp5150_read(sd, reg + i); + if (ret < 0) { + dev_err(sd->dev, "%s: failed with error = %d\n", + __func__, ret); + return 0; + } + pos = ret & 0x0f; + if (pos < ARRAY_SIZE(vbi_ram_default)) + type |= vbi_ram_default[pos].type.vbi_type; + } + + return type; +} + +static int tvp5150_set_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + int fmt = 0; + + /* First tests should be against specific std */ + + if (std == V4L2_STD_NTSC_443) { + fmt = VIDEO_STD_NTSC_4_43_BIT; + } else if (std == V4L2_STD_PAL_M) { + fmt = VIDEO_STD_PAL_M_BIT; + } else if (std == V4L2_STD_PAL_N || std == V4L2_STD_PAL_Nc) { + fmt = VIDEO_STD_PAL_COMBINATION_N_BIT; + } else { + /* Then, test against generic ones */ + if (std & V4L2_STD_NTSC) + fmt = VIDEO_STD_NTSC_MJ_BIT; + else if (std & V4L2_STD_PAL) + fmt = VIDEO_STD_PAL_BDGHIN_BIT; + else if (std & V4L2_STD_SECAM) + fmt = VIDEO_STD_SECAM_BIT; + } + + dev_dbg_lvl(sd->dev, 1, debug, "Set video std register to %d.\n", fmt); + regmap_write(decoder->regmap, TVP5150_VIDEO_STD, fmt); + return 0; +} + +static int tvp5150_g_std(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + *std = decoder->norm; + + return 0; +} + +static int tvp5150_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + struct tvp5150_connector *cur_con = decoder->cur_connector; + v4l2_std_id supported_stds; + + if (decoder->norm == std) + return 0; + + /* In case of no of-connectors are available no limitations are made */ + if (!decoder->connectors_num) + supported_stds = V4L2_STD_ALL; + else + supported_stds = cur_con->base.connector.analog.sdtv_stds; + + /* + * Check if requested std or group of std's is/are supported by the + * connector. + */ + if ((supported_stds & std) == 0) + return -EINVAL; + + /* Change cropping height limits */ + if (std & V4L2_STD_525_60) + decoder->rect.height = TVP5150_V_MAX_525_60; + else + decoder->rect.height = TVP5150_V_MAX_OTHERS; + + /* Set only the specific supported std in case of group of std's. */ + decoder->norm = supported_stds & std; + + return tvp5150_set_std(sd, std); +} + +static v4l2_std_id tvp5150_read_std(struct v4l2_subdev *sd) +{ + int val = tvp5150_read(sd, TVP5150_STATUS_REG_5); + + switch (val & 0x0F) { + case 0x01: + return V4L2_STD_NTSC; + case 0x03: + return V4L2_STD_PAL; + case 0x05: + return V4L2_STD_PAL_M; + case 0x07: + return V4L2_STD_PAL_N | V4L2_STD_PAL_Nc; + case 0x09: + return V4L2_STD_NTSC_443; + case 0xb: + return V4L2_STD_SECAM; + default: + return V4L2_STD_UNKNOWN; + } +} + +static int query_lock(struct v4l2_subdev *sd) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + int status; + + if (decoder->irq) + return decoder->lock; + + regmap_read(decoder->regmap, TVP5150_STATUS_REG_1, &status); + + /* For standard detection, we need the 3 locks */ + return (status & 0x0e) == 0x0e; +} + +static int tvp5150_querystd(struct v4l2_subdev *sd, v4l2_std_id *std_id) +{ + *std_id = query_lock(sd) ? tvp5150_read_std(sd) : V4L2_STD_UNKNOWN; + + return 0; +} + +static const struct v4l2_event tvp5150_ev_fmt = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, +}; + +static irqreturn_t tvp5150_isr(int irq, void *dev_id) +{ + struct tvp5150 *decoder = dev_id; + struct regmap *map = decoder->regmap; + unsigned int mask, active = 0, status = 0; + + mask = TVP5150_MISC_CTL_YCBCR_OE | TVP5150_MISC_CTL_SYNC_OE | + TVP5150_MISC_CTL_CLOCK_OE; + + regmap_read(map, TVP5150_INT_STATUS_REG_A, &status); + if (status) { + regmap_write(map, TVP5150_INT_STATUS_REG_A, status); + + if (status & TVP5150_INT_A_LOCK) { + decoder->lock = !!(status & TVP5150_INT_A_LOCK_STATUS); + dev_dbg_lvl(decoder->sd.dev, 1, debug, + "sync lo%s signal\n", + decoder->lock ? "ck" : "ss"); + v4l2_subdev_notify_event(&decoder->sd, &tvp5150_ev_fmt); + regmap_update_bits(map, TVP5150_MISC_CTL, mask, + decoder->lock ? decoder->oe : 0); + } + + return IRQ_HANDLED; + } + + regmap_read(map, TVP5150_INT_ACTIVE_REG_B, &active); + if (active) { + status = 0; + regmap_read(map, TVP5150_INT_STATUS_REG_B, &status); + if (status) + regmap_write(map, TVP5150_INT_RESET_REG_B, status); + } + + return IRQ_HANDLED; +} + +static int tvp5150_reset(struct v4l2_subdev *sd, u32 val) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + struct regmap *map = decoder->regmap; + + /* Initializes TVP5150 to its default values */ + tvp5150_write_inittab(sd, tvp5150_init_default); + + if (decoder->irq) { + /* Configure pins: FID, VSYNC, INTREQ, SCLK */ + regmap_write(map, TVP5150_CONF_SHARED_PIN, 0x0); + /* Set interrupt polarity to active high */ + regmap_write(map, TVP5150_INT_CONF, TVP5150_VDPOE | 0x1); + regmap_write(map, TVP5150_INTT_CONFIG_REG_B, 0x1); + } else { + /* Configure pins: FID, VSYNC, GPCL/VBLK, SCLK */ + regmap_write(map, TVP5150_CONF_SHARED_PIN, 0x2); + /* Keep interrupt polarity active low */ + regmap_write(map, TVP5150_INT_CONF, TVP5150_VDPOE); + regmap_write(map, TVP5150_INTT_CONFIG_REG_B, 0x0); + } + + /* Initializes VDP registers */ + tvp5150_vdp_init(sd); + + /* Selects decoder input */ + tvp5150_selmux(sd); + + /* Initialize image preferences */ + v4l2_ctrl_handler_setup(&decoder->hdl); + + return 0; +} + +static int tvp5150_enable(struct v4l2_subdev *sd) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + v4l2_std_id std; + + /* Initializes TVP5150 to stream enabled values */ + tvp5150_write_inittab(sd, tvp5150_init_enable); + + if (decoder->norm == V4L2_STD_ALL) + std = tvp5150_read_std(sd); + else + std = decoder->norm; + + /* Disable autoswitch mode */ + tvp5150_set_std(sd, std); + + /* + * Enable the YCbCr and clock outputs. In discrete sync mode + * (non-BT.656) additionally enable the the sync outputs. + */ + switch (decoder->mbus_type) { + case V4L2_MBUS_PARALLEL: + /* 8-bit 4:2:2 YUV with discrete sync output */ + regmap_update_bits(decoder->regmap, TVP5150_DATA_RATE_SEL, + 0x7, 0x0); + decoder->oe = TVP5150_MISC_CTL_YCBCR_OE | + TVP5150_MISC_CTL_CLOCK_OE | + TVP5150_MISC_CTL_SYNC_OE; + break; + case V4L2_MBUS_BT656: + decoder->oe = TVP5150_MISC_CTL_YCBCR_OE | + TVP5150_MISC_CTL_CLOCK_OE; + break; + default: + return -EINVAL; + } + + return 0; +}; + +static int tvp5150_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct tvp5150 *decoder = to_tvp5150(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + regmap_write(decoder->regmap, TVP5150_BRIGHT_CTL, ctrl->val); + return 0; + case V4L2_CID_CONTRAST: + regmap_write(decoder->regmap, TVP5150_CONTRAST_CTL, ctrl->val); + return 0; + case V4L2_CID_SATURATION: + regmap_write(decoder->regmap, TVP5150_SATURATION_CTL, + ctrl->val); + return 0; + case V4L2_CID_HUE: + regmap_write(decoder->regmap, TVP5150_HUE_CTL, ctrl->val); + return 0; + case V4L2_CID_TEST_PATTERN: + decoder->enable = ctrl->val ? false : true; + tvp5150_selmux(sd); + return 0; + } + return -EINVAL; +} + +static void tvp5150_set_default(v4l2_std_id std, struct v4l2_rect *crop) +{ + /* Default is no cropping */ + crop->top = 0; + crop->left = 0; + crop->width = TVP5150_H_MAX; + if (std & V4L2_STD_525_60) + crop->height = TVP5150_V_MAX_525_60; + else + crop->height = TVP5150_V_MAX_OTHERS; +} + +static struct v4l2_rect * +tvp5150_get_pad_crop(struct tvp5150 *decoder, + struct v4l2_subdev_pad_config *cfg, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &decoder->rect; + case V4L2_SUBDEV_FORMAT_TRY: +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + return v4l2_subdev_get_try_crop(&decoder->sd, cfg, pad); +#else + return ERR_PTR(-EINVAL); +#endif + default: + return ERR_PTR(-EINVAL); + } +} + +static int tvp5150_fill_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *f; + struct tvp5150 *decoder = to_tvp5150(sd); + + if (!format || (format->pad != TVP5150_PAD_VID_OUT)) + return -EINVAL; + + f = &format->format; + + f->width = decoder->rect.width; + f->height = decoder->rect.height / 2; + + f->code = TVP5150_MBUS_FMT; + f->field = TVP5150_FIELD; + f->colorspace = TVP5150_COLORSPACE; + + dev_dbg_lvl(sd->dev, 1, debug, "width = %d, height = %d\n", f->width, + f->height); + return 0; +} + +static unsigned int tvp5150_get_hmax(struct v4l2_subdev *sd) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + v4l2_std_id std; + + /* Calculate height based on current standard */ + if (decoder->norm == V4L2_STD_ALL) + std = tvp5150_read_std(sd); + else + std = decoder->norm; + + return (std & V4L2_STD_525_60) ? + TVP5150_V_MAX_525_60 : TVP5150_V_MAX_OTHERS; +} + +static void tvp5150_set_hw_selection(struct v4l2_subdev *sd, + struct v4l2_rect *rect) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + unsigned int hmax = tvp5150_get_hmax(sd); + + regmap_write(decoder->regmap, TVP5150_VERT_BLANKING_START, rect->top); + regmap_write(decoder->regmap, TVP5150_VERT_BLANKING_STOP, + rect->top + rect->height - hmax); + regmap_write(decoder->regmap, TVP5150_ACT_VD_CROP_ST_MSB, + rect->left >> TVP5150_CROP_SHIFT); + regmap_write(decoder->regmap, TVP5150_ACT_VD_CROP_ST_LSB, + rect->left | (1 << TVP5150_CROP_SHIFT)); + regmap_write(decoder->regmap, TVP5150_ACT_VD_CROP_STP_MSB, + (rect->left + rect->width - TVP5150_MAX_CROP_LEFT) >> + TVP5150_CROP_SHIFT); + regmap_write(decoder->regmap, TVP5150_ACT_VD_CROP_STP_LSB, + rect->left + rect->width - TVP5150_MAX_CROP_LEFT); +} + +static int tvp5150_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + struct v4l2_rect *rect = &sel->r; + struct v4l2_rect *crop; + unsigned int hmax; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + dev_dbg_lvl(sd->dev, 1, debug, "%s left=%d, top=%d, width=%d, height=%d\n", + __func__, rect->left, rect->top, rect->width, rect->height); + + /* tvp5150 has some special limits */ + rect->left = clamp(rect->left, 0, TVP5150_MAX_CROP_LEFT); + rect->top = clamp(rect->top, 0, TVP5150_MAX_CROP_TOP); + hmax = tvp5150_get_hmax(sd); + + /* + * alignments: + * - width = 2 due to UYVY colorspace + * - height, image = no special alignment + */ + v4l_bound_align_image(&rect->width, + TVP5150_H_MAX - TVP5150_MAX_CROP_LEFT - rect->left, + TVP5150_H_MAX - rect->left, 1, &rect->height, + hmax - TVP5150_MAX_CROP_TOP - rect->top, + hmax - rect->top, 0, 0); + + if (!IS_ENABLED(CONFIG_VIDEO_V4L2_SUBDEV_API) && + sel->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + crop = tvp5150_get_pad_crop(decoder, cfg, sel->pad, sel->which); + if (IS_ERR(crop)) + return PTR_ERR(crop); + + /* + * Update output image size if the selection (crop) rectangle size or + * position has been modified. + */ + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE && + !v4l2_rect_equal(rect, crop)) + tvp5150_set_hw_selection(sd, rect); + + *crop = *rect; + + return 0; +} + +static int tvp5150_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct tvp5150 *decoder = container_of(sd, struct tvp5150, sd); + struct v4l2_rect *crop; + v4l2_std_id std; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = TVP5150_H_MAX; + + /* Calculate height based on current standard */ + if (decoder->norm == V4L2_STD_ALL) + std = tvp5150_read_std(sd); + else + std = decoder->norm; + if (std & V4L2_STD_525_60) + sel->r.height = TVP5150_V_MAX_525_60; + else + sel->r.height = TVP5150_V_MAX_OTHERS; + return 0; + case V4L2_SEL_TGT_CROP: + crop = tvp5150_get_pad_crop(decoder, cfg, sel->pad, + sel->which); + if (IS_ERR(crop)) + return PTR_ERR(crop); + sel->r = *crop; + return 0; + default: + return -EINVAL; + } +} + +static int tvp5150_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + cfg->type = decoder->mbus_type; + cfg->flags = V4L2_MBUS_MASTER | V4L2_MBUS_PCLK_SAMPLE_RISING + | V4L2_MBUS_FIELD_EVEN_LOW | V4L2_MBUS_DATA_ACTIVE_HIGH; + + return 0; +} + +/**************************************************************************** + V4L2 subdev pad ops + ****************************************************************************/ +static int tvp5150_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + v4l2_std_id std; + + /* + * Reset selection to maximum on subdev_open() if autodetection is on + * and a standard change is detected. + */ + if (decoder->norm == V4L2_STD_ALL) { + std = tvp5150_read_std(sd); + if (std != decoder->detected_norm) { + decoder->detected_norm = std; + tvp5150_set_default(std, &decoder->rect); + } + } + + return 0; +} + +static int tvp5150_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index) + return -EINVAL; + + code->code = TVP5150_MBUS_FMT; + return 0; +} + +static int tvp5150_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + if (fse->index >= 8 || fse->code != TVP5150_MBUS_FMT) + return -EINVAL; + + fse->code = TVP5150_MBUS_FMT; + fse->min_width = decoder->rect.width; + fse->max_width = decoder->rect.width; + fse->min_height = decoder->rect.height / 2; + fse->max_height = decoder->rect.height / 2; + + return 0; +} + +/**************************************************************************** + * Media entity ops + ****************************************************************************/ +#if defined(CONFIG_MEDIA_CONTROLLER) +static int tvp5150_set_link(struct media_pad *connector_pad, + struct media_pad *tvp5150_pad, u32 flags) +{ + struct media_link *link; + + link = media_entity_find_link(connector_pad, tvp5150_pad); + if (!link) + return -EINVAL; + + link->flags = flags; + link->reverse->flags = link->flags; + + return 0; +} + +static int tvp5150_disable_all_input_links(struct tvp5150 *decoder) +{ + struct media_pad *connector_pad; + unsigned int i; + int err; + + for (i = 0; i < TVP5150_NUM_PADS - 1; i++) { + connector_pad = media_entity_remote_pad(&decoder->pads[i]); + if (!connector_pad) + continue; + + err = tvp5150_set_link(connector_pad, &decoder->pads[i], 0); + if (err) + return err; + } + + return 0; +} + +static int tvp5150_s_routing(struct v4l2_subdev *sd, u32 input, u32 output, + u32 config); + +static int tvp5150_link_setup(struct media_entity *entity, + const struct media_pad *tvp5150_pad, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct tvp5150 *decoder = to_tvp5150(sd); + struct media_pad *other_tvp5150_pad = + &decoder->pads[tvp5150_pad->index ^ 1]; + struct v4l2_fwnode_connector *v4l2c; + bool is_svideo = false; + unsigned int i; + int err; + + /* + * The TVP5150 state is determined by the enabled sink pad link(s). + * Enabling or disabling the source pad link has no effect. + */ + if (tvp5150_pad->flags & MEDIA_PAD_FL_SOURCE) + return 0; + + /* Check if the svideo connector should be enabled */ + for (i = 0; i < decoder->connectors_num; i++) { + if (remote->entity == &decoder->connectors[i].ent) { + v4l2c = &decoder->connectors[i].base; + is_svideo = v4l2c->type == V4L2_CONN_SVIDEO; + break; + } + } + + dev_dbg_lvl(sd->dev, 1, debug, "link setup '%s':%d->'%s':%d[%d]", + remote->entity->name, remote->index, + tvp5150_pad->entity->name, tvp5150_pad->index, + flags & MEDIA_LNK_FL_ENABLED); + if (is_svideo) + dev_dbg_lvl(sd->dev, 1, debug, + "link setup '%s':%d->'%s':%d[%d]", + remote->entity->name, remote->index, + other_tvp5150_pad->entity->name, + other_tvp5150_pad->index, + flags & MEDIA_LNK_FL_ENABLED); + + /* + * The TVP5150 has an internal mux which allows the following setup: + * + * comp-connector1 --\ + * |---> AIP1A + * / + * svideo-connector -| + * \ + * |---> AIP1B + * comp-connector2 --/ + * + * We can't rely on user space that the current connector gets disabled + * first before enabling the new connector. Disable all active + * connector links to be on the safe side. + */ + err = tvp5150_disable_all_input_links(decoder); + if (err) + return err; + + tvp5150_s_routing(sd, is_svideo ? TVP5150_SVIDEO : tvp5150_pad->index, + flags & MEDIA_LNK_FL_ENABLED ? TVP5150_NORMAL : + TVP5150_BLACK_SCREEN, 0); + + if (flags & MEDIA_LNK_FL_ENABLED) { + struct v4l2_fwnode_connector_analog *v4l2ca; + u32 new_norm; + + /* + * S-Video connector is conneted to both ports AIP1A and AIP1B. + * Both links must be enabled in one-shot regardless which link + * the user requests. + */ + if (is_svideo) { + err = tvp5150_set_link((struct media_pad *)remote, + other_tvp5150_pad, flags); + if (err) + return err; + } + + if (!decoder->connectors_num) + return 0; + + /* Update the current connector */ + decoder->cur_connector = + container_of(remote, struct tvp5150_connector, pad); + + /* + * Do nothing if the new connector supports the same tv-norms as + * the old one. + */ + v4l2ca = &decoder->cur_connector->base.connector.analog; + new_norm = decoder->norm & v4l2ca->sdtv_stds; + if (decoder->norm == new_norm) + return 0; + + /* + * Fallback to the new connector tv-norms if we can't find any + * common between the current tv-norm and the new one. + */ + tvp5150_s_std(sd, new_norm ? new_norm : v4l2ca->sdtv_stds); + } + + return 0; +} + +static const struct media_entity_operations tvp5150_sd_media_ops = { + .link_setup = tvp5150_link_setup, +}; +#endif +/**************************************************************************** + I2C Command + ****************************************************************************/ +static int __maybe_unused tvp5150_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tvp5150 *decoder = to_tvp5150(sd); + + if (decoder->irq) + /* Disable lock interrupt */ + return regmap_update_bits(decoder->regmap, + TVP5150_INT_ENABLE_REG_A, + TVP5150_INT_A_LOCK, 0); + return 0; +} + +static int __maybe_unused tvp5150_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tvp5150 *decoder = to_tvp5150(sd); + + if (decoder->irq) + /* Enable lock interrupt */ + return regmap_update_bits(decoder->regmap, + TVP5150_INT_ENABLE_REG_A, + TVP5150_INT_A_LOCK, + TVP5150_INT_A_LOCK); + return 0; +} + +static int tvp5150_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + unsigned int mask, val = 0; + int ret; + + mask = TVP5150_MISC_CTL_YCBCR_OE | TVP5150_MISC_CTL_SYNC_OE | + TVP5150_MISC_CTL_CLOCK_OE; + + if (enable) { + ret = pm_runtime_get_sync(sd->dev); + if (ret < 0) { + pm_runtime_put_noidle(sd->dev); + return ret; + } + + tvp5150_enable(sd); + + /* Enable outputs if decoder is locked */ + if (decoder->irq) + val = decoder->lock ? decoder->oe : 0; + else + val = decoder->oe; + + v4l2_subdev_notify_event(&decoder->sd, &tvp5150_ev_fmt); + } else { + pm_runtime_put(sd->dev); + } + + regmap_update_bits(decoder->regmap, TVP5150_MISC_CTL, mask, val); + + return 0; +} + +static int tvp5150_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + decoder->input = input; + decoder->output = output; + + if (output == TVP5150_BLACK_SCREEN) + decoder->enable = false; + else + decoder->enable = true; + + tvp5150_selmux(sd); + return 0; +} + +static int tvp5150_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + /* + * this is for capturing 36 raw vbi lines + * if there's a way to cut off the beginning 2 vbi lines + * with the tvp5150 then the vbi line count could be lowered + * to 17 lines/field again, although I couldn't find a register + * which could do that cropping + */ + + if (fmt->sample_format == V4L2_PIX_FMT_GREY) + regmap_write(decoder->regmap, TVP5150_LUMA_PROC_CTL_1, 0x70); + if (fmt->count[0] == 18 && fmt->count[1] == 18) { + regmap_write(decoder->regmap, TVP5150_VERT_BLANKING_START, + 0x00); + regmap_write(decoder->regmap, TVP5150_VERT_BLANKING_STOP, 0x01); + } + return 0; +} + +static int tvp5150_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + int i; + + if (svbi->service_set != 0) { + for (i = 0; i <= 23; i++) { + svbi->service_lines[1][i] = 0; + svbi->service_lines[0][i] = + tvp5150_set_vbi(sd, svbi->service_lines[0][i], + 0xf0, i, 3); + } + /* Enables FIFO */ + regmap_write(decoder->regmap, TVP5150_FIFO_OUT_CTRL, 1); + } else { + /* Disables FIFO*/ + regmap_write(decoder->regmap, TVP5150_FIFO_OUT_CTRL, 0); + + /* Disable Full Field */ + regmap_write(decoder->regmap, TVP5150_FULL_FIELD_ENA, 0); + + /* Disable Line modes */ + for (i = TVP5150_LINE_MODE_INI; i <= TVP5150_LINE_MODE_END; i++) + regmap_write(decoder->regmap, i, 0xff); + } + return 0; +} + +static int tvp5150_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + int i, mask = 0; + + memset(svbi->service_lines, 0, sizeof(svbi->service_lines)); + + for (i = 0; i <= 23; i++) { + svbi->service_lines[0][i] = + tvp5150_get_vbi(sd, i); + mask |= svbi->service_lines[0][i]; + } + svbi->service_set = mask; + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int tvp5150_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + int res; + + res = tvp5150_read(sd, reg->reg & 0xff); + if (res < 0) { + dev_err(sd->dev, "%s: failed with error = %d\n", __func__, res); + return res; + } + + reg->val = res; + reg->size = 1; + return 0; +} + +static int tvp5150_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + return regmap_write(decoder->regmap, reg->reg & 0xff, reg->val & 0xff); +} +#endif + +static int tvp5150_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static int tvp5150_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + int status = tvp5150_read(sd, 0x88); + + vt->signal = ((status & 0x04) && (status & 0x02)) ? 0xffff : 0x0; + return 0; +} + +static int tvp5150_registered(struct v4l2_subdev *sd) +{ +#if defined(CONFIG_MEDIA_CONTROLLER) + struct tvp5150 *decoder = to_tvp5150(sd); + unsigned int i; + int ret; + + /* + * Setup connector pads and links. Enable the link to the first + * available connector per default. + */ + for (i = 0; i < decoder->connectors_num; i++) { + struct media_entity *con = &decoder->connectors[i].ent; + struct media_pad *pad = &decoder->connectors[i].pad; + struct v4l2_fwnode_connector *v4l2c = + &decoder->connectors[i].base; + struct v4l2_connector_link *link = + v4l2_connector_first_link(v4l2c); + unsigned int port = link->fwnode_link.remote_port; + unsigned int flags = i ? 0 : MEDIA_LNK_FL_ENABLED; + bool is_svideo = v4l2c->type == V4L2_CONN_SVIDEO; + + pad->flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(con, 1, pad); + if (ret < 0) + goto err; + + ret = media_device_register_entity(sd->v4l2_dev->mdev, con); + if (ret < 0) + goto err; + + ret = media_create_pad_link(con, 0, &sd->entity, port, flags); + if (ret < 0) + goto err; + + if (is_svideo) { + /* + * Check tvp5150_link_setup() comments for more + * information. + */ + link = v4l2_connector_last_link(v4l2c); + port = link->fwnode_link.remote_port; + ret = media_create_pad_link(con, 0, &sd->entity, port, + flags); + if (ret < 0) + goto err; + } + + /* Enable default input. */ + if (flags == MEDIA_LNK_FL_ENABLED) { + decoder->input = + is_svideo ? TVP5150_SVIDEO : + port == 0 ? TVP5150_COMPOSITE0 : + TVP5150_COMPOSITE1; + + tvp5150_selmux(sd); + decoder->cur_connector = &decoder->connectors[i]; + tvp5150_s_std(sd, v4l2c->connector.analog.sdtv_stds); + } + } + + return 0; + +err: + for (i = 0; i < decoder->connectors_num; i++) { + media_device_unregister_entity(&decoder->connectors[i].ent); + media_entity_cleanup(&decoder->connectors[i].ent); + } + return ret; +#endif + + return 0; +} + +static int tvp5150_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + int ret; + + ret = pm_runtime_get_sync(sd->dev); + if (ret < 0) { + pm_runtime_put_noidle(sd->dev); + return ret; + } + + return 0; +} + +static int tvp5150_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops tvp5150_ctrl_ops = { + .s_ctrl = tvp5150_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops tvp5150_core_ops = { + .log_status = tvp5150_log_status, + .reset = tvp5150_reset, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = tvp5150_g_register, + .s_register = tvp5150_s_register, +#endif + .subscribe_event = tvp5150_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_tuner_ops tvp5150_tuner_ops = { + .g_tuner = tvp5150_g_tuner, +}; + +static const struct v4l2_subdev_video_ops tvp5150_video_ops = { + .s_std = tvp5150_s_std, + .g_std = tvp5150_g_std, + .querystd = tvp5150_querystd, + .s_stream = tvp5150_s_stream, + .s_routing = tvp5150_s_routing, +}; + +static const struct v4l2_subdev_vbi_ops tvp5150_vbi_ops = { + .g_sliced_vbi_cap = tvp5150_g_sliced_vbi_cap, + .g_sliced_fmt = tvp5150_g_sliced_fmt, + .s_sliced_fmt = tvp5150_s_sliced_fmt, + .s_raw_fmt = tvp5150_s_raw_fmt, +}; + +static const struct v4l2_subdev_pad_ops tvp5150_pad_ops = { + .init_cfg = tvp5150_init_cfg, + .enum_mbus_code = tvp5150_enum_mbus_code, + .enum_frame_size = tvp5150_enum_frame_size, + .set_fmt = tvp5150_fill_fmt, + .get_fmt = tvp5150_fill_fmt, + .get_selection = tvp5150_get_selection, + .set_selection = tvp5150_set_selection, + .get_mbus_config = tvp5150_get_mbus_config, +}; + +static const struct v4l2_subdev_ops tvp5150_ops = { + .core = &tvp5150_core_ops, + .tuner = &tvp5150_tuner_ops, + .video = &tvp5150_video_ops, + .vbi = &tvp5150_vbi_ops, + .pad = &tvp5150_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops tvp5150_internal_ops = { + .registered = tvp5150_registered, + .open = tvp5150_open, + .close = tvp5150_close, +}; + +/**************************************************************************** + I2C Client & Driver + ****************************************************************************/ + +static const struct regmap_range tvp5150_readable_ranges[] = { + { + .range_min = TVP5150_VD_IN_SRC_SEL_1, + .range_max = TVP5150_AUTOSW_MSK, + }, { + .range_min = TVP5150_COLOR_KIL_THSH_CTL, + .range_max = TVP5150_CONF_SHARED_PIN, + }, { + .range_min = TVP5150_ACT_VD_CROP_ST_MSB, + .range_max = TVP5150_HORIZ_SYNC_START, + }, { + .range_min = TVP5150_VERT_BLANKING_START, + .range_max = TVP5150_INTT_CONFIG_REG_B, + }, { + .range_min = TVP5150_VIDEO_STD, + .range_max = TVP5150_VIDEO_STD, + }, { + .range_min = TVP5150_CB_GAIN_FACT, + .range_max = TVP5150_REV_SELECT, + }, { + .range_min = TVP5150_MSB_DEV_ID, + .range_max = TVP5150_STATUS_REG_5, + }, { + .range_min = TVP5150_CC_DATA_INI, + .range_max = TVP5150_TELETEXT_FIL_ENA, + }, { + .range_min = TVP5150_INT_STATUS_REG_A, + .range_max = TVP5150_FIFO_OUT_CTRL, + }, { + .range_min = TVP5150_FULL_FIELD_ENA, + .range_max = TVP5150_FULL_FIELD_MODE_REG, + }, +}; + +static bool tvp5150_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TVP5150_VERT_LN_COUNT_MSB: + case TVP5150_VERT_LN_COUNT_LSB: + case TVP5150_INT_STATUS_REG_A: + case TVP5150_INT_STATUS_REG_B: + case TVP5150_INT_ACTIVE_REG_B: + case TVP5150_STATUS_REG_1: + case TVP5150_STATUS_REG_2: + case TVP5150_STATUS_REG_3: + case TVP5150_STATUS_REG_4: + case TVP5150_STATUS_REG_5: + /* CC, WSS, VPS, VITC data? */ + case TVP5150_VBI_FIFO_READ_DATA: + case TVP5150_VDP_STATUS_REG: + case TVP5150_FIFO_WORD_COUNT: + return true; + default: + return false; + } +} + +static const struct regmap_access_table tvp5150_readable_table = { + .yes_ranges = tvp5150_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(tvp5150_readable_ranges), +}; + +static struct regmap_config tvp5150_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + + .cache_type = REGCACHE_RBTREE, + + .rd_table = &tvp5150_readable_table, + .volatile_reg = tvp5150_volatile_reg, +}; + +static int tvp5150_detect_version(struct tvp5150 *core) +{ + struct v4l2_subdev *sd = &core->sd; + struct i2c_client *c = v4l2_get_subdevdata(sd); + u8 regs[4]; + int res; + + /* + * Read consequent registers - TVP5150_MSB_DEV_ID, TVP5150_LSB_DEV_ID, + * TVP5150_ROM_MAJOR_VER, TVP5150_ROM_MINOR_VER + */ + res = regmap_bulk_read(core->regmap, TVP5150_MSB_DEV_ID, regs, 4); + if (res < 0) { + dev_err(&c->dev, "reading ID registers failed: %d\n", res); + return res; + } + + core->dev_id = (regs[0] << 8) | regs[1]; + core->rom_ver = (regs[2] << 8) | regs[3]; + + dev_info(sd->dev, "tvp%04x (%u.%u) chip found @ 0x%02x (%s)\n", + core->dev_id, regs[2], regs[3], c->addr << 1, + c->adapter->name); + + if (core->dev_id == 0x5150 && core->rom_ver == 0x0321) { + dev_info(sd->dev, "tvp5150a detected.\n"); + } else if (core->dev_id == 0x5150 && core->rom_ver == 0x0400) { + dev_info(sd->dev, "tvp5150am1 detected.\n"); + + /* ITU-T BT.656.4 timing */ + regmap_write(core->regmap, TVP5150_REV_SELECT, 0); + } else if (core->dev_id == 0x5151 && core->rom_ver == 0x0100) { + dev_info(sd->dev, "tvp5151 detected.\n"); + } else { + dev_info(sd->dev, "*** unknown tvp%04x chip detected.\n", + core->dev_id); + } + + return 0; +} + +static int tvp5150_init(struct i2c_client *c) +{ + struct gpio_desc *pdn_gpio; + struct gpio_desc *reset_gpio; + + pdn_gpio = devm_gpiod_get_optional(&c->dev, "pdn", GPIOD_OUT_HIGH); + if (IS_ERR(pdn_gpio)) + return PTR_ERR(pdn_gpio); + + if (pdn_gpio) { + gpiod_set_value_cansleep(pdn_gpio, 0); + /* Delay time between power supplies active and reset */ + msleep(20); + } + + reset_gpio = devm_gpiod_get_optional(&c->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) + return PTR_ERR(reset_gpio); + + if (reset_gpio) { + /* RESETB pulse duration */ + ndelay(500); + gpiod_set_value_cansleep(reset_gpio, 0); + /* Delay time between end of reset to I2C active */ + usleep_range(200, 250); + } + + return 0; +} + +#if defined(CONFIG_MEDIA_CONTROLLER) +static int tvp5150_mc_init(struct tvp5150 *decoder) +{ + struct v4l2_subdev *sd = &decoder->sd; + unsigned int i; + + sd->entity.ops = &tvp5150_sd_media_ops; + sd->entity.function = MEDIA_ENT_F_ATV_DECODER; + + for (i = 0; i < TVP5150_NUM_PADS - 1; i++) { + decoder->pads[i].flags = MEDIA_PAD_FL_SINK; + decoder->pads[i].sig_type = PAD_SIGNAL_ANALOG; + } + + decoder->pads[i].flags = MEDIA_PAD_FL_SOURCE; + decoder->pads[i].sig_type = PAD_SIGNAL_DV; + + return media_entity_pads_init(&sd->entity, TVP5150_NUM_PADS, + decoder->pads); +} + +#else /* !defined(CONFIG_MEDIA_CONTROLLER) */ + +static inline int tvp5150_mc_init(struct tvp5150 *decoder) +{ + return 0; +} +#endif /* defined(CONFIG_MEDIA_CONTROLLER) */ + +static int tvp5150_validate_connectors(struct tvp5150 *decoder) +{ + struct device *dev = decoder->sd.dev; + struct tvp5150_connector *tvpc; + struct v4l2_fwnode_connector *v4l2c; + unsigned int i; + + if (!decoder->connectors_num) { + dev_err(dev, "No valid connector found\n"); + return -ENODEV; + } + + for (i = 0; i < decoder->connectors_num; i++) { + struct v4l2_connector_link *link0 = NULL; + struct v4l2_connector_link *link1; + + tvpc = &decoder->connectors[i]; + v4l2c = &tvpc->base; + + if (v4l2c->type == V4L2_CONN_COMPOSITE) { + if (v4l2c->nr_of_links != 1) { + dev_err(dev, "Composite: connector needs 1 link\n"); + return -EINVAL; + } + link0 = v4l2_connector_first_link(v4l2c); + if (!link0) { + dev_err(dev, "Composite: invalid first link\n"); + return -EINVAL; + } + if (link0->fwnode_link.remote_id == 1) { + dev_err(dev, "Composite: invalid endpoint id\n"); + return -EINVAL; + } + } + + if (v4l2c->type == V4L2_CONN_SVIDEO) { + if (v4l2c->nr_of_links != 2) { + dev_err(dev, "SVideo: connector needs 2 links\n"); + return -EINVAL; + } + link0 = v4l2_connector_first_link(v4l2c); + if (!link0) { + dev_err(dev, "SVideo: invalid first link\n"); + return -EINVAL; + } + link1 = v4l2_connector_last_link(v4l2c); + if (link0->fwnode_link.remote_port == + link1->fwnode_link.remote_port) { + dev_err(dev, "SVideo: invalid link setup\n"); + return -EINVAL; + } + } + + if (!(v4l2c->connector.analog.sdtv_stds & TVP5150_STD_MASK)) { + dev_err(dev, "Unsupported tv-norm on connector %s\n", + v4l2c->name); + return -EINVAL; + } + } + + return 0; +} + +static int tvp5150_parse_dt(struct tvp5150 *decoder, struct device_node *np) +{ + struct device *dev = decoder->sd.dev; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_UNKNOWN + }; + struct device_node *ep_np; + struct tvp5150_connector *tvpc; + struct v4l2_fwnode_connector *v4l2c; + unsigned int flags, ep_num; + unsigned int i; + int ret; + + /* At least 1 output and 1 input */ + ep_num = of_graph_get_endpoint_count(np); + if (ep_num < 2 || ep_num > 5) { + dev_err(dev, "At least 1 input and 1 output must be connected to the device.\n"); + return -EINVAL; + } + + /* Layout if all connectors are used: + * + * tvp-5150 port@0 (AIP1A) + * endpoint@0 -----------> Comp0-Con port + * endpoint@1 --------+--> Svideo-Con port + * tvp-5150 port@1 (AIP1B) | + * endpoint@1 --------+ + * endpoint@0 -----------> Comp1-Con port + * tvp-5150 port@2 + * endpoint (video bitstream output at YOUT[0-7] parallel bus) + */ + for_each_endpoint_of_node(np, ep_np) { + struct fwnode_handle *ep_fwnode = of_fwnode_handle(ep_np); + unsigned int next_connector = decoder->connectors_num; + struct of_endpoint ep; + + of_graph_parse_endpoint(ep_np, &ep); + if (ep.port > 1 || ep.id > 1) { + dev_dbg(dev, "Ignore connector on port@%u/ep@%u\n", + ep.port, ep.id); + continue; + } + + tvpc = &decoder->connectors[next_connector]; + v4l2c = &tvpc->base; + + if (ep.port == 0 || (ep.port == 1 && ep.id == 0)) { + ret = v4l2_fwnode_connector_parse(ep_fwnode, v4l2c); + if (ret) + goto err_put; + ret = v4l2_fwnode_connector_add_link(ep_fwnode, v4l2c); + if (ret) + goto err_put; + decoder->connectors_num++; + } else { + /* Adding the 2nd svideo link */ + for (i = 0; i < TVP5150_MAX_CONNECTORS; i++) { + tvpc = &decoder->connectors[i]; + v4l2c = &tvpc->base; + if (v4l2c->type == V4L2_CONN_SVIDEO) + break; + } + + ret = v4l2_fwnode_connector_add_link(ep_fwnode, v4l2c); + if (ret) + goto err_put; + } + } + + ret = tvp5150_validate_connectors(decoder); + if (ret) + goto err_free; + + for (i = 0; i < decoder->connectors_num; i++) { + tvpc = &decoder->connectors[i]; + v4l2c = &tvpc->base; + tvpc->ent.flags = MEDIA_ENT_FL_CONNECTOR; + tvpc->ent.function = v4l2c->type == V4L2_CONN_SVIDEO ? + MEDIA_ENT_F_CONN_SVIDEO : MEDIA_ENT_F_CONN_COMPOSITE; + tvpc->ent.name = devm_kasprintf(dev, GFP_KERNEL, "%s %s", + v4l2c->name, v4l2c->label ? + v4l2c->label : ""); + if (!tvpc->ent.name) { + ret = -ENOMEM; + goto err_free; + } + } + + ep_np = of_graph_get_endpoint_by_regs(np, TVP5150_PAD_VID_OUT, 0); + if (!ep_np) { + ret = -EINVAL; + dev_err(dev, "Error no output endpoint available\n"); + goto err_free; + } + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep_np), &bus_cfg); + of_node_put(ep_np); + if (ret) + goto err_free; + + flags = bus_cfg.bus.parallel.flags; + if (bus_cfg.bus_type == V4L2_MBUS_PARALLEL && + !(flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH && + flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH && + flags & V4L2_MBUS_FIELD_EVEN_LOW)) { + ret = -EINVAL; + goto err_free; + } + + decoder->mbus_type = bus_cfg.bus_type; + + return 0; + +err_put: + of_node_put(ep_np); +err_free: + for (i = 0; i < TVP5150_MAX_CONNECTORS; i++) + v4l2_fwnode_connector_free(&decoder->connectors[i].base); + + return ret; +} + +static const char * const tvp5150_test_patterns[2] = { + "Disabled", + "Black screen" +}; + +static int tvp5150_probe(struct i2c_client *c) +{ + struct tvp5150 *core; + struct v4l2_subdev *sd; + struct device_node *np = c->dev.of_node; + struct regmap *map; + unsigned int i; + int res; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(c->adapter, + I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -EIO; + + res = tvp5150_init(c); + if (res) + return res; + + core = devm_kzalloc(&c->dev, sizeof(*core), GFP_KERNEL); + if (!core) + return -ENOMEM; + + map = devm_regmap_init_i2c(c, &tvp5150_config); + if (IS_ERR(map)) + return PTR_ERR(map); + + core->regmap = map; + sd = &core->sd; + v4l2_i2c_subdev_init(sd, c, &tvp5150_ops); + sd->internal_ops = &tvp5150_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + + if (IS_ENABLED(CONFIG_OF) && np) { + res = tvp5150_parse_dt(core, np); + if (res) { + dev_err(sd->dev, "DT parsing error: %d\n", res); + return res; + } + } else { + /* Default to BT.656 embedded sync */ + core->mbus_type = V4L2_MBUS_BT656; + } + + res = tvp5150_mc_init(core); + if (res) + return res; + + res = tvp5150_detect_version(core); + if (res < 0) + return res; + + /* + * Iterate over all available connectors in case they are supported and + * successfully parsed. Fallback to default autodetect in case they + * aren't supported. + */ + for (i = 0; i < core->connectors_num; i++) { + struct v4l2_fwnode_connector *v4l2c; + + v4l2c = &core->connectors[i].base; + core->norm |= v4l2c->connector.analog.sdtv_stds; + } + + if (!core->connectors_num) + core->norm = V4L2_STD_ALL; + + core->detected_norm = V4L2_STD_UNKNOWN; + core->input = TVP5150_COMPOSITE1; + core->enable = true; + + v4l2_ctrl_handler_init(&core->hdl, 5); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_PIXEL_RATE, 27000000, + 27000000, 1, 27000000); + v4l2_ctrl_new_std_menu_items(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(tvp5150_test_patterns) - 1, + 0, 0, tvp5150_test_patterns); + sd->ctrl_handler = &core->hdl; + if (core->hdl.error) { + res = core->hdl.error; + goto err; + } + + tvp5150_set_default(tvp5150_read_std(sd), &core->rect); + + core->irq = c->irq; + tvp5150_reset(sd, 0); /* Calls v4l2_ctrl_handler_setup() */ + if (c->irq) { + res = devm_request_threaded_irq(&c->dev, c->irq, NULL, + tvp5150_isr, IRQF_TRIGGER_HIGH | + IRQF_ONESHOT, "tvp5150", core); + if (res) + goto err; + } + + res = v4l2_async_register_subdev(sd); + if (res < 0) + goto err; + + if (debug > 1) + tvp5150_log_status(sd); + + pm_runtime_set_active(&c->dev); + pm_runtime_enable(&c->dev); + pm_runtime_idle(&c->dev); + + return 0; + +err: + v4l2_ctrl_handler_free(&core->hdl); + return res; +} + +static int tvp5150_remove(struct i2c_client *c) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct tvp5150 *decoder = to_tvp5150(sd); + unsigned int i; + + dev_dbg_lvl(sd->dev, 1, debug, + "tvp5150.c: removing tvp5150 adapter on address 0x%x\n", + c->addr << 1); + + for (i = 0; i < decoder->connectors_num; i++) + v4l2_fwnode_connector_free(&decoder->connectors[i].base); + for (i = 0; i < decoder->connectors_num; i++) { + media_device_unregister_entity(&decoder->connectors[i].ent); + media_entity_cleanup(&decoder->connectors[i].ent); + } + v4l2_async_unregister_subdev(sd); + v4l2_ctrl_handler_free(&decoder->hdl); + pm_runtime_disable(&c->dev); + pm_runtime_set_suspended(&c->dev); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct dev_pm_ops tvp5150_pm_ops = { + SET_RUNTIME_PM_OPS(tvp5150_runtime_suspend, + tvp5150_runtime_resume, + NULL) +}; + +static const struct i2c_device_id tvp5150_id[] = { + { "tvp5150", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tvp5150_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tvp5150_of_match[] = { + { .compatible = "ti,tvp5150", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, tvp5150_of_match); +#endif + +static struct i2c_driver tvp5150_driver = { + .driver = { + .of_match_table = of_match_ptr(tvp5150_of_match), + .name = "tvp5150", + .pm = &tvp5150_pm_ops, + }, + .probe_new = tvp5150_probe, + .remove = tvp5150_remove, + .id_table = tvp5150_id, +}; + +module_i2c_driver(tvp5150_driver); diff --git a/drivers/media/i2c/tvp5150_reg.h b/drivers/media/i2c/tvp5150_reg.h new file mode 100644 index 000000000..f716129ad --- /dev/null +++ b/drivers/media/i2c/tvp5150_reg.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * + * tvp5150 - Texas Instruments TVP5150A/AM1 video decoder registers + * + * Copyright (c) 2005,2006 Mauro Carvalho Chehab <mchehab@kernel.org> + */ + +#define TVP5150_VD_IN_SRC_SEL_1 0x00 /* Video input source selection #1 */ +#define TVP5150_ANAL_CHL_CTL 0x01 /* Analog channel controls */ +#define TVP5150_OP_MODE_CTL 0x02 /* Operation mode controls */ +#define TVP5150_MISC_CTL 0x03 /* Miscellaneous controls */ +#define TVP5150_MISC_CTL_VBLK_GPCL BIT(7) +#define TVP5150_MISC_CTL_GPCL BIT(6) +#define TVP5150_MISC_CTL_INTREQ_OE BIT(5) +#define TVP5150_MISC_CTL_HVLK BIT(4) +#define TVP5150_MISC_CTL_YCBCR_OE BIT(3) +#define TVP5150_MISC_CTL_SYNC_OE BIT(2) +#define TVP5150_MISC_CTL_VBLANK BIT(1) +#define TVP5150_MISC_CTL_CLOCK_OE BIT(0) + +#define TVP5150_AUTOSW_MSK 0x04 /* Autoswitch mask: TVP5150A / TVP5150AM */ + +/* Reserved 05h */ + +#define TVP5150_COLOR_KIL_THSH_CTL 0x06 /* Color killer threshold control */ +#define TVP5150_LUMA_PROC_CTL_1 0x07 /* Luminance processing control #1 */ +#define TVP5150_LUMA_PROC_CTL_2 0x08 /* Luminance processing control #2 */ +#define TVP5150_BRIGHT_CTL 0x09 /* Brightness control */ +#define TVP5150_SATURATION_CTL 0x0a /* Color saturation control */ +#define TVP5150_HUE_CTL 0x0b /* Hue control */ +#define TVP5150_CONTRAST_CTL 0x0c /* Contrast control */ +#define TVP5150_DATA_RATE_SEL 0x0d /* Outputs and data rates select */ +#define TVP5150_LUMA_PROC_CTL_3 0x0e /* Luminance processing control #3 */ +#define TVP5150_CONF_SHARED_PIN 0x0f /* Configuration shared pins */ + +/* Reserved 10h */ + +#define TVP5150_ACT_VD_CROP_ST_MSB 0x11 /* Active video cropping start MSB */ +#define TVP5150_ACT_VD_CROP_ST_LSB 0x12 /* Active video cropping start LSB */ +#define TVP5150_ACT_VD_CROP_STP_MSB 0x13 /* Active video cropping stop MSB */ +#define TVP5150_ACT_VD_CROP_STP_LSB 0x14 /* Active video cropping stop LSB */ +#define TVP5150_GENLOCK 0x15 /* Genlock/RTC */ +#define TVP5150_HORIZ_SYNC_START 0x16 /* Horizontal sync start */ + +/* Reserved 17h */ + +#define TVP5150_VERT_BLANKING_START 0x18 /* Vertical blanking start */ +#define TVP5150_VERT_BLANKING_STOP 0x19 /* Vertical blanking stop */ +#define TVP5150_CHROMA_PROC_CTL_1 0x1a /* Chrominance processing control #1 */ +#define TVP5150_CHROMA_PROC_CTL_2 0x1b /* Chrominance processing control #2 */ +#define TVP5150_INT_RESET_REG_B 0x1c /* Interrupt reset register B */ +#define TVP5150_INT_ENABLE_REG_B 0x1d /* Interrupt enable register B */ +#define TVP5150_INTT_CONFIG_REG_B 0x1e /* Interrupt configuration register B */ + +/* Reserved 1Fh-27h */ + +#define VIDEO_STD_MASK (0x07 >> 1) +#define TVP5150_VIDEO_STD 0x28 /* Video standard */ +#define VIDEO_STD_AUTO_SWITCH_BIT 0x00 +#define VIDEO_STD_NTSC_MJ_BIT 0x02 +#define VIDEO_STD_PAL_BDGHIN_BIT 0x04 +#define VIDEO_STD_PAL_M_BIT 0x06 +#define VIDEO_STD_PAL_COMBINATION_N_BIT 0x08 +#define VIDEO_STD_NTSC_4_43_BIT 0x0a +#define VIDEO_STD_SECAM_BIT 0x0c + +#define VIDEO_STD_NTSC_MJ_BIT_AS 0x01 +#define VIDEO_STD_PAL_BDGHIN_BIT_AS 0x03 +#define VIDEO_STD_PAL_M_BIT_AS 0x05 +#define VIDEO_STD_PAL_COMBINATION_N_BIT_AS 0x07 +#define VIDEO_STD_NTSC_4_43_BIT_AS 0x09 +#define VIDEO_STD_SECAM_BIT_AS 0x0b + +/* Reserved 29h-2bh */ + +#define TVP5150_CB_GAIN_FACT 0x2c /* Cb gain factor */ +#define TVP5150_CR_GAIN_FACTOR 0x2d /* Cr gain factor */ +#define TVP5150_MACROVISION_ON_CTR 0x2e /* Macrovision on counter */ +#define TVP5150_MACROVISION_OFF_CTR 0x2f /* Macrovision off counter */ +#define TVP5150_REV_SELECT 0x30 /* revision select (TVP5150AM1 only) */ + +/* Reserved 31h-7Fh */ + +#define TVP5150_MSB_DEV_ID 0x80 /* MSB of device ID */ +#define TVP5150_LSB_DEV_ID 0x81 /* LSB of device ID */ +#define TVP5150_ROM_MAJOR_VER 0x82 /* ROM major version */ +#define TVP5150_ROM_MINOR_VER 0x83 /* ROM minor version */ +#define TVP5150_VERT_LN_COUNT_MSB 0x84 /* Vertical line count MSB */ +#define TVP5150_VERT_LN_COUNT_LSB 0x85 /* Vertical line count LSB */ +#define TVP5150_INT_STATUS_REG_B 0x86 /* Interrupt status register B */ +#define TVP5150_INT_ACTIVE_REG_B 0x87 /* Interrupt active register B */ +#define TVP5150_STATUS_REG_1 0x88 /* Status register #1 */ +#define TVP5150_STATUS_REG_2 0x89 /* Status register #2 */ +#define TVP5150_STATUS_REG_3 0x8a /* Status register #3 */ +#define TVP5150_STATUS_REG_4 0x8b /* Status register #4 */ +#define TVP5150_STATUS_REG_5 0x8c /* Status register #5 */ +/* Reserved 8Dh-8Fh */ + /* Closed caption data registers */ +#define TVP5150_CC_DATA_INI 0x90 +#define TVP5150_CC_DATA_END 0x93 + + /* WSS data registers */ +#define TVP5150_WSS_DATA_INI 0x94 +#define TVP5150_WSS_DATA_END 0x99 + +/* VPS data registers */ +#define TVP5150_VPS_DATA_INI 0x9a +#define TVP5150_VPS_DATA_END 0xa6 + +/* VITC data registers */ +#define TVP5150_VITC_DATA_INI 0xa7 +#define TVP5150_VITC_DATA_END 0xaf + +#define TVP5150_VBI_FIFO_READ_DATA 0xb0 /* VBI FIFO read data */ + +/* Teletext filter 1 */ +#define TVP5150_TELETEXT_FIL1_INI 0xb1 +#define TVP5150_TELETEXT_FIL1_END 0xb5 + +/* Teletext filter 2 */ +#define TVP5150_TELETEXT_FIL2_INI 0xb6 +#define TVP5150_TELETEXT_FIL2_END 0xba + +#define TVP5150_TELETEXT_FIL_ENA 0xbb /* Teletext filter enable */ +/* Reserved BCh-BFh */ +#define TVP5150_INT_STATUS_REG_A 0xc0 /* Interrupt status register A */ +#define TVP5150_INT_A_LOCK_STATUS BIT(7) +#define TVP5150_INT_A_LOCK BIT(6) +#define TVP5150_INT_ENABLE_REG_A 0xc1 /* Interrupt enable register A */ +#define TVP5150_INT_CONF 0xc2 /* Interrupt configuration */ +#define TVP5150_VDPOE BIT(2) +#define TVP5150_VDP_CONF_RAM_DATA 0xc3 /* VDP configuration RAM data */ +#define TVP5150_CONF_RAM_ADDR_LOW 0xc4 /* Configuration RAM address low byte */ +#define TVP5150_CONF_RAM_ADDR_HIGH 0xc5 /* Configuration RAM address high byte */ +#define TVP5150_VDP_STATUS_REG 0xc6 /* VDP status register */ +#define TVP5150_FIFO_WORD_COUNT 0xc7 /* FIFO word count */ +#define TVP5150_FIFO_INT_THRESHOLD 0xc8 /* FIFO interrupt threshold */ +#define TVP5150_FIFO_RESET 0xc9 /* FIFO reset */ +#define TVP5150_LINE_NUMBER_INT 0xca /* Line number interrupt */ +#define TVP5150_PIX_ALIGN_REG_LOW 0xcb /* Pixel alignment register low byte */ +#define TVP5150_PIX_ALIGN_REG_HIGH 0xcc /* Pixel alignment register high byte */ +#define TVP5150_FIFO_OUT_CTRL 0xcd /* FIFO output control */ +/* Reserved CEh */ +#define TVP5150_FULL_FIELD_ENA 0xcf /* Full field enable 1 */ + +/* Line mode registers */ +#define TVP5150_LINE_MODE_INI 0xd0 +#define TVP5150_LINE_MODE_END 0xfb + +#define TVP5150_FULL_FIELD_MODE_REG 0xfc /* Full field mode register */ +/* Reserved FDh-FFh */ diff --git a/drivers/media/i2c/tvp7002.c b/drivers/media/i2c/tvp7002.c new file mode 100644 index 000000000..ada4ec5ef --- /dev/null +++ b/drivers/media/i2c/tvp7002.c @@ -0,0 +1,1085 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Texas Instruments Triple 8-/10-BIT 165-/110-MSPS Video and Graphics + * Digitizer with Horizontal PLL registers + * + * Copyright (C) 2009 Texas Instruments Inc + * Author: Santiago Nunez-Corrales <santiago.nunez@ridgerun.com> + * + * This code is partially based upon the TVP5150 driver + * written by Mauro Carvalho Chehab <mchehab@kernel.org>, + * the TVP514x driver written by Vaibhav Hiremath <hvaibhav@ti.com> + * and the TVP7002 driver in the TI LSP 2.10.00.14. Revisions by + * Muralidharan Karicheri and Snehaprabha Narnakaje (TI). + */ +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/v4l2-dv-timings.h> +#include <media/i2c/tvp7002.h> +#include <media/v4l2-async.h> +#include <media/v4l2-device.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> + +#include "tvp7002_reg.h" + +MODULE_DESCRIPTION("TI TVP7002 Video and Graphics Digitizer driver"); +MODULE_AUTHOR("Santiago Nunez-Corrales <santiago.nunez@ridgerun.com>"); +MODULE_LICENSE("GPL"); + +/* I2C retry attempts */ +#define I2C_RETRY_COUNT (5) + +/* End of registers */ +#define TVP7002_EOR 0x5c + +/* Read write definition for registers */ +#define TVP7002_READ 0 +#define TVP7002_WRITE 1 +#define TVP7002_RESERVED 2 + +/* Interlaced vs progressive mask and shift */ +#define TVP7002_IP_SHIFT 5 +#define TVP7002_INPR_MASK (0x01 << TVP7002_IP_SHIFT) + +/* Shift for CPL and LPF registers */ +#define TVP7002_CL_SHIFT 8 +#define TVP7002_CL_MASK 0x0f + +/* Debug functions */ +static bool debug; +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +/* Structure for register values */ +struct i2c_reg_value { + u8 reg; + u8 value; + u8 type; +}; + +/* + * Register default values (according to tvp7002 datasheet) + * In the case of read-only registers, the value (0xff) is + * never written. R/W functionality is controlled by the + * writable bit in the register struct definition. + */ +static const struct i2c_reg_value tvp7002_init_default[] = { + { TVP7002_CHIP_REV, 0xff, TVP7002_READ }, + { TVP7002_HPLL_FDBK_DIV_MSBS, 0x67, TVP7002_WRITE }, + { TVP7002_HPLL_FDBK_DIV_LSBS, 0x20, TVP7002_WRITE }, + { TVP7002_HPLL_CRTL, 0xa0, TVP7002_WRITE }, + { TVP7002_HPLL_PHASE_SEL, 0x80, TVP7002_WRITE }, + { TVP7002_CLAMP_START, 0x32, TVP7002_WRITE }, + { TVP7002_CLAMP_W, 0x20, TVP7002_WRITE }, + { TVP7002_HSYNC_OUT_W, 0x60, TVP7002_WRITE }, + { TVP7002_B_FINE_GAIN, 0x00, TVP7002_WRITE }, + { TVP7002_G_FINE_GAIN, 0x00, TVP7002_WRITE }, + { TVP7002_R_FINE_GAIN, 0x00, TVP7002_WRITE }, + { TVP7002_B_FINE_OFF_MSBS, 0x80, TVP7002_WRITE }, + { TVP7002_G_FINE_OFF_MSBS, 0x80, TVP7002_WRITE }, + { TVP7002_R_FINE_OFF_MSBS, 0x80, TVP7002_WRITE }, + { TVP7002_SYNC_CTL_1, 0x20, TVP7002_WRITE }, + { TVP7002_HPLL_AND_CLAMP_CTL, 0x2e, TVP7002_WRITE }, + { TVP7002_SYNC_ON_G_THRS, 0x5d, TVP7002_WRITE }, + { TVP7002_SYNC_SEPARATOR_THRS, 0x47, TVP7002_WRITE }, + { TVP7002_HPLL_PRE_COAST, 0x00, TVP7002_WRITE }, + { TVP7002_HPLL_POST_COAST, 0x00, TVP7002_WRITE }, + { TVP7002_SYNC_DETECT_STAT, 0xff, TVP7002_READ }, + { TVP7002_OUT_FORMATTER, 0x47, TVP7002_WRITE }, + { TVP7002_MISC_CTL_1, 0x01, TVP7002_WRITE }, + { TVP7002_MISC_CTL_2, 0x00, TVP7002_WRITE }, + { TVP7002_MISC_CTL_3, 0x01, TVP7002_WRITE }, + { TVP7002_IN_MUX_SEL_1, 0x00, TVP7002_WRITE }, + { TVP7002_IN_MUX_SEL_2, 0x67, TVP7002_WRITE }, + { TVP7002_B_AND_G_COARSE_GAIN, 0x77, TVP7002_WRITE }, + { TVP7002_R_COARSE_GAIN, 0x07, TVP7002_WRITE }, + { TVP7002_FINE_OFF_LSBS, 0x00, TVP7002_WRITE }, + { TVP7002_B_COARSE_OFF, 0x10, TVP7002_WRITE }, + { TVP7002_G_COARSE_OFF, 0x10, TVP7002_WRITE }, + { TVP7002_R_COARSE_OFF, 0x10, TVP7002_WRITE }, + { TVP7002_HSOUT_OUT_START, 0x08, TVP7002_WRITE }, + { TVP7002_MISC_CTL_4, 0x00, TVP7002_WRITE }, + { TVP7002_B_DGTL_ALC_OUT_LSBS, 0xff, TVP7002_READ }, + { TVP7002_G_DGTL_ALC_OUT_LSBS, 0xff, TVP7002_READ }, + { TVP7002_R_DGTL_ALC_OUT_LSBS, 0xff, TVP7002_READ }, + { TVP7002_AUTO_LVL_CTL_ENABLE, 0x80, TVP7002_WRITE }, + { TVP7002_DGTL_ALC_OUT_MSBS, 0xff, TVP7002_READ }, + { TVP7002_AUTO_LVL_CTL_FILTER, 0x53, TVP7002_WRITE }, + { 0x29, 0x08, TVP7002_RESERVED }, + { TVP7002_FINE_CLAMP_CTL, 0x07, TVP7002_WRITE }, + /* PWR_CTL is controlled only by the probe and reset functions */ + { TVP7002_PWR_CTL, 0x00, TVP7002_RESERVED }, + { TVP7002_ADC_SETUP, 0x50, TVP7002_WRITE }, + { TVP7002_COARSE_CLAMP_CTL, 0x00, TVP7002_WRITE }, + { TVP7002_SOG_CLAMP, 0x80, TVP7002_WRITE }, + { TVP7002_RGB_COARSE_CLAMP_CTL, 0x8c, TVP7002_WRITE }, + { TVP7002_SOG_COARSE_CLAMP_CTL, 0x04, TVP7002_WRITE }, + { TVP7002_ALC_PLACEMENT, 0x5a, TVP7002_WRITE }, + { 0x32, 0x18, TVP7002_RESERVED }, + { 0x33, 0x60, TVP7002_RESERVED }, + { TVP7002_MVIS_STRIPPER_W, 0xff, TVP7002_RESERVED }, + { TVP7002_VSYNC_ALGN, 0x10, TVP7002_WRITE }, + { TVP7002_SYNC_BYPASS, 0x00, TVP7002_WRITE }, + { TVP7002_L_FRAME_STAT_LSBS, 0xff, TVP7002_READ }, + { TVP7002_L_FRAME_STAT_MSBS, 0xff, TVP7002_READ }, + { TVP7002_CLK_L_STAT_LSBS, 0xff, TVP7002_READ }, + { TVP7002_CLK_L_STAT_MSBS, 0xff, TVP7002_READ }, + { TVP7002_HSYNC_W, 0xff, TVP7002_READ }, + { TVP7002_VSYNC_W, 0xff, TVP7002_READ }, + { TVP7002_L_LENGTH_TOL, 0x03, TVP7002_WRITE }, + { 0x3e, 0x60, TVP7002_RESERVED }, + { TVP7002_VIDEO_BWTH_CTL, 0x01, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_LSBS, 0x01, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_MSBS, 0x2c, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_LSBS, 0x06, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_MSBS, 0x2c, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_START_L_OFF, 0x05, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_START_L_OFF, 0x00, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_DURATION, 0x1e, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_DURATION, 0x00, TVP7002_WRITE }, + { TVP7002_FBIT_F_0_START_L_OFF, 0x00, TVP7002_WRITE }, + { TVP7002_FBIT_F_1_START_L_OFF, 0x00, TVP7002_WRITE }, + { TVP7002_YUV_Y_G_COEF_LSBS, 0xe3, TVP7002_WRITE }, + { TVP7002_YUV_Y_G_COEF_MSBS, 0x16, TVP7002_WRITE }, + { TVP7002_YUV_Y_B_COEF_LSBS, 0x4f, TVP7002_WRITE }, + { TVP7002_YUV_Y_B_COEF_MSBS, 0x02, TVP7002_WRITE }, + { TVP7002_YUV_Y_R_COEF_LSBS, 0xce, TVP7002_WRITE }, + { TVP7002_YUV_Y_R_COEF_MSBS, 0x06, TVP7002_WRITE }, + { TVP7002_YUV_U_G_COEF_LSBS, 0xab, TVP7002_WRITE }, + { TVP7002_YUV_U_G_COEF_MSBS, 0xf3, TVP7002_WRITE }, + { TVP7002_YUV_U_B_COEF_LSBS, 0x00, TVP7002_WRITE }, + { TVP7002_YUV_U_B_COEF_MSBS, 0x10, TVP7002_WRITE }, + { TVP7002_YUV_U_R_COEF_LSBS, 0x55, TVP7002_WRITE }, + { TVP7002_YUV_U_R_COEF_MSBS, 0xfc, TVP7002_WRITE }, + { TVP7002_YUV_V_G_COEF_LSBS, 0x78, TVP7002_WRITE }, + { TVP7002_YUV_V_G_COEF_MSBS, 0xf1, TVP7002_WRITE }, + { TVP7002_YUV_V_B_COEF_LSBS, 0x88, TVP7002_WRITE }, + { TVP7002_YUV_V_B_COEF_MSBS, 0xfe, TVP7002_WRITE }, + { TVP7002_YUV_V_R_COEF_LSBS, 0x00, TVP7002_WRITE }, + { TVP7002_YUV_V_R_COEF_MSBS, 0x10, TVP7002_WRITE }, + /* This signals end of register values */ + { TVP7002_EOR, 0xff, TVP7002_RESERVED } +}; + +/* Register parameters for 480P */ +static const struct i2c_reg_value tvp7002_parms_480P[] = { + { TVP7002_HPLL_FDBK_DIV_MSBS, 0x35, TVP7002_WRITE }, + { TVP7002_HPLL_FDBK_DIV_LSBS, 0xa0, TVP7002_WRITE }, + { TVP7002_HPLL_CRTL, 0x02, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_LSBS, 0x91, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_MSBS, 0x00, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_LSBS, 0x0B, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_MSBS, 0x00, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_START_L_OFF, 0x03, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_START_L_OFF, 0x01, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_DURATION, 0x13, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_DURATION, 0x13, TVP7002_WRITE }, + { TVP7002_ALC_PLACEMENT, 0x18, TVP7002_WRITE }, + { TVP7002_CLAMP_START, 0x06, TVP7002_WRITE }, + { TVP7002_CLAMP_W, 0x10, TVP7002_WRITE }, + { TVP7002_HPLL_PRE_COAST, 0x03, TVP7002_WRITE }, + { TVP7002_HPLL_POST_COAST, 0x03, TVP7002_WRITE }, + { TVP7002_EOR, 0xff, TVP7002_RESERVED } +}; + +/* Register parameters for 576P */ +static const struct i2c_reg_value tvp7002_parms_576P[] = { + { TVP7002_HPLL_FDBK_DIV_MSBS, 0x36, TVP7002_WRITE }, + { TVP7002_HPLL_FDBK_DIV_LSBS, 0x00, TVP7002_WRITE }, + { TVP7002_HPLL_CRTL, 0x18, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_LSBS, 0x9B, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_MSBS, 0x00, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_LSBS, 0x0F, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_MSBS, 0x00, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_START_L_OFF, 0x00, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_START_L_OFF, 0x00, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_DURATION, 0x2D, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_DURATION, 0x00, TVP7002_WRITE }, + { TVP7002_ALC_PLACEMENT, 0x18, TVP7002_WRITE }, + { TVP7002_CLAMP_START, 0x06, TVP7002_WRITE }, + { TVP7002_CLAMP_W, 0x10, TVP7002_WRITE }, + { TVP7002_HPLL_PRE_COAST, 0x03, TVP7002_WRITE }, + { TVP7002_HPLL_POST_COAST, 0x03, TVP7002_WRITE }, + { TVP7002_EOR, 0xff, TVP7002_RESERVED } +}; + +/* Register parameters for 1080I60 */ +static const struct i2c_reg_value tvp7002_parms_1080I60[] = { + { TVP7002_HPLL_FDBK_DIV_MSBS, 0x89, TVP7002_WRITE }, + { TVP7002_HPLL_FDBK_DIV_LSBS, 0x80, TVP7002_WRITE }, + { TVP7002_HPLL_CRTL, 0x98, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_LSBS, 0x06, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_MSBS, 0x01, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_LSBS, 0x8a, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_MSBS, 0x08, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_START_L_OFF, 0x02, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_START_L_OFF, 0x02, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_DURATION, 0x16, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_DURATION, 0x17, TVP7002_WRITE }, + { TVP7002_ALC_PLACEMENT, 0x5a, TVP7002_WRITE }, + { TVP7002_CLAMP_START, 0x32, TVP7002_WRITE }, + { TVP7002_CLAMP_W, 0x20, TVP7002_WRITE }, + { TVP7002_HPLL_PRE_COAST, 0x01, TVP7002_WRITE }, + { TVP7002_HPLL_POST_COAST, 0x00, TVP7002_WRITE }, + { TVP7002_EOR, 0xff, TVP7002_RESERVED } +}; + +/* Register parameters for 1080P60 */ +static const struct i2c_reg_value tvp7002_parms_1080P60[] = { + { TVP7002_HPLL_FDBK_DIV_MSBS, 0x89, TVP7002_WRITE }, + { TVP7002_HPLL_FDBK_DIV_LSBS, 0x80, TVP7002_WRITE }, + { TVP7002_HPLL_CRTL, 0xE0, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_LSBS, 0x06, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_MSBS, 0x01, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_LSBS, 0x8a, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_MSBS, 0x08, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_START_L_OFF, 0x02, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_START_L_OFF, 0x02, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_DURATION, 0x16, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_DURATION, 0x17, TVP7002_WRITE }, + { TVP7002_ALC_PLACEMENT, 0x5a, TVP7002_WRITE }, + { TVP7002_CLAMP_START, 0x32, TVP7002_WRITE }, + { TVP7002_CLAMP_W, 0x20, TVP7002_WRITE }, + { TVP7002_HPLL_PRE_COAST, 0x01, TVP7002_WRITE }, + { TVP7002_HPLL_POST_COAST, 0x00, TVP7002_WRITE }, + { TVP7002_EOR, 0xff, TVP7002_RESERVED } +}; + +/* Register parameters for 1080I50 */ +static const struct i2c_reg_value tvp7002_parms_1080I50[] = { + { TVP7002_HPLL_FDBK_DIV_MSBS, 0xa5, TVP7002_WRITE }, + { TVP7002_HPLL_FDBK_DIV_LSBS, 0x00, TVP7002_WRITE }, + { TVP7002_HPLL_CRTL, 0x98, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_LSBS, 0x06, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_MSBS, 0x01, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_LSBS, 0x8a, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_MSBS, 0x08, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_START_L_OFF, 0x02, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_START_L_OFF, 0x02, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_DURATION, 0x16, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_DURATION, 0x17, TVP7002_WRITE }, + { TVP7002_ALC_PLACEMENT, 0x5a, TVP7002_WRITE }, + { TVP7002_CLAMP_START, 0x32, TVP7002_WRITE }, + { TVP7002_CLAMP_W, 0x20, TVP7002_WRITE }, + { TVP7002_HPLL_PRE_COAST, 0x01, TVP7002_WRITE }, + { TVP7002_HPLL_POST_COAST, 0x00, TVP7002_WRITE }, + { TVP7002_EOR, 0xff, TVP7002_RESERVED } +}; + +/* Register parameters for 720P60 */ +static const struct i2c_reg_value tvp7002_parms_720P60[] = { + { TVP7002_HPLL_FDBK_DIV_MSBS, 0x67, TVP7002_WRITE }, + { TVP7002_HPLL_FDBK_DIV_LSBS, 0x20, TVP7002_WRITE }, + { TVP7002_HPLL_CRTL, 0xa0, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_LSBS, 0x47, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_MSBS, 0x01, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_LSBS, 0x4B, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_MSBS, 0x06, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_START_L_OFF, 0x05, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_START_L_OFF, 0x00, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_DURATION, 0x2D, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_DURATION, 0x00, TVP7002_WRITE }, + { TVP7002_ALC_PLACEMENT, 0x5a, TVP7002_WRITE }, + { TVP7002_CLAMP_START, 0x32, TVP7002_WRITE }, + { TVP7002_CLAMP_W, 0x20, TVP7002_WRITE }, + { TVP7002_HPLL_PRE_COAST, 0x00, TVP7002_WRITE }, + { TVP7002_HPLL_POST_COAST, 0x00, TVP7002_WRITE }, + { TVP7002_EOR, 0xff, TVP7002_RESERVED } +}; + +/* Register parameters for 720P50 */ +static const struct i2c_reg_value tvp7002_parms_720P50[] = { + { TVP7002_HPLL_FDBK_DIV_MSBS, 0x7b, TVP7002_WRITE }, + { TVP7002_HPLL_FDBK_DIV_LSBS, 0xc0, TVP7002_WRITE }, + { TVP7002_HPLL_CRTL, 0x98, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_LSBS, 0x47, TVP7002_WRITE }, + { TVP7002_AVID_START_PIXEL_MSBS, 0x01, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_LSBS, 0x4B, TVP7002_WRITE }, + { TVP7002_AVID_STOP_PIXEL_MSBS, 0x06, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_START_L_OFF, 0x05, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_START_L_OFF, 0x00, TVP7002_WRITE }, + { TVP7002_VBLK_F_0_DURATION, 0x2D, TVP7002_WRITE }, + { TVP7002_VBLK_F_1_DURATION, 0x00, TVP7002_WRITE }, + { TVP7002_ALC_PLACEMENT, 0x5a, TVP7002_WRITE }, + { TVP7002_CLAMP_START, 0x32, TVP7002_WRITE }, + { TVP7002_CLAMP_W, 0x20, TVP7002_WRITE }, + { TVP7002_HPLL_PRE_COAST, 0x01, TVP7002_WRITE }, + { TVP7002_HPLL_POST_COAST, 0x00, TVP7002_WRITE }, + { TVP7002_EOR, 0xff, TVP7002_RESERVED } +}; + +/* Timings definition for handling device operation */ +struct tvp7002_timings_definition { + struct v4l2_dv_timings timings; + const struct i2c_reg_value *p_settings; + enum v4l2_colorspace color_space; + enum v4l2_field scanmode; + u16 progressive; + u16 lines_per_frame; + u16 cpl_min; + u16 cpl_max; +}; + +/* Struct list for digital video timings */ +static const struct tvp7002_timings_definition tvp7002_timings[] = { + { + V4L2_DV_BT_CEA_1280X720P60, + tvp7002_parms_720P60, + V4L2_COLORSPACE_REC709, + V4L2_FIELD_NONE, + 1, + 0x2EE, + 135, + 153 + }, + { + V4L2_DV_BT_CEA_1920X1080I60, + tvp7002_parms_1080I60, + V4L2_COLORSPACE_REC709, + V4L2_FIELD_INTERLACED, + 0, + 0x465, + 181, + 205 + }, + { + V4L2_DV_BT_CEA_1920X1080I50, + tvp7002_parms_1080I50, + V4L2_COLORSPACE_REC709, + V4L2_FIELD_INTERLACED, + 0, + 0x465, + 217, + 245 + }, + { + V4L2_DV_BT_CEA_1280X720P50, + tvp7002_parms_720P50, + V4L2_COLORSPACE_REC709, + V4L2_FIELD_NONE, + 1, + 0x2EE, + 163, + 183 + }, + { + V4L2_DV_BT_CEA_1920X1080P60, + tvp7002_parms_1080P60, + V4L2_COLORSPACE_REC709, + V4L2_FIELD_NONE, + 1, + 0x465, + 90, + 102 + }, + { + V4L2_DV_BT_CEA_720X480P59_94, + tvp7002_parms_480P, + V4L2_COLORSPACE_SMPTE170M, + V4L2_FIELD_NONE, + 1, + 0x20D, + 0xffff, + 0xffff + }, + { + V4L2_DV_BT_CEA_720X576P50, + tvp7002_parms_576P, + V4L2_COLORSPACE_SMPTE170M, + V4L2_FIELD_NONE, + 1, + 0x271, + 0xffff, + 0xffff + } +}; + +#define NUM_TIMINGS ARRAY_SIZE(tvp7002_timings) + +/* Device definition */ +struct tvp7002 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + const struct tvp7002_config *pdata; + + int ver; + int streaming; + + const struct tvp7002_timings_definition *current_timings; + struct media_pad pad; +}; + +/* + * to_tvp7002 - Obtain device handler TVP7002 + * @sd: ptr to v4l2_subdev struct + * + * Returns device handler tvp7002. + */ +static inline struct tvp7002 *to_tvp7002(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tvp7002, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tvp7002, hdl)->sd; +} + +/* + * tvp7002_read - Read a value from a register in an TVP7002 + * @sd: ptr to v4l2_subdev struct + * @addr: TVP7002 register address + * @dst: pointer to 8-bit destination + * + * Returns value read if successful, or non-zero (-1) otherwise. + */ +static int tvp7002_read(struct v4l2_subdev *sd, u8 addr, u8 *dst) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + int retry; + int error; + + for (retry = 0; retry < I2C_RETRY_COUNT; retry++) { + error = i2c_smbus_read_byte_data(c, addr); + + if (error >= 0) { + *dst = (u8)error; + return 0; + } + + msleep_interruptible(10); + } + v4l2_err(sd, "TVP7002 read error %d\n", error); + return error; +} + +/* + * tvp7002_read_err() - Read a register value with error code + * @sd: pointer to standard V4L2 sub-device structure + * @reg: destination register + * @val: value to be read + * @err: pointer to error value + * + * Read a value in a register and save error value in pointer. + * Also update the register table if successful + */ +static inline void tvp7002_read_err(struct v4l2_subdev *sd, u8 reg, + u8 *dst, int *err) +{ + if (!*err) + *err = tvp7002_read(sd, reg, dst); +} + +/* + * tvp7002_write() - Write a value to a register in TVP7002 + * @sd: ptr to v4l2_subdev struct + * @addr: TVP7002 register address + * @value: value to be written to the register + * + * Write a value to a register in an TVP7002 decoder device. + * Returns zero if successful, or non-zero otherwise. + */ +static int tvp7002_write(struct v4l2_subdev *sd, u8 addr, u8 value) +{ + struct i2c_client *c; + int retry; + int error; + + c = v4l2_get_subdevdata(sd); + + for (retry = 0; retry < I2C_RETRY_COUNT; retry++) { + error = i2c_smbus_write_byte_data(c, addr, value); + + if (error >= 0) + return 0; + + v4l2_warn(sd, "Write: retry ... %d\n", retry); + msleep_interruptible(10); + } + v4l2_err(sd, "TVP7002 write error %d\n", error); + return error; +} + +/* + * tvp7002_write_err() - Write a register value with error code + * @sd: pointer to standard V4L2 sub-device structure + * @reg: destination register + * @val: value to be written + * @err: pointer to error value + * + * Write a value in a register and save error value in pointer. + * Also update the register table if successful + */ +static inline void tvp7002_write_err(struct v4l2_subdev *sd, u8 reg, + u8 val, int *err) +{ + if (!*err) + *err = tvp7002_write(sd, reg, val); +} + +/* + * tvp7002_write_inittab() - Write initialization values + * @sd: ptr to v4l2_subdev struct + * @regs: ptr to i2c_reg_value struct + * + * Write initialization values. + * Returns zero or -EINVAL if read operation fails. + */ +static int tvp7002_write_inittab(struct v4l2_subdev *sd, + const struct i2c_reg_value *regs) +{ + int error = 0; + + /* Initialize the first (defined) registers */ + while (TVP7002_EOR != regs->reg) { + if (TVP7002_WRITE == regs->type) + tvp7002_write_err(sd, regs->reg, regs->value, &error); + regs++; + } + + return error; +} + +static int tvp7002_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *dv_timings) +{ + struct tvp7002 *device = to_tvp7002(sd); + const struct v4l2_bt_timings *bt = &dv_timings->bt; + int i; + + if (dv_timings->type != V4L2_DV_BT_656_1120) + return -EINVAL; + for (i = 0; i < NUM_TIMINGS; i++) { + const struct v4l2_bt_timings *t = &tvp7002_timings[i].timings.bt; + + if (!memcmp(bt, t, &bt->standards - &bt->width)) { + device->current_timings = &tvp7002_timings[i]; + return tvp7002_write_inittab(sd, tvp7002_timings[i].p_settings); + } + } + return -EINVAL; +} + +static int tvp7002_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *dv_timings) +{ + struct tvp7002 *device = to_tvp7002(sd); + + *dv_timings = device->current_timings->timings; + return 0; +} + +/* + * tvp7002_s_ctrl() - Set a control + * @ctrl: ptr to v4l2_ctrl struct + * + * Set a control in TVP7002 decoder device. + * Returns zero when successful or -EINVAL if register access fails. + */ +static int tvp7002_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + int error = 0; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + tvp7002_write_err(sd, TVP7002_R_FINE_GAIN, ctrl->val, &error); + tvp7002_write_err(sd, TVP7002_G_FINE_GAIN, ctrl->val, &error); + tvp7002_write_err(sd, TVP7002_B_FINE_GAIN, ctrl->val, &error); + return error; + } + return -EINVAL; +} + +/* + * tvp7002_query_dv() - query DV timings + * @sd: pointer to standard V4L2 sub-device structure + * @index: index into the tvp7002_timings array + * + * Returns the current DV timings detected by TVP7002. If no active input is + * detected, returns -EINVAL + */ +static int tvp7002_query_dv(struct v4l2_subdev *sd, int *index) +{ + const struct tvp7002_timings_definition *timings = tvp7002_timings; + u8 progressive; + u32 lpfr; + u32 cpln; + int error = 0; + u8 lpf_lsb; + u8 lpf_msb; + u8 cpl_lsb; + u8 cpl_msb; + + /* Return invalid index if no active input is detected */ + *index = NUM_TIMINGS; + + /* Read standards from device registers */ + tvp7002_read_err(sd, TVP7002_L_FRAME_STAT_LSBS, &lpf_lsb, &error); + tvp7002_read_err(sd, TVP7002_L_FRAME_STAT_MSBS, &lpf_msb, &error); + + if (error < 0) + return error; + + tvp7002_read_err(sd, TVP7002_CLK_L_STAT_LSBS, &cpl_lsb, &error); + tvp7002_read_err(sd, TVP7002_CLK_L_STAT_MSBS, &cpl_msb, &error); + + if (error < 0) + return error; + + /* Get lines per frame, clocks per line and interlaced/progresive */ + lpfr = lpf_lsb | ((TVP7002_CL_MASK & lpf_msb) << TVP7002_CL_SHIFT); + cpln = cpl_lsb | ((TVP7002_CL_MASK & cpl_msb) << TVP7002_CL_SHIFT); + progressive = (lpf_msb & TVP7002_INPR_MASK) >> TVP7002_IP_SHIFT; + + /* Do checking of video modes */ + for (*index = 0; *index < NUM_TIMINGS; (*index)++, timings++) + if (lpfr == timings->lines_per_frame && + progressive == timings->progressive) { + if (timings->cpl_min == 0xffff) + break; + if (cpln >= timings->cpl_min && cpln <= timings->cpl_max) + break; + } + + if (*index == NUM_TIMINGS) { + v4l2_dbg(1, debug, sd, "detection failed: lpf = %x, cpl = %x\n", + lpfr, cpln); + return -ENOLINK; + } + + /* Update lines per frame and clocks per line info */ + v4l2_dbg(1, debug, sd, "detected timings: %d\n", *index); + return 0; +} + +static int tvp7002_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + int index; + int err = tvp7002_query_dv(sd, &index); + + if (err) + return err; + *timings = tvp7002_timings[index].timings; + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +/* + * tvp7002_g_register() - Get the value of a register + * @sd: ptr to v4l2_subdev struct + * @reg: ptr to v4l2_dbg_register struct + * + * Get the value of a TVP7002 decoder device register. + * Returns zero when successful, -EINVAL if register read fails or + * access to I2C client fails. + */ +static int tvp7002_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + u8 val; + int ret; + + ret = tvp7002_read(sd, reg->reg & 0xff, &val); + if (ret < 0) + return ret; + reg->val = val; + reg->size = 1; + return 0; +} + +/* + * tvp7002_s_register() - set a control + * @sd: ptr to v4l2_subdev struct + * @reg: ptr to v4l2_dbg_register struct + * + * Get the value of a TVP7002 decoder device register. + * Returns zero when successful, -EINVAL if register read fails. + */ +static int tvp7002_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + return tvp7002_write(sd, reg->reg & 0xff, reg->val & 0xff); +} +#endif + +/* + * tvp7002_s_stream() - V4L2 decoder i/f handler for s_stream + * @sd: pointer to standard V4L2 sub-device structure + * @enable: streaming enable or disable + * + * Sets streaming to enable or disable, if possible. + */ +static int tvp7002_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct tvp7002 *device = to_tvp7002(sd); + int error; + + if (device->streaming == enable) + return 0; + + /* low impedance: on, high impedance: off */ + error = tvp7002_write(sd, TVP7002_MISC_CTL_2, enable ? 0x00 : 0x03); + if (error) { + v4l2_dbg(1, debug, sd, "Fail to set streaming\n"); + return error; + } + + device->streaming = enable; + return 0; +} + +/* + * tvp7002_log_status() - Print information about register settings + * @sd: ptr to v4l2_subdev struct + * + * Log register values of a TVP7002 decoder device. + * Returns zero or -EINVAL if read operation fails. + */ +static int tvp7002_log_status(struct v4l2_subdev *sd) +{ + struct tvp7002 *device = to_tvp7002(sd); + const struct v4l2_bt_timings *bt; + int detected; + + /* Find my current timings */ + tvp7002_query_dv(sd, &detected); + + bt = &device->current_timings->timings.bt; + v4l2_info(sd, "Selected DV Timings: %ux%u\n", bt->width, bt->height); + if (detected == NUM_TIMINGS) { + v4l2_info(sd, "Detected DV Timings: None\n"); + } else { + bt = &tvp7002_timings[detected].timings.bt; + v4l2_info(sd, "Detected DV Timings: %ux%u\n", + bt->width, bt->height); + } + v4l2_info(sd, "Streaming enabled: %s\n", + device->streaming ? "yes" : "no"); + + /* Print the current value of the gain control */ + v4l2_ctrl_handler_log_status(&device->hdl, sd->name); + + return 0; +} + +static int tvp7002_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->pad != 0) + return -EINVAL; + + /* Check requested format index is within range */ + if (timings->index >= NUM_TIMINGS) + return -EINVAL; + + timings->timings = tvp7002_timings[timings->index].timings; + return 0; +} + +static const struct v4l2_ctrl_ops tvp7002_ctrl_ops = { + .s_ctrl = tvp7002_s_ctrl, +}; + +/* + * tvp7002_enum_mbus_code() - Enum supported digital video format on pad + * @sd: pointer to standard V4L2 sub-device structure + * @cfg: pad configuration + * @code: pointer to subdev enum mbus code struct + * + * Enumerate supported digital video formats for pad. + */ +static int +tvp7002_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Check requested format index is within range */ + if (code->index != 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_YUYV10_1X20; + + return 0; +} + +/* + * tvp7002_get_pad_format() - get video format on pad + * @sd: pointer to standard V4L2 sub-device structure + * @cfg: pad configuration + * @fmt: pointer to subdev format struct + * + * get video format for pad. + */ +static int +tvp7002_get_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct tvp7002 *tvp7002 = to_tvp7002(sd); + + fmt->format.code = MEDIA_BUS_FMT_YUYV10_1X20; + fmt->format.width = tvp7002->current_timings->timings.bt.width; + fmt->format.height = tvp7002->current_timings->timings.bt.height; + fmt->format.field = tvp7002->current_timings->scanmode; + fmt->format.colorspace = tvp7002->current_timings->color_space; + + return 0; +} + +/* + * tvp7002_set_pad_format() - set video format on pad + * @sd: pointer to standard V4L2 sub-device structure + * @cfg: pad configuration + * @fmt: pointer to subdev format struct + * + * set video format for pad. + */ +static int +tvp7002_set_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + return tvp7002_get_pad_format(sd, cfg, fmt); +} + +/* V4L2 core operation handlers */ +static const struct v4l2_subdev_core_ops tvp7002_core_ops = { + .log_status = tvp7002_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = tvp7002_g_register, + .s_register = tvp7002_s_register, +#endif +}; + +/* Specific video subsystem operation handlers */ +static const struct v4l2_subdev_video_ops tvp7002_video_ops = { + .g_dv_timings = tvp7002_g_dv_timings, + .s_dv_timings = tvp7002_s_dv_timings, + .query_dv_timings = tvp7002_query_dv_timings, + .s_stream = tvp7002_s_stream, +}; + +/* media pad related operation handlers */ +static const struct v4l2_subdev_pad_ops tvp7002_pad_ops = { + .enum_mbus_code = tvp7002_enum_mbus_code, + .get_fmt = tvp7002_get_pad_format, + .set_fmt = tvp7002_set_pad_format, + .enum_dv_timings = tvp7002_enum_dv_timings, +}; + +/* V4L2 top level operation handlers */ +static const struct v4l2_subdev_ops tvp7002_ops = { + .core = &tvp7002_core_ops, + .video = &tvp7002_video_ops, + .pad = &tvp7002_pad_ops, +}; + +static struct tvp7002_config * +tvp7002_get_pdata(struct i2c_client *client) +{ + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + struct tvp7002_config *pdata = NULL; + struct device_node *endpoint; + unsigned int flags; + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return client->dev.platform_data; + + endpoint = of_graph_get_next_endpoint(client->dev.of_node, NULL); + if (!endpoint) + return NULL; + + if (v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), &bus_cfg)) + goto done; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto done; + + flags = bus_cfg.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + pdata->hs_polarity = 1; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + pdata->vs_polarity = 1; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + pdata->clk_polarity = 1; + + if (flags & V4L2_MBUS_FIELD_EVEN_HIGH) + pdata->fid_polarity = 1; + + if (flags & V4L2_MBUS_VIDEO_SOG_ACTIVE_HIGH) + pdata->sog_polarity = 1; + +done: + of_node_put(endpoint); + return pdata; +} + +/* + * tvp7002_probe - Probe a TVP7002 device + * @c: ptr to i2c_client struct + * @id: ptr to i2c_device_id struct + * + * Initialize the TVP7002 device + * Returns zero when successful, -EINVAL if register read fails or + * -EIO if i2c access is not available. + */ +static int tvp7002_probe(struct i2c_client *c) +{ + struct tvp7002_config *pdata = tvp7002_get_pdata(c); + struct v4l2_subdev *sd; + struct tvp7002 *device; + struct v4l2_dv_timings timings; + int polarity_a; + int polarity_b; + u8 revision; + int error; + + if (pdata == NULL) { + dev_err(&c->dev, "No platform data\n"); + return -EINVAL; + } + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(c->adapter, + I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -EIO; + + device = devm_kzalloc(&c->dev, sizeof(struct tvp7002), GFP_KERNEL); + + if (!device) + return -ENOMEM; + + sd = &device->sd; + device->pdata = pdata; + device->current_timings = tvp7002_timings; + + /* Tell v4l2 the device is ready */ + v4l2_i2c_subdev_init(sd, c, &tvp7002_ops); + v4l_info(c, "tvp7002 found @ 0x%02x (%s)\n", + c->addr, c->adapter->name); + + error = tvp7002_read(sd, TVP7002_CHIP_REV, &revision); + if (error < 0) + return error; + + /* Get revision number */ + v4l2_info(sd, "Rev. %02x detected.\n", revision); + if (revision != 0x02) + v4l2_info(sd, "Unknown revision detected.\n"); + + /* Initializes TVP7002 to its default values */ + error = tvp7002_write_inittab(sd, tvp7002_init_default); + + if (error < 0) + return error; + + /* Set polarity information after registers have been set */ + polarity_a = 0x20 | device->pdata->hs_polarity << 5 + | device->pdata->vs_polarity << 2; + error = tvp7002_write(sd, TVP7002_SYNC_CTL_1, polarity_a); + if (error < 0) + return error; + + polarity_b = 0x01 | device->pdata->fid_polarity << 2 + | device->pdata->sog_polarity << 1 + | device->pdata->clk_polarity; + error = tvp7002_write(sd, TVP7002_MISC_CTL_3, polarity_b); + if (error < 0) + return error; + + /* Set registers according to default video mode */ + timings = device->current_timings->timings; + error = tvp7002_s_dv_timings(sd, &timings); + +#if defined(CONFIG_MEDIA_CONTROLLER) + device->pad.flags = MEDIA_PAD_FL_SOURCE; + device->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + device->sd.entity.function = MEDIA_ENT_F_ATV_DECODER; + + error = media_entity_pads_init(&device->sd.entity, 1, &device->pad); + if (error < 0) + return error; +#endif + + v4l2_ctrl_handler_init(&device->hdl, 1); + v4l2_ctrl_new_std(&device->hdl, &tvp7002_ctrl_ops, + V4L2_CID_GAIN, 0, 255, 1, 0); + sd->ctrl_handler = &device->hdl; + if (device->hdl.error) { + error = device->hdl.error; + goto error; + } + v4l2_ctrl_handler_setup(&device->hdl); + + error = v4l2_async_register_subdev(&device->sd); + if (error) + goto error; + + return 0; + +error: + v4l2_ctrl_handler_free(&device->hdl); +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&device->sd.entity); +#endif + return error; +} + +/* + * tvp7002_remove - Remove TVP7002 device support + * @c: ptr to i2c_client struct + * + * Reset the TVP7002 device + * Returns zero. + */ +static int tvp7002_remove(struct i2c_client *c) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct tvp7002 *device = to_tvp7002(sd); + + v4l2_dbg(1, debug, sd, "Removing tvp7002 adapter" + "on address 0x%x\n", c->addr); + v4l2_async_unregister_subdev(&device->sd); +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&device->sd.entity); +#endif + v4l2_ctrl_handler_free(&device->hdl); + return 0; +} + +/* I2C Device ID table */ +static const struct i2c_device_id tvp7002_id[] = { + { "tvp7002", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tvp7002_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tvp7002_of_match[] = { + { .compatible = "ti,tvp7002", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, tvp7002_of_match); +#endif + +/* I2C driver data */ +static struct i2c_driver tvp7002_driver = { + .driver = { + .of_match_table = of_match_ptr(tvp7002_of_match), + .name = TVP7002_MODULE_NAME, + }, + .probe_new = tvp7002_probe, + .remove = tvp7002_remove, + .id_table = tvp7002_id, +}; + +module_i2c_driver(tvp7002_driver); diff --git a/drivers/media/i2c/tvp7002_reg.h b/drivers/media/i2c/tvp7002_reg.h new file mode 100644 index 000000000..ef3cc9965 --- /dev/null +++ b/drivers/media/i2c/tvp7002_reg.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Texas Instruments Triple 8-/10-BIT 165-/110-MSPS Video and Graphics + * Digitizer with Horizontal PLL registers + * + * Copyright (C) 2009 Texas Instruments Inc + * Author: Santiago Nunez-Corrales <santiago.nunez@ridgerun.com> + * + * This code is partially based upon the TVP5150 driver + * written by Mauro Carvalho Chehab <mchehab@kernel.org>, + * the TVP514x driver written by Vaibhav Hiremath <hvaibhav@ti.com> + * and the TVP7002 driver in the TI LSP 2.10.00.14 + */ + +/* Naming conventions + * ------------------ + * + * FDBK: Feedback + * DIV: Divider + * CTL: Control + * SEL: Select + * IN: Input + * OUT: Output + * R: Red + * G: Green + * B: Blue + * OFF: Offset + * THRS: Threshold + * DGTL: Digital + * LVL: Level + * PWR: Power + * MVIS: Macrovision + * W: Width + * H: Height + * ALGN: Alignment + * CLK: Clocks + * TOL: Tolerance + * BWTH: Bandwidth + * COEF: Coefficient + * STAT: Status + * AUTO: Automatic + * FLD: Field + * L: Line + */ + +#define TVP7002_CHIP_REV 0x00 +#define TVP7002_HPLL_FDBK_DIV_MSBS 0x01 +#define TVP7002_HPLL_FDBK_DIV_LSBS 0x02 +#define TVP7002_HPLL_CRTL 0x03 +#define TVP7002_HPLL_PHASE_SEL 0x04 +#define TVP7002_CLAMP_START 0x05 +#define TVP7002_CLAMP_W 0x06 +#define TVP7002_HSYNC_OUT_W 0x07 +#define TVP7002_B_FINE_GAIN 0x08 +#define TVP7002_G_FINE_GAIN 0x09 +#define TVP7002_R_FINE_GAIN 0x0a +#define TVP7002_B_FINE_OFF_MSBS 0x0b +#define TVP7002_G_FINE_OFF_MSBS 0x0c +#define TVP7002_R_FINE_OFF_MSBS 0x0d +#define TVP7002_SYNC_CTL_1 0x0e +#define TVP7002_HPLL_AND_CLAMP_CTL 0x0f +#define TVP7002_SYNC_ON_G_THRS 0x10 +#define TVP7002_SYNC_SEPARATOR_THRS 0x11 +#define TVP7002_HPLL_PRE_COAST 0x12 +#define TVP7002_HPLL_POST_COAST 0x13 +#define TVP7002_SYNC_DETECT_STAT 0x14 +#define TVP7002_OUT_FORMATTER 0x15 +#define TVP7002_MISC_CTL_1 0x16 +#define TVP7002_MISC_CTL_2 0x17 +#define TVP7002_MISC_CTL_3 0x18 +#define TVP7002_IN_MUX_SEL_1 0x19 +#define TVP7002_IN_MUX_SEL_2 0x1a +#define TVP7002_B_AND_G_COARSE_GAIN 0x1b +#define TVP7002_R_COARSE_GAIN 0x1c +#define TVP7002_FINE_OFF_LSBS 0x1d +#define TVP7002_B_COARSE_OFF 0x1e +#define TVP7002_G_COARSE_OFF 0x1f +#define TVP7002_R_COARSE_OFF 0x20 +#define TVP7002_HSOUT_OUT_START 0x21 +#define TVP7002_MISC_CTL_4 0x22 +#define TVP7002_B_DGTL_ALC_OUT_LSBS 0x23 +#define TVP7002_G_DGTL_ALC_OUT_LSBS 0x24 +#define TVP7002_R_DGTL_ALC_OUT_LSBS 0x25 +#define TVP7002_AUTO_LVL_CTL_ENABLE 0x26 +#define TVP7002_DGTL_ALC_OUT_MSBS 0x27 +#define TVP7002_AUTO_LVL_CTL_FILTER 0x28 +/* Reserved 0x29*/ +#define TVP7002_FINE_CLAMP_CTL 0x2a +#define TVP7002_PWR_CTL 0x2b +#define TVP7002_ADC_SETUP 0x2c +#define TVP7002_COARSE_CLAMP_CTL 0x2d +#define TVP7002_SOG_CLAMP 0x2e +#define TVP7002_RGB_COARSE_CLAMP_CTL 0x2f +#define TVP7002_SOG_COARSE_CLAMP_CTL 0x30 +#define TVP7002_ALC_PLACEMENT 0x31 +/* Reserved 0x32 */ +/* Reserved 0x33 */ +#define TVP7002_MVIS_STRIPPER_W 0x34 +#define TVP7002_VSYNC_ALGN 0x35 +#define TVP7002_SYNC_BYPASS 0x36 +#define TVP7002_L_FRAME_STAT_LSBS 0x37 +#define TVP7002_L_FRAME_STAT_MSBS 0x38 +#define TVP7002_CLK_L_STAT_LSBS 0x39 +#define TVP7002_CLK_L_STAT_MSBS 0x3a +#define TVP7002_HSYNC_W 0x3b +#define TVP7002_VSYNC_W 0x3c +#define TVP7002_L_LENGTH_TOL 0x3d +/* Reserved 0x3e */ +#define TVP7002_VIDEO_BWTH_CTL 0x3f +#define TVP7002_AVID_START_PIXEL_LSBS 0x40 +#define TVP7002_AVID_START_PIXEL_MSBS 0x41 +#define TVP7002_AVID_STOP_PIXEL_LSBS 0x42 +#define TVP7002_AVID_STOP_PIXEL_MSBS 0x43 +#define TVP7002_VBLK_F_0_START_L_OFF 0x44 +#define TVP7002_VBLK_F_1_START_L_OFF 0x45 +#define TVP7002_VBLK_F_0_DURATION 0x46 +#define TVP7002_VBLK_F_1_DURATION 0x47 +#define TVP7002_FBIT_F_0_START_L_OFF 0x48 +#define TVP7002_FBIT_F_1_START_L_OFF 0x49 +#define TVP7002_YUV_Y_G_COEF_LSBS 0x4a +#define TVP7002_YUV_Y_G_COEF_MSBS 0x4b +#define TVP7002_YUV_Y_B_COEF_LSBS 0x4c +#define TVP7002_YUV_Y_B_COEF_MSBS 0x4d +#define TVP7002_YUV_Y_R_COEF_LSBS 0x4e +#define TVP7002_YUV_Y_R_COEF_MSBS 0x4f +#define TVP7002_YUV_U_G_COEF_LSBS 0x50 +#define TVP7002_YUV_U_G_COEF_MSBS 0x51 +#define TVP7002_YUV_U_B_COEF_LSBS 0x52 +#define TVP7002_YUV_U_B_COEF_MSBS 0x53 +#define TVP7002_YUV_U_R_COEF_LSBS 0x54 +#define TVP7002_YUV_U_R_COEF_MSBS 0x55 +#define TVP7002_YUV_V_G_COEF_LSBS 0x56 +#define TVP7002_YUV_V_G_COEF_MSBS 0x57 +#define TVP7002_YUV_V_B_COEF_LSBS 0x58 +#define TVP7002_YUV_V_B_COEF_MSBS 0x59 +#define TVP7002_YUV_V_R_COEF_LSBS 0x5a +#define TVP7002_YUV_V_R_COEF_MSBS 0x5b + diff --git a/drivers/media/i2c/tw2804.c b/drivers/media/i2c/tw2804.c new file mode 100644 index 000000000..cd05f1ff5 --- /dev/null +++ b/drivers/media/i2c/tw2804.c @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/ioctl.h> +#include <linux/slab.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +#define TW2804_REG_AUTOGAIN 0x02 +#define TW2804_REG_HUE 0x0f +#define TW2804_REG_SATURATION 0x10 +#define TW2804_REG_CONTRAST 0x11 +#define TW2804_REG_BRIGHTNESS 0x12 +#define TW2804_REG_COLOR_KILLER 0x14 +#define TW2804_REG_GAIN 0x3c +#define TW2804_REG_CHROMA_GAIN 0x3d +#define TW2804_REG_BLUE_BALANCE 0x3e +#define TW2804_REG_RED_BALANCE 0x3f + +struct tw2804 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + u8 channel:2; + u8 input:1; + int norm; +}; + +static const u8 global_registers[] = { + 0x39, 0x00, + 0x3a, 0xff, + 0x3b, 0x84, + 0x3c, 0x80, + 0x3d, 0x80, + 0x3e, 0x82, + 0x3f, 0x82, + 0x78, 0x00, + 0xff, 0xff, /* Terminator (reg 0xff does not exist) */ +}; + +static const u8 channel_registers[] = { + 0x01, 0xc4, + 0x02, 0xa5, + 0x03, 0x20, + 0x04, 0xd0, + 0x05, 0x20, + 0x06, 0xd0, + 0x07, 0x88, + 0x08, 0x20, + 0x09, 0x07, + 0x0a, 0xf0, + 0x0b, 0x07, + 0x0c, 0xf0, + 0x0d, 0x40, + 0x0e, 0xd2, + 0x0f, 0x80, + 0x10, 0x80, + 0x11, 0x80, + 0x12, 0x80, + 0x13, 0x1f, + 0x14, 0x00, + 0x15, 0x00, + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0xff, + 0x19, 0xff, + 0x1a, 0xff, + 0x1b, 0xff, + 0x1c, 0xff, + 0x1d, 0xff, + 0x1e, 0xff, + 0x1f, 0xff, + 0x20, 0x07, + 0x21, 0x07, + 0x22, 0x00, + 0x23, 0x91, + 0x24, 0x51, + 0x25, 0x03, + 0x26, 0x00, + 0x27, 0x00, + 0x28, 0x00, + 0x29, 0x00, + 0x2a, 0x00, + 0x2b, 0x00, + 0x2c, 0x00, + 0x2d, 0x00, + 0x2e, 0x00, + 0x2f, 0x00, + 0x30, 0x00, + 0x31, 0x00, + 0x32, 0x00, + 0x33, 0x00, + 0x34, 0x00, + 0x35, 0x00, + 0x36, 0x00, + 0x37, 0x00, + 0xff, 0xff, /* Terminator (reg 0xff does not exist) */ +}; + +static int write_reg(struct i2c_client *client, u8 reg, u8 value, u8 channel) +{ + return i2c_smbus_write_byte_data(client, reg | (channel << 6), value); +} + +static int write_regs(struct i2c_client *client, const u8 *regs, u8 channel) +{ + int ret; + int i; + + for (i = 0; regs[i] != 0xff; i += 2) { + ret = i2c_smbus_write_byte_data(client, + regs[i] | (channel << 6), regs[i + 1]); + if (ret < 0) + return ret; + } + return 0; +} + +static int read_reg(struct i2c_client *client, u8 reg, u8 channel) +{ + return i2c_smbus_read_byte_data(client, (reg) | (channel << 6)); +} + +static inline struct tw2804 *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tw2804, sd); +} + +static inline struct tw2804 *to_state_from_ctrl(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct tw2804, hdl); +} + +static int tw2804_log_status(struct v4l2_subdev *sd) +{ + struct tw2804 *state = to_state(sd); + + v4l2_info(sd, "Standard: %s\n", + state->norm & V4L2_STD_525_60 ? "60 Hz" : "50 Hz"); + v4l2_info(sd, "Channel: %d\n", state->channel); + v4l2_info(sd, "Input: %d\n", state->input); + return v4l2_ctrl_subdev_log_status(sd); +} + +/* + * These volatile controls are needed because all four channels share + * these controls. So a change made to them through one channel would + * require another channel to be updated. + * + * Normally this would have been done in a different way, but since the one + * board that uses this driver sees this single chip as if it was on four + * different i2c adapters (each adapter belonging to a separate instance of + * the same USB driver) there is no reliable method that I have found to let + * the instances know about each other. + * + * So implementing these global registers as volatile is the best we can do. + */ +static int tw2804_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct tw2804 *state = to_state_from_ctrl(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(&state->sd); + + switch (ctrl->id) { + case V4L2_CID_GAIN: + ctrl->val = read_reg(client, TW2804_REG_GAIN, 0); + return 0; + + case V4L2_CID_CHROMA_GAIN: + ctrl->val = read_reg(client, TW2804_REG_CHROMA_GAIN, 0); + return 0; + + case V4L2_CID_BLUE_BALANCE: + ctrl->val = read_reg(client, TW2804_REG_BLUE_BALANCE, 0); + return 0; + + case V4L2_CID_RED_BALANCE: + ctrl->val = read_reg(client, TW2804_REG_RED_BALANCE, 0); + return 0; + } + return 0; +} + +static int tw2804_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct tw2804 *state = to_state_from_ctrl(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(&state->sd); + int addr; + int reg; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + addr = TW2804_REG_AUTOGAIN; + reg = read_reg(client, addr, state->channel); + if (reg < 0) + return reg; + if (ctrl->val == 0) + reg &= ~(1 << 7); + else + reg |= 1 << 7; + return write_reg(client, addr, reg, state->channel); + + case V4L2_CID_COLOR_KILLER: + addr = TW2804_REG_COLOR_KILLER; + reg = read_reg(client, addr, state->channel); + if (reg < 0) + return reg; + reg = (reg & ~(0x03)) | (ctrl->val == 0 ? 0x02 : 0x03); + return write_reg(client, addr, reg, state->channel); + + case V4L2_CID_GAIN: + return write_reg(client, TW2804_REG_GAIN, ctrl->val, 0); + + case V4L2_CID_CHROMA_GAIN: + return write_reg(client, TW2804_REG_CHROMA_GAIN, ctrl->val, 0); + + case V4L2_CID_BLUE_BALANCE: + return write_reg(client, TW2804_REG_BLUE_BALANCE, ctrl->val, 0); + + case V4L2_CID_RED_BALANCE: + return write_reg(client, TW2804_REG_RED_BALANCE, ctrl->val, 0); + + case V4L2_CID_BRIGHTNESS: + return write_reg(client, TW2804_REG_BRIGHTNESS, + ctrl->val, state->channel); + + case V4L2_CID_CONTRAST: + return write_reg(client, TW2804_REG_CONTRAST, + ctrl->val, state->channel); + + case V4L2_CID_SATURATION: + return write_reg(client, TW2804_REG_SATURATION, + ctrl->val, state->channel); + + case V4L2_CID_HUE: + return write_reg(client, TW2804_REG_HUE, + ctrl->val, state->channel); + + default: + break; + } + return -EINVAL; +} + +static int tw2804_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct tw2804 *dec = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + bool is_60hz = norm & V4L2_STD_525_60; + u8 regs[] = { + 0x01, is_60hz ? 0xc4 : 0x84, + 0x09, is_60hz ? 0x07 : 0x04, + 0x0a, is_60hz ? 0xf0 : 0x20, + 0x0b, is_60hz ? 0x07 : 0x04, + 0x0c, is_60hz ? 0xf0 : 0x20, + 0x0d, is_60hz ? 0x40 : 0x4a, + 0x16, is_60hz ? 0x00 : 0x40, + 0x17, is_60hz ? 0x00 : 0x40, + 0x20, is_60hz ? 0x07 : 0x0f, + 0x21, is_60hz ? 0x07 : 0x0f, + 0xff, 0xff, + }; + + write_regs(client, regs, dec->channel); + dec->norm = norm; + return 0; +} + +static int tw2804_s_video_routing(struct v4l2_subdev *sd, u32 input, u32 output, + u32 config) +{ + struct tw2804 *dec = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int reg; + + if (config && config - 1 != dec->channel) { + if (config > 4) { + dev_err(&client->dev, + "channel %d is not between 1 and 4!\n", config); + return -EINVAL; + } + dec->channel = config - 1; + dev_dbg(&client->dev, "initializing TW2804 channel %d\n", + dec->channel); + if (dec->channel == 0 && + write_regs(client, global_registers, 0) < 0) { + dev_err(&client->dev, + "error initializing TW2804 global registers\n"); + return -EIO; + } + if (write_regs(client, channel_registers, dec->channel) < 0) { + dev_err(&client->dev, + "error initializing TW2804 channel %d\n", + dec->channel); + return -EIO; + } + } + + if (input > 1) + return -EINVAL; + + if (input == dec->input) + return 0; + + reg = read_reg(client, 0x22, dec->channel); + + if (reg >= 0) { + if (input == 0) + reg &= ~(1 << 2); + else + reg |= 1 << 2; + reg = write_reg(client, 0x22, reg, dec->channel); + } + + if (reg >= 0) + dec->input = input; + else + return reg; + return 0; +} + +static const struct v4l2_ctrl_ops tw2804_ctrl_ops = { + .g_volatile_ctrl = tw2804_g_volatile_ctrl, + .s_ctrl = tw2804_s_ctrl, +}; + +static const struct v4l2_subdev_video_ops tw2804_video_ops = { + .s_std = tw2804_s_std, + .s_routing = tw2804_s_video_routing, +}; + +static const struct v4l2_subdev_core_ops tw2804_core_ops = { + .log_status = tw2804_log_status, +}; + +static const struct v4l2_subdev_ops tw2804_ops = { + .core = &tw2804_core_ops, + .video = &tw2804_video_ops, +}; + +static int tw2804_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct tw2804 *state; + struct v4l2_subdev *sd; + struct v4l2_ctrl *ctrl; + int err; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &tw2804_ops); + state->channel = -1; + state->norm = V4L2_STD_NTSC; + + v4l2_ctrl_handler_init(&state->hdl, 10); + v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops, + V4L2_CID_HUE, 0, 255, 1, 128); + v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops, + V4L2_CID_COLOR_KILLER, 0, 1, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 0); + ctrl = v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops, + V4L2_CID_GAIN, 0, 255, 1, 128); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + ctrl = v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops, + V4L2_CID_CHROMA_GAIN, 0, 255, 1, 128); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + ctrl = v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops, + V4L2_CID_BLUE_BALANCE, 0, 255, 1, 122); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + ctrl = v4l2_ctrl_new_std(&state->hdl, &tw2804_ctrl_ops, + V4L2_CID_RED_BALANCE, 0, 255, 1, 122); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + sd->ctrl_handler = &state->hdl; + err = state->hdl.error; + if (err) { + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + return 0; +} + +static int tw2804_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tw2804 *state = to_state(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); + return 0; +} + +static const struct i2c_device_id tw2804_id[] = { + { "tw2804", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tw2804_id); + +static struct i2c_driver tw2804_driver = { + .driver = { + .name = "tw2804", + }, + .probe = tw2804_probe, + .remove = tw2804_remove, + .id_table = tw2804_id, +}; + +module_i2c_driver(tw2804_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TW2804/TW2802 V4L2 i2c driver"); +MODULE_AUTHOR("Micronas USA Inc"); diff --git a/drivers/media/i2c/tw9903.c b/drivers/media/i2c/tw9903.c new file mode 100644 index 000000000..f8e3ab490 --- /dev/null +++ b/drivers/media/i2c/tw9903.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/ioctl.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <linux/slab.h> + +MODULE_DESCRIPTION("TW9903 I2C subdev driver"); +MODULE_LICENSE("GPL v2"); + +/* + * This driver is based on the wis-tw9903.c source that was in + * drivers/staging/media/go7007. That source had commented out code for + * saturation and scaling (neither seemed to work). If anyone ever gets + * hardware to test this driver, then that code might be useful to look at. + * You need to get the kernel sources of, say, kernel 3.8 where that + * wis-tw9903 driver is still present. + */ + +struct tw9903 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + v4l2_std_id norm; +}; + +static inline struct tw9903 *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tw9903, sd); +} + +static const u8 initial_registers[] = { + 0x02, 0x44, /* input 1, composite */ + 0x03, 0x92, /* correct digital format */ + 0x04, 0x00, + 0x05, 0x80, /* or 0x00 for PAL */ + 0x06, 0x40, /* second internal current reference */ + 0x07, 0x02, /* window */ + 0x08, 0x14, /* window */ + 0x09, 0xf0, /* window */ + 0x0a, 0x81, /* window */ + 0x0b, 0xd0, /* window */ + 0x0c, 0x8c, + 0x0d, 0x00, /* scaling */ + 0x0e, 0x11, /* scaling */ + 0x0f, 0x00, /* scaling */ + 0x10, 0x00, /* brightness */ + 0x11, 0x60, /* contrast */ + 0x12, 0x01, /* sharpness */ + 0x13, 0x7f, /* U gain */ + 0x14, 0x5a, /* V gain */ + 0x15, 0x00, /* hue */ + 0x16, 0xc3, /* sharpness */ + 0x18, 0x00, + 0x19, 0x58, /* vbi */ + 0x1a, 0x80, + 0x1c, 0x0f, /* video norm */ + 0x1d, 0x7f, /* video norm */ + 0x20, 0xa0, /* clamping gain (working 0x50) */ + 0x21, 0x22, + 0x22, 0xf0, + 0x23, 0xfe, + 0x24, 0x3c, + 0x25, 0x38, + 0x26, 0x44, + 0x27, 0x20, + 0x28, 0x00, + 0x29, 0x15, + 0x2a, 0xa0, + 0x2b, 0x44, + 0x2c, 0x37, + 0x2d, 0x00, + 0x2e, 0xa5, /* burst PLL control (working: a9) */ + 0x2f, 0xe0, /* 0xea is blue test frame -- 0xe0 for normal */ + 0x31, 0x00, + 0x33, 0x22, + 0x34, 0x11, + 0x35, 0x35, + 0x3b, 0x05, + 0x06, 0xc0, /* reset device */ + 0x00, 0x00, /* Terminator (reg 0x00 is read-only) */ +}; + +static int write_reg(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int write_regs(struct v4l2_subdev *sd, const u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0x00; i += 2) + if (write_reg(sd, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +static int tw9903_s_video_routing(struct v4l2_subdev *sd, u32 input, + u32 output, u32 config) +{ + write_reg(sd, 0x02, 0x40 | (input << 1)); + return 0; +} + +static int tw9903_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct tw9903 *dec = to_state(sd); + bool is_60hz = norm & V4L2_STD_525_60; + static const u8 config_60hz[] = { + 0x05, 0x80, + 0x07, 0x02, + 0x08, 0x14, + 0x09, 0xf0, + 0, 0, + }; + static const u8 config_50hz[] = { + 0x05, 0x00, + 0x07, 0x12, + 0x08, 0x18, + 0x09, 0x20, + 0, 0, + }; + + write_regs(sd, is_60hz ? config_60hz : config_50hz); + dec->norm = norm; + return 0; +} + + +static int tw9903_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct tw9903 *dec = container_of(ctrl->handler, struct tw9903, hdl); + struct v4l2_subdev *sd = &dec->sd; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + write_reg(sd, 0x10, ctrl->val); + break; + case V4L2_CID_CONTRAST: + write_reg(sd, 0x11, ctrl->val); + break; + case V4L2_CID_HUE: + write_reg(sd, 0x15, ctrl->val); + break; + default: + return -EINVAL; + } + return 0; +} + +static int tw9903_log_status(struct v4l2_subdev *sd) +{ + struct tw9903 *dec = to_state(sd); + bool is_60hz = dec->norm & V4L2_STD_525_60; + + v4l2_info(sd, "Standard: %d Hz\n", is_60hz ? 60 : 50); + v4l2_ctrl_subdev_log_status(sd); + return 0; +} + +/* --------------------------------------------------------------------------*/ + +static const struct v4l2_ctrl_ops tw9903_ctrl_ops = { + .s_ctrl = tw9903_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops tw9903_core_ops = { + .log_status = tw9903_log_status, +}; + +static const struct v4l2_subdev_video_ops tw9903_video_ops = { + .s_std = tw9903_s_std, + .s_routing = tw9903_s_video_routing, +}; + +static const struct v4l2_subdev_ops tw9903_ops = { + .core = &tw9903_core_ops, + .video = &tw9903_video_ops, +}; + +/* --------------------------------------------------------------------------*/ + +static int tw9903_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tw9903 *dec; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + dec = devm_kzalloc(&client->dev, sizeof(*dec), GFP_KERNEL); + if (dec == NULL) + return -ENOMEM; + sd = &dec->sd; + v4l2_i2c_subdev_init(sd, client, &tw9903_ops); + hdl = &dec->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &tw9903_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &tw9903_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 0x60); + v4l2_ctrl_new_std(hdl, &tw9903_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + sd->ctrl_handler = hdl; + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return err; + } + + /* Initialize tw9903 */ + dec->norm = V4L2_STD_NTSC; + + if (write_regs(sd, initial_registers) < 0) { + v4l2_err(client, "error initializing TW9903\n"); + return -EINVAL; + } + + return 0; +} + +static int tw9903_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&to_state(sd)->hdl); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id tw9903_id[] = { + { "tw9903", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tw9903_id); + +static struct i2c_driver tw9903_driver = { + .driver = { + .name = "tw9903", + }, + .probe = tw9903_probe, + .remove = tw9903_remove, + .id_table = tw9903_id, +}; +module_i2c_driver(tw9903_driver); diff --git a/drivers/media/i2c/tw9906.c b/drivers/media/i2c/tw9906.c new file mode 100644 index 000000000..c528eb01f --- /dev/null +++ b/drivers/media/i2c/tw9906.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/ioctl.h> +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +MODULE_DESCRIPTION("TW9906 I2C subdev driver"); +MODULE_LICENSE("GPL v2"); + +struct tw9906 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + v4l2_std_id norm; +}; + +static inline struct tw9906 *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tw9906, sd); +} + +static const u8 initial_registers[] = { + 0x02, 0x40, /* input 0, composite */ + 0x03, 0xa2, /* correct digital format */ + 0x05, 0x81, /* or 0x01 for PAL */ + 0x07, 0x02, /* window */ + 0x08, 0x14, /* window */ + 0x09, 0xf0, /* window */ + 0x0a, 0x10, /* window */ + 0x0b, 0xd0, /* window */ + 0x0d, 0x00, /* scaling */ + 0x0e, 0x11, /* scaling */ + 0x0f, 0x00, /* scaling */ + 0x10, 0x00, /* brightness */ + 0x11, 0x60, /* contrast */ + 0x12, 0x11, /* sharpness */ + 0x13, 0x7e, /* U gain */ + 0x14, 0x7e, /* V gain */ + 0x15, 0x00, /* hue */ + 0x19, 0x57, /* vbi */ + 0x1a, 0x0f, + 0x1b, 0x40, + 0x29, 0x03, + 0x55, 0x00, + 0x6b, 0x26, + 0x6c, 0x36, + 0x6d, 0xf0, + 0x6e, 0x41, + 0x6f, 0x13, + 0xad, 0x70, + 0x00, 0x00, /* Terminator (reg 0x00 is read-only) */ +}; + +static int write_reg(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int write_regs(struct v4l2_subdev *sd, const u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0x00; i += 2) + if (write_reg(sd, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +static int tw9906_s_video_routing(struct v4l2_subdev *sd, u32 input, + u32 output, u32 config) +{ + write_reg(sd, 0x02, 0x40 | (input << 1)); + return 0; +} + +static int tw9906_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct tw9906 *dec = to_state(sd); + bool is_60hz = norm & V4L2_STD_525_60; + static const u8 config_60hz[] = { + 0x05, 0x81, + 0x07, 0x02, + 0x08, 0x14, + 0x09, 0xf0, + 0, 0, + }; + static const u8 config_50hz[] = { + 0x05, 0x01, + 0x07, 0x12, + 0x08, 0x18, + 0x09, 0x20, + 0, 0, + }; + + write_regs(sd, is_60hz ? config_60hz : config_50hz); + dec->norm = norm; + return 0; +} + +static int tw9906_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct tw9906 *dec = container_of(ctrl->handler, struct tw9906, hdl); + struct v4l2_subdev *sd = &dec->sd; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + write_reg(sd, 0x10, ctrl->val); + break; + case V4L2_CID_CONTRAST: + write_reg(sd, 0x11, ctrl->val); + break; + case V4L2_CID_HUE: + write_reg(sd, 0x15, ctrl->val); + break; + default: + return -EINVAL; + } + return 0; +} + +static int tw9906_log_status(struct v4l2_subdev *sd) +{ + struct tw9906 *dec = to_state(sd); + bool is_60hz = dec->norm & V4L2_STD_525_60; + + v4l2_info(sd, "Standard: %d Hz\n", is_60hz ? 60 : 50); + v4l2_ctrl_subdev_log_status(sd); + return 0; +} + +/* --------------------------------------------------------------------------*/ + +static const struct v4l2_ctrl_ops tw9906_ctrl_ops = { + .s_ctrl = tw9906_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops tw9906_core_ops = { + .log_status = tw9906_log_status, +}; + +static const struct v4l2_subdev_video_ops tw9906_video_ops = { + .s_std = tw9906_s_std, + .s_routing = tw9906_s_video_routing, +}; + +static const struct v4l2_subdev_ops tw9906_ops = { + .core = &tw9906_core_ops, + .video = &tw9906_video_ops, +}; + +static int tw9906_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tw9906 *dec; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + dec = devm_kzalloc(&client->dev, sizeof(*dec), GFP_KERNEL); + if (dec == NULL) + return -ENOMEM; + sd = &dec->sd; + v4l2_i2c_subdev_init(sd, client, &tw9906_ops); + hdl = &dec->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 0x60); + v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + sd->ctrl_handler = hdl; + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return err; + } + + /* Initialize tw9906 */ + dec->norm = V4L2_STD_NTSC; + + if (write_regs(sd, initial_registers) < 0) { + v4l2_err(client, "error initializing TW9906\n"); + return -EINVAL; + } + + return 0; +} + +static int tw9906_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&to_state(sd)->hdl); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id tw9906_id[] = { + { "tw9906", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tw9906_id); + +static struct i2c_driver tw9906_driver = { + .driver = { + .name = "tw9906", + }, + .probe = tw9906_probe, + .remove = tw9906_remove, + .id_table = tw9906_id, +}; +module_i2c_driver(tw9906_driver); diff --git a/drivers/media/i2c/tw9910.c b/drivers/media/i2c/tw9910.c new file mode 100644 index 000000000..a25a350b0 --- /dev/null +++ b/drivers/media/i2c/tw9910.c @@ -0,0 +1,1027 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * tw9910 Video Driver + * + * Copyright (C) 2017 Jacopo Mondi <jacopo+renesas@jmondi.org> + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * Based on ov772x driver, + * + * Copyright (C) 2008 Kuninori Morimoto <morimoto.kuninori@renesas.com> + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/v4l2-mediabus.h> +#include <linux/videodev2.h> + +#include <media/i2c/tw9910.h> +#include <media/v4l2-subdev.h> + +#define GET_ID(val) ((val & 0xF8) >> 3) +#define GET_REV(val) (val & 0x07) + +/* + * register offset + */ +#define ID 0x00 /* Product ID Code Register */ +#define STATUS1 0x01 /* Chip Status Register I */ +#define INFORM 0x02 /* Input Format */ +#define OPFORM 0x03 /* Output Format Control Register */ +#define DLYCTR 0x04 /* Hysteresis and HSYNC Delay Control */ +#define OUTCTR1 0x05 /* Output Control I */ +#define ACNTL1 0x06 /* Analog Control Register 1 */ +#define CROP_HI 0x07 /* Cropping Register, High */ +#define VDELAY_LO 0x08 /* Vertical Delay Register, Low */ +#define VACTIVE_LO 0x09 /* Vertical Active Register, Low */ +#define HDELAY_LO 0x0A /* Horizontal Delay Register, Low */ +#define HACTIVE_LO 0x0B /* Horizontal Active Register, Low */ +#define CNTRL1 0x0C /* Control Register I */ +#define VSCALE_LO 0x0D /* Vertical Scaling Register, Low */ +#define SCALE_HI 0x0E /* Scaling Register, High */ +#define HSCALE_LO 0x0F /* Horizontal Scaling Register, Low */ +#define BRIGHT 0x10 /* BRIGHTNESS Control Register */ +#define CONTRAST 0x11 /* CONTRAST Control Register */ +#define SHARPNESS 0x12 /* SHARPNESS Control Register I */ +#define SAT_U 0x13 /* Chroma (U) Gain Register */ +#define SAT_V 0x14 /* Chroma (V) Gain Register */ +#define HUE 0x15 /* Hue Control Register */ +#define CORING1 0x17 +#define CORING2 0x18 /* Coring and IF compensation */ +#define VBICNTL 0x19 /* VBI Control Register */ +#define ACNTL2 0x1A /* Analog Control 2 */ +#define OUTCTR2 0x1B /* Output Control 2 */ +#define SDT 0x1C /* Standard Selection */ +#define SDTR 0x1D /* Standard Recognition */ +#define TEST 0x1F /* Test Control Register */ +#define CLMPG 0x20 /* Clamping Gain */ +#define IAGC 0x21 /* Individual AGC Gain */ +#define AGCGAIN 0x22 /* AGC Gain */ +#define PEAKWT 0x23 /* White Peak Threshold */ +#define CLMPL 0x24 /* Clamp level */ +#define SYNCT 0x25 /* Sync Amplitude */ +#define MISSCNT 0x26 /* Sync Miss Count Register */ +#define PCLAMP 0x27 /* Clamp Position Register */ +#define VCNTL1 0x28 /* Vertical Control I */ +#define VCNTL2 0x29 /* Vertical Control II */ +#define CKILL 0x2A /* Color Killer Level Control */ +#define COMB 0x2B /* Comb Filter Control */ +#define LDLY 0x2C /* Luma Delay and H Filter Control */ +#define MISC1 0x2D /* Miscellaneous Control I */ +#define LOOP 0x2E /* LOOP Control Register */ +#define MISC2 0x2F /* Miscellaneous Control II */ +#define MVSN 0x30 /* Macrovision Detection */ +#define STATUS2 0x31 /* Chip STATUS II */ +#define HFREF 0x32 /* H monitor */ +#define CLMD 0x33 /* CLAMP MODE */ +#define IDCNTL 0x34 /* ID Detection Control */ +#define CLCNTL1 0x35 /* Clamp Control I */ +#define ANAPLLCTL 0x4C +#define VBIMIN 0x4D +#define HSLOWCTL 0x4E +#define WSS3 0x4F +#define FILLDATA 0x50 +#define SDID 0x51 +#define DID 0x52 +#define WSS1 0x53 +#define WSS2 0x54 +#define VVBI 0x55 +#define LCTL6 0x56 +#define LCTL7 0x57 +#define LCTL8 0x58 +#define LCTL9 0x59 +#define LCTL10 0x5A +#define LCTL11 0x5B +#define LCTL12 0x5C +#define LCTL13 0x5D +#define LCTL14 0x5E +#define LCTL15 0x5F +#define LCTL16 0x60 +#define LCTL17 0x61 +#define LCTL18 0x62 +#define LCTL19 0x63 +#define LCTL20 0x64 +#define LCTL21 0x65 +#define LCTL22 0x66 +#define LCTL23 0x67 +#define LCTL24 0x68 +#define LCTL25 0x69 +#define LCTL26 0x6A +#define HSBEGIN 0x6B +#define HSEND 0x6C +#define OVSDLY 0x6D +#define OVSEND 0x6E +#define VBIDELAY 0x6F + +/* + * register detail + */ + +/* INFORM */ +#define FC27_ON 0x40 /* 1 : Input crystal clock frequency is 27MHz */ +#define FC27_FF 0x00 /* 0 : Square pixel mode. */ + /* Must use 24.54MHz for 60Hz field rate */ + /* source or 29.5MHz for 50Hz field rate */ +#define IFSEL_S 0x10 /* 01 : S-video decoding */ +#define IFSEL_C 0x00 /* 00 : Composite video decoding */ + /* Y input video selection */ +#define YSEL_M0 0x00 /* 00 : Mux0 selected */ +#define YSEL_M1 0x04 /* 01 : Mux1 selected */ +#define YSEL_M2 0x08 /* 10 : Mux2 selected */ +#define YSEL_M3 0x10 /* 11 : Mux3 selected */ + +/* OPFORM */ +#define MODE 0x80 /* 0 : CCIR601 compatible YCrCb 4:2:2 format */ + /* 1 : ITU-R-656 compatible data sequence format */ +#define LEN 0x40 /* 0 : 8-bit YCrCb 4:2:2 output format */ + /* 1 : 16-bit YCrCb 4:2:2 output format.*/ +#define LLCMODE 0x20 /* 1 : LLC output mode. */ + /* 0 : free-run output mode */ +#define AINC 0x10 /* Serial interface auto-indexing control */ + /* 0 : auto-increment */ + /* 1 : non-auto */ +#define VSCTL 0x08 /* 1 : Vertical out ctrl by DVALID */ + /* 0 : Vertical out ctrl by HACTIVE and DVALID */ +#define OEN_TRI_SEL_MASK 0x07 +#define OEN_TRI_SEL_ALL_ON 0x00 /* Enable output for Rev0/Rev1 */ +#define OEN_TRI_SEL_ALL_OFF_r0 0x06 /* All tri-stated for Rev0 */ +#define OEN_TRI_SEL_ALL_OFF_r1 0x07 /* All tri-stated for Rev1 */ + +/* OUTCTR1 */ +#define VSP_LO 0x00 /* 0 : VS pin output polarity is active low */ +#define VSP_HI 0x80 /* 1 : VS pin output polarity is active high. */ + /* VS pin output control */ +#define VSSL_VSYNC 0x00 /* 0 : VSYNC */ +#define VSSL_VACT 0x10 /* 1 : VACT */ +#define VSSL_FIELD 0x20 /* 2 : FIELD */ +#define VSSL_VVALID 0x30 /* 3 : VVALID */ +#define VSSL_ZERO 0x70 /* 7 : 0 */ +#define HSP_LOW 0x00 /* 0 : HS pin output polarity is active low */ +#define HSP_HI 0x08 /* 1 : HS pin output polarity is active high.*/ + /* HS pin output control */ +#define HSSL_HACT 0x00 /* 0 : HACT */ +#define HSSL_HSYNC 0x01 /* 1 : HSYNC */ +#define HSSL_DVALID 0x02 /* 2 : DVALID */ +#define HSSL_HLOCK 0x03 /* 3 : HLOCK */ +#define HSSL_ASYNCW 0x04 /* 4 : ASYNCW */ +#define HSSL_ZERO 0x07 /* 7 : 0 */ + +/* ACNTL1 */ +#define SRESET 0x80 /* resets the device to its default state + * but all register content remain unchanged. + * This bit is self-resetting. + */ +#define ACNTL1_PDN_MASK 0x0e +#define CLK_PDN 0x08 /* system clock power down */ +#define Y_PDN 0x04 /* Luma ADC power down */ +#define C_PDN 0x02 /* Chroma ADC power down */ + +/* ACNTL2 */ +#define ACNTL2_PDN_MASK 0x40 +#define PLL_PDN 0x40 /* PLL power down */ + +/* VBICNTL */ + +/* RTSEL : control the real time signal output from the MPOUT pin */ +#define RTSEL_MASK 0x07 +#define RTSEL_VLOSS 0x00 /* 0000 = Video loss */ +#define RTSEL_HLOCK 0x01 /* 0001 = H-lock */ +#define RTSEL_SLOCK 0x02 /* 0010 = S-lock */ +#define RTSEL_VLOCK 0x03 /* 0011 = V-lock */ +#define RTSEL_MONO 0x04 /* 0100 = MONO */ +#define RTSEL_DET50 0x05 /* 0101 = DET50 */ +#define RTSEL_FIELD 0x06 /* 0110 = FIELD */ +#define RTSEL_RTCO 0x07 /* 0111 = RTCO ( Real Time Control ) */ + +/* HSYNC start and end are constant for now */ +#define HSYNC_START 0x0260 +#define HSYNC_END 0x0300 + +/* + * structure + */ + +struct regval_list { + unsigned char reg_num; + unsigned char value; +}; + +struct tw9910_scale_ctrl { + char *name; + unsigned short width; + unsigned short height; + u16 hscale; + u16 vscale; +}; + +struct tw9910_priv { + struct v4l2_subdev subdev; + struct clk *clk; + struct tw9910_video_info *info; + struct gpio_desc *pdn_gpio; + struct gpio_desc *rstb_gpio; + const struct tw9910_scale_ctrl *scale; + v4l2_std_id norm; + u32 revision; +}; + +static const struct tw9910_scale_ctrl tw9910_ntsc_scales[] = { + { + .name = "NTSC SQ", + .width = 640, + .height = 480, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "NTSC CCIR601", + .width = 720, + .height = 480, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "NTSC SQ (CIF)", + .width = 320, + .height = 240, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "NTSC CCIR601 (CIF)", + .width = 360, + .height = 240, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "NTSC SQ (QCIF)", + .width = 160, + .height = 120, + .hscale = 0x0400, + .vscale = 0x0400, + }, + { + .name = "NTSC CCIR601 (QCIF)", + .width = 180, + .height = 120, + .hscale = 0x0400, + .vscale = 0x0400, + }, +}; + +static const struct tw9910_scale_ctrl tw9910_pal_scales[] = { + { + .name = "PAL SQ", + .width = 768, + .height = 576, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "PAL CCIR601", + .width = 720, + .height = 576, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "PAL SQ (CIF)", + .width = 384, + .height = 288, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "PAL CCIR601 (CIF)", + .width = 360, + .height = 288, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "PAL SQ (QCIF)", + .width = 192, + .height = 144, + .hscale = 0x0400, + .vscale = 0x0400, + }, + { + .name = "PAL CCIR601 (QCIF)", + .width = 180, + .height = 144, + .hscale = 0x0400, + .vscale = 0x0400, + }, +}; + +/* + * general function + */ +static struct tw9910_priv *to_tw9910(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct tw9910_priv, + subdev); +} + +static int tw9910_mask_set(struct i2c_client *client, u8 command, + u8 mask, u8 set) +{ + s32 val = i2c_smbus_read_byte_data(client, command); + + if (val < 0) + return val; + + val &= ~mask; + val |= set & mask; + + return i2c_smbus_write_byte_data(client, command, val); +} + +static int tw9910_set_scale(struct i2c_client *client, + const struct tw9910_scale_ctrl *scale) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, SCALE_HI, + (scale->vscale & 0x0F00) >> 4 | + (scale->hscale & 0x0F00) >> 8); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, HSCALE_LO, + scale->hscale & 0x00FF); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, VSCALE_LO, + scale->vscale & 0x00FF); + + return ret; +} + +static int tw9910_set_hsync(struct i2c_client *client) +{ + struct tw9910_priv *priv = to_tw9910(client); + int ret; + + /* bit 10 - 3 */ + ret = i2c_smbus_write_byte_data(client, HSBEGIN, + (HSYNC_START & 0x07F8) >> 3); + if (ret < 0) + return ret; + + /* bit 10 - 3 */ + ret = i2c_smbus_write_byte_data(client, HSEND, + (HSYNC_END & 0x07F8) >> 3); + if (ret < 0) + return ret; + + /* So far only revisions 0 and 1 have been seen. */ + /* bit 2 - 0 */ + if (priv->revision == 1) + ret = tw9910_mask_set(client, HSLOWCTL, 0x77, + (HSYNC_START & 0x0007) << 4 | + (HSYNC_END & 0x0007)); + + return ret; +} + +static void tw9910_reset(struct i2c_client *client) +{ + tw9910_mask_set(client, ACNTL1, SRESET, SRESET); + usleep_range(1000, 5000); +} + +static int tw9910_power(struct i2c_client *client, int enable) +{ + int ret; + u8 acntl1; + u8 acntl2; + + if (enable) { + acntl1 = 0; + acntl2 = 0; + } else { + acntl1 = CLK_PDN | Y_PDN | C_PDN; + acntl2 = PLL_PDN; + } + + ret = tw9910_mask_set(client, ACNTL1, ACNTL1_PDN_MASK, acntl1); + if (ret < 0) + return ret; + + return tw9910_mask_set(client, ACNTL2, ACNTL2_PDN_MASK, acntl2); +} + +static const struct tw9910_scale_ctrl *tw9910_select_norm(v4l2_std_id norm, + u32 width, u32 height) +{ + const struct tw9910_scale_ctrl *scale; + const struct tw9910_scale_ctrl *ret = NULL; + __u32 diff = 0xffffffff, tmp; + int size, i; + + if (norm & V4L2_STD_NTSC) { + scale = tw9910_ntsc_scales; + size = ARRAY_SIZE(tw9910_ntsc_scales); + } else if (norm & V4L2_STD_PAL) { + scale = tw9910_pal_scales; + size = ARRAY_SIZE(tw9910_pal_scales); + } else { + return NULL; + } + + for (i = 0; i < size; i++) { + tmp = abs(width - scale[i].width) + + abs(height - scale[i].height); + if (tmp < diff) { + diff = tmp; + ret = scale + i; + } + } + + return ret; +} + +/* + * subdevice operations + */ +static int tw9910_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + u8 val; + int ret; + + if (!enable) { + switch (priv->revision) { + case 0: + val = OEN_TRI_SEL_ALL_OFF_r0; + break; + case 1: + val = OEN_TRI_SEL_ALL_OFF_r1; + break; + default: + dev_err(&client->dev, "un-supported revision\n"); + return -EINVAL; + } + } else { + val = OEN_TRI_SEL_ALL_ON; + + if (!priv->scale) { + dev_err(&client->dev, "norm select error\n"); + return -EPERM; + } + + dev_dbg(&client->dev, "%s %dx%d\n", + priv->scale->name, + priv->scale->width, + priv->scale->height); + } + + ret = tw9910_mask_set(client, OPFORM, OEN_TRI_SEL_MASK, val); + if (ret < 0) + return ret; + + return tw9910_power(client, enable); +} + +static int tw9910_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + + *norm = priv->norm; + + return 0; +} + +static int tw9910_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + const unsigned int hact = 720; + const unsigned int hdelay = 15; + unsigned int vact; + unsigned int vdelay; + int ret; + + if (!(norm & (V4L2_STD_NTSC | V4L2_STD_PAL))) + return -EINVAL; + + priv->norm = norm; + if (norm & V4L2_STD_525_60) { + vact = 240; + vdelay = 18; + ret = tw9910_mask_set(client, VVBI, 0x10, 0x10); + } else { + vact = 288; + vdelay = 24; + ret = tw9910_mask_set(client, VVBI, 0x10, 0x00); + } + if (!ret) + ret = i2c_smbus_write_byte_data(client, CROP_HI, + ((vdelay >> 2) & 0xc0) | + ((vact >> 4) & 0x30) | + ((hdelay >> 6) & 0x0c) | + ((hact >> 8) & 0x03)); + if (!ret) + ret = i2c_smbus_write_byte_data(client, VDELAY_LO, + vdelay & 0xff); + if (!ret) + ret = i2c_smbus_write_byte_data(client, VACTIVE_LO, + vact & 0xff); + + return ret; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int tw9910_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + if (reg->reg > 0xff) + return -EINVAL; + + reg->size = 1; + ret = i2c_smbus_read_byte_data(client, reg->reg); + if (ret < 0) + return ret; + + /* + * ret = int + * reg->val = __u64 + */ + reg->val = (__u64)ret; + + return 0; +} + +static int tw9910_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0xff || + reg->val > 0xff) + return -EINVAL; + + return i2c_smbus_write_byte_data(client, reg->reg, reg->val); +} +#endif + +static void tw9910_set_gpio_value(struct gpio_desc *desc, int value) +{ + if (desc) { + gpiod_set_value(desc, value); + usleep_range(500, 1000); + } +} + +static int tw9910_power_on(struct tw9910_priv *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev); + int ret; + + if (priv->clk) { + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + } + + tw9910_set_gpio_value(priv->pdn_gpio, 0); + + /* + * FIXME: The reset signal is connected to a shared GPIO on some + * platforms (namely the SuperH Migo-R). Until a framework becomes + * available to handle this cleanly, request the GPIO temporarily + * to avoid conflicts. + */ + priv->rstb_gpio = gpiod_get_optional(&client->dev, "rstb", + GPIOD_OUT_LOW); + if (IS_ERR(priv->rstb_gpio)) { + dev_info(&client->dev, "Unable to get GPIO \"rstb\""); + clk_disable_unprepare(priv->clk); + tw9910_set_gpio_value(priv->pdn_gpio, 1); + return PTR_ERR(priv->rstb_gpio); + } + + if (priv->rstb_gpio) { + tw9910_set_gpio_value(priv->rstb_gpio, 1); + tw9910_set_gpio_value(priv->rstb_gpio, 0); + + gpiod_put(priv->rstb_gpio); + } + + return 0; +} + +static int tw9910_power_off(struct tw9910_priv *priv) +{ + clk_disable_unprepare(priv->clk); + tw9910_set_gpio_value(priv->pdn_gpio, 1); + + return 0; +} + +static int tw9910_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + + return on ? tw9910_power_on(priv) : tw9910_power_off(priv); +} + +static int tw9910_set_frame(struct v4l2_subdev *sd, u32 *width, u32 *height) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + int ret = -EINVAL; + u8 val; + + /* Select suitable norm. */ + priv->scale = tw9910_select_norm(priv->norm, *width, *height); + if (!priv->scale) + goto tw9910_set_fmt_error; + + /* Reset hardware. */ + tw9910_reset(client); + + /* Set bus width. */ + val = 0x00; + if (priv->info->buswidth == 16) + val = LEN; + + ret = tw9910_mask_set(client, OPFORM, LEN, val); + if (ret < 0) + goto tw9910_set_fmt_error; + + /* Select MPOUT behavior. */ + switch (priv->info->mpout) { + case TW9910_MPO_VLOSS: + val = RTSEL_VLOSS; break; + case TW9910_MPO_HLOCK: + val = RTSEL_HLOCK; break; + case TW9910_MPO_SLOCK: + val = RTSEL_SLOCK; break; + case TW9910_MPO_VLOCK: + val = RTSEL_VLOCK; break; + case TW9910_MPO_MONO: + val = RTSEL_MONO; break; + case TW9910_MPO_DET50: + val = RTSEL_DET50; break; + case TW9910_MPO_FIELD: + val = RTSEL_FIELD; break; + case TW9910_MPO_RTCO: + val = RTSEL_RTCO; break; + default: + val = 0; + } + + ret = tw9910_mask_set(client, VBICNTL, RTSEL_MASK, val); + if (ret < 0) + goto tw9910_set_fmt_error; + + /* Set scale. */ + ret = tw9910_set_scale(client, priv->scale); + if (ret < 0) + goto tw9910_set_fmt_error; + + /* Set hsync. */ + ret = tw9910_set_hsync(client); + if (ret < 0) + goto tw9910_set_fmt_error; + + *width = priv->scale->width; + *height = priv->scale->height; + + return ret; + +tw9910_set_fmt_error: + + tw9910_reset(client); + priv->scale = NULL; + + return ret; +} + +static int tw9910_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + /* Only CROP, CROP_DEFAULT and CROP_BOUNDS are supported. */ + if (sel->target > V4L2_SEL_TGT_CROP_BOUNDS) + return -EINVAL; + + sel->r.left = 0; + sel->r.top = 0; + if (priv->norm & V4L2_STD_NTSC) { + sel->r.width = 640; + sel->r.height = 480; + } else { + sel->r.width = 768; + sel->r.height = 576; + } + + return 0; +} + +static int tw9910_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + + if (format->pad) + return -EINVAL; + + if (!priv->scale) { + priv->scale = tw9910_select_norm(priv->norm, 640, 480); + if (!priv->scale) + return -EINVAL; + } + + mf->width = priv->scale->width; + mf->height = priv->scale->height; + mf->code = MEDIA_BUS_FMT_UYVY8_2X8; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->field = V4L2_FIELD_INTERLACED_BT; + + return 0; +} + +static int tw9910_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + u32 width = mf->width, height = mf->height; + int ret; + + WARN_ON(mf->field != V4L2_FIELD_ANY && + mf->field != V4L2_FIELD_INTERLACED_BT); + + /* Check color format. */ + if (mf->code != MEDIA_BUS_FMT_UYVY8_2X8) + return -EINVAL; + + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + + ret = tw9910_set_frame(sd, &width, &height); + if (ret) + return ret; + + mf->width = width; + mf->height = height; + + return 0; +} + +static int tw9910_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + const struct tw9910_scale_ctrl *scale; + + if (format->pad) + return -EINVAL; + + if (mf->field == V4L2_FIELD_ANY) { + mf->field = V4L2_FIELD_INTERLACED_BT; + } else if (mf->field != V4L2_FIELD_INTERLACED_BT) { + dev_err(&client->dev, "Field type %d invalid\n", mf->field); + return -EINVAL; + } + + mf->code = MEDIA_BUS_FMT_UYVY8_2X8; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + + /* Select suitable norm. */ + scale = tw9910_select_norm(priv->norm, mf->width, mf->height); + if (!scale) + return -EINVAL; + + mf->width = scale->width; + mf->height = scale->height; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return tw9910_s_fmt(sd, mf); + + cfg->try_fmt = *mf; + + return 0; +} + +static int tw9910_video_probe(struct i2c_client *client) +{ + struct tw9910_priv *priv = to_tw9910(client); + s32 id; + int ret; + + /* TW9910 only use 8 or 16 bit bus width. */ + if (priv->info->buswidth != 16 && priv->info->buswidth != 8) { + dev_err(&client->dev, "bus width error\n"); + return -ENODEV; + } + + ret = tw9910_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* + * Check and show Product ID. + * So far only revisions 0 and 1 have been seen. + */ + id = i2c_smbus_read_byte_data(client, ID); + priv->revision = GET_REV(id); + id = GET_ID(id); + + if (id != 0x0b || priv->revision > 0x01) { + dev_err(&client->dev, "Product ID error %x:%x\n", + id, priv->revision); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, "tw9910 Product ID %0x:%0x\n", + id, priv->revision); + + priv->norm = V4L2_STD_NTSC; + priv->scale = &tw9910_ntsc_scales[0]; + +done: + tw9910_s_power(&priv->subdev, 0); + + return ret; +} + +static const struct v4l2_subdev_core_ops tw9910_subdev_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = tw9910_g_register, + .s_register = tw9910_s_register, +#endif + .s_power = tw9910_s_power, +}; + +static int tw9910_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_2X8; + + return 0; +} + +static int tw9910_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + *norm = V4L2_STD_NTSC | V4L2_STD_PAL; + + return 0; +} + +static const struct v4l2_subdev_video_ops tw9910_subdev_video_ops = { + .s_std = tw9910_s_std, + .g_std = tw9910_g_std, + .s_stream = tw9910_s_stream, + .g_tvnorms = tw9910_g_tvnorms, +}; + +static const struct v4l2_subdev_pad_ops tw9910_subdev_pad_ops = { + .enum_mbus_code = tw9910_enum_mbus_code, + .get_selection = tw9910_get_selection, + .get_fmt = tw9910_get_fmt, + .set_fmt = tw9910_set_fmt, +}; + +static const struct v4l2_subdev_ops tw9910_subdev_ops = { + .core = &tw9910_subdev_core_ops, + .video = &tw9910_subdev_video_ops, + .pad = &tw9910_subdev_pad_ops, +}; + +/* + * i2c_driver function + */ + +static int tw9910_probe(struct i2c_client *client, + const struct i2c_device_id *did) + +{ + struct tw9910_priv *priv; + struct tw9910_video_info *info; + struct i2c_adapter *adapter = client->adapter; + int ret; + + if (!client->dev.platform_data) { + dev_err(&client->dev, "TW9910: missing platform data!\n"); + return -EINVAL; + } + + info = client->dev.platform_data; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_BYTE_DATA\n"); + return -EIO; + } + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = info; + + v4l2_i2c_subdev_init(&priv->subdev, client, &tw9910_subdev_ops); + + priv->clk = clk_get(&client->dev, "xti"); + if (PTR_ERR(priv->clk) == -ENOENT) { + priv->clk = NULL; + } else if (IS_ERR(priv->clk)) { + dev_err(&client->dev, "Unable to get xti clock\n"); + return PTR_ERR(priv->clk); + } + + priv->pdn_gpio = gpiod_get_optional(&client->dev, "pdn", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->pdn_gpio)) { + dev_info(&client->dev, "Unable to get GPIO \"pdn\""); + ret = PTR_ERR(priv->pdn_gpio); + goto error_clk_put; + } + + ret = tw9910_video_probe(client); + if (ret < 0) + goto error_gpio_put; + + ret = v4l2_async_register_subdev(&priv->subdev); + if (ret) + goto error_gpio_put; + + return ret; + +error_gpio_put: + if (priv->pdn_gpio) + gpiod_put(priv->pdn_gpio); +error_clk_put: + clk_put(priv->clk); + + return ret; +} + +static int tw9910_remove(struct i2c_client *client) +{ + struct tw9910_priv *priv = to_tw9910(client); + + if (priv->pdn_gpio) + gpiod_put(priv->pdn_gpio); + clk_put(priv->clk); + v4l2_async_unregister_subdev(&priv->subdev); + + return 0; +} + +static const struct i2c_device_id tw9910_id[] = { + { "tw9910", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tw9910_id); + +static struct i2c_driver tw9910_i2c_driver = { + .driver = { + .name = "tw9910", + }, + .probe = tw9910_probe, + .remove = tw9910_remove, + .id_table = tw9910_id, +}; + +module_i2c_driver(tw9910_i2c_driver); + +MODULE_DESCRIPTION("V4L2 driver for TW9910 video decoder"); +MODULE_AUTHOR("Kuninori Morimoto"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/uda1342.c b/drivers/media/i2c/uda1342.c new file mode 100644 index 000000000..b0a9c6d71 --- /dev/null +++ b/drivers/media/i2c/uda1342.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/i2c/uda1342.h> +#include <linux/slab.h> + +static int write_reg(struct i2c_client *client, int reg, int value) +{ + /* UDA1342 wants MSB first, but SMBus sends LSB first */ + i2c_smbus_write_word_data(client, reg, swab16(value)); + return 0; +} + +static int uda1342_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + switch (input) { + case UDA1342_IN1: + write_reg(client, 0x00, 0x1241); /* select input 1 */ + break; + case UDA1342_IN2: + write_reg(client, 0x00, 0x1441); /* select input 2 */ + break; + default: + v4l2_err(sd, "input %d not supported\n", input); + break; + } + return 0; +} + +static const struct v4l2_subdev_audio_ops uda1342_audio_ops = { + .s_routing = uda1342_s_routing, +}; + +static const struct v4l2_subdev_ops uda1342_ops = { + .audio = &uda1342_audio_ops, +}; + +static int uda1342_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct v4l2_subdev *sd; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + dev_dbg(&client->dev, "initializing UDA1342 at address %d on %s\n", + client->addr, adapter->name); + + sd = devm_kzalloc(&client->dev, sizeof(*sd), GFP_KERNEL); + if (sd == NULL) + return -ENOMEM; + + v4l2_i2c_subdev_init(sd, client, &uda1342_ops); + + write_reg(client, 0x00, 0x8000); /* reset registers */ + write_reg(client, 0x00, 0x1241); /* select input 1 */ + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + return 0; +} + +static int uda1342_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +static const struct i2c_device_id uda1342_id[] = { + { "uda1342", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, uda1342_id); + +static struct i2c_driver uda1342_driver = { + .driver = { + .name = "uda1342", + }, + .probe = uda1342_probe, + .remove = uda1342_remove, + .id_table = uda1342_id, +}; + +module_i2c_driver(uda1342_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/upd64031a.c b/drivers/media/i2c/upd64031a.c new file mode 100644 index 000000000..ef35c6574 --- /dev/null +++ b/drivers/media/i2c/upd64031a.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * upd64031A - NEC Electronics Ghost Reduction for NTSC in Japan + * + * 2003 by T.Adachi <tadachi@tadachi-net.com> + * 2003 by Takeru KOMORIYA <komoriya@paken.org> + * 2006 by Hans Verkuil <hverkuil@xs4all.nl> + */ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include <media/i2c/upd64031a.h> + +/* --------------------- read registers functions define -------------------- */ + +/* bit masks */ +#define GR_MODE_MASK 0xc0 +#define DIRECT_3DYCS_CONNECT_MASK 0xc0 +#define SYNC_CIRCUIT_MASK 0xa0 + +/* -------------------------------------------------------------------------- */ + +MODULE_DESCRIPTION("uPD64031A driver"); +MODULE_AUTHOR("T. Adachi, Takeru KOMORIYA, Hans Verkuil"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +enum { + R00 = 0, R01, R02, R03, R04, + R05, R06, R07, R08, R09, + R0A, R0B, R0C, R0D, R0E, R0F, + /* unused registers + R10, R11, R12, R13, R14, + R15, R16, R17, + */ + TOT_REGS +}; + +struct upd64031a_state { + struct v4l2_subdev sd; + u8 regs[TOT_REGS]; + u8 gr_mode; + u8 direct_3dycs_connect; + u8 ext_comp_sync; + u8 ext_vert_sync; +}; + +static inline struct upd64031a_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct upd64031a_state, sd); +} + +static u8 upd64031a_init[] = { + 0x00, 0xb8, 0x48, 0xd2, 0xe6, + 0x03, 0x10, 0x0b, 0xaf, 0x7f, + 0x00, 0x00, 0x1d, 0x5e, 0x00, + 0xd0 +}; + +/* ------------------------------------------------------------------------ */ + +static u8 upd64031a_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 buf[2]; + + if (reg >= sizeof(buf)) + return 0xff; + i2c_master_recv(client, buf, 2); + return buf[reg]; +} + +/* ------------------------------------------------------------------------ */ + +static void upd64031a_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 buf[2]; + + buf[0] = reg; + buf[1] = val; + v4l2_dbg(1, debug, sd, "write reg: %02X val: %02X\n", reg, val); + if (i2c_master_send(client, buf, 2) != 2) + v4l2_err(sd, "I/O error write 0x%02x/0x%02x\n", reg, val); +} + +/* ------------------------------------------------------------------------ */ + +/* The input changed due to new input or channel changed */ +static int upd64031a_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *freq) +{ + struct upd64031a_state *state = to_state(sd); + u8 reg = state->regs[R00]; + + v4l2_dbg(1, debug, sd, "changed input or channel\n"); + upd64031a_write(sd, R00, reg | 0x10); + upd64031a_write(sd, R00, reg & ~0x10); + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static int upd64031a_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct upd64031a_state *state = to_state(sd); + u8 r00, r05, r08; + + state->gr_mode = (input & 3) << 6; + state->direct_3dycs_connect = (input & 0xc) << 4; + state->ext_comp_sync = + (input & UPD64031A_COMPOSITE_EXTERNAL) << 1; + state->ext_vert_sync = + (input & UPD64031A_VERTICAL_EXTERNAL) << 2; + r00 = (state->regs[R00] & ~GR_MODE_MASK) | state->gr_mode; + r05 = (state->regs[R00] & ~SYNC_CIRCUIT_MASK) | + state->ext_comp_sync | state->ext_vert_sync; + r08 = (state->regs[R08] & ~DIRECT_3DYCS_CONNECT_MASK) | + state->direct_3dycs_connect; + upd64031a_write(sd, R00, r00); + upd64031a_write(sd, R05, r05); + upd64031a_write(sd, R08, r08); + return upd64031a_s_frequency(sd, NULL); +} + +static int upd64031a_log_status(struct v4l2_subdev *sd) +{ + v4l2_info(sd, "Status: SA00=0x%02x SA01=0x%02x\n", + upd64031a_read(sd, 0), upd64031a_read(sd, 1)); + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int upd64031a_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->val = upd64031a_read(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int upd64031a_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + upd64031a_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops upd64031a_core_ops = { + .log_status = upd64031a_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = upd64031a_g_register, + .s_register = upd64031a_s_register, +#endif +}; + +static const struct v4l2_subdev_tuner_ops upd64031a_tuner_ops = { + .s_frequency = upd64031a_s_frequency, +}; + +static const struct v4l2_subdev_video_ops upd64031a_video_ops = { + .s_routing = upd64031a_s_routing, +}; + +static const struct v4l2_subdev_ops upd64031a_ops = { + .core = &upd64031a_core_ops, + .tuner = &upd64031a_tuner_ops, + .video = &upd64031a_video_ops, +}; + +/* ------------------------------------------------------------------------ */ + +/* i2c implementation */ + +static int upd64031a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct upd64031a_state *state; + struct v4l2_subdev *sd; + int i; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &upd64031a_ops); + memcpy(state->regs, upd64031a_init, sizeof(state->regs)); + state->gr_mode = UPD64031A_GR_ON << 6; + state->direct_3dycs_connect = UPD64031A_3DYCS_COMPOSITE << 4; + state->ext_comp_sync = state->ext_vert_sync = 0; + for (i = 0; i < TOT_REGS; i++) + upd64031a_write(sd, i, state->regs[i]); + return 0; +} + +static int upd64031a_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id upd64031a_id[] = { + { "upd64031a", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, upd64031a_id); + +static struct i2c_driver upd64031a_driver = { + .driver = { + .name = "upd64031a", + }, + .probe = upd64031a_probe, + .remove = upd64031a_remove, + .id_table = upd64031a_id, +}; + +module_i2c_driver(upd64031a_driver); diff --git a/drivers/media/i2c/upd64083.c b/drivers/media/i2c/upd64083.c new file mode 100644 index 000000000..d6a1698ca --- /dev/null +++ b/drivers/media/i2c/upd64083.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * upd6408x - NEC Electronics 3-Dimensional Y/C separation driver + * + * 2003 by T.Adachi (tadachi@tadachi-net.com) + * 2003 by Takeru KOMORIYA <komoriya@paken.org> + * 2006 by Hans Verkuil <hverkuil@xs4all.nl> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include <media/i2c/upd64083.h> + +MODULE_DESCRIPTION("uPD64083 driver"); +MODULE_AUTHOR("T. Adachi, Takeru KOMORIYA, Hans Verkuil"); +MODULE_LICENSE("GPL"); + +static bool debug; +module_param(debug, bool, 0644); + +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +enum { + R00 = 0, R01, R02, R03, R04, + R05, R06, R07, R08, R09, + R0A, R0B, R0C, R0D, R0E, R0F, + R10, R11, R12, R13, R14, + R15, R16, + TOT_REGS +}; + +struct upd64083_state { + struct v4l2_subdev sd; + u8 mode; + u8 ext_y_adc; + u8 regs[TOT_REGS]; +}; + +static inline struct upd64083_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct upd64083_state, sd); +} + +/* Initial values when used in combination with the + NEC upd64031a ghost reduction chip. */ +static u8 upd64083_init[] = { + 0x1f, 0x01, 0xa0, 0x2d, 0x29, /* we use EXCSS=0 */ + 0x36, 0xdd, 0x05, 0x56, 0x48, + 0x00, 0x3a, 0xa0, 0x05, 0x08, + 0x44, 0x60, 0x08, 0x52, 0xf8, + 0x53, 0x60, 0x10 +}; + +/* ------------------------------------------------------------------------ */ + +static void upd64083_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 buf[2]; + + buf[0] = reg; + buf[1] = val; + v4l2_dbg(1, debug, sd, "write reg: %02x val: %02x\n", reg, val); + if (i2c_master_send(client, buf, 2) != 2) + v4l2_err(sd, "I/O error write 0x%02x/0x%02x\n", reg, val); +} + +/* ------------------------------------------------------------------------ */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static u8 upd64083_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 buf[7]; + + if (reg >= sizeof(buf)) + return 0xff; + i2c_master_recv(client, buf, sizeof(buf)); + return buf[reg]; +} +#endif + +/* ------------------------------------------------------------------------ */ + +static int upd64083_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct upd64083_state *state = to_state(sd); + u8 r00, r02; + + if (input > 7 || (input & 6) == 6) + return -EINVAL; + state->mode = (input & 3) << 6; + state->ext_y_adc = (input & UPD64083_EXT_Y_ADC) << 3; + r00 = (state->regs[R00] & ~(3 << 6)) | state->mode; + r02 = (state->regs[R02] & ~(1 << 5)) | state->ext_y_adc; + upd64083_write(sd, R00, r00); + upd64083_write(sd, R02, r02); + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int upd64083_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->val = upd64083_read(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int upd64083_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + upd64083_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static int upd64083_log_status(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 buf[7]; + + i2c_master_recv(client, buf, 7); + v4l2_info(sd, "Status: SA00=%02x SA01=%02x SA02=%02x SA03=%02x " + "SA04=%02x SA05=%02x SA06=%02x\n", + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops upd64083_core_ops = { + .log_status = upd64083_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = upd64083_g_register, + .s_register = upd64083_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops upd64083_video_ops = { + .s_routing = upd64083_s_routing, +}; + +static const struct v4l2_subdev_ops upd64083_ops = { + .core = &upd64083_core_ops, + .video = &upd64083_video_ops, +}; + +/* ------------------------------------------------------------------------ */ + +/* i2c implementation */ + +static int upd64083_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct upd64083_state *state; + struct v4l2_subdev *sd; + int i; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &upd64083_ops); + /* Initially assume that a ghost reduction chip is present */ + state->mode = 0; /* YCS mode */ + state->ext_y_adc = (1 << 5); + memcpy(state->regs, upd64083_init, TOT_REGS); + for (i = 0; i < TOT_REGS; i++) + upd64083_write(sd, i, state->regs[i]); + return 0; +} + +static int upd64083_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id upd64083_id[] = { + { "upd64083", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, upd64083_id); + +static struct i2c_driver upd64083_driver = { + .driver = { + .name = "upd64083", + }, + .probe = upd64083_probe, + .remove = upd64083_remove, + .id_table = upd64083_id, +}; + +module_i2c_driver(upd64083_driver); diff --git a/drivers/media/i2c/video-i2c.c b/drivers/media/i2c/video-i2c.c new file mode 100644 index 000000000..0465832a4 --- /dev/null +++ b/drivers/media/i2c/video-i2c.c @@ -0,0 +1,967 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * video-i2c.c - Support for I2C transport video devices + * + * Copyright (C) 2018 Matt Ranostay <matt.ranostay@konsulko.com> + * + * Supported: + * - Panasonic AMG88xx Grid-Eye Sensors + * - Melexis MLX90640 Thermal Cameras + */ + +#include <linux/delay.h> +#include <linux/freezer.h> +#include <linux/hwmon.h> +#include <linux/kthread.h> +#include <linux/i2c.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/nvmem-provider.h> +#include <linux/regmap.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-v4l2.h> +#include <media/videobuf2-vmalloc.h> + +#define VIDEO_I2C_DRIVER "video-i2c" + +struct video_i2c_chip; + +struct video_i2c_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +struct video_i2c_data { + struct regmap *regmap; + const struct video_i2c_chip *chip; + struct mutex lock; + spinlock_t slock; + unsigned int sequence; + struct mutex queue_lock; + + struct v4l2_device v4l2_dev; + struct video_device vdev; + struct vb2_queue vb_vidq; + + struct task_struct *kthread_vid_cap; + struct list_head vid_cap_active; + + struct v4l2_fract frame_interval; +}; + +static const struct v4l2_fmtdesc amg88xx_format = { + .pixelformat = V4L2_PIX_FMT_Y12, +}; + +static const struct v4l2_frmsize_discrete amg88xx_size = { + .width = 8, + .height = 8, +}; + +static const struct v4l2_fmtdesc mlx90640_format = { + .pixelformat = V4L2_PIX_FMT_Y16_BE, +}; + +static const struct v4l2_frmsize_discrete mlx90640_size = { + .width = 32, + .height = 26, /* 24 lines of pixel data + 2 lines of processing data */ +}; + +static const struct regmap_config amg88xx_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff +}; + +static const struct regmap_config mlx90640_regmap_config = { + .reg_bits = 16, + .val_bits = 16, +}; + +struct video_i2c_chip { + /* video dimensions */ + const struct v4l2_fmtdesc *format; + const struct v4l2_frmsize_discrete *size; + + /* available frame intervals */ + const struct v4l2_fract *frame_intervals; + unsigned int num_frame_intervals; + + /* pixel buffer size */ + unsigned int buffer_size; + + /* pixel size in bits */ + unsigned int bpp; + + const struct regmap_config *regmap_config; + struct nvmem_config *nvmem_config; + + /* setup function */ + int (*setup)(struct video_i2c_data *data); + + /* xfer function */ + int (*xfer)(struct video_i2c_data *data, char *buf); + + /* power control function */ + int (*set_power)(struct video_i2c_data *data, bool on); + + /* hwmon init function */ + int (*hwmon_init)(struct video_i2c_data *data); +}; + +static int mlx90640_nvram_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct video_i2c_data *data = priv; + + return regmap_bulk_read(data->regmap, 0x2400 + offset, val, bytes); +} + +static struct nvmem_config mlx90640_nvram_config = { + .name = "mlx90640_nvram", + .word_size = 2, + .stride = 1, + .size = 1664, + .reg_read = mlx90640_nvram_read, +}; + +/* Power control register */ +#define AMG88XX_REG_PCTL 0x00 +#define AMG88XX_PCTL_NORMAL 0x00 +#define AMG88XX_PCTL_SLEEP 0x10 + +/* Reset register */ +#define AMG88XX_REG_RST 0x01 +#define AMG88XX_RST_FLAG 0x30 +#define AMG88XX_RST_INIT 0x3f + +/* Frame rate register */ +#define AMG88XX_REG_FPSC 0x02 +#define AMG88XX_FPSC_1FPS BIT(0) + +/* Thermistor register */ +#define AMG88XX_REG_TTHL 0x0e + +/* Temperature register */ +#define AMG88XX_REG_T01L 0x80 + +/* Control register */ +#define MLX90640_REG_CTL1 0x800d +#define MLX90640_REG_CTL1_MASK 0x0380 +#define MLX90640_REG_CTL1_MASK_SHIFT 7 + +static int amg88xx_xfer(struct video_i2c_data *data, char *buf) +{ + return regmap_bulk_read(data->regmap, AMG88XX_REG_T01L, buf, + data->chip->buffer_size); +} + +static int mlx90640_xfer(struct video_i2c_data *data, char *buf) +{ + return regmap_bulk_read(data->regmap, 0x400, buf, + data->chip->buffer_size); +} + +static int amg88xx_setup(struct video_i2c_data *data) +{ + unsigned int mask = AMG88XX_FPSC_1FPS; + unsigned int val; + + if (data->frame_interval.numerator == data->frame_interval.denominator) + val = mask; + else + val = 0; + + return regmap_update_bits(data->regmap, AMG88XX_REG_FPSC, mask, val); +} + +static int mlx90640_setup(struct video_i2c_data *data) +{ + unsigned int n, idx; + + for (n = 0; n < data->chip->num_frame_intervals - 1; n++) { + if (V4L2_FRACT_COMPARE(data->frame_interval, ==, + data->chip->frame_intervals[n])) + break; + } + + idx = data->chip->num_frame_intervals - n - 1; + + return regmap_update_bits(data->regmap, MLX90640_REG_CTL1, + MLX90640_REG_CTL1_MASK, + idx << MLX90640_REG_CTL1_MASK_SHIFT); +} + +static int amg88xx_set_power_on(struct video_i2c_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, AMG88XX_REG_PCTL, AMG88XX_PCTL_NORMAL); + if (ret) + return ret; + + msleep(50); + + ret = regmap_write(data->regmap, AMG88XX_REG_RST, AMG88XX_RST_INIT); + if (ret) + return ret; + + usleep_range(2000, 3000); + + ret = regmap_write(data->regmap, AMG88XX_REG_RST, AMG88XX_RST_FLAG); + if (ret) + return ret; + + /* + * Wait two frames before reading thermistor and temperature registers + */ + msleep(200); + + return 0; +} + +static int amg88xx_set_power_off(struct video_i2c_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, AMG88XX_REG_PCTL, AMG88XX_PCTL_SLEEP); + if (ret) + return ret; + /* + * Wait for a while to avoid resuming normal mode immediately after + * entering sleep mode, otherwise the device occasionally goes wrong + * (thermistor and temperature registers are not updated at all) + */ + msleep(100); + + return 0; +} + +static int amg88xx_set_power(struct video_i2c_data *data, bool on) +{ + if (on) + return amg88xx_set_power_on(data); + + return amg88xx_set_power_off(data); +} + +#if IS_REACHABLE(CONFIG_HWMON) + +static const u32 amg88xx_temp_config[] = { + HWMON_T_INPUT, + 0 +}; + +static const struct hwmon_channel_info amg88xx_temp = { + .type = hwmon_temp, + .config = amg88xx_temp_config, +}; + +static const struct hwmon_channel_info *amg88xx_info[] = { + &amg88xx_temp, + NULL +}; + +static umode_t amg88xx_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +static int amg88xx_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct video_i2c_data *data = dev_get_drvdata(dev); + __le16 buf; + int tmp; + + tmp = pm_runtime_get_sync(regmap_get_device(data->regmap)); + if (tmp < 0) { + pm_runtime_put_noidle(regmap_get_device(data->regmap)); + return tmp; + } + + tmp = regmap_bulk_read(data->regmap, AMG88XX_REG_TTHL, &buf, 2); + pm_runtime_mark_last_busy(regmap_get_device(data->regmap)); + pm_runtime_put_autosuspend(regmap_get_device(data->regmap)); + if (tmp) + return tmp; + + tmp = le16_to_cpu(buf); + + /* + * Check for sign bit, this isn't a two's complement value but an + * absolute temperature that needs to be inverted in the case of being + * negative. + */ + if (tmp & BIT(11)) + tmp = -(tmp & 0x7ff); + + *val = (tmp * 625) / 10; + + return 0; +} + +static const struct hwmon_ops amg88xx_hwmon_ops = { + .is_visible = amg88xx_is_visible, + .read = amg88xx_read, +}; + +static const struct hwmon_chip_info amg88xx_chip_info = { + .ops = &amg88xx_hwmon_ops, + .info = amg88xx_info, +}; + +static int amg88xx_hwmon_init(struct video_i2c_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + void *hwmon = devm_hwmon_device_register_with_info(dev, "amg88xx", data, + &amg88xx_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwmon); +} +#else +#define amg88xx_hwmon_init NULL +#endif + +enum { + AMG88XX, + MLX90640, +}; + +static const struct v4l2_fract amg88xx_frame_intervals[] = { + { 1, 10 }, + { 1, 1 }, +}; + +static const struct v4l2_fract mlx90640_frame_intervals[] = { + { 1, 64 }, + { 1, 32 }, + { 1, 16 }, + { 1, 8 }, + { 1, 4 }, + { 1, 2 }, + { 1, 1 }, + { 2, 1 }, +}; + +static const struct video_i2c_chip video_i2c_chip[] = { + [AMG88XX] = { + .size = &amg88xx_size, + .format = &amg88xx_format, + .frame_intervals = amg88xx_frame_intervals, + .num_frame_intervals = ARRAY_SIZE(amg88xx_frame_intervals), + .buffer_size = 128, + .bpp = 16, + .regmap_config = &amg88xx_regmap_config, + .setup = &amg88xx_setup, + .xfer = &amg88xx_xfer, + .set_power = amg88xx_set_power, + .hwmon_init = amg88xx_hwmon_init, + }, + [MLX90640] = { + .size = &mlx90640_size, + .format = &mlx90640_format, + .frame_intervals = mlx90640_frame_intervals, + .num_frame_intervals = ARRAY_SIZE(mlx90640_frame_intervals), + .buffer_size = 1664, + .bpp = 16, + .regmap_config = &mlx90640_regmap_config, + .nvmem_config = &mlx90640_nvram_config, + .setup = mlx90640_setup, + .xfer = mlx90640_xfer, + }, +}; + +static const struct v4l2_file_operations video_i2c_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static int queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct video_i2c_data *data = vb2_get_drv_priv(vq); + unsigned int size = data->chip->buffer_size; + + if (vq->num_buffers + *nbuffers < 2) + *nbuffers = 2; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct video_i2c_data *data = vb2_get_drv_priv(vb->vb2_queue); + unsigned int size = data->chip->buffer_size; + + if (vb2_plane_size(vb, 0) < size) + return -EINVAL; + + vbuf->field = V4L2_FIELD_NONE; + vb2_set_plane_payload(vb, 0, size); + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct video_i2c_data *data = vb2_get_drv_priv(vb->vb2_queue); + struct video_i2c_buffer *buf = + container_of(vbuf, struct video_i2c_buffer, vb); + + spin_lock(&data->slock); + list_add_tail(&buf->list, &data->vid_cap_active); + spin_unlock(&data->slock); +} + +static int video_i2c_thread_vid_cap(void *priv) +{ + struct video_i2c_data *data = priv; + unsigned int delay = mult_frac(HZ, data->frame_interval.numerator, + data->frame_interval.denominator); + + set_freezable(); + + do { + unsigned long start_jiffies = jiffies; + struct video_i2c_buffer *vid_cap_buf = NULL; + int schedule_delay; + + try_to_freeze(); + + spin_lock(&data->slock); + + if (!list_empty(&data->vid_cap_active)) { + vid_cap_buf = list_last_entry(&data->vid_cap_active, + struct video_i2c_buffer, list); + list_del(&vid_cap_buf->list); + } + + spin_unlock(&data->slock); + + if (vid_cap_buf) { + struct vb2_buffer *vb2_buf = &vid_cap_buf->vb.vb2_buf; + void *vbuf = vb2_plane_vaddr(vb2_buf, 0); + int ret; + + ret = data->chip->xfer(data, vbuf); + vb2_buf->timestamp = ktime_get_ns(); + vid_cap_buf->vb.sequence = data->sequence++; + vb2_buffer_done(vb2_buf, ret ? + VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); + } + + schedule_delay = delay - (jiffies - start_jiffies); + + if (time_after(jiffies, start_jiffies + delay)) + schedule_delay = delay; + + schedule_timeout_interruptible(schedule_delay); + } while (!kthread_should_stop()); + + return 0; +} + +static void video_i2c_del_list(struct vb2_queue *vq, enum vb2_buffer_state state) +{ + struct video_i2c_data *data = vb2_get_drv_priv(vq); + struct video_i2c_buffer *buf, *tmp; + + spin_lock(&data->slock); + + list_for_each_entry_safe(buf, tmp, &data->vid_cap_active, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + + spin_unlock(&data->slock); +} + +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct video_i2c_data *data = vb2_get_drv_priv(vq); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + if (data->kthread_vid_cap) + return 0; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + goto error_del_list; + } + + ret = data->chip->setup(data); + if (ret) + goto error_rpm_put; + + data->sequence = 0; + data->kthread_vid_cap = kthread_run(video_i2c_thread_vid_cap, data, + "%s-vid-cap", data->v4l2_dev.name); + ret = PTR_ERR_OR_ZERO(data->kthread_vid_cap); + if (!ret) + return 0; + +error_rpm_put: + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +error_del_list: + video_i2c_del_list(vq, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void stop_streaming(struct vb2_queue *vq) +{ + struct video_i2c_data *data = vb2_get_drv_priv(vq); + + if (data->kthread_vid_cap == NULL) + return; + + kthread_stop(data->kthread_vid_cap); + data->kthread_vid_cap = NULL; + pm_runtime_mark_last_busy(regmap_get_device(data->regmap)); + pm_runtime_put_autosuspend(regmap_get_device(data->regmap)); + + video_i2c_del_list(vq, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops video_i2c_video_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int video_i2c_querycap(struct file *file, void *priv, + struct v4l2_capability *vcap) +{ + struct video_i2c_data *data = video_drvdata(file); + struct device *dev = regmap_get_device(data->regmap); + struct i2c_client *client = to_i2c_client(dev); + + strscpy(vcap->driver, data->v4l2_dev.name, sizeof(vcap->driver)); + strscpy(vcap->card, data->vdev.name, sizeof(vcap->card)); + + sprintf(vcap->bus_info, "I2C:%d-%d", client->adapter->nr, client->addr); + + return 0; +} + +static int video_i2c_g_input(struct file *file, void *fh, unsigned int *inp) +{ + *inp = 0; + + return 0; +} + +static int video_i2c_s_input(struct file *file, void *fh, unsigned int inp) +{ + return (inp > 0) ? -EINVAL : 0; +} + +static int video_i2c_enum_input(struct file *file, void *fh, + struct v4l2_input *vin) +{ + if (vin->index > 0) + return -EINVAL; + + strscpy(vin->name, "Camera", sizeof(vin->name)); + + vin->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int video_i2c_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *fmt) +{ + struct video_i2c_data *data = video_drvdata(file); + enum v4l2_buf_type type = fmt->type; + + if (fmt->index > 0) + return -EINVAL; + + *fmt = *data->chip->format; + fmt->type = type; + + return 0; +} + +static int video_i2c_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + const struct video_i2c_data *data = video_drvdata(file); + const struct v4l2_frmsize_discrete *size = data->chip->size; + + /* currently only one frame size is allowed */ + if (fsize->index > 0) + return -EINVAL; + + if (fsize->pixel_format != data->chip->format->pixelformat) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = size->width; + fsize->discrete.height = size->height; + + return 0; +} + +static int video_i2c_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fe) +{ + const struct video_i2c_data *data = video_drvdata(file); + const struct v4l2_frmsize_discrete *size = data->chip->size; + + if (fe->index >= data->chip->num_frame_intervals) + return -EINVAL; + + if (fe->width != size->width || fe->height != size->height) + return -EINVAL; + + fe->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fe->discrete = data->chip->frame_intervals[fe->index]; + + return 0; +} + +static int video_i2c_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + const struct video_i2c_data *data = video_drvdata(file); + const struct v4l2_frmsize_discrete *size = data->chip->size; + struct v4l2_pix_format *pix = &fmt->fmt.pix; + unsigned int bpp = data->chip->bpp / 8; + + pix->width = size->width; + pix->height = size->height; + pix->pixelformat = data->chip->format->pixelformat; + pix->field = V4L2_FIELD_NONE; + pix->bytesperline = pix->width * bpp; + pix->sizeimage = pix->bytesperline * pix->height; + pix->colorspace = V4L2_COLORSPACE_RAW; + + return 0; +} + +static int video_i2c_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct video_i2c_data *data = video_drvdata(file); + + if (vb2_is_busy(&data->vb_vidq)) + return -EBUSY; + + return video_i2c_try_fmt_vid_cap(file, fh, fmt); +} + +static int video_i2c_g_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + struct video_i2c_data *data = video_drvdata(filp); + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + parm->parm.capture.readbuffers = 1; + parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + parm->parm.capture.timeperframe = data->frame_interval; + + return 0; +} + +static int video_i2c_s_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + struct video_i2c_data *data = video_drvdata(filp); + int i; + + for (i = 0; i < data->chip->num_frame_intervals - 1; i++) { + if (V4L2_FRACT_COMPARE(parm->parm.capture.timeperframe, <=, + data->chip->frame_intervals[i])) + break; + } + data->frame_interval = data->chip->frame_intervals[i]; + + return video_i2c_g_parm(filp, priv, parm); +} + +static const struct v4l2_ioctl_ops video_i2c_ioctl_ops = { + .vidioc_querycap = video_i2c_querycap, + .vidioc_g_input = video_i2c_g_input, + .vidioc_s_input = video_i2c_s_input, + .vidioc_enum_input = video_i2c_enum_input, + .vidioc_enum_fmt_vid_cap = video_i2c_enum_fmt_vid_cap, + .vidioc_enum_framesizes = video_i2c_enum_framesizes, + .vidioc_enum_frameintervals = video_i2c_enum_frameintervals, + .vidioc_g_fmt_vid_cap = video_i2c_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = video_i2c_s_fmt_vid_cap, + .vidioc_g_parm = video_i2c_g_parm, + .vidioc_s_parm = video_i2c_s_parm, + .vidioc_try_fmt_vid_cap = video_i2c_try_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static void video_i2c_release(struct video_device *vdev) +{ + struct video_i2c_data *data = video_get_drvdata(vdev); + + v4l2_device_unregister(&data->v4l2_dev); + mutex_destroy(&data->lock); + mutex_destroy(&data->queue_lock); + regmap_exit(data->regmap); + kfree(data); +} + +static int video_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct video_i2c_data *data; + struct v4l2_device *v4l2_dev; + struct vb2_queue *queue; + int ret = -ENODEV; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (dev_fwnode(&client->dev)) + data->chip = device_get_match_data(&client->dev); + else if (id) + data->chip = &video_i2c_chip[id->driver_data]; + else + goto error_free_device; + + data->regmap = regmap_init_i2c(client, data->chip->regmap_config); + if (IS_ERR(data->regmap)) { + ret = PTR_ERR(data->regmap); + goto error_free_device; + } + + v4l2_dev = &data->v4l2_dev; + strscpy(v4l2_dev->name, VIDEO_I2C_DRIVER, sizeof(v4l2_dev->name)); + + ret = v4l2_device_register(&client->dev, v4l2_dev); + if (ret < 0) + goto error_regmap_exit; + + mutex_init(&data->lock); + mutex_init(&data->queue_lock); + + queue = &data->vb_vidq; + queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + queue->io_modes = VB2_DMABUF | VB2_MMAP | VB2_USERPTR | VB2_READ; + queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + queue->drv_priv = data; + queue->buf_struct_size = sizeof(struct video_i2c_buffer); + queue->min_buffers_needed = 1; + queue->ops = &video_i2c_video_qops; + queue->mem_ops = &vb2_vmalloc_memops; + + ret = vb2_queue_init(queue); + if (ret < 0) + goto error_unregister_device; + + data->vdev.queue = queue; + data->vdev.queue->lock = &data->queue_lock; + + snprintf(data->vdev.name, sizeof(data->vdev.name), + "I2C %d-%d Transport Video", + client->adapter->nr, client->addr); + + data->vdev.v4l2_dev = v4l2_dev; + data->vdev.fops = &video_i2c_fops; + data->vdev.lock = &data->lock; + data->vdev.ioctl_ops = &video_i2c_ioctl_ops; + data->vdev.release = video_i2c_release; + data->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + + spin_lock_init(&data->slock); + INIT_LIST_HEAD(&data->vid_cap_active); + + data->frame_interval = data->chip->frame_intervals[0]; + + video_set_drvdata(&data->vdev, data); + i2c_set_clientdata(client, data); + + if (data->chip->set_power) { + ret = data->chip->set_power(data, true); + if (ret) + goto error_unregister_device; + } + + pm_runtime_get_noresume(&client->dev); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, 2000); + pm_runtime_use_autosuspend(&client->dev); + + if (data->chip->hwmon_init) { + ret = data->chip->hwmon_init(data); + if (ret < 0) { + dev_warn(&client->dev, + "failed to register hwmon device\n"); + } + } + + if (data->chip->nvmem_config) { + struct nvmem_config *config = data->chip->nvmem_config; + struct nvmem_device *device; + + config->priv = data; + config->dev = &client->dev; + + device = devm_nvmem_register(&client->dev, config); + + if (IS_ERR(device)) { + dev_warn(&client->dev, + "failed to register nvmem device\n"); + } + } + + ret = video_register_device(&data->vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) + goto error_pm_disable; + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return 0; + +error_pm_disable: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + if (data->chip->set_power) + data->chip->set_power(data, false); + +error_unregister_device: + v4l2_device_unregister(v4l2_dev); + mutex_destroy(&data->lock); + mutex_destroy(&data->queue_lock); + +error_regmap_exit: + regmap_exit(data->regmap); + +error_free_device: + kfree(data); + + return ret; +} + +static int video_i2c_remove(struct i2c_client *client) +{ + struct video_i2c_data *data = i2c_get_clientdata(client); + + pm_runtime_get_sync(&client->dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + if (data->chip->set_power) + data->chip->set_power(data, false); + + video_unregister_device(&data->vdev); + + return 0; +} + +#ifdef CONFIG_PM + +static int video_i2c_pm_runtime_suspend(struct device *dev) +{ + struct video_i2c_data *data = i2c_get_clientdata(to_i2c_client(dev)); + + if (!data->chip->set_power) + return 0; + + return data->chip->set_power(data, false); +} + +static int video_i2c_pm_runtime_resume(struct device *dev) +{ + struct video_i2c_data *data = i2c_get_clientdata(to_i2c_client(dev)); + + if (!data->chip->set_power) + return 0; + + return data->chip->set_power(data, true); +} + +#endif + +static const struct dev_pm_ops video_i2c_pm_ops = { + SET_RUNTIME_PM_OPS(video_i2c_pm_runtime_suspend, + video_i2c_pm_runtime_resume, NULL) +}; + +static const struct i2c_device_id video_i2c_id_table[] = { + { "amg88xx", AMG88XX }, + { "mlx90640", MLX90640 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, video_i2c_id_table); + +static const struct of_device_id video_i2c_of_match[] = { + { .compatible = "panasonic,amg88xx", .data = &video_i2c_chip[AMG88XX] }, + { .compatible = "melexis,mlx90640", .data = &video_i2c_chip[MLX90640] }, + {} +}; +MODULE_DEVICE_TABLE(of, video_i2c_of_match); + +static struct i2c_driver video_i2c_driver = { + .driver = { + .name = VIDEO_I2C_DRIVER, + .of_match_table = video_i2c_of_match, + .pm = &video_i2c_pm_ops, + }, + .probe = video_i2c_probe, + .remove = video_i2c_remove, + .id_table = video_i2c_id_table, +}; + +module_i2c_driver(video_i2c_driver); + +MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>"); +MODULE_DESCRIPTION("I2C transport video support"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/vp27smpx.c b/drivers/media/i2c/vp27smpx.c new file mode 100644 index 000000000..492af8749 --- /dev/null +++ b/drivers/media/i2c/vp27smpx.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * vp27smpx - driver version 0.0.1 + * + * Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl> + * + * Based on a tvaudio patch from Takahiro Adachi <tadachi@tadachi-net.com> + * and Kazuhiko Kawakami <kazz-0@mail.goo.ne.jp> + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> + +MODULE_DESCRIPTION("vp27smpx driver"); +MODULE_AUTHOR("Hans Verkuil"); +MODULE_LICENSE("GPL"); + + +/* ----------------------------------------------------------------------- */ + +struct vp27smpx_state { + struct v4l2_subdev sd; + int radio; + u32 audmode; +}; + +static inline struct vp27smpx_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct vp27smpx_state, sd); +} + +static void vp27smpx_set_audmode(struct v4l2_subdev *sd, u32 audmode) +{ + struct vp27smpx_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 data[3] = { 0x00, 0x00, 0x04 }; + + switch (audmode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1_LANG2: + data[1] = 0x01; + break; + case V4L2_TUNER_MODE_LANG2: + data[1] = 0x02; + break; + } + + if (i2c_master_send(client, data, sizeof(data)) != sizeof(data)) + v4l2_err(sd, "I/O error setting audmode\n"); + else + state->audmode = audmode; +} + +static int vp27smpx_s_radio(struct v4l2_subdev *sd) +{ + struct vp27smpx_state *state = to_state(sd); + + state->radio = 1; + return 0; +} + +static int vp27smpx_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct vp27smpx_state *state = to_state(sd); + + state->radio = 0; + return 0; +} + +static int vp27smpx_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + struct vp27smpx_state *state = to_state(sd); + + if (!state->radio) + vp27smpx_set_audmode(sd, vt->audmode); + return 0; +} + +static int vp27smpx_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct vp27smpx_state *state = to_state(sd); + + if (state->radio) + return 0; + vt->audmode = state->audmode; + vt->capability = V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; + vt->rxsubchans = V4L2_TUNER_SUB_MONO; + return 0; +} + +static int vp27smpx_log_status(struct v4l2_subdev *sd) +{ + struct vp27smpx_state *state = to_state(sd); + + v4l2_info(sd, "Audio Mode: %u%s\n", state->audmode, + state->radio ? " (Radio)" : ""); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops vp27smpx_core_ops = { + .log_status = vp27smpx_log_status, +}; + +static const struct v4l2_subdev_tuner_ops vp27smpx_tuner_ops = { + .s_radio = vp27smpx_s_radio, + .s_tuner = vp27smpx_s_tuner, + .g_tuner = vp27smpx_g_tuner, +}; + +static const struct v4l2_subdev_video_ops vp27smpx_video_ops = { + .s_std = vp27smpx_s_std, +}; + +static const struct v4l2_subdev_ops vp27smpx_ops = { + .core = &vp27smpx_core_ops, + .tuner = &vp27smpx_tuner_ops, + .video = &vp27smpx_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + +/* i2c implementation */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ + +static int vp27smpx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct vp27smpx_state *state; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &vp27smpx_ops); + state->audmode = V4L2_TUNER_MODE_STEREO; + + /* initialize vp27smpx */ + vp27smpx_set_audmode(sd, state->audmode); + return 0; +} + +static int vp27smpx_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id vp27smpx_id[] = { + { "vp27smpx", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vp27smpx_id); + +static struct i2c_driver vp27smpx_driver = { + .driver = { + .name = "vp27smpx", + }, + .probe = vp27smpx_probe, + .remove = vp27smpx_remove, + .id_table = vp27smpx_id, +}; + +module_i2c_driver(vp27smpx_driver); diff --git a/drivers/media/i2c/vpx3220.c b/drivers/media/i2c/vpx3220.c new file mode 100644 index 000000000..8be03fe59 --- /dev/null +++ b/drivers/media/i2c/vpx3220.c @@ -0,0 +1,557 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * vpx3220a, vpx3216b & vpx3214c video decoder driver version 0.0.1 + * + * Copyright (C) 2001 Laurent Pinchart <lpinchart@freegates.be> + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +MODULE_DESCRIPTION("vpx3220a/vpx3216b/vpx3214c video decoder driver"); +MODULE_AUTHOR("Laurent Pinchart"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +#define VPX_TIMEOUT_COUNT 10 + +/* ----------------------------------------------------------------------- */ + +struct vpx3220 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + unsigned char reg[255]; + + v4l2_std_id norm; + int input; + int enable; +}; + +static inline struct vpx3220 *to_vpx3220(struct v4l2_subdev *sd) +{ + return container_of(sd, struct vpx3220, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct vpx3220, hdl)->sd; +} + +static char *inputs[] = { "internal", "composite", "svideo" }; + +/* ----------------------------------------------------------------------- */ + +static inline int vpx3220_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct vpx3220 *decoder = i2c_get_clientdata(client); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int vpx3220_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int vpx3220_fp_status(struct v4l2_subdev *sd) +{ + unsigned char status; + unsigned int i; + + for (i = 0; i < VPX_TIMEOUT_COUNT; i++) { + status = vpx3220_read(sd, 0x29); + + if (!(status & 4)) + return 0; + + udelay(10); + + if (need_resched()) + cond_resched(); + } + + return -1; +} + +static int vpx3220_fp_write(struct v4l2_subdev *sd, u8 fpaddr, u16 data) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* Write the 16-bit address to the FPWR register */ + if (i2c_smbus_write_word_data(client, 0x27, swab16(fpaddr)) == -1) { + v4l2_dbg(1, debug, sd, "%s: failed\n", __func__); + return -1; + } + + if (vpx3220_fp_status(sd) < 0) + return -1; + + /* Write the 16-bit data to the FPDAT register */ + if (i2c_smbus_write_word_data(client, 0x28, swab16(data)) == -1) { + v4l2_dbg(1, debug, sd, "%s: failed\n", __func__); + return -1; + } + + return 0; +} + +static int vpx3220_fp_read(struct v4l2_subdev *sd, u16 fpaddr) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + s16 data; + + /* Write the 16-bit address to the FPRD register */ + if (i2c_smbus_write_word_data(client, 0x26, swab16(fpaddr)) == -1) { + v4l2_dbg(1, debug, sd, "%s: failed\n", __func__); + return -1; + } + + if (vpx3220_fp_status(sd) < 0) + return -1; + + /* Read the 16-bit data from the FPDAT register */ + data = i2c_smbus_read_word_data(client, 0x28); + if (data == -1) { + v4l2_dbg(1, debug, sd, "%s: failed\n", __func__); + return -1; + } + + return swab16(data); +} + +static int vpx3220_write_block(struct v4l2_subdev *sd, const u8 *data, unsigned int len) +{ + u8 reg; + int ret = -1; + + while (len >= 2) { + reg = *data++; + ret = vpx3220_write(sd, reg, *data++); + if (ret < 0) + break; + len -= 2; + } + + return ret; +} + +static int vpx3220_write_fp_block(struct v4l2_subdev *sd, + const u16 *data, unsigned int len) +{ + u8 reg; + int ret = 0; + + while (len > 1) { + reg = *data++; + ret |= vpx3220_fp_write(sd, reg, *data++); + len -= 2; + } + + return ret; +} + +/* ---------------------------------------------------------------------- */ + +static const unsigned short init_ntsc[] = { + 0x1c, 0x00, /* NTSC tint angle */ + 0x88, 17, /* Window 1 vertical */ + 0x89, 240, /* Vertical lines in */ + 0x8a, 240, /* Vertical lines out */ + 0x8b, 000, /* Horizontal begin */ + 0x8c, 640, /* Horizontal length */ + 0x8d, 640, /* Number of pixels */ + 0x8f, 0xc00, /* Disable window 2 */ + 0xf0, 0x73, /* 13.5 MHz transport, Forced + * mode, latch windows */ + 0xf2, 0x13, /* NTSC M, composite input */ + 0xe7, 0x1e1, /* Enable vertical standard + * locking @ 240 lines */ +}; + +static const unsigned short init_pal[] = { + 0x88, 23, /* Window 1 vertical begin */ + 0x89, 288, /* Vertical lines in (16 lines + * skipped by the VFE) */ + 0x8a, 288, /* Vertical lines out (16 lines + * skipped by the VFE) */ + 0x8b, 16, /* Horizontal begin */ + 0x8c, 768, /* Horizontal length */ + 0x8d, 784, /* Number of pixels + * Must be >= Horizontal begin + Horizontal length */ + 0x8f, 0xc00, /* Disable window 2 */ + 0xf0, 0x77, /* 13.5 MHz transport, Forced + * mode, latch windows */ + 0xf2, 0x3d1, /* PAL B,G,H,I, composite input */ + 0xe7, 0x241, /* PAL/SECAM set to 288 lines */ +}; + +static const unsigned short init_secam[] = { + 0x88, 23, /* Window 1 vertical begin */ + 0x89, 288, /* Vertical lines in (16 lines + * skipped by the VFE) */ + 0x8a, 288, /* Vertical lines out (16 lines + * skipped by the VFE) */ + 0x8b, 16, /* Horizontal begin */ + 0x8c, 768, /* Horizontal length */ + 0x8d, 784, /* Number of pixels + * Must be >= Horizontal begin + Horizontal length */ + 0x8f, 0xc00, /* Disable window 2 */ + 0xf0, 0x77, /* 13.5 MHz transport, Forced + * mode, latch windows */ + 0xf2, 0x3d5, /* SECAM, composite input */ + 0xe7, 0x241, /* PAL/SECAM set to 288 lines */ +}; + +static const unsigned char init_common[] = { + 0xf2, 0x00, /* Disable all outputs */ + 0x33, 0x0d, /* Luma : VIN2, Chroma : CIN + * (clamp off) */ + 0xd8, 0xa8, /* HREF/VREF active high, VREF + * pulse = 2, Odd/Even flag */ + 0x20, 0x03, /* IF compensation 0dB/oct */ + 0xe0, 0xff, /* Open up all comparators */ + 0xe1, 0x00, + 0xe2, 0x7f, + 0xe3, 0x80, + 0xe4, 0x7f, + 0xe5, 0x80, + 0xe6, 0x00, /* Brightness set to 0 */ + 0xe7, 0xe0, /* Contrast to 1.0, noise shaping + * 10 to 8 2-bit error diffusion */ + 0xe8, 0xf8, /* YUV422, CbCr binary offset, + * ... (p.32) */ + 0xea, 0x18, /* LLC2 connected, output FIFO + * reset with VACTintern */ + 0xf0, 0x8a, /* Half full level to 10, bus + * shuffler [7:0, 23:16, 15:8] */ + 0xf1, 0x18, /* Single clock, sync mode, no + * FE delay, no HLEN counter */ + 0xf8, 0x12, /* Port A, PIXCLK, HF# & FE# + * strength to 2 */ + 0xf9, 0x24, /* Port B, HREF, VREF, PREF & + * ALPHA strength to 4 */ +}; + +static const unsigned short init_fp[] = { + 0x59, 0, + 0xa0, 2070, /* ACC reference */ + 0xa3, 0, + 0xa4, 0, + 0xa8, 30, + 0xb2, 768, + 0xbe, 27, + 0x58, 0, + 0x26, 0, + 0x4b, 0x298, /* PLL gain */ +}; + + +static int vpx3220_init(struct v4l2_subdev *sd, u32 val) +{ + struct vpx3220 *decoder = to_vpx3220(sd); + + vpx3220_write_block(sd, init_common, sizeof(init_common)); + vpx3220_write_fp_block(sd, init_fp, sizeof(init_fp) >> 1); + if (decoder->norm & V4L2_STD_NTSC) + vpx3220_write_fp_block(sd, init_ntsc, sizeof(init_ntsc) >> 1); + else if (decoder->norm & V4L2_STD_PAL) + vpx3220_write_fp_block(sd, init_pal, sizeof(init_pal) >> 1); + else if (decoder->norm & V4L2_STD_SECAM) + vpx3220_write_fp_block(sd, init_secam, sizeof(init_secam) >> 1); + else + vpx3220_write_fp_block(sd, init_pal, sizeof(init_pal) >> 1); + return 0; +} + +static int vpx3220_status(struct v4l2_subdev *sd, u32 *pstatus, v4l2_std_id *pstd) +{ + int res = V4L2_IN_ST_NO_SIGNAL, status; + v4l2_std_id std = pstd ? *pstd : V4L2_STD_ALL; + + status = vpx3220_fp_read(sd, 0x0f3); + + v4l2_dbg(1, debug, sd, "status: 0x%04x\n", status); + + if (status < 0) + return status; + + if ((status & 0x20) == 0) { + res = 0; + + switch (status & 0x18) { + case 0x00: + case 0x10: + case 0x14: + case 0x18: + std &= V4L2_STD_PAL; + break; + + case 0x08: + std &= V4L2_STD_SECAM; + break; + + case 0x04: + case 0x0c: + case 0x1c: + std &= V4L2_STD_NTSC; + break; + } + } else { + std = V4L2_STD_UNKNOWN; + } + if (pstd) + *pstd = std; + if (pstatus) + *pstatus = res; + return 0; +} + +static int vpx3220_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + v4l2_dbg(1, debug, sd, "querystd\n"); + return vpx3220_status(sd, NULL, std); +} + +static int vpx3220_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + v4l2_dbg(1, debug, sd, "g_input_status\n"); + return vpx3220_status(sd, status, NULL); +} + +static int vpx3220_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct vpx3220 *decoder = to_vpx3220(sd); + int temp_input; + + /* Here we back up the input selection because it gets + overwritten when we fill the registers with the + chosen video norm */ + temp_input = vpx3220_fp_read(sd, 0xf2); + + v4l2_dbg(1, debug, sd, "s_std %llx\n", (unsigned long long)std); + if (std & V4L2_STD_NTSC) { + vpx3220_write_fp_block(sd, init_ntsc, sizeof(init_ntsc) >> 1); + v4l2_dbg(1, debug, sd, "norm switched to NTSC\n"); + } else if (std & V4L2_STD_PAL) { + vpx3220_write_fp_block(sd, init_pal, sizeof(init_pal) >> 1); + v4l2_dbg(1, debug, sd, "norm switched to PAL\n"); + } else if (std & V4L2_STD_SECAM) { + vpx3220_write_fp_block(sd, init_secam, sizeof(init_secam) >> 1); + v4l2_dbg(1, debug, sd, "norm switched to SECAM\n"); + } else { + return -EINVAL; + } + + decoder->norm = std; + + /* And here we set the backed up video input again */ + vpx3220_fp_write(sd, 0xf2, temp_input | 0x0010); + udelay(10); + return 0; +} + +static int vpx3220_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + int data; + + /* RJ: input = 0: ST8 (PCTV) input + input = 1: COMPOSITE input + input = 2: SVHS input */ + + static const int input_vals[3][2] = { + {0x0c, 0}, + {0x0d, 0}, + {0x0e, 1} + }; + + if (input > 2) + return -EINVAL; + + v4l2_dbg(1, debug, sd, "input switched to %s\n", inputs[input]); + + vpx3220_write(sd, 0x33, input_vals[input][0]); + + data = vpx3220_fp_read(sd, 0xf2) & ~(0x0020); + if (data < 0) + return data; + /* 0x0010 is required to latch the setting */ + vpx3220_fp_write(sd, 0xf2, + data | (input_vals[input][1] << 5) | 0x0010); + + udelay(10); + return 0; +} + +static int vpx3220_s_stream(struct v4l2_subdev *sd, int enable) +{ + v4l2_dbg(1, debug, sd, "s_stream %s\n", enable ? "on" : "off"); + + vpx3220_write(sd, 0xf2, (enable ? 0x1b : 0x00)); + return 0; +} + +static int vpx3220_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + vpx3220_write(sd, 0xe6, ctrl->val); + return 0; + case V4L2_CID_CONTRAST: + /* Bit 7 and 8 is for noise shaping */ + vpx3220_write(sd, 0xe7, ctrl->val + 192); + return 0; + case V4L2_CID_SATURATION: + vpx3220_fp_write(sd, 0xa0, ctrl->val); + return 0; + case V4L2_CID_HUE: + vpx3220_fp_write(sd, 0x1c, ctrl->val); + return 0; + } + return -EINVAL; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops vpx3220_ctrl_ops = { + .s_ctrl = vpx3220_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops vpx3220_core_ops = { + .init = vpx3220_init, +}; + +static const struct v4l2_subdev_video_ops vpx3220_video_ops = { + .s_std = vpx3220_s_std, + .s_routing = vpx3220_s_routing, + .s_stream = vpx3220_s_stream, + .querystd = vpx3220_querystd, + .g_input_status = vpx3220_g_input_status, +}; + +static const struct v4l2_subdev_ops vpx3220_ops = { + .core = &vpx3220_core_ops, + .video = &vpx3220_video_ops, +}; + +/* ----------------------------------------------------------------------- + * Client management code + */ + +static int vpx3220_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct vpx3220 *decoder; + struct v4l2_subdev *sd; + const char *name = NULL; + u8 ver; + u16 pn; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + decoder = devm_kzalloc(&client->dev, sizeof(*decoder), GFP_KERNEL); + if (decoder == NULL) + return -ENOMEM; + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &vpx3220_ops); + decoder->norm = V4L2_STD_PAL; + decoder->input = 0; + decoder->enable = 1; + v4l2_ctrl_handler_init(&decoder->hdl, 4); + v4l2_ctrl_new_std(&decoder->hdl, &vpx3220_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(&decoder->hdl, &vpx3220_ctrl_ops, + V4L2_CID_CONTRAST, 0, 63, 1, 32); + v4l2_ctrl_new_std(&decoder->hdl, &vpx3220_ctrl_ops, + V4L2_CID_SATURATION, 0, 4095, 1, 2048); + v4l2_ctrl_new_std(&decoder->hdl, &vpx3220_ctrl_ops, + V4L2_CID_HUE, -512, 511, 1, 0); + sd->ctrl_handler = &decoder->hdl; + if (decoder->hdl.error) { + int err = decoder->hdl.error; + + v4l2_ctrl_handler_free(&decoder->hdl); + return err; + } + v4l2_ctrl_handler_setup(&decoder->hdl); + + ver = i2c_smbus_read_byte_data(client, 0x00); + pn = (i2c_smbus_read_byte_data(client, 0x02) << 8) + + i2c_smbus_read_byte_data(client, 0x01); + if (ver == 0xec) { + switch (pn) { + case 0x4680: + name = "vpx3220a"; + break; + case 0x4260: + name = "vpx3216b"; + break; + case 0x4280: + name = "vpx3214c"; + break; + } + } + if (name) + v4l2_info(sd, "%s found @ 0x%x (%s)\n", name, + client->addr << 1, client->adapter->name); + else + v4l2_info(sd, "chip (%02x:%04x) found @ 0x%x (%s)\n", + ver, pn, client->addr << 1, client->adapter->name); + + vpx3220_write_block(sd, init_common, sizeof(init_common)); + vpx3220_write_fp_block(sd, init_fp, sizeof(init_fp) >> 1); + /* Default to PAL */ + vpx3220_write_fp_block(sd, init_pal, sizeof(init_pal) >> 1); + return 0; +} + +static int vpx3220_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vpx3220 *decoder = to_vpx3220(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&decoder->hdl); + + return 0; +} + +static const struct i2c_device_id vpx3220_id[] = { + { "vpx3220a", 0 }, + { "vpx3216b", 0 }, + { "vpx3214c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vpx3220_id); + +static struct i2c_driver vpx3220_driver = { + .driver = { + .name = "vpx3220", + }, + .probe = vpx3220_probe, + .remove = vpx3220_remove, + .id_table = vpx3220_id, +}; + +module_i2c_driver(vpx3220_driver); diff --git a/drivers/media/i2c/vs6624_regs.h b/drivers/media/i2c/vs6624_regs.h new file mode 100644 index 000000000..76c9ed0f2 --- /dev/null +++ b/drivers/media/i2c/vs6624_regs.h @@ -0,0 +1,325 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * vs6624 - ST VS6624 CMOS image sensor registers + * + * Copyright (c) 2011 Analog Devices Inc. + */ + +#ifndef _VS6624_REGS_H_ +#define _VS6624_REGS_H_ + +/* low level control registers */ +#define VS6624_MICRO_EN 0xC003 /* power enable for all MCU clock */ +#define VS6624_DIO_EN 0xC044 /* enable digital I/O */ +/* device parameters */ +#define VS6624_DEV_ID_MSB 0x0001 /* device id MSB */ +#define VS6624_DEV_ID_LSB 0x0002 /* device id LSB */ +#define VS6624_FW_VSN_MAJOR 0x0004 /* firmware version major */ +#define VS6624_FW_VSN_MINOR 0x0006 /* firmware version minor */ +#define VS6624_PATCH_VSN_MAJOR 0x0008 /* patch version major */ +#define VS6624_PATCH_VSN_MINOR 0x000A /* patch version minor */ +/* host interface manager control */ +#define VS6624_USER_CMD 0x0180 /* user level control of operating states */ +/* host interface manager status */ +#define VS6624_STATE 0x0202 /* current state of the mode manager */ +/* run mode control */ +#define VS6624_METER_ON 0x0280 /* if false AE and AWB are disabled */ +/* mode setup */ +#define VS6624_ACTIVE_PIPE_SETUP 0x0302 /* select the active bank for non view live mode */ +#define VS6624_SENSOR_MODE 0x0308 /* select the different sensor mode */ +/* pipe setup bank0 */ +#define VS6624_IMAGE_SIZE0 0x0380 /* required output dimension */ +#define VS6624_MAN_HSIZE0_MSB 0x0383 /* input required manual H size MSB */ +#define VS6624_MAN_HSIZE0_LSB 0x0384 /* input required manual H size LSB */ +#define VS6624_MAN_VSIZE0_MSB 0x0387 /* input required manual V size MSB */ +#define VS6624_MAN_VSIZE0_LSB 0x0388 /* input required manual V size LSB */ +#define VS6624_ZOOM_HSTEP0_MSB 0x038B /* set the zoom H step MSB */ +#define VS6624_ZOOM_HSTEP0_LSB 0x038C /* set the zoom H step LSB */ +#define VS6624_ZOOM_VSTEP0_MSB 0x038F /* set the zoom V step MSB */ +#define VS6624_ZOOM_VSTEP0_LSB 0x0390 /* set the zoom V step LSB */ +#define VS6624_ZOOM_CTRL0 0x0392 /* control zoon in, out and stop */ +#define VS6624_PAN_HSTEP0_MSB 0x0395 /* set the pan H step MSB */ +#define VS6624_PAN_HSTEP0_LSB 0x0396 /* set the pan H step LSB */ +#define VS6624_PAN_VSTEP0_MSB 0x0399 /* set the pan V step MSB */ +#define VS6624_PAN_VSTEP0_LSB 0x039A /* set the pan V step LSB */ +#define VS6624_PAN_CTRL0 0x039C /* control pan operation */ +#define VS6624_CROP_CTRL0 0x039E /* select cropping mode */ +#define VS6624_CROP_HSTART0_MSB 0x03A1 /* set the cropping H start address MSB */ +#define VS6624_CROP_HSTART0_LSB 0x03A2 /* set the cropping H start address LSB */ +#define VS6624_CROP_HSIZE0_MSB 0x03A5 /* set the cropping H size MSB */ +#define VS6624_CROP_HSIZE0_LSB 0x03A6 /* set the cropping H size LSB */ +#define VS6624_CROP_VSTART0_MSB 0x03A9 /* set the cropping V start address MSB */ +#define VS6624_CROP_VSTART0_LSB 0x03AA /* set the cropping V start address LSB */ +#define VS6624_CROP_VSIZE0_MSB 0x03AD /* set the cropping V size MSB */ +#define VS6624_CROP_VSIZE0_LSB 0x03AE /* set the cropping V size LSB */ +#define VS6624_IMG_FMT0 0x03B0 /* select required output image format */ +#define VS6624_BAYER_OUT_ALIGN0 0x03B2 /* set bayer output alignment */ +#define VS6624_CONTRAST0 0x03B4 /* contrast control for output */ +#define VS6624_SATURATION0 0x03B6 /* saturation control for output */ +#define VS6624_GAMMA0 0x03B8 /* gamma settings */ +#define VS6624_HMIRROR0 0x03BA /* horizontal image orientation flip */ +#define VS6624_VFLIP0 0x03BC /* vertical image orientation flip */ +#define VS6624_CHANNEL_ID0 0x03BE /* logical DMA channel number */ +/* pipe setup bank1 */ +#define VS6624_IMAGE_SIZE1 0x0400 /* required output dimension */ +#define VS6624_MAN_HSIZE1_MSB 0x0403 /* input required manual H size MSB */ +#define VS6624_MAN_HSIZE1_LSB 0x0404 /* input required manual H size LSB */ +#define VS6624_MAN_VSIZE1_MSB 0x0407 /* input required manual V size MSB */ +#define VS6624_MAN_VSIZE1_LSB 0x0408 /* input required manual V size LSB */ +#define VS6624_ZOOM_HSTEP1_MSB 0x040B /* set the zoom H step MSB */ +#define VS6624_ZOOM_HSTEP1_LSB 0x040C /* set the zoom H step LSB */ +#define VS6624_ZOOM_VSTEP1_MSB 0x040F /* set the zoom V step MSB */ +#define VS6624_ZOOM_VSTEP1_LSB 0x0410 /* set the zoom V step LSB */ +#define VS6624_ZOOM_CTRL1 0x0412 /* control zoon in, out and stop */ +#define VS6624_PAN_HSTEP1_MSB 0x0415 /* set the pan H step MSB */ +#define VS6624_PAN_HSTEP1_LSB 0x0416 /* set the pan H step LSB */ +#define VS6624_PAN_VSTEP1_MSB 0x0419 /* set the pan V step MSB */ +#define VS6624_PAN_VSTEP1_LSB 0x041A /* set the pan V step LSB */ +#define VS6624_PAN_CTRL1 0x041C /* control pan operation */ +#define VS6624_CROP_CTRL1 0x041E /* select cropping mode */ +#define VS6624_CROP_HSTART1_MSB 0x0421 /* set the cropping H start address MSB */ +#define VS6624_CROP_HSTART1_LSB 0x0422 /* set the cropping H start address LSB */ +#define VS6624_CROP_HSIZE1_MSB 0x0425 /* set the cropping H size MSB */ +#define VS6624_CROP_HSIZE1_LSB 0x0426 /* set the cropping H size LSB */ +#define VS6624_CROP_VSTART1_MSB 0x0429 /* set the cropping V start address MSB */ +#define VS6624_CROP_VSTART1_LSB 0x042A /* set the cropping V start address LSB */ +#define VS6624_CROP_VSIZE1_MSB 0x042D /* set the cropping V size MSB */ +#define VS6624_CROP_VSIZE1_LSB 0x042E /* set the cropping V size LSB */ +#define VS6624_IMG_FMT1 0x0430 /* select required output image format */ +#define VS6624_BAYER_OUT_ALIGN1 0x0432 /* set bayer output alignment */ +#define VS6624_CONTRAST1 0x0434 /* contrast control for output */ +#define VS6624_SATURATION1 0x0436 /* saturation control for output */ +#define VS6624_GAMMA1 0x0438 /* gamma settings */ +#define VS6624_HMIRROR1 0x043A /* horizontal image orientation flip */ +#define VS6624_VFLIP1 0x043C /* vertical image orientation flip */ +#define VS6624_CHANNEL_ID1 0x043E /* logical DMA channel number */ +/* view live control */ +#define VS6624_VIEW_LIVE_EN 0x0480 /* enable view live mode */ +#define VS6624_INIT_PIPE_SETUP 0x0482 /* select initial pipe setup bank */ +/* view live status */ +#define VS6624_CUR_PIPE_SETUP 0x0500 /* indicates most recently applied setup bank */ +/* power management */ +#define VS6624_TIME_TO_POWER_DOWN 0x0580 /* automatically transition time to stop mode */ +/* video timing parameter host inputs */ +#define VS6624_EXT_CLK_FREQ_NUM_MSB 0x0605 /* external clock frequency numerator MSB */ +#define VS6624_EXT_CLK_FREQ_NUM_LSB 0x0606 /* external clock frequency numerator LSB */ +#define VS6624_EXT_CLK_FREQ_DEN 0x0608 /* external clock frequency denominator */ +/* video timing control */ +#define VS6624_SYS_CLK_MODE 0x0880 /* decides system clock frequency */ +/* frame dimension parameter host inputs */ +#define VS6624_LIGHT_FREQ 0x0C80 /* AC frequency used for flicker free time */ +#define VS6624_FLICKER_COMPAT 0x0C82 /* flicker compatible frame length */ +/* static frame rate control */ +#define VS6624_FR_NUM_MSB 0x0D81 /* desired frame rate numerator MSB */ +#define VS6624_FR_NUM_LSB 0x0D82 /* desired frame rate numerator LSB */ +#define VS6624_FR_DEN 0x0D84 /* desired frame rate denominator */ +/* automatic frame rate control */ +#define VS6624_DISABLE_FR_DAMPER 0x0E80 /* defines frame rate mode */ +#define VS6624_MIN_DAMPER_OUT_MSB 0x0E8C /* minimum frame rate MSB */ +#define VS6624_MIN_DAMPER_OUT_LSB 0x0E8A /* minimum frame rate LSB */ +/* exposure controls */ +#define VS6624_EXPO_MODE 0x1180 /* exposure mode */ +#define VS6624_EXPO_METER 0x1182 /* weights to be associated with the zones */ +#define VS6624_EXPO_TIME_NUM 0x1184 /* exposure time numerator */ +#define VS6624_EXPO_TIME_DEN 0x1186 /* exposure time denominator */ +#define VS6624_EXPO_TIME_MSB 0x1189 /* exposure time for the Manual Mode MSB */ +#define VS6624_EXPO_TIME_LSB 0x118A /* exposure time for the Manual Mode LSB */ +#define VS6624_EXPO_COMPENSATION 0x1190 /* exposure compensation */ +#define VS6624_DIRECT_COARSE_MSB 0x1195 /* coarse integration lines for Direct Mode MSB */ +#define VS6624_DIRECT_COARSE_LSB 0x1196 /* coarse integration lines for Direct Mode LSB */ +#define VS6624_DIRECT_FINE_MSB 0x1199 /* fine integration pixels for Direct Mode MSB */ +#define VS6624_DIRECT_FINE_LSB 0x119A /* fine integration pixels for Direct Mode LSB */ +#define VS6624_DIRECT_ANAL_GAIN_MSB 0x119D /* analog gain for Direct Mode MSB */ +#define VS6624_DIRECT_ANAL_GAIN_LSB 0x119E /* analog gain for Direct Mode LSB */ +#define VS6624_DIRECT_DIGI_GAIN_MSB 0x11A1 /* digital gain for Direct Mode MSB */ +#define VS6624_DIRECT_DIGI_GAIN_LSB 0x11A2 /* digital gain for Direct Mode LSB */ +#define VS6624_FLASH_COARSE_MSB 0x11A5 /* coarse integration lines for Flash Gun Mode MSB */ +#define VS6624_FLASH_COARSE_LSB 0x11A6 /* coarse integration lines for Flash Gun Mode LSB */ +#define VS6624_FLASH_FINE_MSB 0x11A9 /* fine integration pixels for Flash Gun Mode MSB */ +#define VS6624_FLASH_FINE_LSB 0x11AA /* fine integration pixels for Flash Gun Mode LSB */ +#define VS6624_FLASH_ANAL_GAIN_MSB 0x11AD /* analog gain for Flash Gun Mode MSB */ +#define VS6624_FLASH_ANAL_GAIN_LSB 0x11AE /* analog gain for Flash Gun Mode LSB */ +#define VS6624_FLASH_DIGI_GAIN_MSB 0x11B1 /* digital gain for Flash Gun Mode MSB */ +#define VS6624_FLASH_DIGI_GAIN_LSB 0x11B2 /* digital gain for Flash Gun Mode LSB */ +#define VS6624_FREEZE_AE 0x11B4 /* freeze auto exposure */ +#define VS6624_MAX_INT_TIME_MSB 0x11B7 /* user maximum integration time MSB */ +#define VS6624_MAX_INT_TIME_LSB 0x11B8 /* user maximum integration time LSB */ +#define VS6624_FLASH_AG_THR_MSB 0x11BB /* recommend flash gun analog gain threshold MSB */ +#define VS6624_FLASH_AG_THR_LSB 0x11BC /* recommend flash gun analog gain threshold LSB */ +#define VS6624_ANTI_FLICKER_MODE 0x11C0 /* anti flicker mode */ +/* white balance control */ +#define VS6624_WB_MODE 0x1480 /* set white balance mode */ +#define VS6624_MAN_RG 0x1482 /* user setting for red channel gain */ +#define VS6624_MAN_GG 0x1484 /* user setting for green channel gain */ +#define VS6624_MAN_BG 0x1486 /* user setting for blue channel gain */ +#define VS6624_FLASH_RG_MSB 0x148B /* red gain for Flash Gun MSB */ +#define VS6624_FLASH_RG_LSB 0x148C /* red gain for Flash Gun LSB */ +#define VS6624_FLASH_GG_MSB 0x148F /* green gain for Flash Gun MSB */ +#define VS6624_FLASH_GG_LSB 0x1490 /* green gain for Flash Gun LSB */ +#define VS6624_FLASH_BG_MSB 0x1493 /* blue gain for Flash Gun MSB */ +#define VS6624_FLASH_BG_LSB 0x1494 /* blue gain for Flash Gun LSB */ +/* sensor setup */ +#define VS6624_BC_OFFSET 0x1990 /* Black Correction Offset */ +/* image stability */ +#define VS6624_STABLE_WB 0x1900 /* white balance stable */ +#define VS6624_STABLE_EXPO 0x1902 /* exposure stable */ +#define VS6624_STABLE 0x1906 /* system stable */ +/* flash control */ +#define VS6624_FLASH_MODE 0x1A80 /* flash mode */ +#define VS6624_FLASH_OFF_LINE_MSB 0x1A83 /* off line at flash pulse mode MSB */ +#define VS6624_FLASH_OFF_LINE_LSB 0x1A84 /* off line at flash pulse mode LSB */ +/* flash status */ +#define VS6624_FLASH_RECOM 0x1B00 /* flash gun is recommended */ +#define VS6624_FLASH_GRAB_COMPLETE 0x1B02 /* flash gun image has been grabbed */ +/* scythe filter controls */ +#define VS6624_SCYTHE_FILTER 0x1D80 /* disable scythe defect correction */ +/* jack filter controls */ +#define VS6624_JACK_FILTER 0x1E00 /* disable jack defect correction */ +/* demosaic control */ +#define VS6624_ANTI_ALIAS_FILTER 0x1E80 /* anti alias filter suppress */ +/* color matrix dampers */ +#define VS6624_CM_DISABLE 0x1F00 /* disable color matrix damper */ +#define VS6624_CM_LOW_THR_MSB 0x1F03 /* low threshold for exposure MSB */ +#define VS6624_CM_LOW_THR_LSB 0x1F04 /* low threshold for exposure LSB */ +#define VS6624_CM_HIGH_THR_MSB 0x1F07 /* high threshold for exposure MSB */ +#define VS6624_CM_HIGH_THR_LSB 0x1F08 /* high threshold for exposure LSB */ +#define VS6624_CM_MIN_OUT_MSB 0x1F0B /* minimum possible damper output MSB */ +#define VS6624_CM_MIN_OUT_LSB 0x1F0C /* minimum possible damper output LSB */ +/* peaking control */ +#define VS6624_PEAK_GAIN 0x2000 /* controls peaking gain */ +#define VS6624_PEAK_G_DISABLE 0x2002 /* disable peak gain damping */ +#define VS6624_PEAK_LOW_THR_G_MSB 0x2005 /* low threshold for exposure for gain MSB */ +#define VS6624_PEAK_LOW_THR_G_LSB 0x2006 /* low threshold for exposure for gain LSB */ +#define VS6624_PEAK_HIGH_THR_G_MSB 0x2009 /* high threshold for exposure for gain MSB */ +#define VS6624_PEAK_HIGH_THR_G_LSB 0x200A /* high threshold for exposure for gain LSB */ +#define VS6624_PEAK_MIN_OUT_G_MSB 0x200D /* minimum damper output for gain MSB */ +#define VS6624_PEAK_MIN_OUT_G_LSB 0x200E /* minimum damper output for gain LSB */ +#define VS6624_PEAK_LOW_THR 0x2010 /* adjust degree of coring */ +#define VS6624_PEAK_C_DISABLE 0x2012 /* disable coring damping */ +#define VS6624_PEAK_HIGH_THR 0x2014 /* adjust maximum gain */ +#define VS6624_PEAK_LOW_THR_C_MSB 0x2017 /* low threshold for exposure for coring MSB */ +#define VS6624_PEAK_LOW_THR_C_LSB 0x2018 /* low threshold for exposure for coring LSB */ +#define VS6624_PEAK_HIGH_THR_C_MSB 0x201B /* high threshold for exposure for coring MSB */ +#define VS6624_PEAK_HIGH_THR_C_LSB 0x201C /* high threshold for exposure for coring LSB */ +#define VS6624_PEAK_MIN_OUT_C_MSB 0x201F /* minimum damper output for coring MSB */ +#define VS6624_PEAK_MIN_OUT_C_LSB 0x2020 /* minimum damper output for coring LSB */ +/* pipe 0 RGB to YUV matrix manual control */ +#define VS6624_RYM0_MAN_CTRL 0x2180 /* enable manual RGB to YUV matrix */ +#define VS6624_RYM0_W00_MSB 0x2183 /* row 0 column 0 of YUV matrix MSB */ +#define VS6624_RYM0_W00_LSB 0x2184 /* row 0 column 0 of YUV matrix LSB */ +#define VS6624_RYM0_W01_MSB 0x2187 /* row 0 column 1 of YUV matrix MSB */ +#define VS6624_RYM0_W01_LSB 0x2188 /* row 0 column 1 of YUV matrix LSB */ +#define VS6624_RYM0_W02_MSB 0x218C /* row 0 column 2 of YUV matrix MSB */ +#define VS6624_RYM0_W02_LSB 0x218D /* row 0 column 2 of YUV matrix LSB */ +#define VS6624_RYM0_W10_MSB 0x2190 /* row 1 column 0 of YUV matrix MSB */ +#define VS6624_RYM0_W10_LSB 0x218F /* row 1 column 0 of YUV matrix LSB */ +#define VS6624_RYM0_W11_MSB 0x2193 /* row 1 column 1 of YUV matrix MSB */ +#define VS6624_RYM0_W11_LSB 0x2194 /* row 1 column 1 of YUV matrix LSB */ +#define VS6624_RYM0_W12_MSB 0x2197 /* row 1 column 2 of YUV matrix MSB */ +#define VS6624_RYM0_W12_LSB 0x2198 /* row 1 column 2 of YUV matrix LSB */ +#define VS6624_RYM0_W20_MSB 0x219B /* row 2 column 0 of YUV matrix MSB */ +#define VS6624_RYM0_W20_LSB 0x219C /* row 2 column 0 of YUV matrix LSB */ +#define VS6624_RYM0_W21_MSB 0x21A0 /* row 2 column 1 of YUV matrix MSB */ +#define VS6624_RYM0_W21_LSB 0x219F /* row 2 column 1 of YUV matrix LSB */ +#define VS6624_RYM0_W22_MSB 0x21A3 /* row 2 column 2 of YUV matrix MSB */ +#define VS6624_RYM0_W22_LSB 0x21A4 /* row 2 column 2 of YUV matrix LSB */ +#define VS6624_RYM0_YINY_MSB 0x21A7 /* Y in Y MSB */ +#define VS6624_RYM0_YINY_LSB 0x21A8 /* Y in Y LSB */ +#define VS6624_RYM0_YINCB_MSB 0x21AB /* Y in Cb MSB */ +#define VS6624_RYM0_YINCB_LSB 0x21AC /* Y in Cb LSB */ +#define VS6624_RYM0_YINCR_MSB 0x21B0 /* Y in Cr MSB */ +#define VS6624_RYM0_YINCR_LSB 0x21AF /* Y in Cr LSB */ +/* pipe 1 RGB to YUV matrix manual control */ +#define VS6624_RYM1_MAN_CTRL 0x2200 /* enable manual RGB to YUV matrix */ +#define VS6624_RYM1_W00_MSB 0x2203 /* row 0 column 0 of YUV matrix MSB */ +#define VS6624_RYM1_W00_LSB 0x2204 /* row 0 column 0 of YUV matrix LSB */ +#define VS6624_RYM1_W01_MSB 0x2207 /* row 0 column 1 of YUV matrix MSB */ +#define VS6624_RYM1_W01_LSB 0x2208 /* row 0 column 1 of YUV matrix LSB */ +#define VS6624_RYM1_W02_MSB 0x220C /* row 0 column 2 of YUV matrix MSB */ +#define VS6624_RYM1_W02_LSB 0x220D /* row 0 column 2 of YUV matrix LSB */ +#define VS6624_RYM1_W10_MSB 0x2210 /* row 1 column 0 of YUV matrix MSB */ +#define VS6624_RYM1_W10_LSB 0x220F /* row 1 column 0 of YUV matrix LSB */ +#define VS6624_RYM1_W11_MSB 0x2213 /* row 1 column 1 of YUV matrix MSB */ +#define VS6624_RYM1_W11_LSB 0x2214 /* row 1 column 1 of YUV matrix LSB */ +#define VS6624_RYM1_W12_MSB 0x2217 /* row 1 column 2 of YUV matrix MSB */ +#define VS6624_RYM1_W12_LSB 0x2218 /* row 1 column 2 of YUV matrix LSB */ +#define VS6624_RYM1_W20_MSB 0x221B /* row 2 column 0 of YUV matrix MSB */ +#define VS6624_RYM1_W20_LSB 0x221C /* row 2 column 0 of YUV matrix LSB */ +#define VS6624_RYM1_W21_MSB 0x2220 /* row 2 column 1 of YUV matrix MSB */ +#define VS6624_RYM1_W21_LSB 0x221F /* row 2 column 1 of YUV matrix LSB */ +#define VS6624_RYM1_W22_MSB 0x2223 /* row 2 column 2 of YUV matrix MSB */ +#define VS6624_RYM1_W22_LSB 0x2224 /* row 2 column 2 of YUV matrix LSB */ +#define VS6624_RYM1_YINY_MSB 0x2227 /* Y in Y MSB */ +#define VS6624_RYM1_YINY_LSB 0x2228 /* Y in Y LSB */ +#define VS6624_RYM1_YINCB_MSB 0x222B /* Y in Cb MSB */ +#define VS6624_RYM1_YINCB_LSB 0x222C /* Y in Cb LSB */ +#define VS6624_RYM1_YINCR_MSB 0x2220 /* Y in Cr MSB */ +#define VS6624_RYM1_YINCR_LSB 0x222F /* Y in Cr LSB */ +/* pipe 0 gamma manual control */ +#define VS6624_GAMMA_MAN_CTRL0 0x2280 /* enable manual gamma setup */ +#define VS6624_GAMMA_PEAK_R0 0x2282 /* peaked red channel gamma value */ +#define VS6624_GAMMA_PEAK_G0 0x2284 /* peaked green channel gamma value */ +#define VS6624_GAMMA_PEAK_B0 0x2286 /* peaked blue channel gamma value */ +#define VS6624_GAMMA_UNPEAK_R0 0x2288 /* unpeaked red channel gamma value */ +#define VS6624_GAMMA_UNPEAK_G0 0x228A /* unpeaked green channel gamma value */ +#define VS6624_GAMMA_UNPEAK_B0 0x228C /* unpeaked blue channel gamma value */ +/* pipe 1 gamma manual control */ +#define VS6624_GAMMA_MAN_CTRL1 0x2300 /* enable manual gamma setup */ +#define VS6624_GAMMA_PEAK_R1 0x2302 /* peaked red channel gamma value */ +#define VS6624_GAMMA_PEAK_G1 0x2304 /* peaked green channel gamma value */ +#define VS6624_GAMMA_PEAK_B1 0x2306 /* peaked blue channel gamma value */ +#define VS6624_GAMMA_UNPEAK_R1 0x2308 /* unpeaked red channel gamma value */ +#define VS6624_GAMMA_UNPEAK_G1 0x230A /* unpeaked green channel gamma value */ +#define VS6624_GAMMA_UNPEAK_B1 0x230C /* unpeaked blue channel gamma value */ +/* fade to black */ +#define VS6624_F2B_DISABLE 0x2480 /* disable fade to black */ +#define VS6624_F2B_BLACK_VAL_MSB 0x2483 /* black value MSB */ +#define VS6624_F2B_BLACK_VAL_LSB 0x2484 /* black value LSB */ +#define VS6624_F2B_LOW_THR_MSB 0x2487 /* low threshold for exposure MSB */ +#define VS6624_F2B_LOW_THR_LSB 0x2488 /* low threshold for exposure LSB */ +#define VS6624_F2B_HIGH_THR_MSB 0x248B /* high threshold for exposure MSB */ +#define VS6624_F2B_HIGH_THR_LSB 0x248C /* high threshold for exposure LSB */ +#define VS6624_F2B_MIN_OUT_MSB 0x248F /* minimum damper output MSB */ +#define VS6624_F2B_MIN_OUT_LSB 0x2490 /* minimum damper output LSB */ +/* output formatter control */ +#define VS6624_CODE_CK_EN 0x2580 /* code check enable */ +#define VS6624_BLANK_FMT 0x2582 /* blank format */ +#define VS6624_SYNC_CODE_SETUP 0x2584 /* sync code setup */ +#define VS6624_HSYNC_SETUP 0x2586 /* H sync setup */ +#define VS6624_VSYNC_SETUP 0x2588 /* V sync setup */ +#define VS6624_PCLK_SETUP 0x258A /* PCLK setup */ +#define VS6624_PCLK_EN 0x258C /* PCLK enable */ +#define VS6624_OPF_SP_SETUP 0x258E /* output formatter sp setup */ +#define VS6624_BLANK_DATA_MSB 0x2590 /* blank data MSB */ +#define VS6624_BLANK_DATA_LSB 0x2592 /* blank data LSB */ +#define VS6624_RGB_SETUP 0x2594 /* RGB setup */ +#define VS6624_YUV_SETUP 0x2596 /* YUV setup */ +#define VS6624_VSYNC_RIS_COARSE_H 0x2598 /* V sync rising coarse high */ +#define VS6624_VSYNC_RIS_COARSE_L 0x259A /* V sync rising coarse low */ +#define VS6624_VSYNC_RIS_FINE_H 0x259C /* V sync rising fine high */ +#define VS6624_VSYNC_RIS_FINE_L 0x259E /* V sync rising fine low */ +#define VS6624_VSYNC_FALL_COARSE_H 0x25A0 /* V sync falling coarse high */ +#define VS6624_VSYNC_FALL_COARSE_L 0x25A2 /* V sync falling coarse low */ +#define VS6624_VSYNC_FALL_FINE_H 0x25A4 /* V sync falling fine high */ +#define VS6624_VSYNC_FALL_FINE_L 0x25A6 /* V sync falling fine low */ +#define VS6624_HSYNC_RIS_H 0x25A8 /* H sync rising high */ +#define VS6624_HSYNC_RIS_L 0x25AA /* H sync rising low */ +#define VS6624_HSYNC_FALL_H 0x25AC /* H sync falling high */ +#define VS6624_HSYNC_FALL_L 0x25AE /* H sync falling low */ +#define VS6624_OUT_IF 0x25B0 /* output interface */ +#define VS6624_CCP_EXT_DATA 0x25B2 /* CCP extra data */ +/* NoRA controls */ +#define VS6624_NORA_DISABLE 0x2600 /* NoRA control mode */ +#define VS6624_NORA_USAGE 0x2602 /* usage */ +#define VS6624_NORA_SPLIT_KN 0x2604 /* split kn */ +#define VS6624_NORA_SPLIT_NI 0x2606 /* split ni */ +#define VS6624_NORA_TIGHT_G 0x2608 /* tight green */ +#define VS6624_NORA_DISABLE_NP 0x260A /* disable noro promoting */ +#define VS6624_NORA_LOW_THR_MSB 0x260D /* low threshold for exposure MSB */ +#define VS6624_NORA_LOW_THR_LSB 0x260E /* low threshold for exposure LSB */ +#define VS6624_NORA_HIGH_THR_MSB 0x2611 /* high threshold for exposure MSB */ +#define VS6624_NORA_HIGH_THR_LSB 0x2612 /* high threshold for exposure LSB */ +#define VS6624_NORA_MIN_OUT_MSB 0x2615 /* minimum damper output MSB */ +#define VS6624_NORA_MIN_OUT_LSB 0x2616 /* minimum damper output LSB */ + +#endif diff --git a/drivers/media/i2c/wm8739.c b/drivers/media/i2c/wm8739.c new file mode 100644 index 000000000..ed533834d --- /dev/null +++ b/drivers/media/i2c/wm8739.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * wm8739 + * + * Copyright (C) 2005 T. Adachi <tadachi@tadachi-net.com> + * + * Copyright (C) 2005 Hans Verkuil <hverkuil@xs4all.nl> + * - Cleanup + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +MODULE_DESCRIPTION("wm8739 driver"); +MODULE_AUTHOR("T. Adachi, Hans Verkuil"); +MODULE_LICENSE("GPL"); + +static int debug; + +module_param(debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* ------------------------------------------------------------------------ */ + +enum { + R0 = 0, R1, + R5 = 5, R6, R7, R8, R9, R15 = 15, + TOT_REGS +}; + +struct wm8739_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct { + /* audio cluster */ + struct v4l2_ctrl *volume; + struct v4l2_ctrl *mute; + struct v4l2_ctrl *balance; + }; + u32 clock_freq; +}; + +static inline struct wm8739_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct wm8739_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct wm8739_state, hdl)->sd; +} + +/* ------------------------------------------------------------------------ */ + +static int wm8739_write(struct v4l2_subdev *sd, int reg, u16 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int i; + + if (reg < 0 || reg >= TOT_REGS) { + v4l2_err(sd, "Invalid register R%d\n", reg); + return -1; + } + + v4l2_dbg(1, debug, sd, "write: %02x %02x\n", reg, val); + + for (i = 0; i < 3; i++) + if (i2c_smbus_write_byte_data(client, + (reg << 1) | (val >> 8), val & 0xff) == 0) + return 0; + v4l2_err(sd, "I2C: cannot write %03x to register R%d\n", val, reg); + return -1; +} + +static int wm8739_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct wm8739_state *state = to_state(sd); + unsigned int work_l, work_r; + u8 vol_l; /* +12dB to -34.5dB 1.5dB step (5bit) def:0dB */ + u8 vol_r; /* +12dB to -34.5dB 1.5dB step (5bit) def:0dB */ + u16 mute; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + break; + + default: + return -EINVAL; + } + + /* normalize ( 65535 to 0 -> 31 to 0 (12dB to -34.5dB) ) */ + work_l = (min(65536 - state->balance->val, 32768) * state->volume->val) / 32768; + work_r = (min(state->balance->val, 32768) * state->volume->val) / 32768; + + vol_l = (long)work_l * 31 / 65535; + vol_r = (long)work_r * 31 / 65535; + + /* set audio volume etc. */ + mute = state->mute->val ? 0x80 : 0; + + /* Volume setting: bits 0-4, 0x1f = 12 dB, 0x00 = -34.5 dB + * Default setting: 0x17 = 0 dB + */ + wm8739_write(sd, R0, (vol_l & 0x1f) | mute); + wm8739_write(sd, R1, (vol_r & 0x1f) | mute); + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static int wm8739_s_clock_freq(struct v4l2_subdev *sd, u32 audiofreq) +{ + struct wm8739_state *state = to_state(sd); + + state->clock_freq = audiofreq; + /* de-activate */ + wm8739_write(sd, R9, 0x000); + switch (audiofreq) { + case 44100: + /* 256fps, fs=44.1k */ + wm8739_write(sd, R8, 0x020); + break; + case 48000: + /* 256fps, fs=48k */ + wm8739_write(sd, R8, 0x000); + break; + case 32000: + /* 256fps, fs=32k */ + wm8739_write(sd, R8, 0x018); + break; + default: + break; + } + /* activate */ + wm8739_write(sd, R9, 0x001); + return 0; +} + +static int wm8739_log_status(struct v4l2_subdev *sd) +{ + struct wm8739_state *state = to_state(sd); + + v4l2_info(sd, "Frequency: %u Hz\n", state->clock_freq); + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops wm8739_ctrl_ops = { + .s_ctrl = wm8739_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops wm8739_core_ops = { + .log_status = wm8739_log_status, +}; + +static const struct v4l2_subdev_audio_ops wm8739_audio_ops = { + .s_clock_freq = wm8739_s_clock_freq, +}; + +static const struct v4l2_subdev_ops wm8739_ops = { + .core = &wm8739_core_ops, + .audio = &wm8739_audio_ops, +}; + +/* ------------------------------------------------------------------------ */ + +/* i2c implementation */ + +static int wm8739_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wm8739_state *state; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &wm8739_ops); + v4l2_ctrl_handler_init(&state->hdl, 2); + state->volume = v4l2_ctrl_new_std(&state->hdl, &wm8739_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, 65535, 65535 / 100, 50736); + state->mute = v4l2_ctrl_new_std(&state->hdl, &wm8739_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + state->balance = v4l2_ctrl_new_std(&state->hdl, &wm8739_ctrl_ops, + V4L2_CID_AUDIO_BALANCE, 0, 65535, 65535 / 100, 32768); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + v4l2_ctrl_cluster(3, &state->volume); + + state->clock_freq = 48000; + + /* Initialize wm8739 */ + + /* reset */ + wm8739_write(sd, R15, 0x00); + /* filter setting, high path, offet clear */ + wm8739_write(sd, R5, 0x000); + /* ADC, OSC, Power Off mode Disable */ + wm8739_write(sd, R6, 0x000); + /* Digital Audio interface format: + Enable Master mode, 24 bit, MSB first/left justified */ + wm8739_write(sd, R7, 0x049); + /* sampling control: normal, 256fs, 48KHz sampling rate */ + wm8739_write(sd, R8, 0x000); + /* activate */ + wm8739_write(sd, R9, 0x001); + /* set volume/mute */ + v4l2_ctrl_handler_setup(&state->hdl); + return 0; +} + +static int wm8739_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct wm8739_state *state = to_state(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); + return 0; +} + +static const struct i2c_device_id wm8739_id[] = { + { "wm8739", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8739_id); + +static struct i2c_driver wm8739_driver = { + .driver = { + .name = "wm8739", + }, + .probe = wm8739_probe, + .remove = wm8739_remove, + .id_table = wm8739_id, +}; + +module_i2c_driver(wm8739_driver); diff --git a/drivers/media/i2c/wm8775.c b/drivers/media/i2c/wm8775.c new file mode 100644 index 000000000..d4c83c398 --- /dev/null +++ b/drivers/media/i2c/wm8775.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * wm8775 - driver version 0.0.1 + * + * Copyright (C) 2004 Ulf Eklund <ivtv at eklund.to> + * + * Based on saa7115 driver + * + * Copyright (C) 2005 Hans Verkuil <hverkuil@xs4all.nl> + * - Cleanup + * - V4L2 API update + * - sound fixes + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/i2c/wm8775.h> + +MODULE_DESCRIPTION("wm8775 driver"); +MODULE_AUTHOR("Ulf Eklund, Hans Verkuil"); +MODULE_LICENSE("GPL"); + + + +/* ----------------------------------------------------------------------- */ + +enum { + R7 = 7, R11 = 11, + R12, R13, R14, R15, R16, R17, R18, R19, R20, R21, R23 = 23, + TOT_REGS +}; + +#define ALC_HOLD 0x85 /* R17: use zero cross detection, ALC hold time 42.6 ms */ +#define ALC_EN 0x100 /* R17: ALC enable */ + +struct wm8775_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *mute; + struct v4l2_ctrl *vol; + struct v4l2_ctrl *bal; + struct v4l2_ctrl *loud; + u8 input; /* Last selected input (0-0xf) */ +}; + +static inline struct wm8775_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct wm8775_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct wm8775_state, hdl)->sd; +} + +static int wm8775_write(struct v4l2_subdev *sd, int reg, u16 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int i; + + if (reg < 0 || reg >= TOT_REGS) { + v4l2_err(sd, "Invalid register R%d\n", reg); + return -1; + } + + for (i = 0; i < 3; i++) + if (i2c_smbus_write_byte_data(client, + (reg << 1) | (val >> 8), val & 0xff) == 0) + return 0; + v4l2_err(sd, "I2C: cannot write %03x to register R%d\n", val, reg); + return -1; +} + +static void wm8775_set_audio(struct v4l2_subdev *sd, int quietly) +{ + struct wm8775_state *state = to_state(sd); + u8 vol_l, vol_r; + int muted = 0 != state->mute->val; + u16 volume = (u16)state->vol->val; + u16 balance = (u16)state->bal->val; + + /* normalize ( 65535 to 0 -> 255 to 0 (+24dB to -103dB) ) */ + vol_l = (min(65536 - balance, 32768) * volume) >> 23; + vol_r = (min(balance, (u16)32768) * volume) >> 23; + + /* Mute */ + if (muted || quietly) + wm8775_write(sd, R21, 0x0c0 | state->input); + + wm8775_write(sd, R14, vol_l | 0x100); /* 0x100= Left channel ADC zero cross enable */ + wm8775_write(sd, R15, vol_r | 0x100); /* 0x100= Right channel ADC zero cross enable */ + + /* Un-mute */ + if (!muted) + wm8775_write(sd, R21, state->input); +} + +static int wm8775_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct wm8775_state *state = to_state(sd); + + /* There are 4 inputs and one output. Zero or more inputs + are multiplexed together to the output. Hence there are + 16 combinations. + If only one input is active (the normal case) then the + input values 1, 2, 4 or 8 should be used. */ + if (input > 15) { + v4l2_err(sd, "Invalid input %d.\n", input); + return -EINVAL; + } + state->input = input; + if (v4l2_ctrl_g_ctrl(state->mute)) + return 0; + if (!v4l2_ctrl_g_ctrl(state->vol)) + return 0; + wm8775_set_audio(sd, 1); + return 0; +} + +static int wm8775_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_BALANCE: + wm8775_set_audio(sd, 0); + return 0; + case V4L2_CID_AUDIO_LOUDNESS: + wm8775_write(sd, R17, (ctrl->val ? ALC_EN : 0) | ALC_HOLD); + return 0; + } + return -EINVAL; +} + +static int wm8775_log_status(struct v4l2_subdev *sd) +{ + struct wm8775_state *state = to_state(sd); + + v4l2_info(sd, "Input: %d\n", state->input); + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +static int wm8775_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *freq) +{ + wm8775_set_audio(sd, 0); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops wm8775_ctrl_ops = { + .s_ctrl = wm8775_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops wm8775_core_ops = { + .log_status = wm8775_log_status, +}; + +static const struct v4l2_subdev_tuner_ops wm8775_tuner_ops = { + .s_frequency = wm8775_s_frequency, +}; + +static const struct v4l2_subdev_audio_ops wm8775_audio_ops = { + .s_routing = wm8775_s_routing, +}; + +static const struct v4l2_subdev_ops wm8775_ops = { + .core = &wm8775_core_ops, + .tuner = &wm8775_tuner_ops, + .audio = &wm8775_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ + +/* i2c implementation */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ + +static int wm8775_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wm8775_state *state; + struct v4l2_subdev *sd; + int err; + bool is_nova_s = false; + + if (client->dev.platform_data) { + struct wm8775_platform_data *data = client->dev.platform_data; + is_nova_s = data->is_nova_s; + } + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &wm8775_ops); + state->input = 2; + + v4l2_ctrl_handler_init(&state->hdl, 4); + state->mute = v4l2_ctrl_new_std(&state->hdl, &wm8775_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + state->vol = v4l2_ctrl_new_std(&state->hdl, &wm8775_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, 65535, (65535+99)/100, 0xCF00); /* 0dB*/ + state->bal = v4l2_ctrl_new_std(&state->hdl, &wm8775_ctrl_ops, + V4L2_CID_AUDIO_BALANCE, 0, 65535, (65535+99)/100, 32768); + state->loud = v4l2_ctrl_new_std(&state->hdl, &wm8775_ctrl_ops, + V4L2_CID_AUDIO_LOUDNESS, 0, 1, 1, 1); + sd->ctrl_handler = &state->hdl; + err = state->hdl.error; + if (err) { + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + + /* Initialize wm8775 */ + + /* RESET */ + wm8775_write(sd, R23, 0x000); + /* Disable zero cross detect timeout */ + wm8775_write(sd, R7, 0x000); + /* HPF enable, left justified, 24-bit (Philips) mode */ + wm8775_write(sd, R11, 0x021); + /* Master mode, clock ratio 256fs */ + wm8775_write(sd, R12, 0x102); + /* Powered up */ + wm8775_write(sd, R13, 0x000); + + if (!is_nova_s) { + /* ADC gain +2.5dB, enable zero cross */ + wm8775_write(sd, R14, 0x1d4); + /* ADC gain +2.5dB, enable zero cross */ + wm8775_write(sd, R15, 0x1d4); + /* ALC Stereo, ALC target level -1dB FS max gain +8dB */ + wm8775_write(sd, R16, 0x1bf); + /* Enable gain control, use zero cross detection, + ALC hold time 42.6 ms */ + wm8775_write(sd, R17, 0x185); + } else { + /* ALC stereo, ALC target level -5dB FS, ALC max gain +8dB */ + wm8775_write(sd, R16, 0x1bb); + /* Set ALC mode and hold time */ + wm8775_write(sd, R17, (state->loud->val ? ALC_EN : 0) | ALC_HOLD); + } + /* ALC gain ramp up delay 34 s, ALC gain ramp down delay 33 ms */ + wm8775_write(sd, R18, 0x0a2); + /* Enable noise gate, threshold -72dBfs */ + wm8775_write(sd, R19, 0x005); + if (!is_nova_s) { + /* Transient window 4ms, lower PGA gain limit -1dB */ + wm8775_write(sd, R20, 0x07a); + /* LRBOTH = 1, use input 2. */ + wm8775_write(sd, R21, 0x102); + } else { + /* Transient window 4ms, ALC min gain -5dB */ + wm8775_write(sd, R20, 0x0fb); + + wm8775_set_audio(sd, 1); /* set volume/mute/mux */ + } + return 0; +} + +static int wm8775_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct wm8775_state *state = to_state(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); + return 0; +} + +static const struct i2c_device_id wm8775_id[] = { + { "wm8775", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8775_id); + +static struct i2c_driver wm8775_driver = { + .driver = { + .name = "wm8775", + }, + .probe = wm8775_probe, + .remove = wm8775_remove, + .id_table = wm8775_id, +}; + +module_i2c_driver(wm8775_driver); |