From 53bf0f446bc387eabdd535dca080789cc74607f4 Mon Sep 17 00:00:00 2001 From: Kamil Debski Date: Fri, 25 Jan 2013 06:29:56 -0300 Subject: [media] v4l: Define video buffer flag for the COPY timestamp type Define video buffer flag for the COPY timestamp. In this case the timestamp value is copied from the OUTPUT to the corresponding CAPTURE buffer. Signed-off-by: Kamil Debski Signed-off-by: Kyungmin Park Reviewed-by: Sylwester Nawrocki Acked-by: Hans Verkuil Signed-off-by: Sylwester Nawrocki Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/io.xml | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/io.xml b/Documentation/DocBook/media/v4l/io.xml index e6c58559ca6b..2c4c068dde83 100644 --- a/Documentation/DocBook/media/v4l/io.xml +++ b/Documentation/DocBook/media/v4l/io.xml @@ -1145,6 +1145,12 @@ in which case caches have not been used. same clock outside V4L2, use clock_gettime(2) . + + V4L2_BUF_FLAG_TIMESTAMP_COPY + 0x4000 + The CAPTURE buffer timestamp has been taken from the + corresponding OUTPUT buffer. This flag applies only to mem2mem devices. + -- cgit v1.2.3 From 4159d01bea38ee82f6e49383b7e73e328c118755 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 28 Feb 2013 10:35:56 -0300 Subject: [media] em28xx: Add ISDB support for c3tech Digital duo This is an hybrid board. However, for analog, it requires a new driver for saa7136. So, for now, let's just add support for Digital TV. Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/CARDLIST.em28xx | 1 + drivers/media/usb/em28xx/Kconfig | 1 + drivers/media/usb/em28xx/em28xx-cards.c | 24 ++++++++++++++++++++++++ drivers/media/usb/em28xx/em28xx-dvb.c | 26 ++++++++++++++++++++++++++ drivers/media/usb/em28xx/em28xx.h | 1 + 5 files changed, 53 insertions(+) (limited to 'Documentation') diff --git a/Documentation/video4linux/CARDLIST.em28xx b/Documentation/video4linux/CARDLIST.em28xx index 3f12865b2a88..c59181431df5 100644 --- a/Documentation/video4linux/CARDLIST.em28xx +++ b/Documentation/video4linux/CARDLIST.em28xx @@ -85,3 +85,4 @@ 85 -> PCTV QuatroStick (510e) (em2884) [2304:0242] 86 -> PCTV QuatroStick nano (520e) (em2884) [2013:0251] 87 -> Terratec Cinergy HTC USB XS (em2884) [0ccd:008e,0ccd:00ac] + 88 -> C3 Tech Digital Duo HDTV/SDTV USB (em2884) [1b80:e755] diff --git a/drivers/media/usb/em28xx/Kconfig b/drivers/media/usb/em28xx/Kconfig index c754a80a8d8b..ca5ee6aceb62 100644 --- a/drivers/media/usb/em28xx/Kconfig +++ b/drivers/media/usb/em28xx/Kconfig @@ -46,6 +46,7 @@ config VIDEO_EM28XX_DVB select DVB_A8293 if MEDIA_SUBDRV_AUTOSELECT select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT + select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT ---help--- diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c index 6e62b72376b0..46fff5c3335b 100644 --- a/drivers/media/usb/em28xx/em28xx-cards.c +++ b/drivers/media/usb/em28xx/em28xx-cards.c @@ -345,6 +345,18 @@ static struct em28xx_reg_seq pctv_460e[] = { { -1, -1, -1, -1}, }; +static struct em28xx_reg_seq c3tech_digital_duo_digital[] = { + {EM2874_R80_GPIO, 0xff, 0xff, 10}, + {EM2874_R80_GPIO, 0xfd, 0xff, 10}, /* xc5000 reset */ + {EM2874_R80_GPIO, 0xf9, 0xff, 35}, + {EM2874_R80_GPIO, 0xfd, 0xff, 10}, + {EM2874_R80_GPIO, 0xff, 0xff, 10}, + {EM2874_R80_GPIO, 0xfe, 0xff, 10}, + {EM2874_R80_GPIO, 0xbe, 0xff, 10}, + {EM2874_R80_GPIO, 0xfe, 0xff, 20}, + { -1, -1, -1, -1}, +}; + #if 0 static struct em28xx_reg_seq hauppauge_930c_gpio[] = { {EM2874_R80_GPIO, 0x6f, 0xff, 10}, @@ -978,6 +990,16 @@ struct em28xx_board em28xx_boards[] = { .i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ, }, + [EM2884_BOARD_C3TECH_DIGITAL_DUO] = { + .name = "C3 Tech Digital Duo HDTV/SDTV USB", + .has_dvb = 1, + /* FIXME: Add analog support - need a saa7136 driver */ + .tuner_type = TUNER_ABSENT, /* Digital-only TDA18271HD */ + .ir_codes = RC_MAP_EMPTY, + .def_i2c_bus = 1, + .i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE, + .dvb_gpio = c3tech_digital_duo_digital, + }, [EM2884_BOARD_CINERGY_HTC_STICK] = { .name = "Terratec Cinergy HTC Stick", .has_dvb = 1, @@ -2144,6 +2166,8 @@ struct usb_device_id em28xx_id_table[] = { .driver_info = EM28174_BOARD_PCTV_460E }, { USB_DEVICE(0x2040, 0x1605), .driver_info = EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C }, + { USB_DEVICE(0x1b80, 0xe755), + .driver_info = EM2884_BOARD_C3TECH_DIGITAL_DUO }, { USB_DEVICE(0xeb1a, 0x5006), .driver_info = EM2860_BOARD_HT_VIDBOX_NW03 }, { USB_DEVICE(0x1b80, 0xe309), /* Sveon STV40 */ diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c index 98b95be3be6e..42a6a2696224 100644 --- a/drivers/media/usb/em28xx/em28xx-dvb.c +++ b/drivers/media/usb/em28xx/em28xx-dvb.c @@ -50,6 +50,7 @@ #include "tda10071.h" #include "a8293.h" #include "qt1010.h" +#include "mb86a20s.h" MODULE_DESCRIPTION("driver for em28xx based DVB cards"); MODULE_AUTHOR("Mauro Carvalho Chehab "); @@ -766,9 +767,25 @@ static struct zl10353_config em28xx_zl10353_no_i2c_gate_dev = { }; static struct qt1010_config em28xx_qt1010_config = { .i2c_address = 0x62 +}; + +static const struct mb86a20s_config c3tech_duo_mb86a20s_config = { + .demod_address = 0x10, + .is_serial = true, +}; + +static struct tda18271_std_map mb86a20s_tda18271_config = { + .dvbt_6 = { .if_freq = 4000, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x37, }, +}; +static struct tda18271_config c3tech_duo_tda18271_config = { + .std_map = &mb86a20s_tda18271_config, + .gate = TDA18271_GATE_DIGITAL, + .small_i2c = TDA18271_03_BYTE_CHUNK_INIT, }; + /* ------------------------------------------------------------------ */ static int em28xx_attach_xc3028(u8 addr, struct em28xx *dev) @@ -1177,6 +1194,15 @@ static int em28xx_dvb_init(struct em28xx *dev) dvb->fe[0]->ops.i2c_gate_ctrl(dvb->fe[0], 0); break; + case EM2884_BOARD_C3TECH_DIGITAL_DUO: + dvb->fe[0] = dvb_attach(mb86a20s_attach, + &c3tech_duo_mb86a20s_config, + &dev->i2c_adap[dev->def_i2c_bus]); + if (dvb->fe[0] != NULL) + dvb_attach(tda18271_attach, dvb->fe[0], 0x60, + &dev->i2c_adap[dev->def_i2c_bus], + &c3tech_duo_tda18271_config); + break; case EM28174_BOARD_PCTV_460E: /* attach demod */ dvb->fe[0] = dvb_attach(tda10071_attach, diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h index f6ac1df83816..4c667fd1661d 100644 --- a/drivers/media/usb/em28xx/em28xx.h +++ b/drivers/media/usb/em28xx/em28xx.h @@ -129,6 +129,7 @@ #define EM2884_BOARD_PCTV_510E 85 #define EM2884_BOARD_PCTV_520E 86 #define EM2884_BOARD_TERRATEC_HTC_USB_XS 87 +#define EM2884_BOARD_C3TECH_DIGITAL_DUO 88 /* Limits minimum and default number of buffers */ #define EM28XX_MIN_BUF 4 -- cgit v1.2.3 From fc3a62e9f5ff5585ad9b9c27c2b800338aa3f751 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 19 Mar 2013 15:15:59 -0300 Subject: [media] em28xx: update cardlist There's one missing USB ID at the card list. Add it. Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/CARDLIST.em28xx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/video4linux/CARDLIST.em28xx b/Documentation/video4linux/CARDLIST.em28xx index c59181431df5..e81864405102 100644 --- a/Documentation/video4linux/CARDLIST.em28xx +++ b/Documentation/video4linux/CARDLIST.em28xx @@ -76,7 +76,7 @@ 76 -> KWorld PlusTV 340U or UB435-Q (ATSC) (em2870) [1b80:a340] 77 -> EM2874 Leadership ISDBT (em2874) 78 -> PCTV nanoStick T2 290e (em28174) - 79 -> Terratec Cinergy H5 (em2884) [0ccd:10a2,0ccd:10ad] + 79 -> Terratec Cinergy H5 (em2884) [0ccd:10a2,0ccd:10ad,0ccd:10b6] 80 -> PCTV DVB-S2 Stick (460e) (em28174) 81 -> Hauppauge WinTV HVR 930C (em2884) [2040:1605] 82 -> Terratec Cinergy HTC Stick (em2884) [0ccd:00b2] -- cgit v1.2.3 From e64171b97b88a1adf297d429826fdbb9e232ab53 Mon Sep 17 00:00:00 2001 From: Manjunath Hadli Date: Thu, 7 Feb 2013 13:48:51 -0300 Subject: [media] media: add support for decoder as one of media entity types A lot of SOCs including Texas Instruments Davinci family mainly use video decoders as input devices. This patch adds a flag 'MEDIA_ENT_T_V4L2_SUBDEV_DECODER' media entity type for decoder's. Along side updates the documentation for this media entity type. Signed-off-by: Manjunath Hadli Signed-off-by: Lad, Prabhakar Reviewed-by: Sylwester Nawrocki Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/media-ioc-enum-entities.xml | 10 ++++++++++ include/uapi/linux/media.h | 2 ++ 2 files changed, 12 insertions(+) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/media-ioc-enum-entities.xml b/Documentation/DocBook/media/v4l/media-ioc-enum-entities.xml index 576b68b33f2c..116c301656e0 100644 --- a/Documentation/DocBook/media/v4l/media-ioc-enum-entities.xml +++ b/Documentation/DocBook/media/v4l/media-ioc-enum-entities.xml @@ -272,6 +272,16 @@ MEDIA_ENT_T_V4L2_SUBDEV_LENS Lens controller + + MEDIA_ENT_T_V4L2_SUBDEV_DECODER + Video decoder, the basic function of the video decoder is to + accept analogue video from a wide variety of sources such as + broadcast, DVD players, cameras and video cassette recorders, in + either NTSC, PAL or HD format and still occasionally SECAM, separate + it into its component parts, luminance and chrominance, and output + it in some digital video standard, with appropriate embedded timing + signals. + diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h index 0ef883327de2..ed49574ad757 100644 --- a/include/uapi/linux/media.h +++ b/include/uapi/linux/media.h @@ -56,6 +56,8 @@ struct media_device_info { #define MEDIA_ENT_T_V4L2_SUBDEV_SENSOR (MEDIA_ENT_T_V4L2_SUBDEV + 1) #define MEDIA_ENT_T_V4L2_SUBDEV_FLASH (MEDIA_ENT_T_V4L2_SUBDEV + 2) #define MEDIA_ENT_T_V4L2_SUBDEV_LENS (MEDIA_ENT_T_V4L2_SUBDEV + 3) +/* A converter of analogue video to its digital representation. */ +#define MEDIA_ENT_T_V4L2_SUBDEV_DECODER (MEDIA_ENT_T_V4L2_SUBDEV + 4) #define MEDIA_ENT_FL_DEFAULT (1 << 0) -- cgit v1.2.3 From 842059aa4796d7c59bc3801d48896ba06b1a1287 Mon Sep 17 00:00:00 2001 From: Masanari Iida Date: Sun, 24 Mar 2013 02:25:56 -0300 Subject: [media] documentation: DocBook/media : Fix typo in dvbproperty.xml Correct spelling typos. Signed-off-by: Masanari Iida Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/dvb/dvbproperty.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/dvb/dvbproperty.xml b/Documentation/DocBook/media/dvb/dvbproperty.xml index 4a5eaeed0b9e..31dc4dfd1d6a 100644 --- a/Documentation/DocBook/media/dvb/dvbproperty.xml +++ b/Documentation/DocBook/media/dvb/dvbproperty.xml @@ -1,6 +1,6 @@
<constant>FE_GET_PROPERTY/FE_SET_PROPERTY</constant> -This section describes the DVB version 5 extention of the DVB-API, also +This section describes the DVB version 5 extension of the DVB-API, also called "S2API", as this API were added to provide support for DVB-S2. It was designed to be able to replace the old frontend API. Yet, the DISEQC and the capability ioctls weren't implemented yet via the new way. @@ -952,7 +952,7 @@ enum fe_interleaving { Measures the amount of bits received before the inner code block, during the same period as DTV_STAT_PRE_ERROR_BIT_COUNT measurement was taken. It should be noticed that this measurement can be smaller than the total amount of bits on the transport stream, - as the frontend may need to manually restart the measurement, loosing some data between each measurement interval. + as the frontend may need to manually restart the measurement, losing some data between each measurement interval. This measurement is monotonically increased, as the frontend gets more bit count measurements. The frontend may reset it when a channel/transponder is tuned. Possible scales for this metric are: @@ -981,7 +981,7 @@ enum fe_interleaving { Measures the amount of bits received after the inner coding, during the same period as DTV_STAT_POST_ERROR_BIT_COUNT measurement was taken. It should be noticed that this measurement can be smaller than the total amount of bits on the transport stream, - as the frontend may need to manually restart the measurement, loosing some data between each measurement interval. + as the frontend may need to manually restart the measurement, losing some data between each measurement interval. This measurement is monotonically increased, as the frontend gets more bit count measurements. The frontend may reset it when a channel/transponder is tuned. Possible scales for this metric are: -- cgit v1.2.3 From 9ca5470cc1433200698a43de2d6e683815e536e6 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Sun, 17 Mar 2013 10:34:04 -0300 Subject: [media] v4l2-ctrls: add V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER control Control whether video sequence headers should be repeated. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/controls.xml | 6 ++++++ drivers/media/v4l2-core/v4l2-ctrls.c | 2 ++ include/uapi/linux/v4l2-controls.h | 1 + 3 files changed, 9 insertions(+) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/controls.xml b/Documentation/DocBook/media/v4l/controls.xml index 9e8f85498678..b4952e23201b 100644 --- a/Documentation/DocBook/media/v4l/controls.xml +++ b/Documentation/DocBook/media/v4l/controls.xml @@ -2299,6 +2299,12 @@ Possible values are: + + V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER  + boolean + Repeat the video sequence headers. Repeating these +headers makes random access to the video stream easier. Applicable to the MPEG1, 2 and 4 encoder. + V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER  boolean diff --git a/drivers/media/v4l2-core/v4l2-ctrls.c b/drivers/media/v4l2-core/v4l2-ctrls.c index b36d1ec13ee6..f662df3bfe2d 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls.c +++ b/drivers/media/v4l2-core/v4l2-ctrls.c @@ -695,6 +695,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_MPEG_VIDEO_DEC_PTS: return "Video Decoder PTS"; case V4L2_CID_MPEG_VIDEO_DEC_FRAME: return "Video Decoder Frame Count"; case V4L2_CID_MPEG_VIDEO_VBV_DELAY: return "Initial Delay for VBV Control"; + case V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER: return "Repeat Sequence Header"; /* CAMERA controls */ /* Keep the order of the 'case's the same as in videodev2.h! */ @@ -844,6 +845,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM: case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE: case V4L2_CID_MPEG_VIDEO_MPEG4_QPEL: + case V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER: case V4L2_CID_WIDE_DYNAMIC_RANGE: case V4L2_CID_IMAGE_STABILIZATION: *type = V4L2_CTRL_TYPE_BOOLEAN; diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 7eab0b91827b..844dc0205037 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -360,6 +360,7 @@ enum v4l2_mpeg_video_multi_slice_mode { #define V4L2_CID_MPEG_VIDEO_DEC_PTS (V4L2_CID_MPEG_BASE+223) #define V4L2_CID_MPEG_VIDEO_DEC_FRAME (V4L2_CID_MPEG_BASE+224) #define V4L2_CID_MPEG_VIDEO_VBV_DELAY (V4L2_CID_MPEG_BASE+225) +#define V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER (V4L2_CID_MPEG_BASE+226) #define V4L2_CID_MPEG_VIDEO_H263_I_FRAME_QP (V4L2_CID_MPEG_BASE+300) #define V4L2_CID_MPEG_VIDEO_H263_P_FRAME_QP (V4L2_CID_MPEG_BASE+301) -- cgit v1.2.3 From e4d2a6162d2a0a27be16b75da36f6bba64af63bc Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Sat, 9 Mar 2013 13:14:32 -0300 Subject: [media] tuner: add Sony BTF tuners This adds support for three Sony BTF tuners: TUNER_SONY_BTF_PG472Z: PAL+SECAM TUNER_SONY_BTF_PK467Z: NTSC-M-JP TUNER_SONY_BTF_PB463Z: NTSC-M These come from the go7007 staging driver where they were implemented in the wis-sony-tuner i2c driver. Adding support for these tuners to tuner-types.c is the first step towards removing the wis-sony-tuner driver. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/CARDLIST.tuner | 3 ++ drivers/media/tuners/tuner-types.c | 69 ++++++++++++++++++++++++++++++ drivers/staging/media/go7007/go7007-usb.c | 1 + drivers/staging/media/go7007/go7007-v4l2.c | 1 + drivers/staging/media/go7007/wis-i2c.h | 6 --- include/media/tuner.h | 4 ++ 6 files changed, 78 insertions(+), 6 deletions(-) (limited to 'Documentation') diff --git a/Documentation/video4linux/CARDLIST.tuner b/Documentation/video4linux/CARDLIST.tuner index c83f6e418879..5b83a3ff15c2 100644 --- a/Documentation/video4linux/CARDLIST.tuner +++ b/Documentation/video4linux/CARDLIST.tuner @@ -86,3 +86,6 @@ tuner=85 - Philips FQ1236 MK5 tuner=86 - Tena TNF5337 MFD tuner=87 - Xceive 4000 tuner tuner=88 - Xceive 5000C tuner +tuner=89 - Sony PAL+SECAM (BTF-PG472Z) +tuner=90 - Sony NTSC-M-JP (BTF-PK467Z) +tuner=91 - Sony NTSC-M (BTF-PB463Z) diff --git a/drivers/media/tuners/tuner-types.c b/drivers/media/tuners/tuner-types.c index 2da4440c16ee..98bc15a388be 100644 --- a/drivers/media/tuners/tuner-types.c +++ b/drivers/media/tuners/tuner-types.c @@ -1381,6 +1381,58 @@ static struct tuner_params tuner_philips_fq1236_mk5_params[] = { }, }; +/* --------- Sony BTF-PG472Z PAL/SECAM ------- */ + +static struct tuner_range tuner_sony_btf_pg472z_ranges[] = { + { 16 * 144.25 /*MHz*/, 0xc6, 0x01, }, + { 16 * 427.25 /*MHz*/, 0xc6, 0x02, }, + { 16 * 999.99 , 0xc6, 0x04, }, +}; + +static struct tuner_params tuner_sony_btf_pg472z_params[] = { + { + .type = TUNER_PARAM_TYPE_PAL, + .ranges = tuner_sony_btf_pg472z_ranges, + .count = ARRAY_SIZE(tuner_sony_btf_pg472z_ranges), + .has_tda9887 = 1, + .port1_active = 1, + .port2_invert_for_secam_lc = 1, + }, +}; + +/* 90-99 */ +/* --------- Sony BTF-PG467Z NTSC-M-JP ------- */ + +static struct tuner_range tuner_sony_btf_pg467z_ranges[] = { + { 16 * 220.25 /*MHz*/, 0xc6, 0x01, }, + { 16 * 467.25 /*MHz*/, 0xc6, 0x02, }, + { 16 * 999.99 , 0xc6, 0x04, }, +}; + +static struct tuner_params tuner_sony_btf_pg467z_params[] = { + { + .type = TUNER_PARAM_TYPE_NTSC, + .ranges = tuner_sony_btf_pg467z_ranges, + .count = ARRAY_SIZE(tuner_sony_btf_pg467z_ranges), + }, +}; + +/* --------- Sony BTF-PG463Z NTSC-M ------- */ + +static struct tuner_range tuner_sony_btf_pg463z_ranges[] = { + { 16 * 130.25 /*MHz*/, 0xc6, 0x01, }, + { 16 * 364.25 /*MHz*/, 0xc6, 0x02, }, + { 16 * 999.99 , 0xc6, 0x04, }, +}; + +static struct tuner_params tuner_sony_btf_pg463z_params[] = { + { + .type = TUNER_PARAM_TYPE_NTSC, + .ranges = tuner_sony_btf_pg463z_ranges, + .count = ARRAY_SIZE(tuner_sony_btf_pg463z_ranges), + }, +}; + /* --------------------------------------------------------------------- */ struct tunertype tuners[] = { @@ -1872,6 +1924,23 @@ struct tunertype tuners[] = { .name = "Xceive 5000C tuner", /* see xc5000.c for details */ }, + [TUNER_SONY_BTF_PG472Z] = { + .name = "Sony BTF-PG472Z PAL/SECAM", + .params = tuner_sony_btf_pg472z_params, + .count = ARRAY_SIZE(tuner_sony_btf_pg472z_params), + }, + + /* 90-99 */ + [TUNER_SONY_BTF_PK467Z] = { + .name = "Sony BTF-PK467Z NTSC-M-JP", + .params = tuner_sony_btf_pg467z_params, + .count = ARRAY_SIZE(tuner_sony_btf_pg467z_params), + }, + [TUNER_SONY_BTF_PB463Z] = { + .name = "Sony BTF-PB463Z NTSC-M", + .params = tuner_sony_btf_pg463z_params, + .count = ARRAY_SIZE(tuner_sony_btf_pg463z_params), + }, }; EXPORT_SYMBOL(tuners); diff --git a/drivers/staging/media/go7007/go7007-usb.c b/drivers/staging/media/go7007/go7007-usb.c index 914b247e9652..3333a8f3b654 100644 --- a/drivers/staging/media/go7007/go7007-usb.c +++ b/drivers/staging/media/go7007/go7007-usb.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "go7007-priv.h" #include "wis-i2c.h" diff --git a/drivers/staging/media/go7007/go7007-v4l2.c b/drivers/staging/media/go7007/go7007-v4l2.c index 29fe94da5f71..4ad383ad8758 100644 --- a/drivers/staging/media/go7007/go7007-v4l2.c +++ b/drivers/staging/media/go7007/go7007-v4l2.c @@ -1238,6 +1238,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, if (!go->i2c_adapter_online) return -EIO; + strlcpy(t->name, "Tuner", sizeof(t->name)); return call_all(&go->v4l2_dev, tuner, g_tuner, t); } diff --git a/drivers/staging/media/go7007/wis-i2c.h b/drivers/staging/media/go7007/wis-i2c.h index 6d09c06c8560..97763db80bac 100644 --- a/drivers/staging/media/go7007/wis-i2c.h +++ b/drivers/staging/media/go7007/wis-i2c.h @@ -34,9 +34,3 @@ struct video_decoder_resolution { #define DECODER_SET_RESOLUTION _IOW('d', 200, struct video_decoder_resolution) #define DECODER_SET_CHANNEL _IOW('d', 201, int) - -/* Sony tuner types */ - -#define TUNER_SONY_BTF_PG472Z 200 -#define TUNER_SONY_BTF_PK467Z 201 -#define TUNER_SONY_BTF_PB463Z 202 diff --git a/include/media/tuner.h b/include/media/tuner.h index 926aff9bdf65..24eaafe461bd 100644 --- a/include/media/tuner.h +++ b/include/media/tuner.h @@ -138,6 +138,10 @@ #define TUNER_XC4000 87 /* Xceive Silicon Tuner */ #define TUNER_XC5000C 88 /* Xceive Silicon Tuner */ +#define TUNER_SONY_BTF_PG472Z 89 /* PAL+SECAM */ +#define TUNER_SONY_BTF_PK467Z 90 /* NTSC_JP */ +#define TUNER_SONY_BTF_PB463Z 91 /* NTSC */ + /* tv card specific */ #define TDA9887_PRESENT (1<<0) #define TDA9887_PORT1_INACTIVE (1<<1) -- cgit v1.2.3 From 098cbc38c27dee66e5bc7f088c30b96e5cc96510 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Sun, 24 Mar 2013 06:09:29 -0300 Subject: [media] DocBook/media/v4l: remove the documentation of the obsolete dv_preset API This API is no longer used by any driver and so can be removed from the documentation. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/common.xml | 14 -- Documentation/DocBook/media/v4l/compat.xml | 4 +- Documentation/DocBook/media/v4l/v4l2.xml | 3 - .../DocBook/media/v4l/vidioc-enum-dv-presets.xml | 240 --------------------- .../DocBook/media/v4l/vidioc-enuminput.xml | 5 - .../DocBook/media/v4l/vidioc-enumoutput.xml | 5 - .../DocBook/media/v4l/vidioc-g-dv-preset.xml | 113 ---------- .../DocBook/media/v4l/vidioc-query-dv-preset.xml | 78 ------- 8 files changed, 2 insertions(+), 460 deletions(-) delete mode 100644 Documentation/DocBook/media/v4l/vidioc-enum-dv-presets.xml delete mode 100644 Documentation/DocBook/media/v4l/vidioc-g-dv-preset.xml delete mode 100644 Documentation/DocBook/media/v4l/vidioc-query-dv-preset.xml (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/common.xml b/Documentation/DocBook/media/v4l/common.xml index ae06afbbb3a9..1ddf354aa997 100644 --- a/Documentation/DocBook/media/v4l/common.xml +++ b/Documentation/DocBook/media/v4l/common.xml @@ -749,15 +749,6 @@ polarities, frontporch, backporch etc. The linux/v4l2-dv-timings.h and standards. - - - DV Presets: Digital Video (DV) presets (deprecated). - These are IDs representing a -video timing at the input/output. Presets are pre-defined timings implemented -by the hardware according to video standards. A __u32 data type is used to represent -a preset unlike the bit mask that is used in &v4l2-std-id; allowing future extensions -to support as many different presets as needed. This API is deprecated in favor of the DV Timings -API. To enumerate and query the attributes of the DV timings supported by a device, @@ -766,11 +757,6 @@ API. &VIDIOC-S-DV-TIMINGS; ioctl and to get current DV timings they use the &VIDIOC-G-DV-TIMINGS; ioctl. To detect the DV timings as seen by the video receiver applications use the &VIDIOC-QUERY-DV-TIMINGS; ioctl. - To enumerate and query the attributes of DV presets supported by a device, -applications use the &VIDIOC-ENUM-DV-PRESETS; ioctl. To get the current DV preset, -applications use the &VIDIOC-G-DV-PRESET; ioctl and to set a preset they use the -&VIDIOC-S-DV-PRESET; ioctl. To detect the preset as seen by the video receiver applications -use the &VIDIOC-QUERY-DV-PRESET; ioctl. Applications can make use of the and flags to decide what ioctls are available to set the video timings for the device. diff --git a/Documentation/DocBook/media/v4l/compat.xml b/Documentation/DocBook/media/v4l/compat.xml index 104a1a2b8849..effa5094a2f8 100644 --- a/Documentation/DocBook/media/v4l/compat.xml +++ b/Documentation/DocBook/media/v4l/compat.xml @@ -2625,8 +2625,8 @@ interfaces and should not be implemented in new drivers. . - &VIDIOC-G-DV-PRESET;, &VIDIOC-S-DV-PRESET;, &VIDIOC-ENUM-DV-PRESETS; and - &VIDIOC-QUERY-DV-PRESET; ioctls. Use the DV Timings API (). + VIDIOC_G_DV_PRESET, VIDIOC_S_DV_PRESET, VIDIOC_ENUM_DV_PRESETS and + VIDIOC_QUERY_DV_PRESET ioctls. Use the DV Timings API (). VIDIOC_SUBDEV_G_CROP and diff --git a/Documentation/DocBook/media/v4l/v4l2.xml b/Documentation/DocBook/media/v4l/v4l2.xml index a3cce18384e9..32a10eef3958 100644 --- a/Documentation/DocBook/media/v4l/v4l2.xml +++ b/Documentation/DocBook/media/v4l/v4l2.xml @@ -544,7 +544,6 @@ and discussions on the V4L mailing list. &sub-encoder-cmd; &sub-enumaudio; &sub-enumaudioout; - &sub-enum-dv-presets; &sub-enum-dv-timings; &sub-enum-fmt; &sub-enum-framesizes; @@ -558,7 +557,6 @@ and discussions on the V4L mailing list. &sub-g-audioout; &sub-g-crop; &sub-g-ctrl; - &sub-g-dv-preset; &sub-g-dv-timings; &sub-g-enc-index; &sub-g-ext-ctrls; @@ -582,7 +580,6 @@ and discussions on the V4L mailing list. &sub-querybuf; &sub-querycap; &sub-queryctrl; - &sub-query-dv-preset; &sub-query-dv-timings; &sub-querystd; &sub-reqbufs; diff --git a/Documentation/DocBook/media/v4l/vidioc-enum-dv-presets.xml b/Documentation/DocBook/media/v4l/vidioc-enum-dv-presets.xml deleted file mode 100644 index fced5fb0dbf0..000000000000 --- a/Documentation/DocBook/media/v4l/vidioc-enum-dv-presets.xml +++ /dev/null @@ -1,240 +0,0 @@ - - - ioctl VIDIOC_ENUM_DV_PRESETS - &manvol; - - - - VIDIOC_ENUM_DV_PRESETS - Enumerate supported Digital Video presets - - - - - - int ioctl - int fd - int request - struct v4l2_dv_enum_preset *argp - - - - - - Arguments - - - - fd - - &fd; - - - - request - - VIDIOC_ENUM_DV_PRESETS - - - - argp - - - - - - - - - Description - - This ioctl is deprecated. - New drivers and applications should use &VIDIOC-ENUM-DV-TIMINGS; instead. - - - To query the attributes of a DV preset, applications initialize the -index field and zero the reserved array of &v4l2-dv-enum-preset; -and call the VIDIOC_ENUM_DV_PRESETS ioctl with a pointer to this -structure. Drivers fill the rest of the structure or return an -&EINVAL; when the index is out of bounds. To enumerate all DV Presets supported, -applications shall begin at index zero, incrementing by one until the -driver returns EINVAL. Drivers may enumerate a -different set of DV presets after switching the video input or -output. - - - struct <structname>v4l2_dv_enum_presets</structname> - - &cs-str; - - - __u32 - index - Number of the DV preset, set by the -application. - - - __u32 - preset - This field identifies one of the DV preset values listed in . - - - __u8 - name[24] - Name of the preset, a NUL-terminated ASCII string, for example: "720P-60", "1080I-60". This information is -intended for the user. - - - __u32 - width - Width of the active video in pixels for the DV preset. - - - __u32 - height - Height of the active video in lines for the DV preset. - - - __u32 - reserved[4] - Reserved for future extensions. Drivers must set the array to zero. - - - -
- - - struct <structname>DV Presets</structname> - - &cs-str; - - - Preset - Preset value - Description - - - - - - - - V4L2_DV_INVALID - 0 - Invalid preset value. - - - V4L2_DV_480P59_94 - 1 - 720x480 progressive video at 59.94 fps as per BT.1362. - - - V4L2_DV_576P50 - 2 - 720x576 progressive video at 50 fps as per BT.1362. - - - V4L2_DV_720P24 - 3 - 1280x720 progressive video at 24 fps as per SMPTE 296M. - - - V4L2_DV_720P25 - 4 - 1280x720 progressive video at 25 fps as per SMPTE 296M. - - - V4L2_DV_720P30 - 5 - 1280x720 progressive video at 30 fps as per SMPTE 296M. - - - V4L2_DV_720P50 - 6 - 1280x720 progressive video at 50 fps as per SMPTE 296M. - - - V4L2_DV_720P59_94 - 7 - 1280x720 progressive video at 59.94 fps as per SMPTE 274M. - - - V4L2_DV_720P60 - 8 - 1280x720 progressive video at 60 fps as per SMPTE 274M/296M. - - - V4L2_DV_1080I29_97 - 9 - 1920x1080 interlaced video at 29.97 fps as per BT.1120/SMPTE 274M. - - - V4L2_DV_1080I30 - 10 - 1920x1080 interlaced video at 30 fps as per BT.1120/SMPTE 274M. - - - V4L2_DV_1080I25 - 11 - 1920x1080 interlaced video at 25 fps as per BT.1120. - - - V4L2_DV_1080I50 - 12 - 1920x1080 interlaced video at 50 fps as per SMPTE 296M. - - - V4L2_DV_1080I60 - 13 - 1920x1080 interlaced video at 60 fps as per SMPTE 296M. - - - V4L2_DV_1080P24 - 14 - 1920x1080 progressive video at 24 fps as per SMPTE 296M. - - - V4L2_DV_1080P25 - 15 - 1920x1080 progressive video at 25 fps as per SMPTE 296M. - - - V4L2_DV_1080P30 - 16 - 1920x1080 progressive video at 30 fps as per SMPTE 296M. - - - V4L2_DV_1080P50 - 17 - 1920x1080 progressive video at 50 fps as per BT.1120. - - - V4L2_DV_1080P60 - 18 - 1920x1080 progressive video at 60 fps as per BT.1120. - - - -
-
- - - &return-value; - - - - EINVAL - - The &v4l2-dv-enum-preset; index -is out of bounds. - - - - ENODATA - - Digital video presets are not supported for this input or output. - - - - -
diff --git a/Documentation/DocBook/media/v4l/vidioc-enuminput.xml b/Documentation/DocBook/media/v4l/vidioc-enuminput.xml index 3c9a81305ad4..493a39a8ef21 100644 --- a/Documentation/DocBook/media/v4l/vidioc-enuminput.xml +++ b/Documentation/DocBook/media/v4l/vidioc-enuminput.xml @@ -277,11 +277,6 @@ input/output interface to linux-media@vger.kernel.org on 19 Oct 2009. &cs-def; - - V4L2_IN_CAP_PRESETS - 0x00000001 - This input supports setting DV presets by using VIDIOC_S_DV_PRESET. - V4L2_IN_CAP_DV_TIMINGS 0x00000002 diff --git a/Documentation/DocBook/media/v4l/vidioc-enumoutput.xml b/Documentation/DocBook/media/v4l/vidioc-enumoutput.xml index f4ab0798545d..2654e097df39 100644 --- a/Documentation/DocBook/media/v4l/vidioc-enumoutput.xml +++ b/Documentation/DocBook/media/v4l/vidioc-enumoutput.xml @@ -162,11 +162,6 @@ input/output interface to linux-media@vger.kernel.org on 19 Oct 2009. &cs-def; - - V4L2_OUT_CAP_PRESETS - 0x00000001 - This output supports setting DV presets by using VIDIOC_S_DV_PRESET. - V4L2_OUT_CAP_DV_TIMINGS 0x00000002 diff --git a/Documentation/DocBook/media/v4l/vidioc-g-dv-preset.xml b/Documentation/DocBook/media/v4l/vidioc-g-dv-preset.xml deleted file mode 100644 index b9ea37634f6c..000000000000 --- a/Documentation/DocBook/media/v4l/vidioc-g-dv-preset.xml +++ /dev/null @@ -1,113 +0,0 @@ - - - ioctl VIDIOC_G_DV_PRESET, VIDIOC_S_DV_PRESET - &manvol; - - - - VIDIOC_G_DV_PRESET - VIDIOC_S_DV_PRESET - Query or select the DV preset of the current input or output - - - - - - int ioctl - int fd - int request - struct v4l2_dv_preset *argp - - - - - - Arguments - - - - fd - - &fd; - - - - request - - VIDIOC_G_DV_PRESET, VIDIOC_S_DV_PRESET - - - - argp - - - - - - - - - Description - - These ioctls are deprecated. - New drivers and applications should use &VIDIOC-G-DV-TIMINGS; and &VIDIOC-S-DV-TIMINGS; - instead. - - - To query and select the current DV preset, applications -use the VIDIOC_G_DV_PRESET and VIDIOC_S_DV_PRESET -ioctls which take a pointer to a &v4l2-dv-preset; type as argument. -Applications must zero the reserved array in &v4l2-dv-preset;. -VIDIOC_G_DV_PRESET returns a dv preset in the field -preset of &v4l2-dv-preset;. - - VIDIOC_S_DV_PRESET accepts a pointer to a &v4l2-dv-preset; -that has the preset value to be set. Applications must zero the reserved array in &v4l2-dv-preset;. -If the preset is not supported, it returns an &EINVAL; - - - - &return-value; - - - - EINVAL - - This ioctl is not supported, or the -VIDIOC_S_DV_PRESET,VIDIOC_S_DV_PRESET parameter was unsuitable. - - - - ENODATA - - Digital video presets are not supported for this input or output. - - - - EBUSY - - The device is busy and therefore can not change the preset. - - - - - - struct <structname>v4l2_dv_preset</structname> - - &cs-str; - - - __u32 - preset - Preset value to represent the digital video timings - - - __u32 - reserved[4] - Reserved fields for future use - - - -
-
-
diff --git a/Documentation/DocBook/media/v4l/vidioc-query-dv-preset.xml b/Documentation/DocBook/media/v4l/vidioc-query-dv-preset.xml deleted file mode 100644 index 68b49d09e245..000000000000 --- a/Documentation/DocBook/media/v4l/vidioc-query-dv-preset.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - ioctl VIDIOC_QUERY_DV_PRESET - &manvol; - - - - VIDIOC_QUERY_DV_PRESET - Sense the DV preset received by the current -input - - - - - - int ioctl - int fd - int request - struct v4l2_dv_preset *argp - - - - - - Arguments - - - - fd - - &fd; - - - - request - - VIDIOC_QUERY_DV_PRESET - - - - argp - - - - - - - - - Description - - This ioctl is deprecated. - New drivers and applications should use &VIDIOC-QUERY-DV-TIMINGS; instead. - - - The hardware may be able to detect the current DV preset -automatically, similar to sensing the video standard. To do so, applications -call VIDIOC_QUERY_DV_PRESET with a pointer to a -&v4l2-dv-preset; type. Once the hardware detects a preset, that preset is -returned in the preset field of &v4l2-dv-preset;. If the preset could not be -detected because there was no signal, or the signal was unreliable, or the -signal did not map to a supported preset, then the value V4L2_DV_INVALID is -returned. - - - - &return-value; - - - - ENODATA - - Digital video presets are not supported for this input or output. - - - - - -- cgit v1.2.3 From 4e5f1130070ebfdde1b0ea6277e881404e5f34ce Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Sun, 24 Mar 2013 06:08:00 -0300 Subject: [media] DocBook/media/v4l: Update version number and document 3.10 changes Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/compat.xml | 13 +++++++++++++ Documentation/DocBook/media/v4l/v4l2.xml | 12 ++++++++++++ 2 files changed, 25 insertions(+) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/compat.xml b/Documentation/DocBook/media/v4l/compat.xml index effa5094a2f8..5dab33837344 100644 --- a/Documentation/DocBook/media/v4l/compat.xml +++ b/Documentation/DocBook/media/v4l/compat.xml @@ -2493,6 +2493,19 @@ that used it. It was originally scheduled for removal in 2.6.35.
+
+ V4L2 in Linux 3.10 + + + Removed obsolete and unused DV_PRESET ioctls + VIDIOC_G_DV_PRESET, VIDIOC_S_DV_PRESET, VIDIOC_QUERY_DV_PRESET and + VIDIOC_ENUM_DV_PRESET. Remove the related v4l2_input/output capability + flags V4L2_IN_CAP_PRESETS and V4L2_OUT_CAP_PRESETS. + + + +
+
Relation of V4L2 to other Linux multimedia APIs diff --git a/Documentation/DocBook/media/v4l/v4l2.xml b/Documentation/DocBook/media/v4l/v4l2.xml index 32a10eef3958..dae009aa2440 100644 --- a/Documentation/DocBook/media/v4l/v4l2.xml +++ b/Documentation/DocBook/media/v4l/v4l2.xml @@ -124,6 +124,7 @@ Remote Controller chapter. 2010 2011 2012 + 2013 Bill Dirks, Michael H. Schimek, Hans Verkuil, Martin Rubli, Andy Walls, Muralidharan Karicheri, Mauro Carvalho Chehab, Pawel Osciak @@ -139,6 +140,17 @@ structs, ioctls) must be noted in more detail in the history chapter (compat.xml), along with the possible impact on existing drivers and applications. --> + + 3.10 + 2013-03-24 + hv + Remove obsolete and unused DV_PRESET ioctls: + VIDIOC_G_DV_PRESET, VIDIOC_S_DV_PRESET, VIDIOC_QUERY_DV_PRESET and + VIDIOC_ENUM_DV_PRESET. Remove the related v4l2_input/output capability + flags V4L2_IN_CAP_PRESETS and V4L2_OUT_CAP_PRESETS. + + + 3.9 2012-12-03 -- cgit v1.2.3 From 820eac0ef8b86f7275acd1f8bccfb0b30f17bbe9 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 18 Mar 2013 13:20:00 -0300 Subject: [media] DocBook media: fix syntax problems in dvbproperty.xml Caught by xmllint. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/dvb/dvbproperty.xml | 46 ++++++++++++------------- 1 file changed, 22 insertions(+), 24 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/dvb/dvbproperty.xml b/Documentation/DocBook/media/dvb/dvbproperty.xml index 31dc4dfd1d6a..a9b15e34c5b2 100644 --- a/Documentation/DocBook/media/dvb/dvbproperty.xml +++ b/Documentation/DocBook/media/dvb/dvbproperty.xml @@ -903,14 +903,12 @@ enum fe_interleaving { svalue is for signed values of the measure (dB measures) and uvalue is for unsigned values (counters, relative scale) scale - Scale for the value. It can be: -
- + FE_SCALE_NOT_AVAILABLE - The parameter is supported by the frontend, but it was not possible to collect it (could be a transitory or permanent condition) FE_SCALE_DECIBEL - parameter is a signed value, measured in 1/1000 dB FE_SCALE_RELATIVE - parameter is a unsigned value, where 0 means 0% and 65535 means 100%. FE_SCALE_COUNTER - parameter is a unsigned value that counts the occurrence of an event, like bit error, block error, or lapsed time. -
@@ -918,9 +916,9 @@ enum fe_interleaving { Indicates the signal strength level at the analog part of the tuner or of the demod. Possible scales for this metric are: - FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. - FE_SCALE_DECIBEL - signal strength is in 0.0001 dBm units, power measured in miliwatts. This value is generally negative. - FE_SCALE_RELATIVE - The frontend provides a 0% to 100% measurement for power (actually, 0 to 65535). + FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. + FE_SCALE_DECIBEL - signal strength is in 0.0001 dBm units, power measured in miliwatts. This value is generally negative. + FE_SCALE_RELATIVE - The frontend provides a 0% to 100% measurement for power (actually, 0 to 65535).
@@ -928,9 +926,9 @@ enum fe_interleaving { Indicates the Signal to Noise ratio for the main carrier. Possible scales for this metric are: - FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. - FE_SCALE_DECIBEL - Signal/Noise ratio is in 0.0001 dB units. - FE_SCALE_RELATIVE - The frontend provides a 0% to 100% measurement for Signal/Noise (actually, 0 to 65535). + FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. + FE_SCALE_DECIBEL - Signal/Noise ratio is in 0.0001 dB units. + FE_SCALE_RELATIVE - The frontend provides a 0% to 100% measurement for Signal/Noise (actually, 0 to 65535).
@@ -943,8 +941,8 @@ enum fe_interleaving { The frontend may reset it when a channel/transponder is tuned. Possible scales for this metric are: - FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. - FE_SCALE_COUNTER - Number of error bits counted before the inner coding. + FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. + FE_SCALE_COUNTER - Number of error bits counted before the inner coding.
@@ -957,9 +955,9 @@ enum fe_interleaving { The frontend may reset it when a channel/transponder is tuned. Possible scales for this metric are: - FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. - FE_SCALE_COUNTER - Number of bits counted while measuring - DTV_STAT_PRE_ERROR_BIT_COUNT. + FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. + FE_SCALE_COUNTER - Number of bits counted while measuring + DTV_STAT_PRE_ERROR_BIT_COUNT.
@@ -972,8 +970,8 @@ enum fe_interleaving { The frontend may reset it when a channel/transponder is tuned. Possible scales for this metric are: - FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. - FE_SCALE_COUNTER - Number of error bits counted after the inner coding. + FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. + FE_SCALE_COUNTER - Number of error bits counted after the inner coding.
@@ -986,9 +984,9 @@ enum fe_interleaving { The frontend may reset it when a channel/transponder is tuned. Possible scales for this metric are: - FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. - FE_SCALE_COUNTER - Number of bits counted while measuring - DTV_STAT_POST_ERROR_BIT_COUNT. + FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. + FE_SCALE_COUNTER - Number of bits counted while measuring + DTV_STAT_POST_ERROR_BIT_COUNT.
@@ -998,8 +996,8 @@ enum fe_interleaving { The frontend may reset it when a channel/transponder is tuned. Possible scales for this metric are: - FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. - FE_SCALE_COUNTER - Number of error blocks counted after the outer coding. + FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. + FE_SCALE_COUNTER - Number of error blocks counted after the outer coding.
@@ -1011,9 +1009,9 @@ enum fe_interleaving { by DTV-STAT-TOTAL-BLOCK-COUNT. Possible scales for this metric are: - FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. - FE_SCALE_COUNTER - Number of blocks counted while measuring - DTV_STAT_ERROR_BLOCK_COUNT. + FE_SCALE_NOT_AVAILABLE - it failed to measure it, or the measurement was not complete yet. + FE_SCALE_COUNTER - Number of blocks counted while measuring + DTV_STAT_ERROR_BLOCK_COUNT.
-- cgit v1.2.3 From b5958dc329f0e4584511c5d8a177122e3d23a657 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 18 Mar 2013 13:28:12 -0300 Subject: [media] DocBook media: add VIDIOC_DBG_G_CHIP_NAME documentation And update the other debug ioctls accordingly. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/v4l2.xml | 4 +- .../DocBook/media/v4l/vidioc-dbg-g-chip-ident.xml | 14 +- .../DocBook/media/v4l/vidioc-dbg-g-chip-name.xml | 234 +++++++++++++++++++++ .../DocBook/media/v4l/vidioc-dbg-g-register.xml | 42 +++- 4 files changed, 280 insertions(+), 14 deletions(-) create mode 100644 Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/v4l2.xml b/Documentation/DocBook/media/v4l/v4l2.xml index dae009aa2440..ec3a0c7276df 100644 --- a/Documentation/DocBook/media/v4l/v4l2.xml +++ b/Documentation/DocBook/media/v4l/v4l2.xml @@ -156,8 +156,7 @@ applications. --> 2012-12-03 sa, sn Added timestamp types to v4l2_buffer. - Added V4L2_EVENT_CTRL_CH_RANGE control - event changes flag, see . + Added V4L2_EVENT_CTRL_CH_RANGE control event changes flag. @@ -549,6 +548,7 @@ and discussions on the V4L mailing list. &sub-create-bufs; &sub-cropcap; &sub-dbg-g-chip-ident; + &sub-dbg-g-chip-name; &sub-dbg-g-register; &sub-decoder-cmd; &sub-dqevent; diff --git a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-ident.xml b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-ident.xml index 4ecd966808de..82e43c6c72b8 100644 --- a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-ident.xml +++ b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-ident.xml @@ -200,10 +200,10 @@ the values from . &cs-def; - V4L2_CHIP_MATCH_HOST + V4L2_CHIP_MATCH_BRIDGE 0 Match the nth chip on the card, zero for the - host chip. Does not match &i2c; chips. + bridge chip. Does not match sub-devices. V4L2_CHIP_MATCH_I2C_DRIVER @@ -220,6 +220,16 @@ the values from . 3 Match the nth anciliary AC97 chip. + + V4L2_CHIP_MATCH_SUBDEV_NAME + 4 + Match the sub-device by name. Can't be used with this ioctl. + + + V4L2_CHIP_MATCH_SUBDEV_IDX + 5 + Match the nth sub-device. Can't be used with this ioctl. + diff --git a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml new file mode 100644 index 000000000000..4921346fabd7 --- /dev/null +++ b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml @@ -0,0 +1,234 @@ + + + ioctl VIDIOC_DBG_G_CHIP_NAME + &manvol; + + + + VIDIOC_DBG_G_CHIP_NAME + Identify the chips on a TV card + + + + + + int ioctl + int fd + int request + struct v4l2_dbg_chip_name +*argp + + + + + + Arguments + + + + fd + + &fd; + + + + request + + VIDIOC_DBG_G_CHIP_NAME + + + + argp + + + + + + + + + Description + + + Experimental + + This is an experimental interface and may change in +the future. + + + For driver debugging purposes this ioctl allows test +applications to query the driver about the chips present on the TV +card. Regular applications must not use it. When you found a chip +specific bug, please contact the linux-media mailing list (&v4l-ml;) +so it can be fixed. + + To query the driver applications must initialize the +match.type and +match.addr or match.name +fields of a &v4l2-dbg-chip-name; +and call VIDIOC_DBG_G_CHIP_NAME with a pointer to +this structure. On success the driver stores information about the +selected chip in the name and +flags fields. On failure the structure +remains unchanged. + + When match.type is +V4L2_CHIP_MATCH_BRIDGE, +match.addr selects the nth bridge 'chip' +on the TV card. You can enumerate all chips by starting at zero and +incrementing match.addr by one until +VIDIOC_DBG_G_CHIP_NAME fails with an &EINVAL;. +The number zero always selects the bridge chip itself, ⪚ the chip +connected to the PCI or USB bus. Non-zero numbers identify specific +parts of the bridge chip such as an AC97 register block. + + When match.type is +V4L2_CHIP_MATCH_SUBDEV_NAME, +match.name contains the name of a sub-device. +For instance +"saa7127 6-0044" will match the saa7127 sub-device +at the given i2c bus. This match type is not very useful for this ioctl +and is here only for consistency. + + + When match.type is +V4L2_CHIP_MATCH_SUBDEV_IDX, +match.addr selects the nth sub-device. This +allows you to enumerate over all sub-devices. + + On success, the name field will +contain a chip name and the flags field will +contain V4L2_CHIP_FL_READABLE if the driver supports +reading registers from the device or V4L2_CHIP_FL_WRITABLE +if the driver supports writing registers to the device. + + We recommended the v4l2-dbg +utility over calling this ioctl directly. It is available from the +LinuxTV v4l-dvb repository; see http://linuxtv.org/repo/ for +access instructions. + + + + struct <structname>v4l2_dbg_match</structname> + + &cs-ustr; + + + __u32 + type + See for a list of +possible types. + + + union + (anonymous) + + + + __u32 + addr + Match a chip by this number, interpreted according +to the type field. + + + + char + name[32] + Match a chip by this name, interpreted according +to the type field. + + + +
+ + + struct <structname>v4l2_dbg_chip_name</structname> + + &cs-str; + + + struct v4l2_dbg_match + match + How to match the chip, see . + + + char + name[32] + The name of the chip. + + + __u32 + flags + Set by the driver. If V4L2_CHIP_FL_READABLE +is set, then the driver supports reading registers from the device. If +V4L2_CHIP_FL_WRITABLE is set, then it supports writing registers. + + + __u32 + reserved[8] + Reserved fields, both application and driver must set these to 0. + + + +
+ + + + Chip Match Types + + &cs-def; + + + V4L2_CHIP_MATCH_BRIDGE + 0 + Match the nth chip on the card, zero for the + bridge chip. Does not match sub-devices. + + + V4L2_CHIP_MATCH_I2C_DRIVER + 1 + Match an &i2c; chip by its driver name. Can't be used with this ioctl. + + + V4L2_CHIP_MATCH_I2C_ADDR + 2 + Match a chip by its 7 bit &i2c; bus address. Can't be used with this ioctl. + + + V4L2_CHIP_MATCH_AC97 + 3 + Match the nth anciliary AC97 chip. Can't be used with this ioctl. + + + V4L2_CHIP_MATCH_SUBDEV_NAME + 4 + Match the sub-device by name. + + + V4L2_CHIP_MATCH_SUBDEV_IDX + 5 + Match the nth sub-device. + + + +
+
+ + + &return-value; + + + + EINVAL + + The match_type is invalid or +no device could be matched. + + + + +
diff --git a/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml b/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml index a44aebc7997a..3082b4149dbe 100644 --- a/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml +++ b/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml @@ -87,7 +87,7 @@ written into the register. To read a register applications must initialize the match.type, -match.chip or match.name and +match.addr or match.name and reg fields, and call VIDIOC_DBG_G_REGISTER with a pointer to this structure. On success the driver stores the register value in the @@ -95,11 +95,11 @@ structure. On success the driver stores the register value in the unchanged. When match.type is -V4L2_CHIP_MATCH_HOST, -match.addr selects the nth non-&i2c; chip +V4L2_CHIP_MATCH_BRIDGE, +match.addr selects the nth non-sub-device chip on the TV card. The number zero always selects the host chip, ⪚ the chip connected to the PCI or USB bus. You can find out which chips are -present with the &VIDIOC-DBG-G-CHIP-IDENT; ioctl. +present with the &VIDIOC-DBG-G-CHIP-NAME; ioctl. When match.type is V4L2_CHIP_MATCH_I2C_DRIVER, @@ -109,7 +109,7 @@ For instance supported by the saa7127 driver, regardless of its &i2c; bus address. When multiple chips supported by the same driver are present, the effect of these ioctls is undefined. Again with the -&VIDIOC-DBG-G-CHIP-IDENT; ioctl you can find out which &i2c; chips are +&VIDIOC-DBG-G-CHIP-NAME; ioctl you can find out which &i2c; chips are present. When match.type is @@ -122,19 +122,31 @@ bus address. match.addr selects the nth AC97 chip on the TV card. + When match.type is +V4L2_CHIP_MATCH_SUBDEV_NAME, +match.name contains the sub-device name. +For instance +"saa7127 6-0044" will match this specific saa7127 +sub-device. Again with the &VIDIOC-DBG-G-CHIP-NAME; ioctl you can find +out which sub-devices are present. + + When match.type is +V4L2_CHIP_MATCH_SUBDEV_IDX, +match.addr selects the nth sub-device. + Success not guaranteed Due to a flaw in the Linux &i2c; bus driver these ioctls may return successfully without actually reading or writing a register. To -catch the most likely failure we recommend a &VIDIOC-DBG-G-CHIP-IDENT; +catch the most likely failure we recommend a &VIDIOC-DBG-G-CHIP-NAME; call confirming the presence of the selected &i2c; chip. These ioctls are optional, not all drivers may support them. However when a driver supports these ioctls it must also support -&VIDIOC-DBG-G-CHIP-IDENT;. Conversely it may support -VIDIOC_DBG_G_CHIP_IDENT but not these ioctls. +&VIDIOC-DBG-G-CHIP-NAME;. Conversely it may support +VIDIOC_DBG_G_CHIP_NAME but not these ioctls. VIDIOC_DBG_G_REGISTER and VIDIOC_DBG_S_REGISTER were introduced in Linux @@ -217,10 +229,10 @@ register. &cs-def; - V4L2_CHIP_MATCH_HOST + V4L2_CHIP_MATCH_BRIDGE 0 Match the nth chip on the card, zero for the - host chip. Does not match &i2c; chips. + bridge chip. Does not match sub-devices. V4L2_CHIP_MATCH_I2C_DRIVER @@ -237,6 +249,16 @@ register. 3 Match the nth anciliary AC97 chip. + + V4L2_CHIP_MATCH_SUBDEV_NAME + 4 + Match the sub-device by name. + + + V4L2_CHIP_MATCH_SUBDEV_IDX + 5 + Match the nth sub-device. + -- cgit v1.2.3 From e3f7586310e80be3f75fe03dabda0a64f54d6d73 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 25 Mar 2013 08:49:15 -0300 Subject: [media] DocBook media: document 3.10 changes Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/compat.xml | 4 ++++ Documentation/DocBook/media/v4l/v4l2.xml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/compat.xml b/Documentation/DocBook/media/v4l/compat.xml index 5dab33837344..8c2b3687a6a4 100644 --- a/Documentation/DocBook/media/v4l/compat.xml +++ b/Documentation/DocBook/media/v4l/compat.xml @@ -2503,6 +2503,10 @@ that used it. It was originally scheduled for removal in 2.6.35. flags V4L2_IN_CAP_PRESETS and V4L2_OUT_CAP_PRESETS. + + Added new debugging ioctl &VIDIOC-DBG-G-CHIP-NAME;. + + diff --git a/Documentation/DocBook/media/v4l/v4l2.xml b/Documentation/DocBook/media/v4l/v4l2.xml index ec3a0c7276df..c1f334084213 100644 --- a/Documentation/DocBook/media/v4l/v4l2.xml +++ b/Documentation/DocBook/media/v4l/v4l2.xml @@ -142,12 +142,12 @@ applications. --> 3.10 - 2013-03-24 + 2013-03-25 hv Remove obsolete and unused DV_PRESET ioctls: VIDIOC_G_DV_PRESET, VIDIOC_S_DV_PRESET, VIDIOC_QUERY_DV_PRESET and VIDIOC_ENUM_DV_PRESET. Remove the related v4l2_input/output capability - flags V4L2_IN_CAP_PRESETS and V4L2_OUT_CAP_PRESETS. + flags V4L2_IN_CAP_PRESETS and V4L2_OUT_CAP_PRESETS. Added VIDIOC_DBG_G_CHIP_NAME. -- cgit v1.2.3 From af720c7c414befed0aad1c92f6e33271cf4f83d3 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Tue, 26 Mar 2013 22:47:22 -0300 Subject: [media] v4l2: Fix the type of V4L2_CID_TUNE_PREEMPHASIS in the documentation Change the type of V4L2_CID_TUNE_PREEMPHASIS from 'integer' to 'enum v4l2_preemphasis' Acked-by: Hans Verkuil Signed-off-by: Andrey Smirnov Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/controls.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/controls.xml b/Documentation/DocBook/media/v4l/controls.xml index b4952e23201b..06e9a5facb40 100644 --- a/Documentation/DocBook/media/v4l/controls.xml +++ b/Documentation/DocBook/media/v4l/controls.xml @@ -3854,7 +3854,7 @@ in Hz. The range and step are driver-specific. V4L2_CID_TUNE_PREEMPHASIS  - integer + enum v4l2_preemphasis Configures the pre-emphasis value for broadcasting. A pre-emphasis filter is applied to the broadcast to accentuate the high audio frequencies. -- cgit v1.2.3 From b4e96a7e9f9511d1b57a10a08cd10af6e517966b Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Tue, 26 Mar 2013 22:47:24 -0300 Subject: [media] v4l2: Add documentation for the FM RX controls Add appropriate documentation for all the newly added standard controls. Based on the patch by Manjunatha Halli [1] [1] http://lists-archives.com/linux-kernel/27641303-media-update-docs-for-v4l2-fm-new-features.html Acked-by: Hans Verkuil Signed-off-by: Andrey Smirnov Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/compat.xml | 3 + Documentation/DocBook/media/v4l/controls.xml | 72 ++++++++++++++++++++++ .../DocBook/media/v4l/vidioc-g-ext-ctrls.xml | 9 +++ 3 files changed, 84 insertions(+) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/compat.xml b/Documentation/DocBook/media/v4l/compat.xml index 8c2b3687a6a4..e44161ffdd07 100644 --- a/Documentation/DocBook/media/v4l/compat.xml +++ b/Documentation/DocBook/media/v4l/compat.xml @@ -2310,6 +2310,9 @@ more information. Added FM Modulator (FM TX) Extended Control Class: V4L2_CTRL_CLASS_FM_TX and their Control IDs. + + Added FM Receiver (FM RX) Extended Control Class: V4L2_CTRL_CLASS_FM_RX and their Control IDs. + Added Remote Controller chapter, describing the default Remote Controller mapping for media devices. diff --git a/Documentation/DocBook/media/v4l/controls.xml b/Documentation/DocBook/media/v4l/controls.xml index 06e9a5facb40..c8eb6c222274 100644 --- a/Documentation/DocBook/media/v4l/controls.xml +++ b/Documentation/DocBook/media/v4l/controls.xml @@ -4693,4 +4693,76 @@ interface and may change in the future. + +
+ FM Receiver Control Reference + + The FM Receiver (FM_RX) class includes controls for common features of + FM Reception capable devices. + + + FM_RX Control IDs + + + + + + + + + + + ID + Type + Description + + + + + + V4L2_CID_FM_RX_CLASS  + class + The FM_RX class +descriptor. Calling &VIDIOC-QUERYCTRL; for this control will return a +description of this control class. + + + V4L2_CID_RDS_RECEPTION  + boolean + Enables/disables RDS + reception by the radio tuner + + + V4L2_CID_TUNE_DEEMPHASIS  + enum v4l2_deemphasis + + Configures the de-emphasis value for reception. +A de-emphasis filter is applied to the broadcast to accentuate the high audio frequencies. +Depending on the region, a time constant of either 50 or 75 useconds is used. The enum v4l2_deemphasis +defines possible values for de-emphasis. Here they are: + + + + + V4L2_DEEMPHASIS_DISABLED  + No de-emphasis is applied. + + + V4L2_DEEMPHASIS_50_uS  + A de-emphasis of 50 uS is used. + + + V4L2_DEEMPHASIS_75_uS  + A de-emphasis of 75 uS is used. + + + + + + + + +
+ +
diff --git a/Documentation/DocBook/media/v4l/vidioc-g-ext-ctrls.xml b/Documentation/DocBook/media/v4l/vidioc-g-ext-ctrls.xml index 4e16112df992..b3bb9575b2e0 100644 --- a/Documentation/DocBook/media/v4l/vidioc-g-ext-ctrls.xml +++ b/Documentation/DocBook/media/v4l/vidioc-g-ext-ctrls.xml @@ -319,6 +319,15 @@ These controls are described in .
+ + + V4L2_CTRL_CLASS_FM_RX + 0xa10000 + The class containing FM Receiver (FM RX) controls. +These controls are described in . + + -- cgit v1.2.3 From 30bac9110455402fa8888740c6819dd3daa2666f Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Tue, 26 Mar 2013 22:47:26 -0300 Subject: [media] v4l2: Add a V4L2 driver for SI476X MFD This commit adds a driver that exposes all the radio related functionality of the Si476x series of chips via the V4L2 subsystem. Acked-by: Hans Verkuil Signed-off-by: Andrey Smirnov Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/si476x.txt | 187 ++++ drivers/media/radio/Kconfig | 17 + drivers/media/radio/Makefile | 1 + drivers/media/radio/radio-si476x.c | 1599 ++++++++++++++++++++++++++++++++++ include/media/si476x.h | 426 +++++++++ 5 files changed, 2230 insertions(+) create mode 100644 Documentation/video4linux/si476x.txt create mode 100644 drivers/media/radio/radio-si476x.c create mode 100644 include/media/si476x.h (limited to 'Documentation') diff --git a/Documentation/video4linux/si476x.txt b/Documentation/video4linux/si476x.txt new file mode 100644 index 000000000000..d1a08db2cbd9 --- /dev/null +++ b/Documentation/video4linux/si476x.txt @@ -0,0 +1,187 @@ +SI476x Driver Readme +------------------------------------------------ + Copyright (C) 2013 Andrey Smirnov + +TODO for the driver +------------------------------ + +- According to the SiLabs' datasheet it is possible to update the + firmware of the radio chip in the run-time, thus bringing it to the + most recent version. Unfortunately I couldn't find any mentioning of + the said firmware update for the old chips that I tested the driver + against, so for chips like that the driver only exposes the old + functionality. + + +Parameters exposed over debugfs +------------------------------- +SI476x allow user to get multiple characteristics that can be very +useful for EoL testing/RF performance estimation, parameters that have +very little to do with V4L2 subsystem. Such parameters are exposed via +debugfs and can be accessed via regular file I/O operations. + +The drivers exposes following files: + +* /sys/kernel/debug//acf + This file contains ACF(Automatically Controlled Features) status + information. The contents of the file is binary data of the + following layout: + + Offset | Name | Description + ==================================================================== + 0x00 | blend_int | Flag, set when stereo separation has + | | crossed below the blend threshold + -------------------------------------------------------------------- + 0x01 | hblend_int | Flag, set when HiBlend cutoff + | | frequency is lower than threshold + -------------------------------------------------------------------- + 0x02 | hicut_int | Flag, set when HiCut cutoff + | | frequency is lower than threshold + -------------------------------------------------------------------- + 0x03 | chbw_int | Flag, set when channel filter + | | bandwidth is less than threshold + -------------------------------------------------------------------- + 0x04 | softmute_int | Flag indicating that softmute + | | attenuation has increased above + | | softmute threshold + -------------------------------------------------------------------- + 0x05 | smute | 0 - Audio is not soft muted + | | 1 - Audio is soft muted + -------------------------------------------------------------------- + 0x06 | smattn | Soft mute attenuation level in dB + -------------------------------------------------------------------- + 0x07 | chbw | Channel filter bandwidth in kHz + -------------------------------------------------------------------- + 0x08 | hicut | HiCut cutoff frequency in units of + | | 100Hz + -------------------------------------------------------------------- + 0x09 | hiblend | HiBlend cutoff frequency in units + | | of 100 Hz + -------------------------------------------------------------------- + 0x10 | pilot | 0 - Stereo pilot is not present + | | 1 - Stereo pilot is present + -------------------------------------------------------------------- + 0x11 | stblend | Stereo blend in % + -------------------------------------------------------------------- + + +* /sys/kernel/debug//rds_blckcnt + This file contains statistics about RDS receptions. It's binary data + has the following layout: + + Offset | Name | Description + ==================================================================== + 0x00 | expected | Number of expected RDS blocks + -------------------------------------------------------------------- + 0x02 | received | Number of received RDS blocks + -------------------------------------------------------------------- + 0x04 | uncorrectable | Number of uncorrectable RDS blocks + -------------------------------------------------------------------- + +* /sys/kernel/debug//agc + This file contains information about parameters pertaining to + AGC(Automatic Gain Control) + + The layout is: + Offset | Name | Description + ==================================================================== + 0x00 | mxhi | 0 - FM Mixer PD high threshold is + | | not tripped + | | 1 - FM Mixer PD high threshold is + | | tripped + -------------------------------------------------------------------- + 0x01 | mxlo | ditto for FM Mixer PD low + -------------------------------------------------------------------- + 0x02 | lnahi | ditto for FM LNA PD high + -------------------------------------------------------------------- + 0x03 | lnalo | ditto for FM LNA PD low + -------------------------------------------------------------------- + 0x04 | fmagc1 | FMAGC1 attenuator resistance + | | (see datasheet for more detail) + -------------------------------------------------------------------- + 0x05 | fmagc2 | ditto for FMAGC2 + -------------------------------------------------------------------- + 0x06 | pgagain | PGA gain in dB + -------------------------------------------------------------------- + 0x07 | fmwblang | FM/WB LNA Gain in dB + -------------------------------------------------------------------- + +* /sys/kernel/debug//rsq + This file contains information about parameters pertaining to + RSQ(Received Signal Quality) + + The layout is: + Offset | Name | Description + ==================================================================== + 0x00 | multhint | 0 - multipath value has not crossed + | | the Multipath high threshold + | | 1 - multipath value has crossed + | | the Multipath high threshold + -------------------------------------------------------------------- + 0x01 | multlint | ditto for Multipath low threshold + -------------------------------------------------------------------- + 0x02 | snrhint | 0 - received signal's SNR has not + | | crossed high threshold + | | 1 - received signal's SNR has + | | crossed high threshold + -------------------------------------------------------------------- + 0x03 | snrlint | ditto for low threshold + -------------------------------------------------------------------- + 0x04 | rssihint | ditto for RSSI high threshold + -------------------------------------------------------------------- + 0x05 | rssilint | ditto for RSSI low threshold + -------------------------------------------------------------------- + 0x06 | bltf | Flag indicating if seek command + | | reached/wrapped seek band limit + -------------------------------------------------------------------- + 0x07 | snr_ready | Indicates that SNR metrics is ready + -------------------------------------------------------------------- + 0x08 | rssiready | ditto for RSSI metrics + -------------------------------------------------------------------- + 0x09 | injside | 0 - Low-side injection is being used + | | 1 - High-side injection is used + -------------------------------------------------------------------- + 0x10 | afcrl | Flag indicating if AFC rails + -------------------------------------------------------------------- + 0x11 | valid | Flag indicating if channel is valid + -------------------------------------------------------------------- + 0x12 | readfreq | Current tuned frequency + -------------------------------------------------------------------- + 0x14 | freqoff | Singed frequency offset in units of + | | 2ppm + -------------------------------------------------------------------- + 0x15 | rssi | Signed value of RSSI in dBuV + -------------------------------------------------------------------- + 0x16 | snr | Signed RF SNR in dB + -------------------------------------------------------------------- + 0x17 | issi | Signed Image Strength Signal + | | indicator + -------------------------------------------------------------------- + 0x18 | lassi | Signed Low side adjacent Channel + | | Strength indicator + -------------------------------------------------------------------- + 0x19 | hassi | ditto fpr High side + -------------------------------------------------------------------- + 0x20 | mult | Multipath indicator + -------------------------------------------------------------------- + 0x21 | dev | Frequency deviation + -------------------------------------------------------------------- + 0x24 | assi | Adjascent channel SSI + -------------------------------------------------------------------- + 0x25 | usn | Ultrasonic noise indicator + -------------------------------------------------------------------- + 0x26 | pilotdev | Pilot deviation in units of 100 Hz + -------------------------------------------------------------------- + 0x27 | rdsdev | ditto for RDS + -------------------------------------------------------------------- + 0x28 | assidev | ditto for ASSI + -------------------------------------------------------------------- + 0x29 | strongdev | Frequency deviation + -------------------------------------------------------------------- + 0x30 | rdspi | RDS PI code + -------------------------------------------------------------------- + +* /sys/kernel/debug//rsq_primary + This file contains information about parameters pertaining to + RSQ(Received Signal Quality) for primary tuner only. Layout is as + the one above. diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 24e64a09884c..28ded247abc0 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -18,6 +18,23 @@ config RADIO_SI470X source "drivers/media/radio/si470x/Kconfig" +config RADIO_SI476X + tristate "Silicon Laboratories Si476x I2C FM Radio" + depends on I2C && VIDEO_V4L2 + select MFD_CORE + select MFD_SI476X_CORE + select SND_SOC_SI476X + ---help--- + Choose Y here if you have this FM radio chip. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux 2 API. Information on + this API and pointers to "v4l2" programs may be found at + . + + To compile this driver as a module, choose M here: the + module will be called radio-si476x. + config USB_MR800 tristate "AverMedia MR 800 USB FM radio support" depends on USB && VIDEO_V4L2 diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 303eaebdb85a..0dcdb320cfc7 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o obj-$(CONFIG_RADIO_TRUST) += radio-trust.o obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o +obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o obj-$(CONFIG_USB_DSBR) += dsbr100.o obj-$(CONFIG_RADIO_SI470X) += si470x/ diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c new file mode 100644 index 000000000000..0895a0c23787 --- /dev/null +++ b/drivers/media/radio/radio-si476x.c @@ -0,0 +1,1599 @@ +/* + * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FM_FREQ_RANGE_LOW 64000000 +#define FM_FREQ_RANGE_HIGH 108000000 + +#define AM_FREQ_RANGE_LOW 520000 +#define AM_FREQ_RANGE_HIGH 30000000 + +#define PWRLINEFLTR (1 << 8) + +#define FREQ_MUL (10000000 / 625) + +#define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0b10000000 & (status)) + +#define DRIVER_NAME "si476x-radio" +#define DRIVER_CARD "SI476x AM/FM Receiver" + +enum si476x_freq_bands { + SI476X_BAND_FM, + SI476X_BAND_AM, +}; + +static const struct v4l2_frequency_band si476x_bands[] = { + [SI476X_BAND_FM] = { + .type = V4L2_TUNER_RADIO, + .index = SI476X_BAND_FM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 64 * FREQ_MUL, + .rangehigh = 108 * FREQ_MUL, + .modulation = V4L2_BAND_MODULATION_FM, + }, + [SI476X_BAND_AM] = { + .type = V4L2_TUNER_RADIO, + .index = SI476X_BAND_AM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 0.52 * FREQ_MUL, + .rangehigh = 30 * FREQ_MUL, + .modulation = V4L2_BAND_MODULATION_AM, + }, +}; + +static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band) +{ + return freq >= si476x_bands[band].rangelow && + freq <= si476x_bands[band].rangehigh; +} + +static inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high, + int band) +{ + return low >= si476x_bands[band].rangelow && + high <= si476x_bands[band].rangehigh; +} + +static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl); +static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); + +enum phase_diversity_modes_idx { + SI476X_IDX_PHDIV_DISABLED, + SI476X_IDX_PHDIV_PRIMARY_COMBINING, + SI476X_IDX_PHDIV_PRIMARY_ANTENNA, + SI476X_IDX_PHDIV_SECONDARY_ANTENNA, + SI476X_IDX_PHDIV_SECONDARY_COMBINING, +}; + +static const char * const phase_diversity_modes[] = { + [SI476X_IDX_PHDIV_DISABLED] = "Disabled", + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary", + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna", + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna", + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary", +}; + +static inline enum phase_diversity_modes_idx +si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode) +{ + switch (mode) { + default: /* FALLTHROUGH */ + case SI476X_PHDIV_DISABLED: + return SI476X_IDX_PHDIV_DISABLED; + case SI476X_PHDIV_PRIMARY_COMBINING: + return SI476X_IDX_PHDIV_PRIMARY_COMBINING; + case SI476X_PHDIV_PRIMARY_ANTENNA: + return SI476X_IDX_PHDIV_PRIMARY_ANTENNA; + case SI476X_PHDIV_SECONDARY_ANTENNA: + return SI476X_IDX_PHDIV_SECONDARY_ANTENNA; + case SI476X_PHDIV_SECONDARY_COMBINING: + return SI476X_IDX_PHDIV_SECONDARY_COMBINING; + } +} + +static inline enum si476x_phase_diversity_mode +si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx) +{ + static const int idx_to_value[] = { + [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED, + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING, + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA, + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA, + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING, + }; + + return idx_to_value[idx]; +} + +static const struct v4l2_ctrl_ops si476x_ctrl_ops = { + .g_volatile_ctrl = si476x_radio_g_volatile_ctrl, + .s_ctrl = si476x_radio_s_ctrl, +}; + + +enum si476x_ctrl_idx { + SI476X_IDX_RSSI_THRESHOLD, + SI476X_IDX_SNR_THRESHOLD, + SI476X_IDX_MAX_TUNE_ERROR, + SI476X_IDX_HARMONICS_COUNT, + SI476X_IDX_DIVERSITY_MODE, + SI476X_IDX_INTERCHIP_LINK, +}; +static struct v4l2_ctrl_config si476x_ctrls[] = { + + /** + * SI476X during its station seeking(or tuning) process uses several + * parameters to detrmine if "the station" is valid: + * + * - Signal's SNR(in dBuV) must be lower than + * #V4L2_CID_SI476X_SNR_THRESHOLD + * - Signal's RSSI(in dBuV) must be greater than + * #V4L2_CID_SI476X_RSSI_THRESHOLD + * - Signal's frequency deviation(in units of 2ppm) must not be + * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR + */ + [SI476X_IDX_RSSI_THRESHOLD] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_RSSI_THRESHOLD, + .name = "Valid RSSI Threshold", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = -128, + .max = 127, + .step = 1, + }, + [SI476X_IDX_SNR_THRESHOLD] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_SNR_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Valid SNR Threshold", + .min = -128, + .max = 127, + .step = 1, + }, + [SI476X_IDX_MAX_TUNE_ERROR] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_MAX_TUNE_ERROR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Max Tune Errors", + .min = 0, + .max = 126 * 2, + .step = 2, + }, + + /** + * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics + * built-in power-line noise supression filter is to reject + * during AM-mode operation. + */ + [SI476X_IDX_HARMONICS_COUNT] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_HARMONICS_COUNT, + .type = V4L2_CTRL_TYPE_INTEGER, + + .name = "Count of Harmonics to Reject", + .min = 0, + .max = 20, + .step = 1, + }, + + /** + * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which + * two tuners working in diversity mode are to work in. + * + * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled + * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is + * on, primary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is + * off, primary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is + * off, secondary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is + * on, secondary tuner's antenna is the main one. + */ + [SI476X_IDX_DIVERSITY_MODE] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_DIVERSITY_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Phase Diversity Mode", + .qmenu = phase_diversity_modes, + .min = 0, + .max = ARRAY_SIZE(phase_diversity_modes) - 1, + }, + + /** + * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in + * diversity mode indicator. Allows user to determine if two + * chips working in diversity mode have established a link + * between each other and if the system as a whole uses + * signals from both antennas to receive FM radio. + */ + [SI476X_IDX_INTERCHIP_LINK] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_INTERCHIP_LINK, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, + .name = "Inter-Chip Link", + .min = 0, + .max = 1, + .step = 1, + }, +}; + +struct si476x_radio; + +/** + * struct si476x_radio_ops - vtable of tuner functions + * + * This table holds pointers to functions implementing particular + * operations depending on the mode in which the tuner chip was + * configured to start in. If the function is not supported + * corresponding element is set to #NULL. + * + * @tune_freq: Tune chip to a specific frequency + * @seek_start: Star station seeking + * @rsq_status: Get Recieved Signal Quality(RSQ) status + * @rds_blckcnt: Get recived RDS blocks count + * @phase_diversity: Change phase diversity mode of the tuner + * @phase_div_status: Get phase diversity mode status + * @acf_status: Get the status of Automatically Controlled + * Features(ACF) + * @agc_status: Get Automatic Gain Control(AGC) status + */ +struct si476x_radio_ops { + int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *); + int (*seek_start)(struct si476x_core *, bool, bool); + int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *, + struct si476x_rsq_status_report *); + int (*rds_blckcnt)(struct si476x_core *, bool, + struct si476x_rds_blockcount_report *); + + int (*phase_diversity)(struct si476x_core *, + enum si476x_phase_diversity_mode); + int (*phase_div_status)(struct si476x_core *); + int (*acf_status)(struct si476x_core *, + struct si476x_acf_status_report *); + int (*agc_status)(struct si476x_core *, + struct si476x_agc_status_report *); +}; + +/** + * struct si476x_radio - radio device + * + * @core: Pointer to underlying core device + * @videodev: Pointer to video device created by V4L2 subsystem + * @ops: Vtable of functions. See struct si476x_radio_ops for details + * @kref: Reference counter + * @core_lock: An r/w semaphore to brebvent the deletion of underlying + * core structure is the radio device is being used + */ +struct si476x_radio { + struct v4l2_device v4l2dev; + struct video_device videodev; + struct v4l2_ctrl_handler ctrl_handler; + + struct si476x_core *core; + /* This field should not be accesses unless core lock is held */ + const struct si476x_radio_ops *ops; + + struct dentry *debugfs; + u32 audmode; +}; + +static inline struct si476x_radio * +v4l2_dev_to_radio(struct v4l2_device *d) +{ + return container_of(d, struct si476x_radio, v4l2dev); +} + +static inline struct si476x_radio * +v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) +{ + return container_of(d, struct si476x_radio, ctrl_handler); +} + +/* + * si476x_vidioc_querycap - query device capabilities + */ +static int si476x_radio_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct si476x_radio *radio = video_drvdata(file); + + strlcpy(capability->driver, radio->v4l2dev.name, + sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", radio->v4l2dev.name); + + capability->device_caps = V4L2_CAP_TUNER + | V4L2_CAP_RADIO + | V4L2_CAP_HW_FREQ_SEEK; + + si476x_core_lock(radio->core); + if (!si476x_core_is_a_secondary_tuner(radio->core)) + capability->device_caps |= V4L2_CAP_RDS_CAPTURE + | V4L2_CAP_READWRITE; + si476x_core_unlock(radio->core); + + capability->capabilities = capability->device_caps + | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int si476x_radio_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (band->tuner != 0) + return -EINVAL; + + switch (radio->core->chip_id) { + /* AM/FM tuners -- all bands are supported */ + case SI476X_CHIP_SI4761: + case SI476X_CHIP_SI4764: + if (band->index < ARRAY_SIZE(si476x_bands)) { + *band = si476x_bands[band->index]; + err = 0; + } else { + err = -EINVAL; + } + break; + /* FM companion tuner chips -- only FM bands are + * supported */ + case SI476X_CHIP_SI4768: + if (band->index == SI476X_BAND_FM) { + *band = si476x_bands[band->index]; + err = 0; + } else { + err = -EINVAL; + } + break; + default: + err = -EINVAL; + } + + return err; +} + +static int si476x_radio_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + int err; + struct si476x_rsq_status_report report; + struct si476x_radio *radio = video_drvdata(file); + + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + if (tuner->index != 0) + return -EINVAL; + + tuner->type = V4L2_TUNER_RADIO; + tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies + * in multiples of + * 62.5 Hz */ + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_HWSEEK_BOUNDED + | V4L2_TUNER_CAP_HWSEEK_WRAP + | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; + + si476x_core_lock(radio->core); + + if (si476x_core_is_a_secondary_tuner(radio->core)) { + strlcpy(tuner->name, "FM (secondary)", sizeof(tuner->name)); + tuner->rxsubchans = 0; + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; + } else if (si476x_core_has_am(radio->core)) { + if (si476x_core_is_a_primary_tuner(radio->core)) + strlcpy(tuner->name, "AM/FM (primary)", + sizeof(tuner->name)); + else + strlcpy(tuner->name, "AM/FM", sizeof(tuner->name)); + + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO + | V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + + tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow; + } else { + strlcpy(tuner->name, "FM", sizeof(tuner->name)); + tuner->rxsubchans = V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; + } + + tuner->audmode = radio->audmode; + + tuner->afc = 1; + tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh; + + err = radio->ops->rsq_status(radio->core, + &args, &report); + if (err < 0) { + tuner->signal = 0; + } else { + /* + * tuner->signal value range: 0x0000 .. 0xFFFF, + * report.rssi: -128 .. 127 + */ + tuner->signal = (report.rssi + 128) * 257; + } + si476x_core_unlock(radio->core); + + return err; +} + +static int si476x_radio_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct si476x_radio *radio = video_drvdata(file); + + if (tuner->index != 0) + return -EINVAL; + + if (tuner->audmode == V4L2_TUNER_MODE_MONO || + tuner->audmode == V4L2_TUNER_MODE_STEREO) + radio->audmode = tuner->audmode; + else + radio->audmode = V4L2_TUNER_MODE_STEREO; + + return 0; +} + +static int si476x_radio_init_vtable(struct si476x_radio *radio, + enum si476x_func func) +{ + static const struct si476x_radio_ops fm_ops = { + .tune_freq = si476x_core_cmd_fm_tune_freq, + .seek_start = si476x_core_cmd_fm_seek_start, + .rsq_status = si476x_core_cmd_fm_rsq_status, + .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount, + .phase_diversity = si476x_core_cmd_fm_phase_diversity, + .phase_div_status = si476x_core_cmd_fm_phase_div_status, + .acf_status = si476x_core_cmd_fm_acf_status, + .agc_status = si476x_core_cmd_agc_status, + }; + + static const struct si476x_radio_ops am_ops = { + .tune_freq = si476x_core_cmd_am_tune_freq, + .seek_start = si476x_core_cmd_am_seek_start, + .rsq_status = si476x_core_cmd_am_rsq_status, + .rds_blckcnt = NULL, + .phase_diversity = NULL, + .phase_div_status = NULL, + .acf_status = si476x_core_cmd_am_acf_status, + .agc_status = NULL, + }; + + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + radio->ops = &fm_ops; + return 0; + + case SI476X_FUNC_AM_RECEIVER: + radio->ops = &am_ops; + return 0; + default: + WARN(1, "Unexpected tuner function value\n"); + return -EINVAL; + } +} + +static int si476x_radio_pretune(struct si476x_radio *radio, + enum si476x_func func) +{ + int retval; + + struct si476x_tune_freq_args args = { + .zifsr = false, + .hd = false, + .injside = SI476X_INJSIDE_AUTO, + .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE, + .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO, + .antcap = 0, + }; + + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + args.freq = v4l2_to_si476x(radio->core, + 92 * FREQ_MUL); + retval = radio->ops->tune_freq(radio->core, &args); + break; + case SI476X_FUNC_AM_RECEIVER: + args.freq = v4l2_to_si476x(radio->core, + 0.6 * FREQ_MUL); + retval = radio->ops->tune_freq(radio->core, &args); + break; + default: + WARN(1, "Unexpected tuner function value\n"); + retval = -EINVAL; + } + + return retval; +} +static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio, + enum si476x_func func) +{ + int err; + + /* regcache_mark_dirty(radio->core->regmap); */ + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE, + SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_AUDIO_DEEMPHASIS, + SI476X_PROP_AUDIO_PWR_LINE_FILTER); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_INT_CTL_ENABLE, + SI476X_PROP_INT_CTL_ENABLE); + if (err < 0) + return err; + + /* + * Is there any point in restoring SNR and the like + * when switching between AM/FM? + */ + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_VALID_MAX_TUNE_ERROR, + SI476X_PROP_VALID_MAX_TUNE_ERROR); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_VALID_SNR_THRESHOLD, + SI476X_PROP_VALID_RSSI_THRESHOLD); + if (err < 0) + return err; + + if (func == SI476X_FUNC_FM_RECEIVER) { + if (si476x_core_has_diversity(radio->core)) { + err = si476x_core_cmd_fm_phase_diversity(radio->core, + radio->core->diversity_mode); + if (err < 0) + return err; + } + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, + SI476X_PROP_FM_RDS_CONFIG); + if (err < 0) + return err; + } + + return si476x_radio_init_vtable(radio, func); + +} + +static int si476x_radio_change_func(struct si476x_radio *radio, + enum si476x_func func) +{ + int err; + bool soft; + /* + * Since power/up down is a very time consuming operation, + * try to avoid doing it if the requested mode matches the one + * the tuner is in + */ + if (func == radio->core->power_up_parameters.func) + return 0; + + soft = true; + err = si476x_core_stop(radio->core, soft); + if (err < 0) { + /* + * OK, if the chip does not want to play nice let's + * try to reset it in more brutal way + */ + soft = false; + err = si476x_core_stop(radio->core, soft); + if (err < 0) + return err; + } + /* + Set the desired radio tuner function + */ + radio->core->power_up_parameters.func = func; + + err = si476x_core_start(radio->core, soft); + if (err < 0) + return err; + + /* + * No need to do the rest of manipulations for the bootlader + * mode + */ + if (func != SI476X_FUNC_FM_RECEIVER && + func != SI476X_FUNC_AM_RECEIVER) + return err; + + return si476x_radio_do_post_powerup_init(radio, func); +} + +static int si476x_radio_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + if (radio->ops->rsq_status) { + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = true, + .cancel = false, + .stcack = false, + }; + + err = radio->ops->rsq_status(radio->core, &args, &report); + if (!err) + f->frequency = si476x_to_v4l2(radio->core, + report.readfreq); + } else { + err = -EINVAL; + } + + si476x_core_unlock(radio->core); + + return err; +} + +static int si476x_radio_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + int err; + struct si476x_tune_freq_args args; + struct si476x_radio *radio = video_drvdata(file); + + const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh + + si476x_bands[SI476X_BAND_FM].rangelow) / 2; + const int band = (f->frequency > midrange) ? + SI476X_BAND_FM : SI476X_BAND_AM; + const enum si476x_func func = (band == SI476X_BAND_AM) ? + SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER; + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + f->frequency = clamp(f->frequency, + si476x_bands[band].rangelow, + si476x_bands[band].rangehigh); + + if (si476x_radio_freq_is_inside_of_the_band(f->frequency, + SI476X_BAND_AM) && + (!si476x_core_has_am(radio->core) || + si476x_core_is_a_secondary_tuner(radio->core))) { + err = -EINVAL; + goto unlock; + } + + err = si476x_radio_change_func(radio, func); + if (err < 0) + goto unlock; + + args.zifsr = false; + args.hd = false; + args.injside = SI476X_INJSIDE_AUTO; + args.freq = v4l2_to_si476x(radio->core, + f->frequency); + args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE; + args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO; + args.antcap = 0; + + err = radio->ops->tune_freq(radio->core, &args); + +unlock: + si476x_core_unlock(radio->core); + return err; +} + +static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + int err; + enum si476x_func func; + u32 rangelow, rangehigh; + struct si476x_radio *radio = video_drvdata(file); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (seek->tuner != 0 || + seek->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + if (!seek->rangelow) { + err = regmap_read(radio->core->regmap, + SI476X_PROP_SEEK_BAND_BOTTOM, + &rangelow); + if (!err) + rangelow = si476x_to_v4l2(radio->core, rangelow); + else + goto unlock; + } + if (!seek->rangehigh) { + err = regmap_read(radio->core->regmap, + SI476X_PROP_SEEK_BAND_TOP, + &rangehigh); + if (!err) + rangehigh = si476x_to_v4l2(radio->core, rangehigh); + else + goto unlock; + } + + if (rangelow > rangehigh) { + err = -EINVAL; + goto unlock; + } + + if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, + SI476X_BAND_FM)) { + func = SI476X_FUNC_FM_RECEIVER; + + } else if (si476x_core_has_am(radio->core) && + si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, + SI476X_BAND_AM)) { + func = SI476X_FUNC_AM_RECEIVER; + } else { + err = -EINVAL; + goto unlock; + } + + err = si476x_radio_change_func(radio, func); + if (err < 0) + goto unlock; + + if (seek->rangehigh) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_BAND_TOP, + v4l2_to_si476x(radio->core, + seek->rangehigh)); + if (err) + goto unlock; + } + if (seek->rangelow) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_BAND_BOTTOM, + v4l2_to_si476x(radio->core, + seek->rangelow)); + if (err) + goto unlock; + } + if (seek->spacing) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_FREQUENCY_SPACING, + v4l2_to_si476x(radio->core, + seek->spacing)); + if (err) + goto unlock; + } + + err = radio->ops->seek_start(radio->core, + seek->seek_upward, + seek->wrap_around); +unlock: + si476x_core_unlock(radio->core); + + + + return err; +} + +static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + int retval; + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + si476x_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_SI476X_INTERCHIP_LINK: + if (si476x_core_has_diversity(radio->core)) { + if (radio->ops->phase_diversity) { + retval = radio->ops->phase_div_status(radio->core); + if (retval < 0) + break; + + ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval); + retval = 0; + break; + } else { + retval = -ENOTTY; + break; + } + } + retval = -EINVAL; + break; + default: + retval = -EINVAL; + break; + } + si476x_core_unlock(radio->core); + return retval; + +} + +static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + int retval; + enum si476x_phase_diversity_mode mode; + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + si476x_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_SI476X_HARMONICS_COUNT: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_HARMONICS_MASK, + ctrl->val); + break; + case V4L2_CID_POWER_LINE_FREQUENCY: + switch (ctrl->val) { + case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_ENABLE_MASK, + 0); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_GRID_MASK, + SI476X_PROP_PWR_GRID_50HZ); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_GRID_MASK, + SI476X_PROP_PWR_GRID_60HZ); + break; + default: + retval = -EINVAL; + break; + } + break; + case V4L2_CID_SI476X_RSSI_THRESHOLD: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_RSSI_THRESHOLD, + ctrl->val); + break; + case V4L2_CID_SI476X_SNR_THRESHOLD: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_SNR_THRESHOLD, + ctrl->val); + break; + case V4L2_CID_SI476X_MAX_TUNE_ERROR: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_MAX_TUNE_ERROR, + ctrl->val); + break; + case V4L2_CID_RDS_RECEPTION: + /* + * It looks like RDS related properties are + * inaccesable when tuner is in AM mode, so cache the + * changes + */ + if (si476x_core_is_in_am_receiver_mode(radio->core)) + regcache_cache_only(radio->core->regmap, true); + + if (ctrl->val) { + retval = regmap_write(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT, + radio->core->rds_fifo_depth); + if (retval < 0) + break; + + if (radio->core->client->irq) { + retval = regmap_write(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, + SI476X_RDSRECV); + if (retval < 0) + break; + } + + /* Drain RDS FIFO before enabling RDS processing */ + retval = si476x_core_cmd_fm_rds_status(radio->core, + false, + true, + true, + NULL); + if (retval < 0) + break; + + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_FM_RDS_CONFIG, + SI476X_PROP_RDSEN_MASK, + SI476X_PROP_RDSEN); + } else { + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_FM_RDS_CONFIG, + SI476X_PROP_RDSEN_MASK, + !SI476X_PROP_RDSEN); + } + + if (si476x_core_is_in_am_receiver_mode(radio->core)) + regcache_cache_only(radio->core->regmap, false); + break; + case V4L2_CID_TUNE_DEEMPHASIS: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_AUDIO_DEEMPHASIS, + ctrl->val); + break; + + case V4L2_CID_SI476X_DIVERSITY_MODE: + mode = si476x_phase_diversity_idx_to_mode(ctrl->val); + + if (mode == radio->core->diversity_mode) { + retval = 0; + break; + } + + if (si476x_core_is_in_am_receiver_mode(radio->core)) { + /* + * Diversity cannot be configured while tuner + * is in AM mode so save the changes and carry on. + */ + radio->core->diversity_mode = mode; + retval = 0; + } else { + retval = radio->ops->phase_diversity(radio->core, mode); + if (!retval) + radio->core->diversity_mode = mode; + } + break; + + default: + retval = -EINVAL; + break; + } + + si476x_core_unlock(radio->core); + + return retval; +} + +static int si476x_radio_g_chip_ident(struct file *file, void *fh, + struct v4l2_dbg_chip_ident *chip) +{ + if (chip->match.type == V4L2_CHIP_MATCH_HOST && + v4l2_chip_match_host(&chip->match)) + return 0; + return -EINVAL; +} + + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int si476x_radio_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + int err; + unsigned int value; + struct si476x_radio *radio = video_drvdata(file); + + si476x_core_lock(radio->core); + reg->size = 2; + err = regmap_read(radio->core->regmap, + (unsigned int)reg->reg, &value); + reg->val = value; + si476x_core_unlock(radio->core); + + return err; +} +static int si476x_radio_s_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + + int err; + struct si476x_radio *radio = video_drvdata(file); + + si476x_core_lock(radio->core); + err = regmap_write(radio->core->regmap, + (unsigned int)reg->reg, + (unsigned int)reg->val); + si476x_core_unlock(radio->core); + + return err; +} +#endif + +static int si476x_radio_fops_open(struct file *file) +{ + struct si476x_radio *radio = video_drvdata(file); + int err; + + err = v4l2_fh_open(file); + if (err) + return err; + + if (v4l2_fh_is_singular_file(file)) { + si476x_core_lock(radio->core); + err = si476x_core_set_power_state(radio->core, + SI476X_POWER_UP_FULL); + if (err < 0) + goto done; + + err = si476x_radio_do_post_powerup_init(radio, + radio->core->power_up_parameters.func); + if (err < 0) + goto power_down; + + err = si476x_radio_pretune(radio, + radio->core->power_up_parameters.func); + if (err < 0) + goto power_down; + + si476x_core_unlock(radio->core); + /*Must be done after si476x_core_unlock to prevent a deadlock*/ + v4l2_ctrl_handler_setup(&radio->ctrl_handler); + } + + return err; + +power_down: + si476x_core_set_power_state(radio->core, + SI476X_POWER_DOWN); +done: + si476x_core_unlock(radio->core); + v4l2_fh_release(file); + + return err; +} + +static int si476x_radio_fops_release(struct file *file) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (v4l2_fh_is_singular_file(file) && + atomic_read(&radio->core->is_alive)) + si476x_core_set_power_state(radio->core, + SI476X_POWER_DOWN); + + err = v4l2_fh_release(file); + + return err; +} + +static ssize_t si476x_radio_fops_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t rval; + size_t fifo_len; + unsigned int copied; + + struct si476x_radio *radio = video_drvdata(file); + + /* block if no new data available */ + if (kfifo_is_empty(&radio->core->rds_fifo)) { + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + rval = wait_event_interruptible(radio->core->rds_read_queue, + (!kfifo_is_empty(&radio->core->rds_fifo) || + !atomic_read(&radio->core->is_alive))); + if (rval < 0) + return -EINTR; + + if (!atomic_read(&radio->core->is_alive)) + return -ENODEV; + } + + fifo_len = kfifo_len(&radio->core->rds_fifo); + + if (kfifo_to_user(&radio->core->rds_fifo, buf, + min(fifo_len, count), + &copied) != 0) { + dev_warn(&radio->videodev.dev, + "Error during FIFO to userspace copy\n"); + rval = -EIO; + } else { + rval = (ssize_t)copied; + } + + return rval; +} + +static unsigned int si476x_radio_fops_poll(struct file *file, + struct poll_table_struct *pts) +{ + struct si476x_radio *radio = video_drvdata(file); + unsigned long req_events = poll_requested_events(pts); + unsigned int err = v4l2_ctrl_poll(file, pts); + + if (req_events & (POLLIN | POLLRDNORM)) { + if (atomic_read(&radio->core->is_alive)) + poll_wait(file, &radio->core->rds_read_queue, pts); + + if (!atomic_read(&radio->core->is_alive)) + err = POLLHUP; + + if (!kfifo_is_empty(&radio->core->rds_fifo)) + err = POLLIN | POLLRDNORM; + } + + return err; +} + +static const struct v4l2_file_operations si476x_fops = { + .owner = THIS_MODULE, + .read = si476x_radio_fops_read, + .poll = si476x_radio_fops_poll, + .unlocked_ioctl = video_ioctl2, + .open = si476x_radio_fops_open, + .release = si476x_radio_fops_release, +}; + + +static const struct v4l2_ioctl_ops si4761_ioctl_ops = { + .vidioc_querycap = si476x_radio_querycap, + .vidioc_g_tuner = si476x_radio_g_tuner, + .vidioc_s_tuner = si476x_radio_s_tuner, + + .vidioc_g_frequency = si476x_radio_g_frequency, + .vidioc_s_frequency = si476x_radio_s_frequency, + .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek, + .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_g_chip_ident = si476x_radio_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = si476x_radio_g_register, + .vidioc_s_register = si476x_radio_s_register, +#endif +}; + + +static const struct video_device si476x_viddev_template = { + .fops = &si476x_fops, + .name = DRIVER_NAME, + .release = video_device_release_empty, +}; + + + +static ssize_t si476x_radio_read_acf_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_acf_status_report report; + + si476x_core_lock(radio->core); + if (radio->ops->acf_status) + err = radio->ops->acf_status(radio->core, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_acf_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_acf_blob, +}; + +static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rds_blockcount_report report; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rds_blckcnt(radio->core, true, + &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rds_blckcnt_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rds_blckcnt_blob, +}; + +static ssize_t si476x_radio_read_agc_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_agc_status_report report; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->agc_status(radio->core, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_agc_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_agc_blob, +}; + +static ssize_t si476x_radio_read_rsq_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rsq_status(radio->core, &args, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rsq_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rsq_blob, +}; + +static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = true, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rsq_status(radio->core, &args, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rsq_primary_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rsq_primary_blob, +}; + + +static int si476x_radio_init_debugfs(struct si476x_radio *radio) +{ + struct dentry *dentry; + int ret; + + dentry = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto exit; + } + radio->debugfs = dentry; + + dentry = debugfs_create_file("acf", S_IRUGO, + radio->debugfs, radio, &radio_acf_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rds_blckcnt", S_IRUGO, + radio->debugfs, radio, + &radio_rds_blckcnt_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("agc", S_IRUGO, + radio->debugfs, radio, &radio_agc_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rsq", S_IRUGO, + radio->debugfs, radio, &radio_rsq_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rsq_primary", S_IRUGO, + radio->debugfs, radio, + &radio_rsq_primary_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + return 0; +cleanup: + debugfs_remove_recursive(radio->debugfs); +exit: + return ret; +} + + +static int si476x_radio_add_new_custom(struct si476x_radio *radio, + enum si476x_ctrl_idx idx) +{ + int rval; + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, + &si476x_ctrls[idx], + NULL); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) + dev_err(radio->v4l2dev.dev, + "Could not initialize '%s' control %d\n", + si476x_ctrls[idx].name, rval); + + return rval; +} + +static int si476x_radio_probe(struct platform_device *pdev) +{ + int rval; + struct si476x_radio *radio; + struct v4l2_ctrl *ctrl; + + static atomic_t instance = ATOMIC_INIT(0); + + radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); + if (!radio) + return -ENOMEM; + + radio->core = i2c_mfd_cell_to_core(&pdev->dev); + + v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); + + rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev); + if (rval) { + dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); + return rval; + } + + memcpy(&radio->videodev, &si476x_viddev_template, + sizeof(struct video_device)); + + radio->videodev.v4l2_dev = &radio->v4l2dev; + radio->videodev.ioctl_ops = &si4761_ioctl_ops; + + video_set_drvdata(&radio->videodev, radio); + platform_set_drvdata(pdev, radio); + + set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags); + + radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; + v4l2_ctrl_handler_init(&radio->ctrl_handler, + 1 + ARRAY_SIZE(si476x_ctrls)); + + if (si476x_core_has_am(radio->core)) { + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, + &si476x_ctrl_ops, + V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_60HZ, + 0, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n", + rval); + goto exit; + } + + rval = si476x_radio_add_new_custom(radio, + SI476X_IDX_HARMONICS_COUNT); + if (rval < 0) + goto exit; + } + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD); + if (rval < 0) + goto exit; + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD); + if (rval < 0) + goto exit; + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR); + if (rval < 0) + goto exit; + + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, + &si476x_ctrl_ops, + V4L2_CID_TUNE_DEEMPHASIS, + V4L2_DEEMPHASIS_75_uS, 0, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", + rval); + goto exit; + } + + ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, + V4L2_CID_RDS_RECEPTION, + 0, 1, 1, 1); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n", + rval); + goto exit; + } + + if (si476x_core_has_diversity(radio->core)) { + si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = + si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); + si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE); + if (rval < 0) + goto exit; + + si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK); + if (rval < 0) + goto exit; + } + + /* register video device */ + rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); + if (rval < 0) { + dev_err(&pdev->dev, "Could not register video device\n"); + goto exit; + } + + rval = si476x_radio_init_debugfs(radio); + if (rval < 0) { + dev_err(&pdev->dev, "Could not creat debugfs interface\n"); + goto exit; + } + + return 0; +exit: + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + return rval; +} + +static int si476x_radio_remove(struct platform_device *pdev) +{ + struct si476x_radio *radio = platform_get_drvdata(pdev); + + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + video_unregister_device(&radio->videodev); + v4l2_device_unregister(&radio->v4l2dev); + debugfs_remove_recursive(radio->debugfs); + + return 0; +} + +MODULE_ALIAS("platform:si476x-radio"); + +static struct platform_driver si476x_radio_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = si476x_radio_probe, + .remove = si476x_radio_remove, +}; +module_platform_driver(si476x_radio_driver); + +MODULE_AUTHOR("Andrey Smirnov "); +MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell"); +MODULE_LICENSE("GPL"); diff --git a/include/media/si476x.h b/include/media/si476x.h new file mode 100644 index 000000000000..beb6433d6958 --- /dev/null +++ b/include/media/si476x.h @@ -0,0 +1,426 @@ +/* + * include/media/si476x.h -- Common definitions for si476x driver + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#ifndef SI476X_H +#define SI476X_H + +#include +#include + +struct si476x_device; + +/* It is possible to select one of the four adresses using pins A0 + * and A1 on SI476x */ +#define SI476X_I2C_ADDR_1 0x60 +#define SI476X_I2C_ADDR_2 0x61 +#define SI476X_I2C_ADDR_3 0x62 +#define SI476X_I2C_ADDR_4 0x63 + +enum si476x_iqclk_config { + SI476X_IQCLK_NOOP = 0, + SI476X_IQCLK_TRISTATE = 1, + SI476X_IQCLK_IQ = 21, +}; +enum si476x_iqfs_config { + SI476X_IQFS_NOOP = 0, + SI476X_IQFS_TRISTATE = 1, + SI476X_IQFS_IQ = 21, +}; +enum si476x_iout_config { + SI476X_IOUT_NOOP = 0, + SI476X_IOUT_TRISTATE = 1, + SI476X_IOUT_OUTPUT = 22, +}; +enum si476x_qout_config { + SI476X_QOUT_NOOP = 0, + SI476X_QOUT_TRISTATE = 1, + SI476X_QOUT_OUTPUT = 22, +}; + +enum si476x_dclk_config { + SI476X_DCLK_NOOP = 0, + SI476X_DCLK_TRISTATE = 1, + SI476X_DCLK_DAUDIO = 10, +}; + +enum si476x_dfs_config { + SI476X_DFS_NOOP = 0, + SI476X_DFS_TRISTATE = 1, + SI476X_DFS_DAUDIO = 10, +}; + +enum si476x_dout_config { + SI476X_DOUT_NOOP = 0, + SI476X_DOUT_TRISTATE = 1, + SI476X_DOUT_I2S_OUTPUT = 12, + SI476X_DOUT_I2S_INPUT = 13, +}; + +enum si476x_xout_config { + SI476X_XOUT_NOOP = 0, + SI476X_XOUT_TRISTATE = 1, + SI476X_XOUT_I2S_INPUT = 13, + SI476X_XOUT_MODE_SELECT = 23, +}; + + +enum si476x_icin_config { + SI476X_ICIN_NOOP = 0, + SI476X_ICIN_TRISTATE = 1, + SI476X_ICIN_GPO1_HIGH = 2, + SI476X_ICIN_GPO1_LOW = 3, + SI476X_ICIN_IC_LINK = 30, +}; + +enum si476x_icip_config { + SI476X_ICIP_NOOP = 0, + SI476X_ICIP_TRISTATE = 1, + SI476X_ICIP_GPO2_HIGH = 2, + SI476X_ICIP_GPO2_LOW = 3, + SI476X_ICIP_IC_LINK = 30, +}; + +enum si476x_icon_config { + SI476X_ICON_NOOP = 0, + SI476X_ICON_TRISTATE = 1, + SI476X_ICON_I2S = 10, + SI476X_ICON_IC_LINK = 30, +}; + +enum si476x_icop_config { + SI476X_ICOP_NOOP = 0, + SI476X_ICOP_TRISTATE = 1, + SI476X_ICOP_I2S = 10, + SI476X_ICOP_IC_LINK = 30, +}; + + +enum si476x_lrout_config { + SI476X_LROUT_NOOP = 0, + SI476X_LROUT_TRISTATE = 1, + SI476X_LROUT_AUDIO = 2, + SI476X_LROUT_MPX = 3, +}; + + +enum si476x_intb_config { + SI476X_INTB_NOOP = 0, + SI476X_INTB_TRISTATE = 1, + SI476X_INTB_DAUDIO = 10, + SI476X_INTB_IRQ = 40, +}; + +enum si476x_a1_config { + SI476X_A1_NOOP = 0, + SI476X_A1_TRISTATE = 1, + SI476X_A1_IRQ = 40, +}; + +enum si476x_part_revisions { + SI476X_REVISION_A10 = 0, + SI476X_REVISION_A20 = 1, + SI476X_REVISION_A30 = 2, +}; + +struct si476x_pinmux { + enum si476x_dclk_config dclk; + enum si476x_dfs_config dfs; + enum si476x_dout_config dout; + enum si476x_xout_config xout; + + enum si476x_iqclk_config iqclk; + enum si476x_iqfs_config iqfs; + enum si476x_iout_config iout; + enum si476x_qout_config qout; + + enum si476x_icin_config icin; + enum si476x_icip_config icip; + enum si476x_icon_config icon; + enum si476x_icop_config icop; + + enum si476x_lrout_config lrout; + + enum si476x_intb_config intb; + enum si476x_a1_config a1; +}; + +/** + * enum si476x_phase_diversity_mode - possbile phase diversity modes + * for SI4764/5/6/7 chips. + * + * @SI476X_PHDIV_DISABLED: Phase diversity feature is + * disabled. + * @SI476X_PHDIV_PRIMARY_COMBINING: Tuner works as a primary tuner + * in combination with a + * secondary one. + * @SI476X_PHDIV_PRIMARY_ANTENNA: Tuner works as a primary tuner + * using only its own antenna. + * @SI476X_PHDIV_SECONDARY_ANTENNA: Tuner works as a primary tuner + * usning seconary tuner's antenna. + * @SI476X_PHDIV_SECONDARY_COMBINING: Tuner works as a secondary + * tuner in combination with the + * primary one. + */ +enum si476x_phase_diversity_mode { + SI476X_PHDIV_DISABLED = 0, + SI476X_PHDIV_PRIMARY_COMBINING = 1, + SI476X_PHDIV_PRIMARY_ANTENNA = 2, + SI476X_PHDIV_SECONDARY_ANTENNA = 3, + SI476X_PHDIV_SECONDARY_COMBINING = 5, +}; + +enum si476x_ibias6x { + SI476X_IBIAS6X_OTHER = 0, + SI476X_IBIAS6X_RCVR1_NON_4MHZ_CLK = 1, +}; + +enum si476x_xstart { + SI476X_XSTART_MULTIPLE_TUNER = 0x11, + SI476X_XSTART_NORMAL = 0x77, +}; + +enum si476x_freq { + SI476X_FREQ_4_MHZ = 0, + SI476X_FREQ_37P209375_MHZ = 1, + SI476X_FREQ_36P4_MHZ = 2, + SI476X_FREQ_37P8_MHZ = 3, +}; + +enum si476x_xmode { + SI476X_XMODE_CRYSTAL_RCVR1 = 1, + SI476X_XMODE_EXT_CLOCK = 2, + SI476X_XMODE_CRYSTAL_RCVR2_3 = 3, +}; + +enum si476x_xbiashc { + SI476X_XBIASHC_SINGLE_RECEIVER = 0, + SI476X_XBIASHC_MULTIPLE_RECEIVER = 1, +}; + +enum si476x_xbias { + SI476X_XBIAS_RCVR2_3 = 0, + SI476X_XBIAS_4MHZ_RCVR1 = 3, + SI476X_XBIAS_RCVR1 = 7, +}; + +enum si476x_func { + SI476X_FUNC_BOOTLOADER = 0, + SI476X_FUNC_FM_RECEIVER = 1, + SI476X_FUNC_AM_RECEIVER = 2, + SI476X_FUNC_WB_RECEIVER = 3, +}; + + +/** + * @xcload: Selects the amount of additional on-chip capacitance to + * be connected between XTAL1 and gnd and between XTAL2 and + * GND. One half of the capacitance value shown here is the + * additional load capacitance presented to the xtal. The + * minimum step size is 0.277 pF. Recommended value is 0x28 + * but it will be layout dependent. Range is 0–0x3F i.e. + * (0–16.33 pF) + * @ctsien: enable CTSINT(interrupt request when CTS condition + * arises) when set + * @intsel: when set A1 pin becomes the interrupt pin; otherwise, + * INTB is the interrupt pin + * @func: selects the boot function of the device. I.e. + * SI476X_BOOTLOADER - Boot loader + * SI476X_FM_RECEIVER - FM receiver + * SI476X_AM_RECEIVER - AM receiver + * SI476X_WB_RECEIVER - Weatherband receiver + * @freq: oscillator's crystal frequency: + * SI476X_XTAL_37P209375_MHZ - 37.209375 Mhz + * SI476X_XTAL_36P4_MHZ - 36.4 Mhz + * SI476X_XTAL_37P8_MHZ - 37.8 Mhz + */ +struct si476x_power_up_args { + enum si476x_ibias6x ibias6x; + enum si476x_xstart xstart; + u8 xcload; + bool fastboot; + enum si476x_xbiashc xbiashc; + enum si476x_xbias xbias; + enum si476x_func func; + enum si476x_freq freq; + enum si476x_xmode xmode; +}; + + +enum si476x_ctrl_id { + V4L2_CID_SI476X_RSSI_THRESHOLD = (V4L2_CID_USER_SI476X_BASE + 1), + V4L2_CID_SI476X_SNR_THRESHOLD = (V4L2_CID_USER_SI476X_BASE + 2), + V4L2_CID_SI476X_MAX_TUNE_ERROR = (V4L2_CID_USER_SI476X_BASE + 3), + V4L2_CID_SI476X_HARMONICS_COUNT = (V4L2_CID_USER_SI476X_BASE + 4), + V4L2_CID_SI476X_DIVERSITY_MODE = (V4L2_CID_USER_SI476X_BASE + 5), + V4L2_CID_SI476X_INTERCHIP_LINK = (V4L2_CID_USER_SI476X_BASE + 6), +}; + +/* + * Platform dependent definition + */ +struct si476x_platform_data { + int gpio_reset; /* < 0 if not used */ + + struct si476x_power_up_args power_up_parameters; + enum si476x_phase_diversity_mode diversity_mode; + + struct si476x_pinmux pinmux; +}; + +/** + * struct si476x_rsq_status - structure containing received signal + * quality + * @multhint: Multipath Detect High. + * true - Indicatedes that the value is below + * FM_RSQ_MULTIPATH_HIGH_THRESHOLD + * false - Indicatedes that the value is above + * FM_RSQ_MULTIPATH_HIGH_THRESHOLD + * @multlint: Multipath Detect Low. + * true - Indicatedes that the value is below + * FM_RSQ_MULTIPATH_LOW_THRESHOLD + * false - Indicatedes that the value is above + * FM_RSQ_MULTIPATH_LOW_THRESHOLD + * @snrhint: SNR Detect High. + * true - Indicatedes that the value is below + * FM_RSQ_SNR_HIGH_THRESHOLD + * false - Indicatedes that the value is above + * FM_RSQ_SNR_HIGH_THRESHOLD + * @snrlint: SNR Detect Low. + * true - Indicatedes that the value is below + * FM_RSQ_SNR_LOW_THRESHOLD + * false - Indicatedes that the value is above + * FM_RSQ_SNR_LOW_THRESHOLD + * @rssihint: RSSI Detect High. + * true - Indicatedes that the value is below + * FM_RSQ_RSSI_HIGH_THRESHOLD + * false - Indicatedes that the value is above + * FM_RSQ_RSSI_HIGH_THRESHOLD + * @rssilint: RSSI Detect Low. + * true - Indicatedes that the value is below + * FM_RSQ_RSSI_LOW_THRESHOLD + * false - Indicatedes that the value is above + * FM_RSQ_RSSI_LOW_THRESHOLD + * @bltf: Band Limit. + * Set if seek command hits the band limit or wrapped to + * the original frequency. + * @snr_ready: SNR measurement in progress. + * @rssiready: RSSI measurement in progress. + * @afcrl: Set if FREQOFF >= MAX_TUNE_ERROR + * @valid: Set if the channel is valid + * rssi < FM_VALID_RSSI_THRESHOLD + * snr < FM_VALID_SNR_THRESHOLD + * tune_error < FM_VALID_MAX_TUNE_ERROR + * @readfreq: Current tuned frequency. + * @freqoff: Signed frequency offset. + * @rssi: Received Signal Strength Indicator(dBuV). + * @snr: RF SNR Indicator(dB). + * @lassi: + * @hassi: Low/High side Adjacent(100 kHz) Channel Strength Indicator + * @mult: Multipath indicator + * @dev: Who knows? But values may vary. + * @readantcap: Antenna tuning capacity value. + * @assi: Adjacent Channel(+/- 200kHz) Strength Indicator + * @usn: Ultrasonic Noise Inticator in -DBFS + */ +struct si476x_rsq_status_report { + __u8 multhint, multlint; + __u8 snrhint, snrlint; + __u8 rssihint, rssilint; + __u8 bltf; + __u8 snr_ready; + __u8 rssiready; + __u8 injside; + __u8 afcrl; + __u8 valid; + + __u16 readfreq; + __s8 freqoff; + __s8 rssi; + __s8 snr; + __s8 issi; + __s8 lassi, hassi; + __s8 mult; + __u8 dev; + __u16 readantcap; + __s8 assi; + __s8 usn; + + __u8 pilotdev; + __u8 rdsdev; + __u8 assidev; + __u8 strongdev; + __u16 rdspi; +} __packed; + +/** + * si476x_acf_status_report - ACF report results + * + * @blend_int: If set, indicates that stereo separation has crossed + * below the blend threshold as set by FM_ACF_BLEND_THRESHOLD + * @hblend_int: If set, indicates that HiBlend cutoff frequency is + * lower than threshold as set by FM_ACF_HBLEND_THRESHOLD + * @hicut_int: If set, indicates that HiCut cutoff frequency is lower + * than the threshold set by ACF_ + + */ +struct si476x_acf_status_report { + __u8 blend_int; + __u8 hblend_int; + __u8 hicut_int; + __u8 chbw_int; + __u8 softmute_int; + __u8 smute; + __u8 smattn; + __u8 chbw; + __u8 hicut; + __u8 hiblend; + __u8 pilot; + __u8 stblend; +} __packed; + +enum si476x_fmagc { + SI476X_FMAGC_10K_OHM = 0, + SI476X_FMAGC_800_OHM = 1, + SI476X_FMAGC_400_OHM = 2, + SI476X_FMAGC_200_OHM = 4, + SI476X_FMAGC_100_OHM = 8, + SI476X_FMAGC_50_OHM = 16, + SI476X_FMAGC_25_OHM = 32, + SI476X_FMAGC_12P5_OHM = 64, + SI476X_FMAGC_6P25_OHM = 128, +}; + +struct si476x_agc_status_report { + __u8 mxhi; + __u8 mxlo; + __u8 lnahi; + __u8 lnalo; + __u8 fmagc1; + __u8 fmagc2; + __u8 pgagain; + __u8 fmwblang; +} __packed; + +struct si476x_rds_blockcount_report { + __u16 expected; + __u16 received; + __u16 uncorrectable; +} __packed; + +#endif /* SI476X_H*/ -- cgit v1.2.3 From 53c5b6c95f1850b0f95b445e0c6f1c133b00bfef Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 11 Sep 2012 12:44:18 -0300 Subject: [media] Add common video interfaces OF bindings documentation This patch adds a document describing common OF bindings for video capture, output and video processing devices. It is curently mainly focused on video capture devices, with data busses defined by standards such as ITU-R BT.656 or MIPI-CSI2. It also documents a method of describing data links between devices. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Sylwester Nawrocki Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/video-interfaces.txt | 228 +++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/video-interfaces.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/media/video-interfaces.txt b/Documentation/devicetree/bindings/media/video-interfaces.txt new file mode 100644 index 000000000000..e022d2dc4962 --- /dev/null +++ b/Documentation/devicetree/bindings/media/video-interfaces.txt @@ -0,0 +1,228 @@ +Common bindings for video receiver and transmitter interfaces + +General concept +--------------- + +Video data pipelines usually consist of external devices, e.g. camera sensors, +controlled over an I2C, SPI or UART bus, and SoC internal IP blocks, including +video DMA engines and video data processors. + +SoC internal blocks are described by DT nodes, placed similarly to other SoC +blocks. External devices are represented as child nodes of their respective +bus controller nodes, e.g. I2C. + +Data interfaces on all video devices are described by their child 'port' nodes. +Configuration of a port depends on other devices participating in the data +transfer and is described by 'endpoint' subnodes. + +device { + ... + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + ... + endpoint@0 { ... }; + endpoint@1 { ... }; + }; + port@1 { ... }; + }; +}; + +If a port can be configured to work with more than one remote device on the same +bus, an 'endpoint' child node must be provided for each of them. If more than +one port is present in a device node or there is more than one endpoint at a +port, or port node needs to be associated with a selected hardware interface, +a common scheme using '#address-cells', '#size-cells' and 'reg' properties is +used. + +All 'port' nodes can be grouped under optional 'ports' node, which allows to +specify #address-cells, #size-cells properties independently for the 'port' +and 'endpoint' nodes and any child device nodes a device might have. + +Two 'endpoint' nodes are linked with each other through their 'remote-endpoint' +phandles. An endpoint subnode of a device contains all properties needed for +configuration of this device for data exchange with other device. In most +cases properties at the peer 'endpoint' nodes will be identical, however they +might need to be different when there is any signal modifications on the bus +between two devices, e.g. there are logic signal inverters on the lines. + +It is allowed for multiple endpoints at a port to be active simultaneously, +where supported by a device. For example, in case where a data interface of +a device is partitioned into multiple data busses, e.g. 16-bit input port +divided into two separate ITU-R BT.656 8-bit busses. In such case bus-width +and data-shift properties can be used to assign physical data lines to each +endpoint node (logical bus). + + +Required properties +------------------- + +If there is more than one 'port' or more than one 'endpoint' node or 'reg' +property is present in port and/or endpoint nodes the following properties +are required in a relevant parent node: + + - #address-cells : number of cells required to define port/endpoint + identifier, should be 1. + - #size-cells : should be zero. + +Optional endpoint properties +---------------------------- + +- remote-endpoint: phandle to an 'endpoint' subnode of a remote device node. +- slave-mode: a boolean property indicating that the link is run in slave mode. + The default when this property is not specified is master mode. In the slave + mode horizontal and vertical synchronization signals are provided to the + slave device (data source) by the master device (data sink). In the master + mode the data source device is also the source of the synchronization signals. +- bus-width: number of data lines actively used, valid for the parallel busses. +- data-shift: on the parallel data busses, if bus-width is used to specify the + number of data lines, data-shift can be used to specify which data lines are + used, e.g. "bus-width=<8>; data-shift=<2>;" means, that lines 9:2 are used. +- hsync-active: active state of the HSYNC signal, 0/1 for LOW/HIGH respectively. +- vsync-active: active state of the VSYNC signal, 0/1 for LOW/HIGH respectively. + Note, that if HSYNC and VSYNC polarities are not specified, embedded + synchronization may be required, where supported. +- data-active: similar to HSYNC and VSYNC, specifies data line polarity. +- field-even-active: field signal level during the even field data transmission. +- pclk-sample: sample data on rising (1) or falling (0) edge of the pixel clock + signal. +- data-lanes: an array of physical data lane indexes. Position of an entry + determines the logical lane number, while the value of an entry indicates + physical lane, e.g. for 2-lane MIPI CSI-2 bus we could have + "data-lanes = <1 2>;", assuming the clock lane is on hardware lane 0. + This property is valid for serial busses only (e.g. MIPI CSI-2). +- clock-lanes: an array of physical clock lane indexes. Position of an entry + determines the logical lane number, while the value of an entry indicates + physical lane, e.g. for a MIPI CSI-2 bus we could have "clock-lanes = <0>;", + which places the clock lane on hardware lane 0. This property is valid for + serial busses only (e.g. MIPI CSI-2). Note that for the MIPI CSI-2 bus this + array contains only one entry. +- clock-noncontinuous: a boolean property to allow MIPI CSI-2 non-continuous + clock mode. + + +Example +------- + +The example snippet below describes two data pipelines. ov772x and imx074 are +camera sensors with a parallel and serial (MIPI CSI-2) video bus respectively. +Both sensors are on the I2C control bus corresponding to the i2c0 controller +node. ov772x sensor is linked directly to the ceu0 video host interface. +imx074 is linked to ceu0 through the MIPI CSI-2 receiver (csi2). ceu0 has a +(single) DMA engine writing captured data to memory. ceu0 node has a single +'port' node which may indicate that at any time only one of the following data +pipelines can be active: ov772x -> ceu0 or imx074 -> csi2 -> ceu0. + + ceu0: ceu@0xfe910000 { + compatible = "renesas,sh-mobile-ceu"; + reg = <0xfe910000 0xa0>; + interrupts = <0x880>; + + mclk: master_clock { + compatible = "renesas,ceu-clock"; + #clock-cells = <1>; + clock-frequency = <50000000>; /* Max clock frequency */ + clock-output-names = "mclk"; + }; + + port { + #address-cells = <1>; + #size-cells = <0>; + + /* Parallel bus endpoint */ + ceu0_1: endpoint@1 { + reg = <1>; /* Local endpoint # */ + remote = <&ov772x_1_1>; /* Remote phandle */ + bus-width = <8>; /* Used data lines */ + data-shift = <2>; /* Lines 9:2 are used */ + + /* If hsync-active/vsync-active are missing, + embedded BT.656 sync is used */ + hsync-active = <0>; /* Active low */ + vsync-active = <0>; /* Active low */ + data-active = <1>; /* Active high */ + pclk-sample = <1>; /* Rising */ + }; + + /* MIPI CSI-2 bus endpoint */ + ceu0_0: endpoint@0 { + reg = <0>; + remote = <&csi2_2>; + }; + }; + }; + + i2c0: i2c@0xfff20000 { + ... + ov772x_1: camera@0x21 { + compatible = "omnivision,ov772x"; + reg = <0x21>; + vddio-supply = <®ulator1>; + vddcore-supply = <®ulator2>; + + clock-frequency = <20000000>; + clocks = <&mclk 0>; + clock-names = "xclk"; + + port { + /* With 1 endpoint per port no need for addresses. */ + ov772x_1_1: endpoint { + bus-width = <8>; + remote-endpoint = <&ceu0_1>; + hsync-active = <1>; + vsync-active = <0>; /* Who came up with an + inverter here ?... */ + data-active = <1>; + pclk-sample = <1>; + }; + }; + }; + + imx074: camera@0x1a { + compatible = "sony,imx074"; + reg = <0x1a>; + vddio-supply = <®ulator1>; + vddcore-supply = <®ulator2>; + + clock-frequency = <30000000>; /* Shared clock with ov772x_1 */ + clocks = <&mclk 0>; + clock-names = "sysclk"; /* Assuming this is the + name in the datasheet */ + port { + imx074_1: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + remote-endpoint = <&csi2_1>; + }; + }; + }; + }; + + csi2: csi2@0xffc90000 { + compatible = "renesas,sh-mobile-csi2"; + reg = <0xffc90000 0x1000>; + interrupts = <0x17a0>; + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + compatible = "renesas,csi2c"; /* One of CSI2I and CSI2C. */ + reg = <1>; /* CSI-2 PHY #1 of 2: PHY_S, + PHY_M has port address 0, + is unused. */ + csi2_1: endpoint { + clock-lanes = <0>; + data-lanes = <2 1>; + remote-endpoint = <&imx074_1>; + }; + }; + port@2 { + reg = <2>; /* port 2: link to the CEU */ + + csi2_2: endpoint { + remote-endpoint = <&ceu0_0>; + }; + }; + }; -- cgit v1.2.3 From 02399e35e6bb716ce9636eba006b792362270034 Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Tue, 26 Mar 2013 08:20:30 -0300 Subject: [media] s5p-csis: Add device tree support This patch support for binding the driver to the MIPI-CSIS devices instantiated from device tree and parsing the SoC and board specific properties. The MIPI CSI-2 channel is determined by the value of reg property placed in csis' port subnode. Signed-off-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park Signed-off-by: Mauro Carvalho Chehab --- .../bindings/media/samsung-mipi-csis.txt | 81 +++++++++++ drivers/media/platform/s5p-fimc/mipi-csis.c | 155 ++++++++++++++++----- drivers/media/platform/s5p-fimc/mipi-csis.h | 1 + include/media/s5p_fimc.h | 13 ++ 4 files changed, 215 insertions(+), 35 deletions(-) create mode 100644 Documentation/devicetree/bindings/media/samsung-mipi-csis.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/media/samsung-mipi-csis.txt b/Documentation/devicetree/bindings/media/samsung-mipi-csis.txt new file mode 100644 index 000000000000..5f8e28e2484f --- /dev/null +++ b/Documentation/devicetree/bindings/media/samsung-mipi-csis.txt @@ -0,0 +1,81 @@ +Samsung S5P/EXYNOS SoC series MIPI CSI-2 receiver (MIPI CSIS) +------------------------------------------------------------- + +Required properties: + +- compatible : "samsung,s5pv210-csis" for S5PV210 (S5PC110), + "samsung,exynos4210-csis" for Exynos4210 (S5PC210), + "samsung,exynos4212-csis" for Exynos4212/Exynos4412 + SoC series; +- reg : offset and length of the register set for the device; +- interrupts : should contain MIPI CSIS interrupt; the format of the + interrupt specifier depends on the interrupt controller; +- bus-width : maximum number of data lanes supported (SoC specific); +- vddio-supply : MIPI CSIS I/O and PLL voltage supply (e.g. 1.8V); +- vddcore-supply : MIPI CSIS Core voltage supply (e.g. 1.1V); +- clocks : list of clock specifiers, corresponding to entries in + clock-names property; +- clock-names : must contain "csis", "sclk_csis" entries, matching entries + in the clocks property. + +Optional properties: + +- clock-frequency : The IP's main (system bus) clock frequency in Hz, default + value when this property is not specified is 166 MHz; +- samsung,csis-wclk : CSI-2 wrapper clock selection. If this property is present + external clock from CMU will be used, or the bus clock if + if it's not specified. + +The device node should contain one 'port' child node with one child 'endpoint' +node, according to the bindings defined in Documentation/devicetree/bindings/ +media/video-interfaces.txt. The following are properties specific to those nodes. + +port node +--------- + +- reg : (required) must be 3 for camera C input (CSIS0) or 4 for + camera D input (CSIS1); + +endpoint node +------------- + +- data-lanes : (required) an array specifying active physical MIPI-CSI2 + data input lanes and their mapping to logical lanes; the + array's content is unused, only its length is meaningful; + +- samsung,csis-hs-settle : (optional) differential receiver (HS-RX) settle time; + + +Example: + + reg0: regulator@0 { + }; + + reg1: regulator@1 { + }; + +/* SoC properties */ + + csis_0: csis@11880000 { + compatible = "samsung,exynos4210-csis"; + reg = <0x11880000 0x1000>; + interrupts = <0 78 0>; + #address-cells = <1>; + #size-cells = <0>; + }; + +/* Board properties */ + + csis_0: csis@11880000 { + clock-frequency = <166000000>; + vddio-supply = <®0>; + vddcore-supply = <®1>; + port { + reg = <3>; /* 3 - CSIS0, 4 - CSIS1 */ + csis0_ep: endpoint { + remote-endpoint = <...>; + data-lanes = <1>, <2>; + samsung,csis-hs-settle = <12>; + }; + }; + }; diff --git a/drivers/media/platform/s5p-fimc/mipi-csis.c b/drivers/media/platform/s5p-fimc/mipi-csis.c index 981863d05aaa..8636bcddde1b 100644 --- a/drivers/media/platform/s5p-fimc/mipi-csis.c +++ b/drivers/media/platform/s5p-fimc/mipi-csis.c @@ -19,14 +19,18 @@ #include #include #include +#include +#include #include #include #include #include #include #include +#include +#include #include -#include + #include "mipi-csis.h" static int debug; @@ -113,6 +117,7 @@ static char *csi_clock_name[] = { [CSIS_CLK_GATE] = "csis", }; #define NUM_CSIS_CLOCKS ARRAY_SIZE(csi_clock_name) +#define DEFAULT_SCLK_CSIS_FREQ 166000000UL static const char * const csis_supply_name[] = { "vddcore", /* CSIS Core (1.0V, 1.1V or 1.2V) suppply */ @@ -167,6 +172,11 @@ struct csis_pktbuf { * @clock: CSIS clocks * @irq: requested s5p-mipi-csis irq number * @flags: the state variable for power and streaming control + * @clock_frequency: device bus clock frequency + * @hs_settle: HS-RX settle time + * @num_lanes: number of MIPI-CSI data lanes used + * @max_num_lanes: maximum number of MIPI-CSI data lanes supported + * @wclk_ext: CSI wrapper clock: 0 - bus clock, 1 - external SCLK_CAM * @csis_fmt: current CSIS pixel format * @format: common media bus format for the source and sink pad * @slock: spinlock protecting structure members below @@ -184,6 +194,13 @@ struct csis_state { struct clk *clock[NUM_CSIS_CLOCKS]; int irq; u32 flags; + + u32 clk_frequency; + u32 hs_settle; + u32 num_lanes; + u32 max_num_lanes; + u8 wclk_ext; + const struct csis_pix_format *csis_fmt; struct v4l2_mbus_framefmt format; @@ -273,7 +290,6 @@ static void s5pcsis_reset(struct csis_state *state) static void s5pcsis_system_enable(struct csis_state *state, int on) { - struct s5p_platform_mipi_csis *pdata = state->pdev->dev.platform_data; u32 val, mask; val = s5pcsis_read(state, S5PCSIS_CTRL); @@ -286,7 +302,7 @@ static void s5pcsis_system_enable(struct csis_state *state, int on) val = s5pcsis_read(state, S5PCSIS_DPHYCTRL); val &= ~S5PCSIS_DPHYCTRL_ENABLE; if (on) { - mask = (1 << (pdata->lanes + 1)) - 1; + mask = (1 << (state->num_lanes + 1)) - 1; val |= (mask & S5PCSIS_DPHYCTRL_ENABLE); } s5pcsis_write(state, S5PCSIS_DPHYCTRL, val); @@ -321,15 +337,14 @@ static void s5pcsis_set_hsync_settle(struct csis_state *state, int settle) static void s5pcsis_set_params(struct csis_state *state) { - struct s5p_platform_mipi_csis *pdata = state->pdev->dev.platform_data; u32 val; val = s5pcsis_read(state, S5PCSIS_CONFIG); - val = (val & ~S5PCSIS_CFG_NR_LANE_MASK) | (pdata->lanes - 1); + val = (val & ~S5PCSIS_CFG_NR_LANE_MASK) | (state->num_lanes - 1); s5pcsis_write(state, S5PCSIS_CONFIG, val); __s5pcsis_set_format(state); - s5pcsis_set_hsync_settle(state, pdata->hs_settle); + s5pcsis_set_hsync_settle(state, state->hs_settle); val = s5pcsis_read(state, S5PCSIS_CTRL); if (state->csis_fmt->data_alignment == 32) @@ -338,7 +353,7 @@ static void s5pcsis_set_params(struct csis_state *state) val &= ~S5PCSIS_CTRL_ALIGN_32BIT; val &= ~S5PCSIS_CTRL_WCLK_EXTCLK; - if (pdata->wclk_source) + if (state->wclk_ext) val |= S5PCSIS_CTRL_WCLK_EXTCLK; s5pcsis_write(state, S5PCSIS_CTRL, val); @@ -701,52 +716,111 @@ static irqreturn_t s5pcsis_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +static int s5pcsis_get_platform_data(struct platform_device *pdev, + struct csis_state *state) +{ + struct s5p_platform_mipi_csis *pdata = pdev->dev.platform_data; + + if (pdata == NULL) { + dev_err(&pdev->dev, "Platform data not specified\n"); + return -EINVAL; + } + + state->clk_frequency = pdata->clk_rate; + state->num_lanes = pdata->lanes; + state->hs_settle = pdata->hs_settle; + state->index = max(0, pdev->id); + state->max_num_lanes = state->index ? CSIS1_MAX_LANES : + CSIS0_MAX_LANES; + return 0; +} + +#ifdef CONFIG_OF +static int s5pcsis_parse_dt(struct platform_device *pdev, + struct csis_state *state) +{ + struct device_node *node = pdev->dev.of_node; + struct v4l2_of_endpoint endpoint; + + if (of_property_read_u32(node, "clock-frequency", + &state->clk_frequency)) + state->clk_frequency = DEFAULT_SCLK_CSIS_FREQ; + if (of_property_read_u32(node, "bus-width", + &state->max_num_lanes)) + return -EINVAL; + + node = v4l2_of_get_next_endpoint(node, NULL); + if (!node) { + dev_err(&pdev->dev, "No port node at %s\n", + node->full_name); + return -EINVAL; + } + /* Get port node and validate MIPI-CSI channel id. */ + v4l2_of_parse_endpoint(node, &endpoint); + + state->index = endpoint.port - FIMC_INPUT_MIPI_CSI2_0; + if (state->index < 0 || state->index >= CSIS_MAX_ENTITIES) + return -ENXIO; + + /* Get MIPI CSI-2 bus configration from the endpoint node. */ + of_property_read_u32(node, "samsung,csis-hs-settle", + &state->hs_settle); + state->wclk_ext = of_property_read_bool(node, + "samsung,csis-wclk"); + + state->num_lanes = endpoint.bus.mipi_csi2.num_data_lanes; + + of_node_put(node); + return 0; +} +#else +#define s5pcsis_parse_dt(pdev, state) (-ENOSYS) +#endif + static int s5pcsis_probe(struct platform_device *pdev) { - struct s5p_platform_mipi_csis *pdata; + struct device *dev = &pdev->dev; struct resource *mem_res; struct csis_state *state; int ret = -ENOMEM; int i; - state = devm_kzalloc(&pdev->dev, sizeof(*state), GFP_KERNEL); + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); if (!state) return -ENOMEM; mutex_init(&state->lock); spin_lock_init(&state->slock); - state->pdev = pdev; - state->index = max(0, pdev->id); - pdata = pdev->dev.platform_data; - if (pdata == NULL) { - dev_err(&pdev->dev, "Platform data not fully specified\n"); - return -EINVAL; - } + if (dev->of_node) + ret = s5pcsis_parse_dt(pdev, state); + else + ret = s5pcsis_get_platform_data(pdev, state); + if (ret < 0) + return ret; - if ((state->index == 1 && pdata->lanes > CSIS1_MAX_LANES) || - pdata->lanes > CSIS0_MAX_LANES) { - dev_err(&pdev->dev, "Unsupported number of data lanes: %d\n", - pdata->lanes); + if (state->num_lanes == 0 || state->num_lanes > state->max_num_lanes) { + dev_err(dev, "Unsupported number of data lanes: %d (max. %d)\n", + state->num_lanes, state->max_num_lanes); return -EINVAL; } mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - state->regs = devm_ioremap_resource(&pdev->dev, mem_res); + state->regs = devm_ioremap_resource(dev, mem_res); if (IS_ERR(state->regs)) return PTR_ERR(state->regs); state->irq = platform_get_irq(pdev, 0); if (state->irq < 0) { - dev_err(&pdev->dev, "Failed to get irq\n"); + dev_err(dev, "Failed to get irq\n"); return state->irq; } for (i = 0; i < CSIS_NUM_SUPPLIES; i++) state->supplies[i].supply = csis_supply_name[i]; - ret = devm_regulator_bulk_get(&pdev->dev, CSIS_NUM_SUPPLIES, + ret = devm_regulator_bulk_get(dev, CSIS_NUM_SUPPLIES, state->supplies); if (ret) return ret; @@ -755,11 +829,11 @@ static int s5pcsis_probe(struct platform_device *pdev) if (ret < 0) return ret; - if (pdata->clk_rate) + if (state->clk_frequency) ret = clk_set_rate(state->clock[CSIS_CLK_MUX], - pdata->clk_rate); + state->clk_frequency); else - dev_WARN(&pdev->dev, "No clock frequency specified!\n"); + dev_WARN(dev, "No clock frequency specified!\n"); if (ret < 0) goto e_clkput; @@ -767,16 +841,17 @@ static int s5pcsis_probe(struct platform_device *pdev) if (ret < 0) goto e_clkput; - ret = devm_request_irq(&pdev->dev, state->irq, s5pcsis_irq_handler, - 0, dev_name(&pdev->dev), state); + ret = devm_request_irq(dev, state->irq, s5pcsis_irq_handler, + 0, dev_name(dev), state); if (ret) { - dev_err(&pdev->dev, "Interrupt request failed\n"); + dev_err(dev, "Interrupt request failed\n"); goto e_clkdis; } v4l2_subdev_init(&state->sd, &s5pcsis_subdev_ops); state->sd.owner = THIS_MODULE; - strlcpy(state->sd.name, dev_name(&pdev->dev), sizeof(state->sd.name)); + snprintf(state->sd.name, sizeof(state->sd.name), "%s.%d", + CSIS_SUBDEV_NAME, state->index); state->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; state->csis_fmt = &s5pcsis_formats[0]; @@ -796,10 +871,12 @@ static int s5pcsis_probe(struct platform_device *pdev) /* .. and a pointer to the subdev. */ platform_set_drvdata(pdev, &state->sd); - memcpy(state->events, s5pcsis_events, sizeof(state->events)); + pm_runtime_enable(dev); - pm_runtime_enable(&pdev->dev); + dev_info(&pdev->dev, "lanes: %d, hs_settle: %d, wclk: %d, freq: %u\n", + state->num_lanes, state->hs_settle, state->wclk_ext, + state->clk_frequency); return 0; e_clkdis: @@ -923,13 +1000,21 @@ static const struct dev_pm_ops s5pcsis_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(s5pcsis_suspend, s5pcsis_resume) }; +static const struct of_device_id s5pcsis_of_match[] = { + { .compatible = "samsung,s5pv210-csis" }, + { .compatible = "samsung,exynos4210-csis" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, s5pcsis_of_match); + static struct platform_driver s5pcsis_driver = { .probe = s5pcsis_probe, .remove = s5pcsis_remove, .driver = { - .name = CSIS_DRIVER_NAME, - .owner = THIS_MODULE, - .pm = &s5pcsis_pm_ops, + .of_match_table = s5pcsis_of_match, + .name = CSIS_DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &s5pcsis_pm_ops, }, }; diff --git a/drivers/media/platform/s5p-fimc/mipi-csis.h b/drivers/media/platform/s5p-fimc/mipi-csis.h index 2709286396e1..28c11c4085d8 100644 --- a/drivers/media/platform/s5p-fimc/mipi-csis.h +++ b/drivers/media/platform/s5p-fimc/mipi-csis.h @@ -11,6 +11,7 @@ #define S5P_MIPI_CSIS_H_ #define CSIS_DRIVER_NAME "s5p-mipi-csis" +#define CSIS_SUBDEV_NAME CSIS_DRIVER_NAME #define CSIS_MAX_ENTITIES 2 #define CSIS0_MAX_LANES 4 #define CSIS1_MAX_LANES 2 diff --git a/include/media/s5p_fimc.h b/include/media/s5p_fimc.h index 28f3590aa031..d6dbb791f423 100644 --- a/include/media/s5p_fimc.h +++ b/include/media/s5p_fimc.h @@ -14,6 +14,19 @@ #include +/* + * Enumeration of data inputs to the camera subsystem. + */ +enum fimc_input { + FIMC_INPUT_PARALLEL_0 = 1, + FIMC_INPUT_PARALLEL_1, + FIMC_INPUT_MIPI_CSI2_0 = 3, + FIMC_INPUT_MIPI_CSI2_1, + FIMC_INPUT_WRITEBACK_A = 5, + FIMC_INPUT_WRITEBACK_B, + FIMC_INPUT_WRITEBACK_ISP = 5, +}; + /* * Enumeration of the FIMC data bus types. */ -- cgit v1.2.3 From e80cb1fae55ceb01b6e886e5499461c07b69c454 Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Tue, 26 Mar 2013 08:22:21 -0300 Subject: [media] s5p-fimc: Add device tree support for FIMC device driver This patch adds device tree support for FIMC driver on S5PV210 and Exynos4 SoCs. The FIMC IP block's features and quirks encoded statically in the driver are now parsed from the device tree. Once all relevant platforms are converted to device tree based booting the FIMC variant data structures will all be removed from the driver. Signed-off-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/samsung-fimc.txt | 101 +++++++++ drivers/media/platform/s5p-fimc/fimc-capture.c | 6 +- drivers/media/platform/s5p-fimc/fimc-core.c | 238 ++++++++++++--------- drivers/media/platform/s5p-fimc/fimc-core.h | 21 +- drivers/media/platform/s5p-fimc/fimc-m2m.c | 2 +- drivers/media/platform/s5p-fimc/fimc-reg.c | 6 +- 6 files changed, 260 insertions(+), 114 deletions(-) create mode 100644 Documentation/devicetree/bindings/media/samsung-fimc.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/media/samsung-fimc.txt b/Documentation/devicetree/bindings/media/samsung-fimc.txt new file mode 100644 index 000000000000..22e2889162c3 --- /dev/null +++ b/Documentation/devicetree/bindings/media/samsung-fimc.txt @@ -0,0 +1,101 @@ +Samsung S5P/EXYNOS SoC Camera Subsystem (FIMC) +---------------------------------------------- + +The S5P/Exynos SoC Camera subsystem comprises of multiple sub-devices +represented by separate device tree nodes. Currently this includes: FIMC (in +the S5P SoCs series known as CAMIF), MIPI CSIS, FIMC-LITE and FIMC-IS (ISP). + +The sub-subdevices are defined as child nodes of the common 'camera' node which +also includes common properties of the whole subsystem not really specific to +any single sub-device, like common camera port pins or the CAMCLK clock outputs +for external image sensors attached to an SoC. + +Common 'camera' node +-------------------- + +Required properties: + +- compatible : must be "samsung,fimc", "simple-bus" +- clocks : list of clock specifiers, corresponding to entries in + clock-names property; +- clock-names : must contain "fimc", "sclk_fimc" entries, matching entries + in the clocks property. + +The 'camera' node must include at least one 'fimc' child node. + + +'fimc' device nodes +------------------- + +Required properties: + +- compatible: "samsung,s5pv210-fimc" for S5PV210, "samsung,exynos4210-fimc" + for Exynos4210 and "samsung,exynos4212-fimc" for Exynos4x12 SoCs; +- reg: physical base address and length of the registers set for the device; +- interrupts: should contain FIMC interrupt; +- clocks: list of clock specifiers, must contain an entry for each required + entry in clock-names; +- clock-names: must include "fimc", "sclk_fimc", "mux" entries and optionally + "parent" entry. +- samsung,pix-limits: an array of maximum supported image sizes in pixels, for + details refer to Table 2-1 in the S5PV210 SoC User Manual; The meaning of + each cell is as follows: + 0 - scaler input horizontal size, + 1 - input horizontal size for the scaler bypassed, + 2 - REAL_WIDTH without input rotation, + 3 - REAL_HEIGHT with input rotation, +- samsung,sysreg: a phandle to the SYSREG node. + +Each FIMC device should have an alias in the aliases node, in the form of +fimc, where is an integer specifying the IP block instance. + +Optional properties: + +- clock-frequency: maximum FIMC local clock (LCLK) frequency; +- samsung,min-pix-sizes: an array specyfing minimum image size in pixels at + the FIMC input and output DMA, in the first and second cell respectively. + Default value when this property is not present is <16 16>; +- samsung,min-pix-alignment: minimum supported image height alignment (first + cell) and the horizontal image offset (second cell). The values are in pixels + and default to <2 1> when this property is not present; +- samsung,mainscaler-ext: a boolean property indicating whether the FIMC IP + supports extended image size and has CIEXTEN register; +- samsung,rotators: a bitmask specifying whether this IP has the input and + the output rotator. Bits 4 and 0 correspond to input and output rotator + respectively. If a rotator is present its corresponding bit should be set. + Default value when this property is not specified is 0x11. +- samsung,cam-if: a bolean property indicating whether the IP block includes + the camera input interface. +- samsung,isp-wb: this property must be present if the IP block has the ISP + writeback input. +- samsung,lcd-wb: this property must be present if the IP block has the LCD + writeback input. + + +Example: + + aliases { + fimc0 = &fimc_0; + }; + + camera { + compatible = "samsung,fimc", "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + status = "okay"; + + fimc_0: fimc@11800000 { + compatible = "samsung,exynos4210-fimc"; + reg = <0x11800000 0x1000>; + interrupts = <0 85 0>; + status = "okay"; + }; + + csis_0: csis@11880000 { + compatible = "samsung,exynos4210-csis"; + reg = <0x11880000 0x1000>; + interrupts = <0 78 0>; + }; + }; + +The MIPI-CSIS device binding is defined in samsung-mipi-csis.txt. diff --git a/drivers/media/platform/s5p-fimc/fimc-capture.c b/drivers/media/platform/s5p-fimc/fimc-capture.c index e9928cda6387..724cd8b4933d 100644 --- a/drivers/media/platform/s5p-fimc/fimc-capture.c +++ b/drivers/media/platform/s5p-fimc/fimc-capture.c @@ -65,7 +65,7 @@ static int fimc_capture_hw_init(struct fimc_dev *fimc) fimc_hw_set_effect(ctx); fimc_hw_set_output_path(ctx); fimc_hw_set_out_dma(ctx); - if (fimc->variant->has_alpha) + if (fimc->drv_data->alpha_color) fimc_hw_set_rgb_alpha(ctx); clear_bit(ST_CAPT_APPLY_CFG, &fimc->state); } @@ -168,7 +168,7 @@ static int fimc_capture_config_update(struct fimc_ctx *ctx) fimc_hw_set_effect(ctx); fimc_prepare_dma_offset(ctx, &ctx->d_frame); fimc_hw_set_out_dma(ctx); - if (fimc->variant->has_alpha) + if (fimc->drv_data->alpha_color) fimc_hw_set_rgb_alpha(ctx); clear_bit(ST_CAPT_APPLY_CFG, &fimc->state); @@ -1802,7 +1802,7 @@ int fimc_initialize_capture_subdev(struct fimc_dev *fimc) v4l2_subdev_init(sd, &fimc_subdev_ops); sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE; - snprintf(sd->name, sizeof(sd->name), "FIMC.%d", fimc->pdev->id); + snprintf(sd->name, sizeof(sd->name), "FIMC.%d", fimc->id); fimc->vid_cap.sd_pads[FIMC_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK; fimc->vid_cap.sd_pads[FIMC_SD_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; diff --git a/drivers/media/platform/s5p-fimc/fimc-core.c b/drivers/media/platform/s5p-fimc/fimc-core.c index e3916bde45cf..d39e47abb229 100644 --- a/drivers/media/platform/s5p-fimc/fimc-core.c +++ b/drivers/media/platform/s5p-fimc/fimc-core.c @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -440,14 +442,14 @@ void fimc_set_yuv_order(struct fimc_ctx *ctx) void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f) { - const struct fimc_variant *variant = ctx->fimc_dev->variant; + bool pix_hoff = ctx->fimc_dev->drv_data->dma_pix_hoff; u32 i, depth = 0; for (i = 0; i < f->fmt->colplanes; i++) depth += f->fmt->depth[i]; f->dma_offset.y_h = f->offs_h; - if (!variant->pix_hoff) + if (!pix_hoff) f->dma_offset.y_h *= (depth >> 3); f->dma_offset.y_v = f->offs_v; @@ -458,7 +460,7 @@ void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f) f->dma_offset.cr_h = f->offs_h; f->dma_offset.cr_v = f->offs_v; - if (!variant->pix_hoff) { + if (!pix_hoff) { if (f->fmt->colplanes == 3) { f->dma_offset.cb_h >>= 1; f->dma_offset.cr_h >>= 1; @@ -589,7 +591,6 @@ static const struct v4l2_ctrl_ops fimc_ctrl_ops = { int fimc_ctrls_create(struct fimc_ctx *ctx) { - const struct fimc_variant *variant = ctx->fimc_dev->variant; unsigned int max_alpha = fimc_get_alpha_mask(ctx->d_frame.fmt); struct fimc_ctrls *ctrls = &ctx->ctrls; struct v4l2_ctrl_handler *handler = &ctrls->handler; @@ -606,7 +607,7 @@ int fimc_ctrls_create(struct fimc_ctx *ctx) ctrls->vflip = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); - if (variant->has_alpha) + if (ctx->fimc_dev->drv_data->alpha_color) ctrls->alpha = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops, V4L2_CID_ALPHA_COMPONENT, 0, max_alpha, 1, 0); @@ -677,7 +678,7 @@ void fimc_alpha_ctrl_update(struct fimc_ctx *ctx) struct fimc_dev *fimc = ctx->fimc_dev; struct v4l2_ctrl *ctrl = ctx->ctrls.alpha; - if (ctrl == NULL || !fimc->variant->has_alpha) + if (ctrl == NULL || !fimc->drv_data->alpha_color) return; v4l2_ctrl_lock(ctrl); @@ -863,43 +864,109 @@ static int fimc_m2m_resume(struct fimc_dev *fimc) return 0; } +static const struct of_device_id fimc_of_match[]; + +static int fimc_parse_dt(struct fimc_dev *fimc, u32 *clk_freq) +{ + struct device *dev = &fimc->pdev->dev; + struct device_node *node = dev->of_node; + const struct of_device_id *of_id; + struct fimc_variant *v; + struct fimc_pix_limit *lim; + u32 args[FIMC_PIX_LIMITS_MAX]; + int ret; + + if (of_property_read_bool(node, "samsung,lcd-wb")) + return -ENODEV; + + v = devm_kzalloc(dev, sizeof(*v) + sizeof(*lim), GFP_KERNEL); + if (!v) + return -ENOMEM; + + of_id = of_match_node(fimc_of_match, node); + if (!of_id) + return -EINVAL; + fimc->drv_data = of_id->data; + ret = of_property_read_u32_array(node, "samsung,pix-limits", + args, FIMC_PIX_LIMITS_MAX); + if (ret < 0) + return ret; + + lim = (struct fimc_pix_limit *)&v[1]; + + lim->scaler_en_w = args[0]; + lim->scaler_dis_w = args[1]; + lim->out_rot_en_w = args[2]; + lim->out_rot_dis_w = args[3]; + v->pix_limit = lim; + + ret = of_property_read_u32_array(node, "samsung,min-pix-sizes", + args, 2); + v->min_inp_pixsize = ret ? FIMC_DEF_MIN_SIZE : args[0]; + v->min_out_pixsize = ret ? FIMC_DEF_MIN_SIZE : args[1]; + ret = of_property_read_u32_array(node, "samsung,min-pix-alignment", + args, 2); + v->min_vsize_align = ret ? FIMC_DEF_HEIGHT_ALIGN : args[0]; + v->hor_offs_align = ret ? FIMC_DEF_HOR_OFFS_ALIGN : args[1]; + + ret = of_property_read_u32(node, "samsung,rotators", &args[1]); + v->has_inp_rot = ret ? 1 : args[1] & 0x01; + v->has_out_rot = ret ? 1 : args[1] & 0x10; + v->has_mainscaler_ext = of_property_read_bool(node, + "samsung,mainscaler-ext"); + + v->has_isp_wb = of_property_read_bool(node, "samsung,isp-wb"); + v->has_cam_if = of_property_read_bool(node, "samsung,cam-if"); + of_property_read_u32(node, "clock-frequency", clk_freq); + fimc->id = of_alias_get_id(node, "fimc"); + + fimc->variant = v; + return 0; +} + static int fimc_probe(struct platform_device *pdev) { - const struct fimc_drvdata *drv_data = fimc_get_drvdata(pdev); - struct s5p_platform_fimc *pdata; + struct device *dev = &pdev->dev; + u32 lclk_freq = 0; struct fimc_dev *fimc; struct resource *res; int ret = 0; - if (pdev->id >= drv_data->num_entities) { - dev_err(&pdev->dev, "Invalid platform device id: %d\n", - pdev->id); - return -EINVAL; - } - - fimc = devm_kzalloc(&pdev->dev, sizeof(*fimc), GFP_KERNEL); + fimc = devm_kzalloc(dev, sizeof(*fimc), GFP_KERNEL); if (!fimc) return -ENOMEM; - fimc->id = pdev->id; - - fimc->variant = drv_data->variant[fimc->id]; fimc->pdev = pdev; - pdata = pdev->dev.platform_data; - fimc->pdata = pdata; + + if (dev->of_node) { + ret = fimc_parse_dt(fimc, &lclk_freq); + if (ret < 0) + return ret; + } else { + fimc->drv_data = fimc_get_drvdata(pdev); + fimc->id = pdev->id; + } + if (!fimc->drv_data || fimc->id >= fimc->drv_data->num_entities || + fimc->id < 0) { + dev_err(dev, "Invalid driver data or device id (%d/%d)\n", + fimc->id, fimc->drv_data->num_entities); + return -EINVAL; + } + if (!dev->of_node) + fimc->variant = fimc->drv_data->variant[fimc->id]; init_waitqueue_head(&fimc->irq_queue); spin_lock_init(&fimc->slock); mutex_init(&fimc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - fimc->regs = devm_ioremap_resource(&pdev->dev, res); + fimc->regs = devm_ioremap_resource(dev, res); if (IS_ERR(fimc->regs)) return PTR_ERR(fimc->regs); res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (res == NULL) { - dev_err(&pdev->dev, "Failed to get IRQ resource\n"); + dev_err(dev, "Failed to get IRQ resource\n"); return -ENXIO; } @@ -907,7 +974,10 @@ static int fimc_probe(struct platform_device *pdev) if (ret) return ret; - ret = clk_set_rate(fimc->clock[CLK_BUS], drv_data->lclk_frequency); + if (lclk_freq == 0) + lclk_freq = fimc->drv_data->lclk_frequency; + + ret = clk_set_rate(fimc->clock[CLK_BUS], lclk_freq); if (ret < 0) return ret; @@ -915,10 +985,10 @@ static int fimc_probe(struct platform_device *pdev) if (ret < 0) return ret; - ret = devm_request_irq(&pdev->dev, res->start, fimc_irq_handler, - 0, dev_name(&pdev->dev), fimc); + ret = devm_request_irq(dev, res->start, fimc_irq_handler, + 0, dev_name(dev), fimc); if (ret) { - dev_err(&pdev->dev, "failed to install irq (%d)\n", ret); + dev_err(dev, "failed to install irq (%d)\n", ret); goto err_clk; } @@ -927,23 +997,23 @@ static int fimc_probe(struct platform_device *pdev) goto err_clk; platform_set_drvdata(pdev, fimc); - pm_runtime_enable(&pdev->dev); - ret = pm_runtime_get_sync(&pdev->dev); + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); if (ret < 0) goto err_sd; /* Initialize contiguous memory allocator */ - fimc->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + fimc->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(fimc->alloc_ctx)) { ret = PTR_ERR(fimc->alloc_ctx); goto err_pm; } - dev_dbg(&pdev->dev, "FIMC.%d registered successfully\n", fimc->id); + dev_dbg(dev, "FIMC.%d registered successfully\n", fimc->id); - pm_runtime_put(&pdev->dev); + pm_runtime_put(dev); return 0; err_pm: - pm_runtime_put(&pdev->dev); + pm_runtime_put(dev); err_sd: fimc_unregister_capture_subdev(fimc); err_clk: @@ -1046,24 +1116,18 @@ static const struct fimc_pix_limit s5p_pix_limit[4] = { [0] = { .scaler_en_w = 3264, .scaler_dis_w = 8192, - .in_rot_en_h = 1920, - .in_rot_dis_w = 8192, .out_rot_en_w = 1920, .out_rot_dis_w = 4224, }, [1] = { .scaler_en_w = 4224, .scaler_dis_w = 8192, - .in_rot_en_h = 1920, - .in_rot_dis_w = 8192, .out_rot_en_w = 1920, .out_rot_dis_w = 4224, }, [2] = { .scaler_en_w = 1920, .scaler_dis_w = 8192, - .in_rot_en_h = 1280, - .in_rot_dis_w = 8192, .out_rot_en_w = 1280, .out_rot_dis_w = 1920, }, @@ -1085,7 +1149,6 @@ static const struct fimc_variant fimc0_variant_s5p = { .min_out_pixsize = 16, .hor_offs_align = 8, .min_vsize_align = 16, - .out_buf_count = 4, .pix_limit = &s5p_pix_limit[0], }; @@ -1095,12 +1158,10 @@ static const struct fimc_variant fimc2_variant_s5p = { .min_out_pixsize = 16, .hor_offs_align = 8, .min_vsize_align = 16, - .out_buf_count = 4, .pix_limit = &s5p_pix_limit[1], }; static const struct fimc_variant fimc0_variant_s5pv210 = { - .pix_hoff = 1, .has_inp_rot = 1, .has_out_rot = 1, .has_cam_if = 1, @@ -1108,12 +1169,10 @@ static const struct fimc_variant fimc0_variant_s5pv210 = { .min_out_pixsize = 16, .hor_offs_align = 8, .min_vsize_align = 16, - .out_buf_count = 4, .pix_limit = &s5p_pix_limit[1], }; static const struct fimc_variant fimc1_variant_s5pv210 = { - .pix_hoff = 1, .has_inp_rot = 1, .has_out_rot = 1, .has_cam_if = 1, @@ -1122,80 +1181,39 @@ static const struct fimc_variant fimc1_variant_s5pv210 = { .min_out_pixsize = 16, .hor_offs_align = 1, .min_vsize_align = 1, - .out_buf_count = 4, .pix_limit = &s5p_pix_limit[2], }; static const struct fimc_variant fimc2_variant_s5pv210 = { .has_cam_if = 1, - .pix_hoff = 1, .min_inp_pixsize = 16, .min_out_pixsize = 16, .hor_offs_align = 8, .min_vsize_align = 16, - .out_buf_count = 4, .pix_limit = &s5p_pix_limit[2], }; static const struct fimc_variant fimc0_variant_exynos4210 = { - .pix_hoff = 1, .has_inp_rot = 1, .has_out_rot = 1, .has_cam_if = 1, - .has_cistatus2 = 1, .has_mainscaler_ext = 1, - .has_alpha = 1, .min_inp_pixsize = 16, .min_out_pixsize = 16, .hor_offs_align = 2, .min_vsize_align = 1, - .out_buf_count = 32, .pix_limit = &s5p_pix_limit[1], }; static const struct fimc_variant fimc3_variant_exynos4210 = { - .pix_hoff = 1, - .has_cistatus2 = 1, .has_mainscaler_ext = 1, - .has_alpha = 1, .min_inp_pixsize = 16, .min_out_pixsize = 16, .hor_offs_align = 2, .min_vsize_align = 1, - .out_buf_count = 32, .pix_limit = &s5p_pix_limit[3], }; -static const struct fimc_variant fimc0_variant_exynos4x12 = { - .pix_hoff = 1, - .has_inp_rot = 1, - .has_out_rot = 1, - .has_cam_if = 1, - .has_isp_wb = 1, - .has_cistatus2 = 1, - .has_mainscaler_ext = 1, - .has_alpha = 1, - .min_inp_pixsize = 16, - .min_out_pixsize = 16, - .hor_offs_align = 2, - .min_vsize_align = 1, - .out_buf_count = 32, - .pix_limit = &s5p_pix_limit[1], -}; - -static const struct fimc_variant fimc3_variant_exynos4x12 = { - .pix_hoff = 1, - .has_cistatus2 = 1, - .has_mainscaler_ext = 1, - .has_alpha = 1, - .min_inp_pixsize = 16, - .min_out_pixsize = 16, - .hor_offs_align = 2, - .min_vsize_align = 1, - .out_buf_count = 32, - .pix_limit = &s5p_pix_limit[3], -}; - /* S5PC100 */ static const struct fimc_drvdata fimc_drvdata_s5p = { .variant = { @@ -1203,8 +1221,9 @@ static const struct fimc_drvdata fimc_drvdata_s5p = { [1] = &fimc0_variant_s5p, [2] = &fimc2_variant_s5p, }, - .num_entities = 3, + .num_entities = 3, .lclk_frequency = 133000000UL, + .out_buf_count = 4, }; /* S5PV210, S5PC110 */ @@ -1214,8 +1233,10 @@ static const struct fimc_drvdata fimc_drvdata_s5pv210 = { [1] = &fimc1_variant_s5pv210, [2] = &fimc2_variant_s5pv210, }, - .num_entities = 3, - .lclk_frequency = 166000000UL, + .num_entities = 3, + .lclk_frequency = 166000000UL, + .out_buf_count = 4, + .dma_pix_hoff = 1, }; /* EXYNOS4210, S5PV310, S5PC210 */ @@ -1226,20 +1247,22 @@ static const struct fimc_drvdata fimc_drvdata_exynos4210 = { [2] = &fimc0_variant_exynos4210, [3] = &fimc3_variant_exynos4210, }, - .num_entities = 4, + .num_entities = 4, .lclk_frequency = 166000000UL, + .dma_pix_hoff = 1, + .cistatus2 = 1, + .alpha_color = 1, + .out_buf_count = 32, }; /* EXYNOS4212, EXYNOS4412 */ static const struct fimc_drvdata fimc_drvdata_exynos4x12 = { - .variant = { - [0] = &fimc0_variant_exynos4x12, - [1] = &fimc0_variant_exynos4x12, - [2] = &fimc0_variant_exynos4x12, - [3] = &fimc3_variant_exynos4x12, - }, - .num_entities = 4, - .lclk_frequency = 166000000UL, + .num_entities = 4, + .lclk_frequency = 166000000UL, + .dma_pix_hoff = 1, + .cistatus2 = 1, + .alpha_color = 1, + .out_buf_count = 32, }; static const struct platform_device_id fimc_driver_ids[] = { @@ -1256,10 +1279,24 @@ static const struct platform_device_id fimc_driver_ids[] = { .name = "exynos4x12-fimc", .driver_data = (unsigned long)&fimc_drvdata_exynos4x12, }, - {}, + { }, }; MODULE_DEVICE_TABLE(platform, fimc_driver_ids); +static const struct of_device_id fimc_of_match[] = { + { + .compatible = "samsung,s5pv210-fimc", + .data = &fimc_drvdata_s5pv210, + }, { + .compatible = "samsung,exynos4210-fimc", + .data = &fimc_drvdata_exynos4210, + }, { + .compatible = "samsung,exynos4212-fimc", + .data = &fimc_drvdata_exynos4x12, + }, + { /* sentinel */ }, +}; + static const struct dev_pm_ops fimc_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(fimc_suspend, fimc_resume) SET_RUNTIME_PM_OPS(fimc_runtime_suspend, fimc_runtime_resume, NULL) @@ -1270,9 +1307,10 @@ static struct platform_driver fimc_driver = { .remove = fimc_remove, .id_table = fimc_driver_ids, .driver = { - .name = FIMC_MODULE_NAME, - .owner = THIS_MODULE, - .pm = &fimc_pm_ops, + .of_match_table = fimc_of_match, + .name = FIMC_MODULE_NAME, + .owner = THIS_MODULE, + .pm = &fimc_pm_ops, } }; diff --git a/drivers/media/platform/s5p-fimc/fimc-core.h b/drivers/media/platform/s5p-fimc/fimc-core.h index 412d50708f75..145b8cc77af0 100644 --- a/drivers/media/platform/s5p-fimc/fimc-core.h +++ b/drivers/media/platform/s5p-fimc/fimc-core.h @@ -42,6 +42,10 @@ #define FIMC_CAMIF_MAX_HEIGHT 0x2000 #define FIMC_MAX_JPEG_BUF_SIZE (10 * SZ_1M) #define FIMC_MAX_PLANES 3 +#define FIMC_PIX_LIMITS_MAX 4 +#define FIMC_DEF_MIN_SIZE 16 +#define FIMC_DEF_HEIGHT_ALIGN 2 +#define FIMC_DEF_HOR_OFFS_ALIGN 1 /* indices to the clocks array */ enum { @@ -365,10 +369,8 @@ struct fimc_pix_limit { /** * struct fimc_variant - FIMC device variant information - * @pix_hoff: indicate whether horizontal offset is in pixels or in bytes * @has_inp_rot: set if has input rotator * @has_out_rot: set if has output rotator - * @has_cistatus2: 1 if CISTATUS2 register is present in this IP revision * @has_mainscaler_ext: 1 if extended mainscaler ratios in CIEXTEN register * are present in this IP revision * @has_cam_if: set if this instance has a camera input interface @@ -378,23 +380,18 @@ struct fimc_pix_limit { * @min_out_pixsize: minimum output pixel size * @hor_offs_align: horizontal pixel offset aligment * @min_vsize_align: minimum vertical pixel size alignment - * @out_buf_count: the number of buffers in output DMA sequence */ struct fimc_variant { - unsigned int pix_hoff:1; unsigned int has_inp_rot:1; unsigned int has_out_rot:1; - unsigned int has_cistatus2:1; unsigned int has_mainscaler_ext:1; unsigned int has_cam_if:1; unsigned int has_isp_wb:1; - unsigned int has_alpha:1; const struct fimc_pix_limit *pix_limit; u16 min_inp_pixsize; u16 min_out_pixsize; u16 hor_offs_align; u16 min_vsize_align; - u16 out_buf_count; }; /** @@ -402,11 +399,20 @@ struct fimc_variant { * @variant: variant information for this device * @num_entities: number of fimc instances available in a SoC * @lclk_frequency: local bus clock frequency + * @cistatus2: 1 if the FIMC IPs have CISTATUS2 register + * @dma_pix_hoff: the horizontal DMA offset unit: 1 - pixels, 0 - bytes + * @alpha_color: 1 if alpha color component is supported + * @out_buf_count: maximum number of output DMA buffers supported */ struct fimc_drvdata { const struct fimc_variant *variant[FIMC_MAX_DEVS]; int num_entities; unsigned long lclk_frequency; + /* Fields common to all FIMC IP instances */ + u8 cistatus2; + u8 dma_pix_hoff; + u8 alpha_color; + u8 out_buf_count; }; #define fimc_get_drvdata(_pdev) \ @@ -438,6 +444,7 @@ struct fimc_dev { struct platform_device *pdev; struct s5p_platform_fimc *pdata; const struct fimc_variant *variant; + const struct fimc_drvdata *drv_data; u16 id; struct clk *clock[MAX_FIMC_CLOCKS]; void __iomem *regs; diff --git a/drivers/media/platform/s5p-fimc/fimc-m2m.c b/drivers/media/platform/s5p-fimc/fimc-m2m.c index c8509dd1776d..a224dac36b3a 100644 --- a/drivers/media/platform/s5p-fimc/fimc-m2m.c +++ b/drivers/media/platform/s5p-fimc/fimc-m2m.c @@ -152,7 +152,7 @@ static void fimc_device_run(void *priv) fimc_hw_set_rotation(ctx); fimc_hw_set_effect(ctx); fimc_hw_set_out_dma(ctx); - if (fimc->variant->has_alpha) + if (fimc->drv_data->alpha_color) fimc_hw_set_rgb_alpha(ctx); fimc_hw_set_output_path(ctx); } diff --git a/drivers/media/platform/s5p-fimc/fimc-reg.c b/drivers/media/platform/s5p-fimc/fimc-reg.c index 50b97c75b956..4d2fc69d2cba 100644 --- a/drivers/media/platform/s5p-fimc/fimc-reg.c +++ b/drivers/media/platform/s5p-fimc/fimc-reg.c @@ -35,7 +35,7 @@ void fimc_hw_reset(struct fimc_dev *dev) cfg &= ~FIMC_REG_CIGCTRL_SWRST; writel(cfg, dev->regs + FIMC_REG_CIGCTRL); - if (dev->variant->out_buf_count > 4) + if (dev->drv_data->out_buf_count > 4) fimc_hw_set_dma_seq(dev, 0xF); } @@ -747,7 +747,7 @@ s32 fimc_hw_get_frame_index(struct fimc_dev *dev) { s32 reg; - if (dev->variant->has_cistatus2) { + if (dev->drv_data->cistatus2) { reg = readl(dev->regs + FIMC_REG_CISTATUS2) & 0x3f; return reg - 1; } @@ -763,7 +763,7 @@ s32 fimc_hw_get_prev_frame_index(struct fimc_dev *dev) { s32 reg; - if (!dev->variant->has_cistatus2) + if (!dev->drv_data->cistatus2) return -1; reg = readl(dev->regs + FIMC_REG_CISTATUS2); -- cgit v1.2.3 From eb62d9e9d6327322a5fd3894bfecb3a3aa9391ba Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Tue, 26 Mar 2013 09:33:54 -0300 Subject: [media] s5p-fimc: Add device tree support for FIMC-LITE device driver This patch adds the device tree support for FIMC-LITE device driver. The bindings include compatible property for the Exynos5 SoC series, however the actual implementation for these SoCs will be added in a separate patch. Signed-off-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/exynos-fimc-lite.txt | 14 +++++ drivers/media/platform/s5p-fimc/fimc-lite.c | 63 +++++++++++++++------- 2 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 Documentation/devicetree/bindings/media/exynos-fimc-lite.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/media/exynos-fimc-lite.txt b/Documentation/devicetree/bindings/media/exynos-fimc-lite.txt new file mode 100644 index 000000000000..3f62adfb3e0b --- /dev/null +++ b/Documentation/devicetree/bindings/media/exynos-fimc-lite.txt @@ -0,0 +1,14 @@ +Exynos4x12/Exynos5 SoC series camera host interface (FIMC-LITE) + +Required properties: + +- compatible : should be "samsung,exynos4212-fimc" for Exynos4212 and + Exynos4412 SoCs; +- reg : physical base address and size of the device memory mapped + registers; +- interrupts : should contain FIMC-LITE interrupt; +- clocks : FIMC LITE gate clock should be specified in this property. +- clock-names : should contain "flite" entry. + +Each FIMC device should have an alias in the aliases node, in the form of +fimc-lite, where is an integer specifying the IP block instance. diff --git a/drivers/media/platform/s5p-fimc/fimc-lite.c b/drivers/media/platform/s5p-fimc/fimc-lite.c index b64297ba35d3..65082fe2578c 100644 --- a/drivers/media/platform/s5p-fimc/fimc-lite.c +++ b/drivers/media/platform/s5p-fimc/fimc-lite.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -1400,18 +1401,34 @@ static int fimc_lite_clk_get(struct fimc_lite *fimc) return ret; } +static const struct of_device_id flite_of_match[]; + static int fimc_lite_probe(struct platform_device *pdev) { - struct flite_drvdata *drv_data = fimc_lite_get_drvdata(pdev); + struct flite_drvdata *drv_data = NULL; + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; struct fimc_lite *fimc; struct resource *res; int ret; - fimc = devm_kzalloc(&pdev->dev, sizeof(*fimc), GFP_KERNEL); + fimc = devm_kzalloc(dev, sizeof(*fimc), GFP_KERNEL); if (!fimc) return -ENOMEM; - fimc->index = pdev->id; + if (dev->of_node) { + of_id = of_match_node(flite_of_match, dev->of_node); + if (of_id) + drv_data = (struct flite_drvdata *)of_id->data; + fimc->index = of_alias_get_id(dev->of_node, "fimc-lite"); + } else { + drv_data = fimc_lite_get_drvdata(pdev); + fimc->index = pdev->id; + } + + if (!drv_data || fimc->index < 0 || fimc->index >= FIMC_LITE_MAX_DEVS) + return -EINVAL; + fimc->variant = drv_data->variant[fimc->index]; fimc->pdev = pdev; @@ -1420,13 +1437,13 @@ static int fimc_lite_probe(struct platform_device *pdev) mutex_init(&fimc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - fimc->regs = devm_ioremap_resource(&pdev->dev, res); + fimc->regs = devm_ioremap_resource(dev, res); if (IS_ERR(fimc->regs)) return PTR_ERR(fimc->regs); res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (res == NULL) { - dev_err(&pdev->dev, "Failed to get IRQ resource\n"); + dev_err(dev, "Failed to get IRQ resource\n"); return -ENXIO; } @@ -1434,10 +1451,10 @@ static int fimc_lite_probe(struct platform_device *pdev) if (ret) return ret; - ret = devm_request_irq(&pdev->dev, res->start, flite_irq_handler, - 0, dev_name(&pdev->dev), fimc); + ret = devm_request_irq(dev, res->start, flite_irq_handler, + 0, dev_name(dev), fimc); if (ret) { - dev_err(&pdev->dev, "Failed to install irq (%d)\n", ret); + dev_err(dev, "Failed to install irq (%d)\n", ret); goto err_clk; } @@ -1447,23 +1464,23 @@ static int fimc_lite_probe(struct platform_device *pdev) goto err_clk; platform_set_drvdata(pdev, fimc); - pm_runtime_enable(&pdev->dev); - ret = pm_runtime_get_sync(&pdev->dev); + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); if (ret < 0) goto err_sd; - fimc->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + fimc->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(fimc->alloc_ctx)) { ret = PTR_ERR(fimc->alloc_ctx); goto err_pm; } - pm_runtime_put(&pdev->dev); + pm_runtime_put(dev); - dev_dbg(&pdev->dev, "FIMC-LITE.%d registered successfully\n", + dev_dbg(dev, "FIMC-LITE.%d registered successfully\n", fimc->index); return 0; err_pm: - pm_runtime_put(&pdev->dev); + pm_runtime_put(dev); err_sd: fimc_lite_unregister_capture_subdev(fimc); err_clk: @@ -1554,6 +1571,12 @@ static int fimc_lite_remove(struct platform_device *pdev) return 0; } +static const struct dev_pm_ops fimc_lite_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fimc_lite_suspend, fimc_lite_resume) + SET_RUNTIME_PM_OPS(fimc_lite_runtime_suspend, fimc_lite_runtime_resume, + NULL) +}; + static struct flite_variant fimc_lite0_variant_exynos4 = { .max_width = 8192, .max_height = 8192, @@ -1579,17 +1602,21 @@ static struct platform_device_id fimc_lite_driver_ids[] = { }; MODULE_DEVICE_TABLE(platform, fimc_lite_driver_ids); -static const struct dev_pm_ops fimc_lite_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(fimc_lite_suspend, fimc_lite_resume) - SET_RUNTIME_PM_OPS(fimc_lite_runtime_suspend, fimc_lite_runtime_resume, - NULL) +static const struct of_device_id flite_of_match[] = { + { + .compatible = "samsung,exynos4212-fimc-lite", + .data = &fimc_lite_drvdata_exynos4, + }, + { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, flite_of_match); static struct platform_driver fimc_lite_driver = { .probe = fimc_lite_probe, .remove = fimc_lite_remove, .id_table = fimc_lite_driver_ids, .driver = { + .of_match_table = flite_of_match, .name = FIMC_LITE_DRV_NAME, .owner = THIS_MODULE, .pm = &fimc_lite_pm_ops, -- cgit v1.2.3 From 2b13f7d4e3822ed4de37b73b009ff81932e884bb Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Fri, 29 Mar 2013 14:12:39 -0300 Subject: [media] s5p-fimc: Add device tree based sensors registration The sensor (I2C and/or SPI client) devices are instantiated by their corresponding control bus drivers. Since the I2C client's master clock is often provided by a video bus receiver (host interface) or other than I2C/SPI controller device, the drivers of those client devices are not accessing hardware in their driver's probe() callback. Instead, after enabling clock, the host driver calls back into a sub-device when it wants to activate them. This pattern is used by some in-tree drivers and this patch also uses it for DT case. This patch is intended as a first step for adding device tree support to the S5P/Exynos SoC camera drivers. The second one is adding support for asynchronous sub-devices registration and clock control from sub-device driver level. The bindings shall not change when asynchronous probing support is added. Signed-off-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/samsung-fimc.txt | 86 ++++++++ drivers/media/platform/s5p-fimc/fimc-mdevice.c | 240 ++++++++++++++++++--- include/media/s5p_fimc.h | 3 + 3 files changed, 303 insertions(+), 26 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/media/samsung-fimc.txt b/Documentation/devicetree/bindings/media/samsung-fimc.txt index 22e2889162c3..bcb0de7462a2 100644 --- a/Documentation/devicetree/bindings/media/samsung-fimc.txt +++ b/Documentation/devicetree/bindings/media/samsung-fimc.txt @@ -72,18 +72,95 @@ Optional properties: writeback input. +'parallel-ports' node +--------------------- + +This node should contain child 'port' nodes specifying active parallel video +input ports. It includes camera A and camera B inputs. 'reg' property in the +port nodes specifies data input - 0, 1 indicates input A, B respectively. + +Optional properties + +- samsung,camclk-out : specifies clock output for remote sensor, + 0 - CAM_A_CLKOUT, 1 - CAM_B_CLKOUT; + +Image sensor nodes +------------------ + +The sensor device nodes should be added to their control bus controller (e.g. +I2C0) nodes and linked to a port node in the csis or the parallel-ports node, +using the common video interfaces bindings, defined in video-interfaces.txt. +The implementation of this bindings requires clock-frequency property to be +present in the sensor device nodes. + Example: aliases { fimc0 = &fimc_0; }; + /* Parallel bus IF sensor */ + i2c_0: i2c@13860000 { + s5k6aa: sensor@3c { + compatible = "samsung,s5k6aafx"; + reg = <0x3c>; + vddio-supply = <...>; + + clock-frequency = <24000000>; + clocks = <...>; + clock-names = "mclk"; + + port { + s5k6aa_ep: endpoint { + remote-endpoint = <&fimc0_ep>; + bus-width = <8>; + hsync-active = <0>; + vsync-active = <1>; + pclk-sample = <1>; + }; + }; + }; + }; + + /* MIPI CSI-2 bus IF sensor */ + s5c73m3: sensor@0x1a { + compatible = "samsung,s5c73m3"; + reg = <0x1a>; + vddio-supply = <...>; + + clock-frequency = <24000000>; + clocks = <...>; + clock-names = "mclk"; + + port { + s5c73m3_1: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&csis0_ep>; + }; + }; + }; + camera { compatible = "samsung,fimc", "simple-bus"; #address-cells = <1>; #size-cells = <1>; status = "okay"; + /* parallel camera ports */ + parallel-ports { + /* camera A input */ + port@0 { + reg = <0>; + fimc0_ep: endpoint { + remote-endpoint = <&s5k6aa_ep>; + bus-width = <8>; + hsync-active = <0>; + vsync-active = <1>; + pclk-sample = <1>; + }; + }; + }; + fimc_0: fimc@11800000 { compatible = "samsung,exynos4210-fimc"; reg = <0x11800000 0x1000>; @@ -95,6 +172,15 @@ Example: compatible = "samsung,exynos4210-csis"; reg = <0x11880000 0x1000>; interrupts = <0 78 0>; + /* camera C input */ + port@3 { + reg = <3>; + csis0_ep: endpoint { + remote-endpoint = <&s5c73m3_ep>; + data-lanes = <1 2 3 4>; + samsung,csis-hs-settle = <12>; + }; + }; }; }; diff --git a/drivers/media/platform/s5p-fimc/fimc-mdevice.c b/drivers/media/platform/s5p-fimc/fimc-mdevice.c index d34106c66427..bfa02abd94c8 100644 --- a/drivers/media/platform/s5p-fimc/fimc-mdevice.c +++ b/drivers/media/platform/s5p-fimc/fimc-mdevice.c @@ -251,7 +251,7 @@ static struct v4l2_subdev *fimc_md_register_sensor(struct fimc_md *fmd, sd->grp_id = GRP_ID_SENSOR; v4l2_info(&fmd->v4l2_dev, "Registered sensor subdevice %s\n", - s_info->pdata.board_info->type); + sd->name); return sd; } @@ -263,13 +263,191 @@ static void fimc_md_unregister_sensor(struct v4l2_subdev *sd) if (!client) return; v4l2_device_unregister_subdev(sd); - adapter = client->adapter; - i2c_unregister_device(client); - if (adapter) - i2c_put_adapter(adapter); + + if (!client->dev.of_node) { + adapter = client->adapter; + i2c_unregister_device(client); + if (adapter) + i2c_put_adapter(adapter); + } } #ifdef CONFIG_OF +/* Register I2C client subdev associated with @node. */ +static int fimc_md_of_add_sensor(struct fimc_md *fmd, + struct device_node *node, int index) +{ + struct fimc_sensor_info *si; + struct i2c_client *client; + struct v4l2_subdev *sd; + int ret; + + if (WARN_ON(index >= ARRAY_SIZE(fmd->sensor))) + return -EINVAL; + si = &fmd->sensor[index]; + + client = of_find_i2c_device_by_node(node); + if (!client) + return -EPROBE_DEFER; + + device_lock(&client->dev); + + if (!client->driver || + !try_module_get(client->driver->driver.owner)) { + ret = -EPROBE_DEFER; + v4l2_info(&fmd->v4l2_dev, "No driver found for %s\n", + node->full_name); + goto dev_put; + } + + /* Enable sensor's master clock */ + ret = __fimc_md_set_camclk(fmd, si, true); + if (ret < 0) + goto mod_put; + sd = i2c_get_clientdata(client); + + ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd); + __fimc_md_set_camclk(fmd, si, false); + if (ret < 0) + goto mod_put; + + v4l2_set_subdev_hostdata(sd, si); + sd->grp_id = GRP_ID_SENSOR; + si->subdev = sd; + v4l2_info(&fmd->v4l2_dev, "Registered sensor subdevice: %s (%d)\n", + sd->name, fmd->num_sensors); + fmd->num_sensors++; + +mod_put: + module_put(client->driver->driver.owner); +dev_put: + device_unlock(&client->dev); + put_device(&client->dev); + return ret; +} + +/* Parse port node and register as a sub-device any sensor specified there. */ +static int fimc_md_parse_port_node(struct fimc_md *fmd, + struct device_node *port, + unsigned int index) +{ + struct device_node *rem, *ep, *np; + struct fimc_source_info *pd; + struct v4l2_of_endpoint endpoint; + int ret; + u32 val; + + pd = &fmd->sensor[index].pdata; + + /* Assume here a port node can have only one endpoint node. */ + ep = of_get_next_child(port, NULL); + if (!ep) + return 0; + + v4l2_of_parse_endpoint(ep, &endpoint); + if (WARN_ON(endpoint.port == 0) || index >= FIMC_MAX_SENSORS) + return -EINVAL; + + pd->mux_id = (endpoint.port - 1) & 0x1; + + rem = v4l2_of_get_remote_port_parent(ep); + of_node_put(ep); + if (rem == NULL) { + v4l2_info(&fmd->v4l2_dev, "Remote device at %s not found\n", + ep->full_name); + return 0; + } + if (!of_property_read_u32(rem, "samsung,camclk-out", &val)) + pd->clk_id = val; + + if (!of_property_read_u32(rem, "clock-frequency", &val)) + pd->clk_frequency = val; + + if (pd->clk_frequency == 0) { + v4l2_err(&fmd->v4l2_dev, "Wrong clock frequency at node %s\n", + rem->full_name); + of_node_put(rem); + return -EINVAL; + } + + if (fimc_input_is_parallel(endpoint.port)) { + if (endpoint.bus_type == V4L2_MBUS_PARALLEL) + pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_601; + else + pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_656; + pd->flags = endpoint.bus.parallel.flags; + } else if (fimc_input_is_mipi_csi(endpoint.port)) { + /* + * MIPI CSI-2: only input mux selection and + * the sensor's clock frequency is needed. + */ + pd->sensor_bus_type = FIMC_BUS_TYPE_MIPI_CSI2; + } else { + v4l2_err(&fmd->v4l2_dev, "Wrong port id (%u) at node %s\n", + endpoint.port, rem->full_name); + } + /* + * For FIMC-IS handled sensors, that are placed under i2c-isp device + * node, FIMC is connected to the FIMC-IS through its ISP Writeback + * input. Sensors are attached to the FIMC-LITE hostdata interface + * directly or through MIPI-CSIS, depending on the external media bus + * used. This needs to be handled in a more reliable way, not by just + * checking parent's node name. + */ + np = of_get_parent(rem); + + if (np && !of_node_cmp(np->name, "i2c-isp")) + pd->fimc_bus_type = FIMC_BUS_TYPE_ISP_WRITEBACK; + else + pd->fimc_bus_type = pd->sensor_bus_type; + + ret = fimc_md_of_add_sensor(fmd, rem, index); + of_node_put(rem); + + return ret; +} + +/* Register all SoC external sub-devices */ +static int fimc_md_of_sensors_register(struct fimc_md *fmd, + struct device_node *np) +{ + struct device_node *parent = fmd->pdev->dev.of_node; + struct device_node *node, *ports; + int index = 0; + int ret; + + /* Attach sensors linked to MIPI CSI-2 receivers */ + for_each_available_child_of_node(parent, node) { + struct device_node *port; + + if (of_node_cmp(node->name, "csis")) + continue; + /* The csis node can have only port subnode. */ + port = of_get_next_child(node, NULL); + if (!port) + continue; + + ret = fimc_md_parse_port_node(fmd, port, index); + if (ret < 0) + return ret; + index++; + } + + /* Attach sensors listed in the parallel-ports node */ + ports = of_get_child_by_name(parent, "parallel-ports"); + if (!ports) + return 0; + + for_each_child_of_node(ports, node) { + ret = fimc_md_parse_port_node(fmd, node, index); + if (ret < 0) + break; + index++; + } + + return 0; +} + static int __of_get_csis_id(struct device_node *np) { u32 reg = 0; @@ -281,14 +459,17 @@ static int __of_get_csis_id(struct device_node *np) return reg - FIMC_INPUT_MIPI_CSI2_0; } #else +#define fimc_md_of_sensors_register(fmd, np) (-ENOSYS) #define __of_get_csis_id(np) (-ENOSYS) #endif static int fimc_md_register_sensor_entities(struct fimc_md *fmd) { struct s5p_platform_fimc *pdata = fmd->pdev->dev.platform_data; + struct device_node *of_node = fmd->pdev->dev.of_node; struct fimc_dev *fd = NULL; - int num_clients, ret, i; + int num_clients = 0; + int ret, i; /* * Runtime resume one of the FIMC entities to make sure @@ -299,34 +480,41 @@ static int fimc_md_register_sensor_entities(struct fimc_md *fmd) fd = fmd->fimc[i]; if (!fd) return -ENXIO; + ret = pm_runtime_get_sync(&fd->pdev->dev); if (ret < 0) return ret; - WARN_ON(pdata->num_clients > ARRAY_SIZE(fmd->sensor)); - num_clients = min_t(u32, pdata->num_clients, ARRAY_SIZE(fmd->sensor)); + if (of_node) { + fmd->num_sensors = 0; + ret = fimc_md_of_sensors_register(fmd, of_node); + } else if (pdata) { + WARN_ON(pdata->num_clients > ARRAY_SIZE(fmd->sensor)); + num_clients = min_t(u32, pdata->num_clients, + ARRAY_SIZE(fmd->sensor)); + fmd->num_sensors = num_clients; - fmd->num_sensors = num_clients; - for (i = 0; i < num_clients; i++) { - struct v4l2_subdev *sd; + for (i = 0; i < num_clients; i++) { + struct v4l2_subdev *sd; - fmd->sensor[i].pdata = pdata->source_info[i]; - ret = __fimc_md_set_camclk(fmd, &fmd->sensor[i], true); - if (ret) - break; - sd = fimc_md_register_sensor(fmd, &fmd->sensor[i]); - ret = __fimc_md_set_camclk(fmd, &fmd->sensor[i], false); - - if (!IS_ERR(sd)) { + fmd->sensor[i].pdata = pdata->source_info[i]; + ret = __fimc_md_set_camclk(fmd, &fmd->sensor[i], true); + if (ret) + break; + sd = fimc_md_register_sensor(fmd, &fmd->sensor[i]); + ret = __fimc_md_set_camclk(fmd, &fmd->sensor[i], false); + + if (IS_ERR(sd)) { + fmd->sensor[i].subdev = NULL; + ret = PTR_ERR(sd); + break; + } fmd->sensor[i].subdev = sd; - } else { - fmd->sensor[i].subdev = NULL; - ret = PTR_ERR(sd); - break; + if (ret) + break; } - if (ret) - break; } + pm_runtime_put(&fd->pdev->dev); return ret; } @@ -1037,7 +1225,7 @@ static int fimc_md_probe(struct platform_device *pdev) if (ret) goto err_unlock; - if (dev->platform_data) { + if (dev->platform_data || dev->of_node) { ret = fimc_md_register_sensor_entities(fmd); if (ret) goto err_unlock; diff --git a/include/media/s5p_fimc.h b/include/media/s5p_fimc.h index e2c598962698..e2434bb0b308 100644 --- a/include/media/s5p_fimc.h +++ b/include/media/s5p_fimc.h @@ -45,6 +45,9 @@ enum fimc_bus_type { FIMC_BUS_TYPE_ISP_WRITEBACK = FIMC_BUS_TYPE_LCD_WRITEBACK_B, }; +#define fimc_input_is_parallel(x) ((x) == 1 || (x) == 2) +#define fimc_input_is_mipi_csi(x) ((x) == 3 || (x) == 4) + struct i2c_board_info; /** -- cgit v1.2.3 From 4163851f7b997e24602cad8e0eae96d31a252548 Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Fri, 8 Mar 2013 10:41:47 -0300 Subject: [media] s5p-fimc: Use pinctrl API for camera ports configuration Before the camera ports can be used the pinmux needs to be configured properly. This patch adds a function to set the camera ports pinctrl to a default state within the media driver's probe(). The camera port(s) are then configured for the video bus operation. Signed-off-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/samsung-fimc.txt | 12 ++++++++++ drivers/media/platform/s5p-fimc/fimc-mdevice.c | 26 ++++++++++++++++++++++ drivers/media/platform/s5p-fimc/fimc-mdevice.h | 11 +++++++++ 3 files changed, 49 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/media/samsung-fimc.txt b/Documentation/devicetree/bindings/media/samsung-fimc.txt index bcb0de7462a2..2a63ddd23d16 100644 --- a/Documentation/devicetree/bindings/media/samsung-fimc.txt +++ b/Documentation/devicetree/bindings/media/samsung-fimc.txt @@ -21,6 +21,15 @@ Required properties: - clock-names : must contain "fimc", "sclk_fimc" entries, matching entries in the clocks property. +The pinctrl bindings defined in ../pinctrl/pinctrl-bindings.txt must be used +to define a required pinctrl state named "default" and optional pinctrl states: +"idle", "active-a", active-b". These optional states can be used to switch the +camera port pinmux at runtime. The "idle" state should configure both the camera +ports A and B into high impedance state, especially the CAMCLK clock output +should be inactive. For the "active-a" state the camera port A must be activated +and the port B deactivated and for the state "active-b" it should be the other +way around. + The 'camera' node must include at least one 'fimc' child node. @@ -146,6 +155,9 @@ Example: #size-cells = <1>; status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&cam_port_a_clk_active>; + /* parallel camera ports */ parallel-ports { /* camera A input */ diff --git a/drivers/media/platform/s5p-fimc/fimc-mdevice.c b/drivers/media/platform/s5p-fimc/fimc-mdevice.c index bfa02abd94c8..5e96775fca8d 100644 --- a/drivers/media/platform/s5p-fimc/fimc-mdevice.c +++ b/drivers/media/platform/s5p-fimc/fimc-mdevice.c @@ -1174,6 +1174,25 @@ static ssize_t fimc_md_sysfs_store(struct device *dev, static DEVICE_ATTR(subdev_conf_mode, S_IWUSR | S_IRUGO, fimc_md_sysfs_show, fimc_md_sysfs_store); +static int fimc_md_get_pinctrl(struct fimc_md *fmd) +{ + struct device *dev = &fmd->pdev->dev; + struct fimc_pinctrl *pctl = &fmd->pinctl; + + pctl->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(pctl->pinctrl)) + return PTR_ERR(pctl->pinctrl); + + pctl->state_default = pinctrl_lookup_state(pctl->pinctrl, + PINCTRL_STATE_DEFAULT); + if (IS_ERR(pctl->state_default)) + return PTR_ERR(pctl->state_default); + + pctl->state_idle = pinctrl_lookup_state(pctl->pinctrl, + PINCTRL_STATE_IDLE); + return 0; +} + static int fimc_md_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1217,6 +1236,13 @@ static int fimc_md_probe(struct platform_device *pdev) /* Protect the media graph while we're registering entities */ mutex_lock(&fmd->media_dev.graph_mutex); + ret = fimc_md_get_pinctrl(fmd); + if (ret < 0) { + if (ret != EPROBE_DEFER) + dev_err(dev, "Failed to get pinctrl: %d\n", ret); + goto err_unlock; + } + if (dev->of_node) ret = fimc_md_register_of_platform_entities(fmd, dev->of_node); else diff --git a/drivers/media/platform/s5p-fimc/fimc-mdevice.h b/drivers/media/platform/s5p-fimc/fimc-mdevice.h index b6ceb5984664..5d6146e76802 100644 --- a/drivers/media/platform/s5p-fimc/fimc-mdevice.h +++ b/drivers/media/platform/s5p-fimc/fimc-mdevice.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,8 @@ #define FIMC_IS_OF_NODE_NAME "fimc-is" #define CSIS_OF_NODE_NAME "csis" +#define PINCTRL_STATE_IDLE "idle" + /* Group IDs of sensor, MIPI-CSIS, FIMC-LITE and the writeback subdevs. */ #define GRP_ID_SENSOR (1 << 8) #define GRP_ID_FIMC_IS_SENSOR (1 << 9) @@ -73,6 +76,9 @@ struct fimc_sensor_info { * @media_dev: top level media device * @v4l2_dev: top level v4l2_device holding up the subdevs * @pdev: platform device this media device is hooked up into + * @pinctrl: camera port pinctrl handle + * @state_default: pinctrl default state handle + * @state_idle: pinctrl idle state handle * @user_subdev_api: true if subdevs are not configured by the host driver * @slock: spinlock protecting @sensor array */ @@ -86,6 +92,11 @@ struct fimc_md { struct media_device media_dev; struct v4l2_device v4l2_dev; struct platform_device *pdev; + struct fimc_pinctrl { + struct pinctrl *pinctrl; + struct pinctrl_state *state_default; + struct pinctrl_state *state_idle; + } pinctl; bool user_subdev_api; spinlock_t slock; }; -- cgit v1.2.3 From fc39f46b54b600f053bf9bab757023344e97925e Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Thu, 14 Mar 2013 07:01:24 -0300 Subject: [media] V4L: Add MATRIX option to V4L2_CID_EXPOSURE_METERING control This patch adds a menu option to the V4L2_CID_EXPOSURE_METERING control for multi-zone metering. Signed-off-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/controls.xml | 7 +++++++ drivers/media/v4l2-core/v4l2-ctrls.c | 1 + include/uapi/linux/v4l2-controls.h | 1 + 3 files changed, 9 insertions(+) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/controls.xml b/Documentation/DocBook/media/v4l/controls.xml index c8eb6c222274..8d7a77928d49 100644 --- a/Documentation/DocBook/media/v4l/controls.xml +++ b/Documentation/DocBook/media/v4l/controls.xml @@ -3142,6 +3142,13 @@ giving priority to the center of the metered area. V4L2_EXPOSURE_METERING_SPOT  Measure only very small area at the center of the frame. + + V4L2_EXPOSURE_METERING_MATRIX  + A multi-zone metering. The light intensity is measured +in several points of the frame and the the results are combined. The +algorithm of the zones selection and their significance in calculating the +final value is device dependant. + diff --git a/drivers/media/v4l2-core/v4l2-ctrls.c b/drivers/media/v4l2-core/v4l2-ctrls.c index ec89fd16361c..ebb8e48619a2 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls.c +++ b/drivers/media/v4l2-core/v4l2-ctrls.c @@ -234,6 +234,7 @@ const char * const *v4l2_ctrl_get_menu(u32 id) "Average", "Center Weighted", "Spot", + "Matrix", NULL }; static const char * const camera_auto_focus_range[] = { diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 7da22cec30cd..69bd5bb0d5af 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -658,6 +658,7 @@ enum v4l2_exposure_metering { V4L2_EXPOSURE_METERING_AVERAGE = 0, V4L2_EXPOSURE_METERING_CENTER_WEIGHTED = 1, V4L2_EXPOSURE_METERING_SPOT = 2, + V4L2_EXPOSURE_METERING_MATRIX = 3, }; #define V4L2_CID_SCENE_MODE (V4L2_CID_CAMERA_CLASS_BASE+26) -- cgit v1.2.3 From 7b88fc086a217be7d16ec68a7f66093d344e39d7 Mon Sep 17 00:00:00 2001 From: Phil Edworthy Date: Mon, 18 Mar 2013 08:47:59 -0300 Subject: [media] soc_camera: Add RGB666 & RGB888 formats Based on work done by Katsuya Matsubara. Signed-off-by: Phil Edworthy Signed-off-by: Guennadi Liakhovetski Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/subdev-formats.xml | 206 ++++++++++++++++++++- Documentation/DocBook/media_api.tmpl | 1 + drivers/media/platform/soc_camera/soc_mediabus.c | 42 +++++ include/media/soc_camera.h | 7 +- include/media/soc_mediabus.h | 3 + include/uapi/linux/v4l2-mediabus.h | 6 +- 6 files changed, 253 insertions(+), 12 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/subdev-formats.xml b/Documentation/DocBook/media/v4l/subdev-formats.xml index cc51372ed5e0..adc61982df7b 100644 --- a/Documentation/DocBook/media/v4l/subdev-formats.xml +++ b/Documentation/DocBook/media/v4l/subdev-formats.xml @@ -93,19 +93,35 @@ RGB formats - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + Identifier @@ -117,6 +133,22 @@ Bit + 23 + 22 + 21 + 20 + 19 + 18 + 17 + 16 + 15 + 14 + 13 + 12 + 11 + 10 + 9 + 8 7 6 5 @@ -132,6 +164,7 @@ V4L2_MBUS_FMT_RGB444_2X8_PADHI_BE 0x1001 + &dash-ent-16; 0 0 0 @@ -145,6 +178,7 @@ + &dash-ent-16; g3 g2 g1 @@ -158,6 +192,7 @@ V4L2_MBUS_FMT_RGB444_2X8_PADHI_LE 0x1002 + &dash-ent-16; g3 g2 g1 @@ -171,6 +206,7 @@ + &dash-ent-16; 0 0 0 @@ -184,6 +220,7 @@ V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE 0x1003 + &dash-ent-16; 0 r4 r3 @@ -197,6 +234,7 @@ + &dash-ent-16; g2 g1 g0 @@ -210,6 +248,7 @@ V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE 0x1004 + &dash-ent-16; g2 g1 g0 @@ -223,6 +262,7 @@ + &dash-ent-16; 0 r4 r3 @@ -236,6 +276,7 @@ V4L2_MBUS_FMT_BGR565_2X8_BE 0x1005 + &dash-ent-16; b4 b3 b2 @@ -249,6 +290,7 @@ + &dash-ent-16; g2 g1 g0 @@ -262,6 +304,7 @@ V4L2_MBUS_FMT_BGR565_2X8_LE 0x1006 + &dash-ent-16; g2 g1 g0 @@ -275,6 +318,7 @@ + &dash-ent-16; b4 b3 b2 @@ -288,6 +332,7 @@ V4L2_MBUS_FMT_RGB565_2X8_BE 0x1007 + &dash-ent-16; r4 r3 r2 @@ -301,6 +346,7 @@ + &dash-ent-16; g2 g1 g0 @@ -314,6 +360,7 @@ V4L2_MBUS_FMT_RGB565_2X8_LE 0x1008 + &dash-ent-16; g2 g1 g0 @@ -327,6 +374,27 @@ + &dash-ent-16; + r4 + r3 + r2 + r1 + r0 + g5 + g4 + g3 + + + V4L2_MBUS_FMT_RGB666_1X18 + 0x1009 + + - + - + - + - + - + - + r5 r4 r3 r2 @@ -335,6 +403,124 @@ g5 g4 g3 + g2 + g1 + g0 + b5 + b4 + b3 + b2 + b1 + b0 + + + V4L2_MBUS_FMT_RGB888_1X24 + 0x100a + + r7 + r6 + r5 + r4 + r3 + r2 + r1 + r0 + g7 + g6 + g5 + g4 + g3 + g2 + g1 + g0 + b7 + b6 + b5 + b4 + b3 + b2 + b1 + b0 + + + V4L2_MBUS_FMT_RGB888_2X12_BE + 0x100b + + &dash-ent-10; + - + - + r7 + r6 + r5 + r4 + r3 + r2 + r1 + r0 + g7 + g6 + g5 + g4 + + + + + + &dash-ent-10; + - + - + g3 + g2 + g1 + g0 + b7 + b6 + b5 + b4 + b3 + b2 + b1 + b0 + + + V4L2_MBUS_FMT_RGB888_2X12_LE + 0x100c + + &dash-ent-10; + - + - + g3 + g2 + g1 + g0 + b7 + b6 + b5 + b4 + b3 + b2 + b1 + b0 + + + + + + &dash-ent-10; + - + - + r7 + r6 + r5 + r4 + r3 + r2 + r1 + r0 + g7 + g6 + g5 + g4 diff --git a/Documentation/DocBook/media_api.tmpl b/Documentation/DocBook/media_api.tmpl index 1f6593deb995..6a8b7158697f 100644 --- a/Documentation/DocBook/media_api.tmpl +++ b/Documentation/DocBook/media_api.tmpl @@ -23,6 +23,7 @@ http://linuxtv.org/repo/"> ----------"> +----------------"> ]> diff --git a/drivers/media/platform/soc_camera/soc_mediabus.c b/drivers/media/platform/soc_camera/soc_mediabus.c index 89dce097a827..7569e7746c92 100644 --- a/drivers/media/platform/soc_camera/soc_mediabus.c +++ b/drivers/media/platform/soc_camera/soc_mediabus.c @@ -96,6 +96,42 @@ static const struct soc_mbus_lookup mbus_fmt[] = { .order = SOC_MBUS_ORDER_LE, .layout = SOC_MBUS_LAYOUT_PACKED, }, +}, { + .code = V4L2_MBUS_FMT_RGB666_1X18, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB32, + .name = "RGB666/32bpp", + .bits_per_sample = 18, + .packing = SOC_MBUS_PACKING_EXTEND32, + .order = SOC_MBUS_ORDER_LE, + }, +}, { + .code = V4L2_MBUS_FMT_RGB888_1X24, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB32, + .name = "RGB888/32bpp", + .bits_per_sample = 24, + .packing = SOC_MBUS_PACKING_EXTEND32, + .order = SOC_MBUS_ORDER_LE, + }, +}, { + .code = V4L2_MBUS_FMT_RGB888_2X12_BE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB32, + .name = "RGB888/32bpp", + .bits_per_sample = 12, + .packing = SOC_MBUS_PACKING_EXTEND32, + .order = SOC_MBUS_ORDER_BE, + }, +}, { + .code = V4L2_MBUS_FMT_RGB888_2X12_LE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB32, + .name = "RGB888/32bpp", + .bits_per_sample = 12, + .packing = SOC_MBUS_PACKING_EXTEND32, + .order = SOC_MBUS_ORDER_LE, + }, }, { .code = V4L2_MBUS_FMT_SBGGR8_1X8, .fmt = { @@ -358,6 +394,10 @@ int soc_mbus_samples_per_pixel(const struct soc_mbus_pixelfmt *mf, *numerator = 1; *denominator = 1; return 0; + case SOC_MBUS_PACKING_EXTEND32: + *numerator = 1; + *denominator = 1; + return 0; case SOC_MBUS_PACKING_2X8_PADHI: case SOC_MBUS_PACKING_2X8_PADLO: *numerator = 2; @@ -392,6 +432,8 @@ s32 soc_mbus_bytes_per_line(u32 width, const struct soc_mbus_pixelfmt *mf) return width * 3 / 2; case SOC_MBUS_PACKING_VARIABLE: return 0; + case SOC_MBUS_PACKING_EXTEND32: + return width * 4; } return -EINVAL; } diff --git a/include/media/soc_camera.h b/include/media/soc_camera.h index 2cc70cf318bf..ff77d08c30fd 100644 --- a/include/media/soc_camera.h +++ b/include/media/soc_camera.h @@ -292,12 +292,17 @@ struct soc_camera_sense { #define SOCAM_DATAWIDTH_8 SOCAM_DATAWIDTH(8) #define SOCAM_DATAWIDTH_9 SOCAM_DATAWIDTH(9) #define SOCAM_DATAWIDTH_10 SOCAM_DATAWIDTH(10) +#define SOCAM_DATAWIDTH_12 SOCAM_DATAWIDTH(12) #define SOCAM_DATAWIDTH_15 SOCAM_DATAWIDTH(15) #define SOCAM_DATAWIDTH_16 SOCAM_DATAWIDTH(16) +#define SOCAM_DATAWIDTH_18 SOCAM_DATAWIDTH(18) +#define SOCAM_DATAWIDTH_24 SOCAM_DATAWIDTH(24) #define SOCAM_DATAWIDTH_MASK (SOCAM_DATAWIDTH_4 | SOCAM_DATAWIDTH_8 | \ SOCAM_DATAWIDTH_9 | SOCAM_DATAWIDTH_10 | \ - SOCAM_DATAWIDTH_15 | SOCAM_DATAWIDTH_16) + SOCAM_DATAWIDTH_12 | SOCAM_DATAWIDTH_15 | \ + SOCAM_DATAWIDTH_16 | SOCAM_DATAWIDTH_18 | \ + SOCAM_DATAWIDTH_24) static inline void soc_camera_limit_side(int *start, int *length, unsigned int start_min, diff --git a/include/media/soc_mediabus.h b/include/media/soc_mediabus.h index 0dc6f4625b92..d33f6d059692 100644 --- a/include/media/soc_mediabus.h +++ b/include/media/soc_mediabus.h @@ -26,6 +26,8 @@ * @SOC_MBUS_PACKING_VARIABLE: compressed formats with variable packing * @SOC_MBUS_PACKING_1_5X8: used for packed YUV 4:2:0 formats, where 4 * pixels occupy 6 bytes in RAM + * @SOC_MBUS_PACKING_EXTEND32: sample width (e.g., 24 bits) has to be extended + * to 32 bits */ enum soc_mbus_packing { SOC_MBUS_PACKING_NONE, @@ -34,6 +36,7 @@ enum soc_mbus_packing { SOC_MBUS_PACKING_EXTEND16, SOC_MBUS_PACKING_VARIABLE, SOC_MBUS_PACKING_1_5X8, + SOC_MBUS_PACKING_EXTEND32, }; /** diff --git a/include/uapi/linux/v4l2-mediabus.h b/include/uapi/linux/v4l2-mediabus.h index b9b7bea04537..6ee63d09b32d 100644 --- a/include/uapi/linux/v4l2-mediabus.h +++ b/include/uapi/linux/v4l2-mediabus.h @@ -37,7 +37,7 @@ enum v4l2_mbus_pixelcode { V4L2_MBUS_FMT_FIXED = 0x0001, - /* RGB - next is 0x1009 */ + /* RGB - next is 0x100d */ V4L2_MBUS_FMT_RGB444_2X8_PADHI_BE = 0x1001, V4L2_MBUS_FMT_RGB444_2X8_PADHI_LE = 0x1002, V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE = 0x1003, @@ -46,6 +46,10 @@ enum v4l2_mbus_pixelcode { V4L2_MBUS_FMT_BGR565_2X8_LE = 0x1006, V4L2_MBUS_FMT_RGB565_2X8_BE = 0x1007, V4L2_MBUS_FMT_RGB565_2X8_LE = 0x1008, + V4L2_MBUS_FMT_RGB666_1X18 = 0x1009, + V4L2_MBUS_FMT_RGB888_1X24 = 0x100a, + V4L2_MBUS_FMT_RGB888_2X12_BE = 0x100b, + V4L2_MBUS_FMT_RGB888_2X12_LE = 0x100c, /* YUV (including grey) - next is 0x2017 */ V4L2_MBUS_FMT_Y8_1X8 = 0x2001, -- cgit v1.2.3 From 05a70a4079f2a73de6c61abf4dd70b6bcf1b3200 Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Tue, 2 Apr 2013 06:45:59 -0300 Subject: [media] exynos4-is: Correct clock properties description at the DT binding documentation The 'camera' DT node needs to have sclk_cam0/1 and pxl_async0/1 clocks specified, while 'fimc' nodes should have only "fimc" and "sclk_fimc". "mux" and "parent" are leftovers from early versions of patches adding DT support, when the IP bus clock parent clock was being set by the driver. A better solution is needed to have e.g. clocks driver setting all required parent clocks, before clock consumers start using the clocks. Currently this binding doesn't describe parent clocks setup, it needs to be specified and handled somewhere else. Signed-off-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park Signed-off-by: Mauro Carvalho Chehab --- Documentation/devicetree/bindings/media/samsung-fimc.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/media/samsung-fimc.txt b/Documentation/devicetree/bindings/media/samsung-fimc.txt index 2a63ddd23d16..51c776b7f7a3 100644 --- a/Documentation/devicetree/bindings/media/samsung-fimc.txt +++ b/Documentation/devicetree/bindings/media/samsung-fimc.txt @@ -17,9 +17,9 @@ Required properties: - compatible : must be "samsung,fimc", "simple-bus" - clocks : list of clock specifiers, corresponding to entries in - clock-names property; -- clock-names : must contain "fimc", "sclk_fimc" entries, matching entries - in the clocks property. + the clock-names property; +- clock-names : must contain "sclk_cam0", "sclk_cam1", "pxl_async0", + "pxl_async1" entries, matching entries in the clocks property. The pinctrl bindings defined in ../pinctrl/pinctrl-bindings.txt must be used to define a required pinctrl state named "default" and optional pinctrl states: @@ -32,7 +32,6 @@ way around. The 'camera' node must include at least one 'fimc' child node. - 'fimc' device nodes ------------------- @@ -44,8 +43,7 @@ Required properties: - interrupts: should contain FIMC interrupt; - clocks: list of clock specifiers, must contain an entry for each required entry in clock-names; -- clock-names: must include "fimc", "sclk_fimc", "mux" entries and optionally - "parent" entry. +- clock-names: must contain "fimc", "sclk_fimc" entries. - samsung,pix-limits: an array of maximum supported image sizes in pixels, for details refer to Table 2-1 in the S5PV210 SoC User Manual; The meaning of each cell is as follows: -- cgit v1.2.3 From 0580abc2b5d04fca31578c7a9cc4adc0ed3677ac Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Tue, 26 Mar 2013 09:21:23 -0300 Subject: [media] exynos4-is: Add Exynos4x12 FIMC-IS device tree binding documentation This patch adds DT binding documentaton for the Imaging Subsystem (camera ISP) found on Samsung Exynos4x12 SoCs. Signed-off-by: Sylwester Nawrocki Signed-off-by: Kyungmin Park Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/exynos4-fimc-is.txt | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/exynos4-fimc-is.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/media/exynos4-fimc-is.txt b/Documentation/devicetree/bindings/media/exynos4-fimc-is.txt new file mode 100644 index 000000000000..55c9ad6f9599 --- /dev/null +++ b/Documentation/devicetree/bindings/media/exynos4-fimc-is.txt @@ -0,0 +1,49 @@ +Exynos4x12 SoC series Imaging Subsystem (FIMC-IS) + +The FIMC-IS is a subsystem for processing image signal from an image sensor. +The Exynos4x12 SoC series FIMC-IS V1.5 comprises of a dedicated ARM Cortex-A5 +processor, ISP, DRC and FD IP blocks and peripheral devices such as UART, I2C +and SPI bus controllers, PWM and ADC. + +fimc-is node +------------ + +Required properties: +- compatible : should be "samsung,exynos4212-fimc-is" for Exynos4212 and + Exynos4412 SoCs; +- reg : physical base address and length of the registers set; +- interrupts : must contain two FIMC-IS interrupts, in order: ISP0, ISP1; +- clocks : list of clock specifiers, corresponding to entries in + clock-names property; +- clock-names : must contain "ppmuispx", "ppmuispx", "lite0", "lite1" + "mpll", "sysreg", "isp", "drc", "fd", "mcuisp", "uart", + "ispdiv0", "ispdiv1", "mcuispdiv0", "mcuispdiv1", "aclk200", + "div_aclk200", "aclk400mcuisp", "div_aclk400mcuisp" entries, + matching entries in the clocks property. +pmu subnode +----------- + +Required properties: + - reg : must contain PMU physical base address and size of the register set. + +The following are the FIMC-IS peripheral device nodes and can be specified +either standalone or as the fimc-is node child nodes. + +i2c-isp (ISP I2C bus controller) nodes +------------------------------------------ + +Required properties: + +- compatible : should be "samsung,exynos4212-i2c-isp" for Exynos4212 and + Exynos4412 SoCs; +- reg : physical base address and length of the registers set; +- clocks : must contain gate clock specifier for this controller; +- clock-names : must contain "i2c_isp" entry. + +For the above nodes it is required to specify a pinctrl state named "default", +according to the pinctrl bindings defined in ../pinctrl/pinctrl-bindings.txt. + +Device tree nodes of the image sensors' controlled directly by the FIMC-IS +firmware must be child nodes of their corresponding ISP I2C bus controller node. +The data link of these image sensors must be specified using the common video +interfaces bindings, defined in video-interfaces.txt. -- cgit v1.2.3 From cd634f1bfc182e564f33809fdca33027bb99fceb Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 27 Mar 2013 08:04:23 -0300 Subject: [media] v4l2: put VIDIOC_DBG_G_CHIP_NAME under ADV_DEBUG Only enable this ioctl if the VIDEO_ADV_DEBUG config option is set. This prevents abuse from both userspace and kernelspace (some bridge drivers abuse DBG_G_CHIP_IDENT, lets prevent that from happening again with this ioctl). Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml | 3 +++ drivers/media/usb/em28xx/em28xx-video.c | 4 ++-- drivers/media/v4l2-core/v4l2-dev.c | 2 +- drivers/media/v4l2-core/v4l2-ioctl.c | 8 ++++---- include/media/v4l2-ioctl.h | 6 +++--- 5 files changed, 13 insertions(+), 10 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml index 4921346fabd7..5fce8d84288e 100644 --- a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml +++ b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml @@ -63,6 +63,9 @@ card. Regular applications must not use it. When you found a chip specific bug, please contact the linux-media mailing list (&v4l-ml;) so it can be fixed. + Additionally the Linux kernel must be compiled with the +CONFIG_VIDEO_ADV_DEBUG option to enable this ioctl. + To query the driver applications must initialize the match.type and match.addr or match.name diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c index 792ead1025d7..39951f5731e1 100644 --- a/drivers/media/usb/em28xx/em28xx-video.c +++ b/drivers/media/usb/em28xx/em28xx-video.c @@ -1331,6 +1331,7 @@ static int vidioc_g_chip_ident(struct file *file, void *priv, return 0; } +#ifdef CONFIG_VIDEO_ADV_DEBUG static int vidioc_g_chip_name(struct file *file, void *priv, struct v4l2_dbg_chip_name *chip) { @@ -1346,7 +1347,6 @@ static int vidioc_g_chip_name(struct file *file, void *priv, return 0; } -#ifdef CONFIG_VIDEO_ADV_DEBUG static int em28xx_reg_len(int reg) { switch (reg) { @@ -1796,8 +1796,8 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = { .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, .vidioc_g_chip_ident = vidioc_g_chip_ident, - .vidioc_g_chip_name = vidioc_g_chip_name, #ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_chip_name = vidioc_g_chip_name, .vidioc_g_register = vidioc_g_register, .vidioc_s_register = vidioc_s_register, #endif diff --git a/drivers/media/v4l2-core/v4l2-dev.c b/drivers/media/v4l2-core/v4l2-dev.c index 670b9ca8ecbe..1c3b43cf773d 100644 --- a/drivers/media/v4l2-core/v4l2-dev.c +++ b/drivers/media/v4l2-core/v4l2-dev.c @@ -591,8 +591,8 @@ static void determine_valid_ioctls(struct video_device *vdev) SET_VALID_IOCTL(ops, VIDIOC_G_FREQUENCY, vidioc_g_frequency); SET_VALID_IOCTL(ops, VIDIOC_S_FREQUENCY, vidioc_s_frequency); SET_VALID_IOCTL(ops, VIDIOC_LOG_STATUS, vidioc_log_status); - set_bit(_IOC_NR(VIDIOC_DBG_G_CHIP_NAME), valid_ioctls); #ifdef CONFIG_VIDEO_ADV_DEBUG + set_bit(_IOC_NR(VIDIOC_DBG_G_CHIP_NAME), valid_ioctls); set_bit(_IOC_NR(VIDIOC_DBG_G_REGISTER), valid_ioctls); set_bit(_IOC_NR(VIDIOC_DBG_S_REGISTER), valid_ioctls); #endif diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 336ed2dd607c..feac07e50293 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1873,6 +1873,7 @@ static int v4l_dbg_g_chip_ident(const struct v4l2_ioctl_ops *ops, static int v4l_dbg_g_chip_name(const struct v4l2_ioctl_ops *ops, struct file *file, void *fh, void *arg) { +#ifdef CONFIG_VIDEO_ADV_DEBUG struct video_device *vfd = video_devdata(file); struct v4l2_dbg_chip_name *p = arg; struct v4l2_subdev *sd; @@ -1880,12 +1881,10 @@ static int v4l_dbg_g_chip_name(const struct v4l2_ioctl_ops *ops, switch (p->match.type) { case V4L2_CHIP_MATCH_BRIDGE: -#ifdef CONFIG_VIDEO_ADV_DEBUG if (ops->vidioc_s_register) p->flags |= V4L2_CHIP_FL_WRITABLE; if (ops->vidioc_g_register) p->flags |= V4L2_CHIP_FL_READABLE; -#endif if (ops->vidioc_g_chip_name) return ops->vidioc_g_chip_name(file, fh, arg); if (p->match.addr) @@ -1904,12 +1903,10 @@ static int v4l_dbg_g_chip_name(const struct v4l2_ioctl_ops *ops, break; v4l2_device_for_each_subdev(sd, vfd->v4l2_dev) { if (v4l_dbg_found_match(&p->match, sd, idx++)) { -#ifdef CONFIG_VIDEO_ADV_DEBUG if (sd->ops->core && sd->ops->core->s_register) p->flags |= V4L2_CHIP_FL_WRITABLE; if (sd->ops->core && sd->ops->core->g_register) p->flags |= V4L2_CHIP_FL_READABLE; -#endif strlcpy(p->name, sd->name, sizeof(p->name)); return 0; } @@ -1917,6 +1914,9 @@ static int v4l_dbg_g_chip_name(const struct v4l2_ioctl_ops *ops, break; } return -EINVAL; +#else + return -ENOTTY; +#endif } static int v4l_dqevent(const struct v4l2_ioctl_ops *ops, diff --git a/include/media/v4l2-ioctl.h b/include/media/v4l2-ioctl.h index b273f0e81818..6b917d69e408 100644 --- a/include/media/v4l2-ioctl.h +++ b/include/media/v4l2-ioctl.h @@ -243,12 +243,12 @@ struct v4l2_ioctl_ops { struct v4l2_dbg_register *reg); int (*vidioc_s_register) (struct file *file, void *fh, const struct v4l2_dbg_register *reg); -#endif - int (*vidioc_g_chip_ident) (struct file *file, void *fh, - struct v4l2_dbg_chip_ident *chip); int (*vidioc_g_chip_name) (struct file *file, void *fh, struct v4l2_dbg_chip_name *chip); +#endif + int (*vidioc_g_chip_ident) (struct file *file, void *fh, + struct v4l2_dbg_chip_ident *chip); int (*vidioc_enum_framesizes) (struct file *file, void *fh, struct v4l2_frmsizeenum *fsize); -- cgit v1.2.3 From 3eef25107cab65a1158b11ba373fb9b4fc25b4b8 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 3 Apr 2013 04:08:19 -0300 Subject: [media] v4l2: drop V4L2_CHIP_MATCH_SUBDEV_NAME After using the new VIDIOC_DBG_G_CHIP_NAME ioctl I realized that the matching by name possibility is useless. Just drop it and rename MATCH_SUBDEV_IDX to just MATCH_SUBDEV. The v4l2-dbg utility is much better placed to match by name by just enumerating all bridge and subdev devices until chip_name.name matches. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- .../DocBook/media/v4l/vidioc-dbg-g-chip-ident.xml | 7 +-- .../DocBook/media/v4l/vidioc-dbg-g-chip-name.xml | 18 +------ .../DocBook/media/v4l/vidioc-dbg-g-register.xml | 17 +------ drivers/media/v4l2-core/v4l2-common.c | 3 +- drivers/media/v4l2-core/v4l2-ioctl.c | 55 ++++++++-------------- include/uapi/linux/videodev2.h | 3 +- 6 files changed, 26 insertions(+), 77 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-ident.xml b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-ident.xml index 82e43c6c72b8..921e18550d26 100644 --- a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-ident.xml +++ b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-ident.xml @@ -221,13 +221,8 @@ the values from . Match the nth anciliary AC97 chip. - V4L2_CHIP_MATCH_SUBDEV_NAME + V4L2_CHIP_MATCH_SUBDEV 4 - Match the sub-device by name. Can't be used with this ioctl. - - - V4L2_CHIP_MATCH_SUBDEV_IDX - 5 Match the nth sub-device. Can't be used with this ioctl. diff --git a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml index 5fce8d84288e..fa3bd42ab167 100644 --- a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml +++ b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml @@ -87,16 +87,7 @@ connected to the PCI or USB bus. Non-zero numbers identify specific parts of the bridge chip such as an AC97 register block. When match.type is -V4L2_CHIP_MATCH_SUBDEV_NAME, -match.name contains the name of a sub-device. -For instance -"saa7127 6-0044" will match the saa7127 sub-device -at the given i2c bus. This match type is not very useful for this ioctl -and is here only for consistency. - - - When match.type is -V4L2_CHIP_MATCH_SUBDEV_IDX, +V4L2_CHIP_MATCH_SUBDEV, match.addr selects the nth sub-device. This allows you to enumerate over all sub-devices. @@ -207,13 +198,8 @@ is set, then the driver supports reading registers from the device. If Match the nth anciliary AC97 chip. Can't be used with this ioctl. - V4L2_CHIP_MATCH_SUBDEV_NAME + V4L2_CHIP_MATCH_SUBDEV 4 - Match the sub-device by name. - - - V4L2_CHIP_MATCH_SUBDEV_IDX - 5 Match the nth sub-device. diff --git a/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml b/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml index 3082b4149dbe..db7844f2439f 100644 --- a/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml +++ b/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml @@ -123,15 +123,7 @@ bus address. on the TV card. When match.type is -V4L2_CHIP_MATCH_SUBDEV_NAME, -match.name contains the sub-device name. -For instance -"saa7127 6-0044" will match this specific saa7127 -sub-device. Again with the &VIDIOC-DBG-G-CHIP-NAME; ioctl you can find -out which sub-devices are present. - - When match.type is -V4L2_CHIP_MATCH_SUBDEV_IDX, +V4L2_CHIP_MATCH_SUBDEV, match.addr selects the nth sub-device. @@ -250,13 +242,8 @@ register. Match the nth anciliary AC97 chip. - V4L2_CHIP_MATCH_SUBDEV_NAME + V4L2_CHIP_MATCH_SUBDEV 4 - Match the sub-device by name. - - - V4L2_CHIP_MATCH_SUBDEV_IDX - 5 Match the nth sub-device. diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index f8fac9cefc3c..3fed63f4e026 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -254,8 +254,7 @@ int v4l2_chip_match_i2c_client(struct i2c_client *c, const struct v4l2_dbg_match return len && !strncmp(c->driver->driver.name, match->name, len); case V4L2_CHIP_MATCH_I2C_ADDR: return c->addr == match->addr; - case V4L2_CHIP_MATCH_SUBDEV_IDX: - case V4L2_CHIP_MATCH_SUBDEV_NAME: + case V4L2_CHIP_MATCH_SUBDEV: return 1; default: return 0; diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index feac07e50293..7a96162f544f 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -629,8 +629,7 @@ static void v4l_print_dbg_chip_ident(const void *arg, bool write_only) const struct v4l2_dbg_chip_ident *p = arg; pr_cont("type=%u, ", p->match.type); - if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER || - p->match.type == V4L2_CHIP_MATCH_SUBDEV_NAME) + if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER) pr_cont("name=%.*s, ", (int)sizeof(p->match.name), p->match.name); else @@ -644,8 +643,7 @@ static void v4l_print_dbg_chip_name(const void *arg, bool write_only) const struct v4l2_dbg_chip_name *p = arg; pr_cont("type=%u, ", p->match.type); - if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER || - p->match.type == V4L2_CHIP_MATCH_SUBDEV_NAME) + if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER) pr_cont("name=%.*s, ", (int)sizeof(p->match.name), p->match.name); else @@ -658,8 +656,7 @@ static void v4l_print_dbg_register(const void *arg, bool write_only) const struct v4l2_dbg_register *p = arg; pr_cont("type=%u, ", p->match.type); - if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER || - p->match.type == V4L2_CHIP_MATCH_SUBDEV_NAME) + if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER) pr_cont("name=%.*s, ", (int)sizeof(p->match.name), p->match.name); else @@ -1791,14 +1788,6 @@ static int v4l_log_status(const struct v4l2_ioctl_ops *ops, return ret; } -static bool v4l_dbg_found_match(const struct v4l2_dbg_match *match, - struct v4l2_subdev *sd, int idx) -{ - if (match->type == V4L2_CHIP_MATCH_SUBDEV_IDX) - return match->addr == idx; - return !strcmp(match->name, sd->name); -} - static int v4l_dbg_g_register(const struct v4l2_ioctl_ops *ops, struct file *file, void *fh, void *arg) { @@ -1810,14 +1799,12 @@ static int v4l_dbg_g_register(const struct v4l2_ioctl_ops *ops, if (!capable(CAP_SYS_ADMIN)) return -EPERM; - if (p->match.type == V4L2_CHIP_MATCH_SUBDEV_IDX || - p->match.type == V4L2_CHIP_MATCH_SUBDEV_NAME) { + if (p->match.type == V4L2_CHIP_MATCH_SUBDEV) { if (vfd->v4l2_dev == NULL) return -EINVAL; - v4l2_device_for_each_subdev(sd, vfd->v4l2_dev) { - if (v4l_dbg_found_match(&p->match, sd, idx++)) + v4l2_device_for_each_subdev(sd, vfd->v4l2_dev) + if (p->match.addr == idx++) return v4l2_subdev_call(sd, core, g_register, p); - } return -EINVAL; } if (ops->vidioc_g_register) @@ -1839,14 +1826,12 @@ static int v4l_dbg_s_register(const struct v4l2_ioctl_ops *ops, if (!capable(CAP_SYS_ADMIN)) return -EPERM; - if (p->match.type == V4L2_CHIP_MATCH_SUBDEV_IDX || - p->match.type == V4L2_CHIP_MATCH_SUBDEV_NAME) { + if (p->match.type == V4L2_CHIP_MATCH_SUBDEV) { if (vfd->v4l2_dev == NULL) return -EINVAL; - v4l2_device_for_each_subdev(sd, vfd->v4l2_dev) { - if (v4l_dbg_found_match(&p->match, sd, idx++)) + v4l2_device_for_each_subdev(sd, vfd->v4l2_dev) + if (p->match.addr == idx++) return v4l2_subdev_call(sd, core, s_register, p); - } return -EINVAL; } if (ops->vidioc_s_register) @@ -1864,8 +1849,7 @@ static int v4l_dbg_g_chip_ident(const struct v4l2_ioctl_ops *ops, p->ident = V4L2_IDENT_NONE; p->revision = 0; - if (p->match.type == V4L2_CHIP_MATCH_SUBDEV_NAME || - p->match.type == V4L2_CHIP_MATCH_SUBDEV_IDX) + if (p->match.type == V4L2_CHIP_MATCH_SUBDEV) return -EINVAL; return ops->vidioc_g_chip_ident(file, fh, p); } @@ -1897,19 +1881,18 @@ static int v4l_dbg_g_chip_name(const struct v4l2_ioctl_ops *ops, strlcpy(p->name, "bridge", sizeof(p->name)); return 0; - case V4L2_CHIP_MATCH_SUBDEV_IDX: - case V4L2_CHIP_MATCH_SUBDEV_NAME: + case V4L2_CHIP_MATCH_SUBDEV: if (vfd->v4l2_dev == NULL) break; v4l2_device_for_each_subdev(sd, vfd->v4l2_dev) { - if (v4l_dbg_found_match(&p->match, sd, idx++)) { - if (sd->ops->core && sd->ops->core->s_register) - p->flags |= V4L2_CHIP_FL_WRITABLE; - if (sd->ops->core && sd->ops->core->g_register) - p->flags |= V4L2_CHIP_FL_READABLE; - strlcpy(p->name, sd->name, sizeof(p->name)); - return 0; - } + if (p->match.addr != idx++) + continue; + if (sd->ops->core && sd->ops->core->s_register) + p->flags |= V4L2_CHIP_FL_WRITABLE; + if (sd->ops->core && sd->ops->core->g_register) + p->flags |= V4L2_CHIP_FL_READABLE; + strlcpy(p->name, sd->name, sizeof(p->name)); + return 0; } break; } diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index e9c49c5e6416..4c941c103c44 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -1812,8 +1812,7 @@ struct v4l2_event_subscription { #define V4L2_CHIP_MATCH_I2C_DRIVER 1 /* Match against I2C driver name */ #define V4L2_CHIP_MATCH_I2C_ADDR 2 /* Match against I2C 7-bit address */ #define V4L2_CHIP_MATCH_AC97 3 /* Match against anciliary AC97 chip */ -#define V4L2_CHIP_MATCH_SUBDEV_NAME 4 /* Match against subdev name */ -#define V4L2_CHIP_MATCH_SUBDEV_IDX 5 /* Match against subdev index */ +#define V4L2_CHIP_MATCH_SUBDEV 4 /* Match against subdev index */ struct v4l2_dbg_match { __u32 type; /* Match type */ -- cgit v1.2.3 From 96b03d2a3078d5e95a8b106634faa7cea88ebe5e Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Sat, 6 Apr 2013 06:16:58 -0300 Subject: [media] v4l2: rename VIDIOC_DBG_G_CHIP_NAME to _CHIP_INFO This ioctl will be extended to return more information than just the name. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/media/v4l/compat.xml | 2 +- Documentation/DocBook/media/v4l/v4l2.xml | 4 +- .../DocBook/media/v4l/vidioc-dbg-g-chip-info.xml | 223 +++++++++++++++++++++ .../DocBook/media/v4l/vidioc-dbg-g-chip-name.xml | 223 --------------------- .../DocBook/media/v4l/vidioc-dbg-g-register.xml | 10 +- drivers/media/usb/em28xx/em28xx-video.c | 8 +- drivers/media/v4l2-core/v4l2-dev.c | 2 +- drivers/media/v4l2-core/v4l2-ioctl.c | 14 +- include/media/v4l2-ioctl.h | 4 +- include/uapi/linux/videodev2.h | 8 +- 10 files changed, 249 insertions(+), 249 deletions(-) create mode 100644 Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-info.xml delete mode 100644 Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml (limited to 'Documentation') diff --git a/Documentation/DocBook/media/v4l/compat.xml b/Documentation/DocBook/media/v4l/compat.xml index e44161ffdd07..f43542ae2981 100644 --- a/Documentation/DocBook/media/v4l/compat.xml +++ b/Documentation/DocBook/media/v4l/compat.xml @@ -2507,7 +2507,7 @@ that used it. It was originally scheduled for removal in 2.6.35. - Added new debugging ioctl &VIDIOC-DBG-G-CHIP-NAME;. + Added new debugging ioctl &VIDIOC-DBG-G-CHIP-INFO;. diff --git a/Documentation/DocBook/media/v4l/v4l2.xml b/Documentation/DocBook/media/v4l/v4l2.xml index c1f334084213..bfc93cdcf696 100644 --- a/Documentation/DocBook/media/v4l/v4l2.xml +++ b/Documentation/DocBook/media/v4l/v4l2.xml @@ -147,7 +147,7 @@ applications. --> Remove obsolete and unused DV_PRESET ioctls: VIDIOC_G_DV_PRESET, VIDIOC_S_DV_PRESET, VIDIOC_QUERY_DV_PRESET and VIDIOC_ENUM_DV_PRESET. Remove the related v4l2_input/output capability - flags V4L2_IN_CAP_PRESETS and V4L2_OUT_CAP_PRESETS. Added VIDIOC_DBG_G_CHIP_NAME. + flags V4L2_IN_CAP_PRESETS and V4L2_OUT_CAP_PRESETS. Added VIDIOC_DBG_G_CHIP_INFO. @@ -548,7 +548,7 @@ and discussions on the V4L mailing list. &sub-create-bufs; &sub-cropcap; &sub-dbg-g-chip-ident; - &sub-dbg-g-chip-name; + &sub-dbg-g-chip-info; &sub-dbg-g-register; &sub-decoder-cmd; &sub-dqevent; diff --git a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-info.xml b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-info.xml new file mode 100644 index 000000000000..e1cece6c5de1 --- /dev/null +++ b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-info.xml @@ -0,0 +1,223 @@ + + + ioctl VIDIOC_DBG_G_CHIP_INFO + &manvol; + + + + VIDIOC_DBG_G_CHIP_INFO + Identify the chips on a TV card + + + + + + int ioctl + int fd + int request + struct v4l2_dbg_chip_info +*argp + + + + + + Arguments + + + + fd + + &fd; + + + + request + + VIDIOC_DBG_G_CHIP_INFO + + + + argp + + + + + + + + + Description + + + Experimental + + This is an experimental interface and may change in +the future. + + + For driver debugging purposes this ioctl allows test +applications to query the driver about the chips present on the TV +card. Regular applications must not use it. When you found a chip +specific bug, please contact the linux-media mailing list (&v4l-ml;) +so it can be fixed. + + Additionally the Linux kernel must be compiled with the +CONFIG_VIDEO_ADV_DEBUG option to enable this ioctl. + + To query the driver applications must initialize the +match.type and +match.addr or match.name +fields of a &v4l2-dbg-chip-info; +and call VIDIOC_DBG_G_CHIP_INFO with a pointer to +this structure. On success the driver stores information about the +selected chip in the name and +flags fields. On failure the structure +remains unchanged. + + When match.type is +V4L2_CHIP_MATCH_BRIDGE, +match.addr selects the nth bridge 'chip' +on the TV card. You can enumerate all chips by starting at zero and +incrementing match.addr by one until +VIDIOC_DBG_G_CHIP_INFO fails with an &EINVAL;. +The number zero always selects the bridge chip itself, ⪚ the chip +connected to the PCI or USB bus. Non-zero numbers identify specific +parts of the bridge chip such as an AC97 register block. + + When match.type is +V4L2_CHIP_MATCH_SUBDEV, +match.addr selects the nth sub-device. This +allows you to enumerate over all sub-devices. + + On success, the name field will +contain a chip name and the flags field will +contain V4L2_CHIP_FL_READABLE if the driver supports +reading registers from the device or V4L2_CHIP_FL_WRITABLE +if the driver supports writing registers to the device. + + We recommended the v4l2-dbg +utility over calling this ioctl directly. It is available from the +LinuxTV v4l-dvb repository; see http://linuxtv.org/repo/ for +access instructions. + + +
+ struct <structname>v4l2_dbg_match</structname> + + &cs-ustr; + + + __u32 + type + See for a list of +possible types. + + + union + (anonymous) + + + + __u32 + addr + Match a chip by this number, interpreted according +to the type field. + + + + char + name[32] + Match a chip by this name, interpreted according +to the type field. + + + +
+ + + struct <structname>v4l2_dbg_chip_info</structname> + + &cs-str; + + + struct v4l2_dbg_match + match + How to match the chip, see . + + + char + name[32] + The name of the chip. + + + __u32 + flags + Set by the driver. If V4L2_CHIP_FL_READABLE +is set, then the driver supports reading registers from the device. If +V4L2_CHIP_FL_WRITABLE is set, then it supports writing registers. + + + __u32 + reserved[8] + Reserved fields, both application and driver must set these to 0. + + + +
+ + + + Chip Match Types + + &cs-def; + + + V4L2_CHIP_MATCH_BRIDGE + 0 + Match the nth chip on the card, zero for the + bridge chip. Does not match sub-devices. + + + V4L2_CHIP_MATCH_I2C_DRIVER + 1 + Match an &i2c; chip by its driver name. Can't be used with this ioctl. + + + V4L2_CHIP_MATCH_I2C_ADDR + 2 + Match a chip by its 7 bit &i2c; bus address. Can't be used with this ioctl. + + + V4L2_CHIP_MATCH_AC97 + 3 + Match the nth anciliary AC97 chip. Can't be used with this ioctl. + + + V4L2_CHIP_MATCH_SUBDEV + 4 + Match the nth sub-device. + + + +
+ + + + &return-value; + + + + EINVAL + + The match_type is invalid or +no device could be matched. + + + + + diff --git a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml b/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml deleted file mode 100644 index fa3bd42ab167..000000000000 --- a/Documentation/DocBook/media/v4l/vidioc-dbg-g-chip-name.xml +++ /dev/null @@ -1,223 +0,0 @@ - - - ioctl VIDIOC_DBG_G_CHIP_NAME - &manvol; - - - - VIDIOC_DBG_G_CHIP_NAME - Identify the chips on a TV card - - - - - - int ioctl - int fd - int request - struct v4l2_dbg_chip_name -*argp - - - - - - Arguments - - - - fd - - &fd; - - - - request - - VIDIOC_DBG_G_CHIP_NAME - - - - argp - - - - - - - - - Description - - - Experimental - - This is an experimental interface and may change in -the future. - - - For driver debugging purposes this ioctl allows test -applications to query the driver about the chips present on the TV -card. Regular applications must not use it. When you found a chip -specific bug, please contact the linux-media mailing list (&v4l-ml;) -so it can be fixed. - - Additionally the Linux kernel must be compiled with the -CONFIG_VIDEO_ADV_DEBUG option to enable this ioctl. - - To query the driver applications must initialize the -match.type and -match.addr or match.name -fields of a &v4l2-dbg-chip-name; -and call VIDIOC_DBG_G_CHIP_NAME with a pointer to -this structure. On success the driver stores information about the -selected chip in the name and -flags fields. On failure the structure -remains unchanged. - - When match.type is -V4L2_CHIP_MATCH_BRIDGE, -match.addr selects the nth bridge 'chip' -on the TV card. You can enumerate all chips by starting at zero and -incrementing match.addr by one until -VIDIOC_DBG_G_CHIP_NAME fails with an &EINVAL;. -The number zero always selects the bridge chip itself, ⪚ the chip -connected to the PCI or USB bus. Non-zero numbers identify specific -parts of the bridge chip such as an AC97 register block. - - When match.type is -V4L2_CHIP_MATCH_SUBDEV, -match.addr selects the nth sub-device. This -allows you to enumerate over all sub-devices. - - On success, the name field will -contain a chip name and the flags field will -contain V4L2_CHIP_FL_READABLE if the driver supports -reading registers from the device or V4L2_CHIP_FL_WRITABLE -if the driver supports writing registers to the device. - - We recommended the v4l2-dbg -utility over calling this ioctl directly. It is available from the -LinuxTV v4l-dvb repository; see http://linuxtv.org/repo/ for -access instructions. - - - - struct <structname>v4l2_dbg_match</structname> - - &cs-ustr; - - - __u32 - type - See for a list of -possible types. - - - union - (anonymous) - - - - __u32 - addr - Match a chip by this number, interpreted according -to the type field. - - - - char - name[32] - Match a chip by this name, interpreted according -to the type field. - - - -
- - - struct <structname>v4l2_dbg_chip_name</structname> - - &cs-str; - - - struct v4l2_dbg_match - match - How to match the chip, see . - - - char - name[32] - The name of the chip. - - - __u32 - flags - Set by the driver. If V4L2_CHIP_FL_READABLE -is set, then the driver supports reading registers from the device. If -V4L2_CHIP_FL_WRITABLE is set, then it supports writing registers. - - - __u32 - reserved[8] - Reserved fields, both application and driver must set these to 0. - - - -
- - - - Chip Match Types - - &cs-def; - - - V4L2_CHIP_MATCH_BRIDGE - 0 - Match the nth chip on the card, zero for the - bridge chip. Does not match sub-devices. - - - V4L2_CHIP_MATCH_I2C_DRIVER - 1 - Match an &i2c; chip by its driver name. Can't be used with this ioctl. - - - V4L2_CHIP_MATCH_I2C_ADDR - 2 - Match a chip by its 7 bit &i2c; bus address. Can't be used with this ioctl. - - - V4L2_CHIP_MATCH_AC97 - 3 - Match the nth anciliary AC97 chip. Can't be used with this ioctl. - - - V4L2_CHIP_MATCH_SUBDEV - 4 - Match the nth sub-device. - - - -
-
- - - &return-value; - - - - EINVAL - - The match_type is invalid or -no device could be matched. - - - - -
diff --git a/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml b/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml index db7844f2439f..d13bac9e2445 100644 --- a/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml +++ b/Documentation/DocBook/media/v4l/vidioc-dbg-g-register.xml @@ -99,7 +99,7 @@ unchanged. match.addr selects the nth non-sub-device chip on the TV card. The number zero always selects the host chip, ⪚ the chip connected to the PCI or USB bus. You can find out which chips are -present with the &VIDIOC-DBG-G-CHIP-NAME; ioctl. +present with the &VIDIOC-DBG-G-CHIP-INFO; ioctl. When match.type is V4L2_CHIP_MATCH_I2C_DRIVER, @@ -109,7 +109,7 @@ For instance supported by the saa7127 driver, regardless of its &i2c; bus address. When multiple chips supported by the same driver are present, the effect of these ioctls is undefined. Again with the -&VIDIOC-DBG-G-CHIP-NAME; ioctl you can find out which &i2c; chips are +&VIDIOC-DBG-G-CHIP-INFO; ioctl you can find out which &i2c; chips are present. When match.type is @@ -131,14 +131,14 @@ on the TV card. Due to a flaw in the Linux &i2c; bus driver these ioctls may return successfully without actually reading or writing a register. To -catch the most likely failure we recommend a &VIDIOC-DBG-G-CHIP-NAME; +catch the most likely failure we recommend a &VIDIOC-DBG-G-CHIP-INFO; call confirming the presence of the selected &i2c; chip. These ioctls are optional, not all drivers may support them. However when a driver supports these ioctls it must also support -&VIDIOC-DBG-G-CHIP-NAME;. Conversely it may support -VIDIOC_DBG_G_CHIP_NAME but not these ioctls. +&VIDIOC-DBG-G-CHIP-INFO;. Conversely it may support +VIDIOC_DBG_G_CHIP_INFO but not these ioctls. VIDIOC_DBG_G_REGISTER and VIDIOC_DBG_S_REGISTER were introduced in Linux diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c index 39951f5731e1..c27c1f671396 100644 --- a/drivers/media/usb/em28xx/em28xx-video.c +++ b/drivers/media/usb/em28xx/em28xx-video.c @@ -1332,8 +1332,8 @@ static int vidioc_g_chip_ident(struct file *file, void *priv, } #ifdef CONFIG_VIDEO_ADV_DEBUG -static int vidioc_g_chip_name(struct file *file, void *priv, - struct v4l2_dbg_chip_name *chip) +static int vidioc_g_chip_info(struct file *file, void *priv, + struct v4l2_dbg_chip_info *chip) { struct em28xx_fh *fh = priv; struct em28xx *dev = fh->dev; @@ -1797,7 +1797,7 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = { .vidioc_unsubscribe_event = v4l2_event_unsubscribe, .vidioc_g_chip_ident = vidioc_g_chip_ident, #ifdef CONFIG_VIDEO_ADV_DEBUG - .vidioc_g_chip_name = vidioc_g_chip_name, + .vidioc_g_chip_info = vidioc_g_chip_info, .vidioc_g_register = vidioc_g_register, .vidioc_s_register = vidioc_s_register, #endif @@ -1827,7 +1827,7 @@ static const struct v4l2_ioctl_ops radio_ioctl_ops = { .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, .vidioc_g_chip_ident = vidioc_g_chip_ident, - .vidioc_g_chip_name = vidioc_g_chip_name, + .vidioc_g_chip_info = vidioc_g_chip_info, #ifdef CONFIG_VIDEO_ADV_DEBUG .vidioc_g_register = vidioc_g_register, .vidioc_s_register = vidioc_s_register, diff --git a/drivers/media/v4l2-core/v4l2-dev.c b/drivers/media/v4l2-core/v4l2-dev.c index 1c3b43cf773d..5923c5dfacd5 100644 --- a/drivers/media/v4l2-core/v4l2-dev.c +++ b/drivers/media/v4l2-core/v4l2-dev.c @@ -592,7 +592,7 @@ static void determine_valid_ioctls(struct video_device *vdev) SET_VALID_IOCTL(ops, VIDIOC_S_FREQUENCY, vidioc_s_frequency); SET_VALID_IOCTL(ops, VIDIOC_LOG_STATUS, vidioc_log_status); #ifdef CONFIG_VIDEO_ADV_DEBUG - set_bit(_IOC_NR(VIDIOC_DBG_G_CHIP_NAME), valid_ioctls); + set_bit(_IOC_NR(VIDIOC_DBG_G_CHIP_INFO), valid_ioctls); set_bit(_IOC_NR(VIDIOC_DBG_G_REGISTER), valid_ioctls); set_bit(_IOC_NR(VIDIOC_DBG_S_REGISTER), valid_ioctls); #endif diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index c48d0acd8bb9..f81bda1a48ec 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -638,9 +638,9 @@ static void v4l_print_dbg_chip_ident(const void *arg, bool write_only) p->ident, p->revision); } -static void v4l_print_dbg_chip_name(const void *arg, bool write_only) +static void v4l_print_dbg_chip_info(const void *arg, bool write_only) { - const struct v4l2_dbg_chip_name *p = arg; + const struct v4l2_dbg_chip_info *p = arg; pr_cont("type=%u, ", p->match.type); if (p->match.type == V4L2_CHIP_MATCH_I2C_DRIVER) @@ -1854,12 +1854,12 @@ static int v4l_dbg_g_chip_ident(const struct v4l2_ioctl_ops *ops, return ops->vidioc_g_chip_ident(file, fh, p); } -static int v4l_dbg_g_chip_name(const struct v4l2_ioctl_ops *ops, +static int v4l_dbg_g_chip_info(const struct v4l2_ioctl_ops *ops, struct file *file, void *fh, void *arg) { #ifdef CONFIG_VIDEO_ADV_DEBUG struct video_device *vfd = video_devdata(file); - struct v4l2_dbg_chip_name *p = arg; + struct v4l2_dbg_chip_info *p = arg; struct v4l2_subdev *sd; int idx = 0; @@ -1875,8 +1875,8 @@ static int v4l_dbg_g_chip_name(const struct v4l2_ioctl_ops *ops, strlcpy(p->name, vfd->parent->driver->name, sizeof(p->name)); else strlcpy(p->name, "bridge", sizeof(p->name)); - if (ops->vidioc_g_chip_name) - return ops->vidioc_g_chip_name(file, fh, arg); + if (ops->vidioc_g_chip_info) + return ops->vidioc_g_chip_info(file, fh, arg); if (p->match.addr) return -EINVAL; return 0; @@ -2116,7 +2116,7 @@ static struct v4l2_ioctl_info v4l2_ioctls[] = { IOCTL_INFO_STD(VIDIOC_QUERY_DV_TIMINGS, vidioc_query_dv_timings, v4l_print_dv_timings, 0), IOCTL_INFO_STD(VIDIOC_DV_TIMINGS_CAP, vidioc_dv_timings_cap, v4l_print_dv_timings_cap, INFO_FL_CLEAR(v4l2_dv_timings_cap, type)), IOCTL_INFO_FNC(VIDIOC_ENUM_FREQ_BANDS, v4l_enum_freq_bands, v4l_print_freq_band, 0), - IOCTL_INFO_FNC(VIDIOC_DBG_G_CHIP_NAME, v4l_dbg_g_chip_name, v4l_print_dbg_chip_name, INFO_FL_CLEAR(v4l2_dbg_chip_name, match)), + IOCTL_INFO_FNC(VIDIOC_DBG_G_CHIP_INFO, v4l_dbg_g_chip_info, v4l_print_dbg_chip_info, INFO_FL_CLEAR(v4l2_dbg_chip_info, match)), }; #define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls) diff --git a/include/media/v4l2-ioctl.h b/include/media/v4l2-ioctl.h index 6b917d69e408..931652f0e2af 100644 --- a/include/media/v4l2-ioctl.h +++ b/include/media/v4l2-ioctl.h @@ -244,8 +244,8 @@ struct v4l2_ioctl_ops { int (*vidioc_s_register) (struct file *file, void *fh, const struct v4l2_dbg_register *reg); - int (*vidioc_g_chip_name) (struct file *file, void *fh, - struct v4l2_dbg_chip_name *chip); + int (*vidioc_g_chip_info) (struct file *file, void *fh, + struct v4l2_dbg_chip_info *chip); #endif int (*vidioc_g_chip_ident) (struct file *file, void *fh, struct v4l2_dbg_chip_ident *chip); diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 4c941c103c44..be43b4659527 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -1839,8 +1839,8 @@ struct v4l2_dbg_chip_ident { #define V4L2_CHIP_FL_READABLE (1 << 0) #define V4L2_CHIP_FL_WRITABLE (1 << 1) -/* VIDIOC_DBG_G_CHIP_NAME */ -struct v4l2_dbg_chip_name { +/* VIDIOC_DBG_G_CHIP_INFO */ +struct v4l2_dbg_chip_info { struct v4l2_dbg_match match; char name[32]; __u32 flags; @@ -1938,7 +1938,7 @@ struct v4l2_create_buffers { /* Experimental, meant for debugging, testing and internal use. Never use this ioctl in applications! - Note: this ioctl is deprecated in favor of VIDIOC_DBG_G_CHIP_NAME and + Note: this ioctl is deprecated in favor of VIDIOC_DBG_G_CHIP_INFO and will go away in the future. */ #define VIDIOC_DBG_G_CHIP_IDENT _IOWR('V', 81, struct v4l2_dbg_chip_ident) @@ -1976,7 +1976,7 @@ struct v4l2_create_buffers { /* Experimental, meant for debugging, testing and internal use. Never use these in applications! */ -#define VIDIOC_DBG_G_CHIP_NAME _IOWR('V', 102, struct v4l2_dbg_chip_name) +#define VIDIOC_DBG_G_CHIP_INFO _IOWR('V', 102, struct v4l2_dbg_chip_info) /* Reminder: when adding new ioctls please add support for them to drivers/media/video/v4l2-compat-ioctl32.c as well! */ -- cgit v1.2.3 From 82cd0b278fddc1c0bc7e187ff82fd0e273520233 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Wed, 17 Apr 2013 06:05:18 -0300 Subject: Revert "[media] v4l2: Add a V4L2 driver for SI476X MFD" As requested by Andrey Smirnov , revert this patch. This reverts commit 30bac9110455402fa8888740c6819dd3daa2666f. Conflicts: drivers/media/radio/Kconfig drivers/media/radio/radio-si476x.c Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/si476x.txt | 187 ---- drivers/media/radio/Kconfig | 17 - drivers/media/radio/Makefile | 1 - drivers/media/radio/radio-si476x.c | 1599 ---------------------------------- include/media/si476x.h | 426 --------- 5 files changed, 2230 deletions(-) delete mode 100644 Documentation/video4linux/si476x.txt delete mode 100644 drivers/media/radio/radio-si476x.c delete mode 100644 include/media/si476x.h (limited to 'Documentation') diff --git a/Documentation/video4linux/si476x.txt b/Documentation/video4linux/si476x.txt deleted file mode 100644 index d1a08db2cbd9..000000000000 --- a/Documentation/video4linux/si476x.txt +++ /dev/null @@ -1,187 +0,0 @@ -SI476x Driver Readme ------------------------------------------------- - Copyright (C) 2013 Andrey Smirnov - -TODO for the driver ------------------------------- - -- According to the SiLabs' datasheet it is possible to update the - firmware of the radio chip in the run-time, thus bringing it to the - most recent version. Unfortunately I couldn't find any mentioning of - the said firmware update for the old chips that I tested the driver - against, so for chips like that the driver only exposes the old - functionality. - - -Parameters exposed over debugfs -------------------------------- -SI476x allow user to get multiple characteristics that can be very -useful for EoL testing/RF performance estimation, parameters that have -very little to do with V4L2 subsystem. Such parameters are exposed via -debugfs and can be accessed via regular file I/O operations. - -The drivers exposes following files: - -* /sys/kernel/debug//acf - This file contains ACF(Automatically Controlled Features) status - information. The contents of the file is binary data of the - following layout: - - Offset | Name | Description - ==================================================================== - 0x00 | blend_int | Flag, set when stereo separation has - | | crossed below the blend threshold - -------------------------------------------------------------------- - 0x01 | hblend_int | Flag, set when HiBlend cutoff - | | frequency is lower than threshold - -------------------------------------------------------------------- - 0x02 | hicut_int | Flag, set when HiCut cutoff - | | frequency is lower than threshold - -------------------------------------------------------------------- - 0x03 | chbw_int | Flag, set when channel filter - | | bandwidth is less than threshold - -------------------------------------------------------------------- - 0x04 | softmute_int | Flag indicating that softmute - | | attenuation has increased above - | | softmute threshold - -------------------------------------------------------------------- - 0x05 | smute | 0 - Audio is not soft muted - | | 1 - Audio is soft muted - -------------------------------------------------------------------- - 0x06 | smattn | Soft mute attenuation level in dB - -------------------------------------------------------------------- - 0x07 | chbw | Channel filter bandwidth in kHz - -------------------------------------------------------------------- - 0x08 | hicut | HiCut cutoff frequency in units of - | | 100Hz - -------------------------------------------------------------------- - 0x09 | hiblend | HiBlend cutoff frequency in units - | | of 100 Hz - -------------------------------------------------------------------- - 0x10 | pilot | 0 - Stereo pilot is not present - | | 1 - Stereo pilot is present - -------------------------------------------------------------------- - 0x11 | stblend | Stereo blend in % - -------------------------------------------------------------------- - - -* /sys/kernel/debug//rds_blckcnt - This file contains statistics about RDS receptions. It's binary data - has the following layout: - - Offset | Name | Description - ==================================================================== - 0x00 | expected | Number of expected RDS blocks - -------------------------------------------------------------------- - 0x02 | received | Number of received RDS blocks - -------------------------------------------------------------------- - 0x04 | uncorrectable | Number of uncorrectable RDS blocks - -------------------------------------------------------------------- - -* /sys/kernel/debug//agc - This file contains information about parameters pertaining to - AGC(Automatic Gain Control) - - The layout is: - Offset | Name | Description - ==================================================================== - 0x00 | mxhi | 0 - FM Mixer PD high threshold is - | | not tripped - | | 1 - FM Mixer PD high threshold is - | | tripped - -------------------------------------------------------------------- - 0x01 | mxlo | ditto for FM Mixer PD low - -------------------------------------------------------------------- - 0x02 | lnahi | ditto for FM LNA PD high - -------------------------------------------------------------------- - 0x03 | lnalo | ditto for FM LNA PD low - -------------------------------------------------------------------- - 0x04 | fmagc1 | FMAGC1 attenuator resistance - | | (see datasheet for more detail) - -------------------------------------------------------------------- - 0x05 | fmagc2 | ditto for FMAGC2 - -------------------------------------------------------------------- - 0x06 | pgagain | PGA gain in dB - -------------------------------------------------------------------- - 0x07 | fmwblang | FM/WB LNA Gain in dB - -------------------------------------------------------------------- - -* /sys/kernel/debug//rsq - This file contains information about parameters pertaining to - RSQ(Received Signal Quality) - - The layout is: - Offset | Name | Description - ==================================================================== - 0x00 | multhint | 0 - multipath value has not crossed - | | the Multipath high threshold - | | 1 - multipath value has crossed - | | the Multipath high threshold - -------------------------------------------------------------------- - 0x01 | multlint | ditto for Multipath low threshold - -------------------------------------------------------------------- - 0x02 | snrhint | 0 - received signal's SNR has not - | | crossed high threshold - | | 1 - received signal's SNR has - | | crossed high threshold - -------------------------------------------------------------------- - 0x03 | snrlint | ditto for low threshold - -------------------------------------------------------------------- - 0x04 | rssihint | ditto for RSSI high threshold - -------------------------------------------------------------------- - 0x05 | rssilint | ditto for RSSI low threshold - -------------------------------------------------------------------- - 0x06 | bltf | Flag indicating if seek command - | | reached/wrapped seek band limit - -------------------------------------------------------------------- - 0x07 | snr_ready | Indicates that SNR metrics is ready - -------------------------------------------------------------------- - 0x08 | rssiready | ditto for RSSI metrics - -------------------------------------------------------------------- - 0x09 | injside | 0 - Low-side injection is being used - | | 1 - High-side injection is used - -------------------------------------------------------------------- - 0x10 | afcrl | Flag indicating if AFC rails - -------------------------------------------------------------------- - 0x11 | valid | Flag indicating if channel is valid - -------------------------------------------------------------------- - 0x12 | readfreq | Current tuned frequency - -------------------------------------------------------------------- - 0x14 | freqoff | Singed frequency offset in units of - | | 2ppm - -------------------------------------------------------------------- - 0x15 | rssi | Signed value of RSSI in dBuV - -------------------------------------------------------------------- - 0x16 | snr | Signed RF SNR in dB - -------------------------------------------------------------------- - 0x17 | issi | Signed Image Strength Signal - | | indicator - -------------------------------------------------------------------- - 0x18 | lassi | Signed Low side adjacent Channel - | | Strength indicator - -------------------------------------------------------------------- - 0x19 | hassi | ditto fpr High side - -------------------------------------------------------------------- - 0x20 | mult | Multipath indicator - -------------------------------------------------------------------- - 0x21 | dev | Frequency deviation - -------------------------------------------------------------------- - 0x24 | assi | Adjascent channel SSI - -------------------------------------------------------------------- - 0x25 | usn | Ultrasonic noise indicator - -------------------------------------------------------------------- - 0x26 | pilotdev | Pilot deviation in units of 100 Hz - -------------------------------------------------------------------- - 0x27 | rdsdev | ditto for RDS - -------------------------------------------------------------------- - 0x28 | assidev | ditto for ASSI - -------------------------------------------------------------------- - 0x29 | strongdev | Frequency deviation - -------------------------------------------------------------------- - 0x30 | rdspi | RDS PI code - -------------------------------------------------------------------- - -* /sys/kernel/debug//rsq_primary - This file contains information about parameters pertaining to - RSQ(Received Signal Quality) for primary tuner only. Layout is as - the one above. diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index fef427e386c1..24e64a09884c 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -18,23 +18,6 @@ config RADIO_SI470X source "drivers/media/radio/si470x/Kconfig" -config RADIO_SI476X - tristate "Silicon Laboratories Si476x I2C FM Radio" - depends on I2C && VIDEO_V4L2 && SND && SND_SOC - select MFD_CORE - select MFD_SI476X_CORE - select SND_SOC_SI476X - ---help--- - Choose Y here if you have this FM radio chip. - - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux 2 API. Information on - this API and pointers to "v4l2" programs may be found at - . - - To compile this driver as a module, choose M here: the - module will be called radio-si476x. - config USB_MR800 tristate "AverMedia MR 800 USB FM radio support" depends on USB && VIDEO_V4L2 diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 0dcdb320cfc7..303eaebdb85a 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -19,7 +19,6 @@ obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o obj-$(CONFIG_RADIO_TRUST) += radio-trust.o obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o -obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o obj-$(CONFIG_USB_DSBR) += dsbr100.o obj-$(CONFIG_RADIO_SI470X) += si470x/ diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c deleted file mode 100644 index 9430c6a29937..000000000000 --- a/drivers/media/radio/radio-si476x.c +++ /dev/null @@ -1,1599 +0,0 @@ -/* - * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips - * - * Copyright (C) 2012 Innovative Converged Devices(ICD) - * Copyright (C) 2013 Andrey Smirnov - * - * Author: Andrey Smirnov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define FM_FREQ_RANGE_LOW 64000000 -#define FM_FREQ_RANGE_HIGH 108000000 - -#define AM_FREQ_RANGE_LOW 520000 -#define AM_FREQ_RANGE_HIGH 30000000 - -#define PWRLINEFLTR (1 << 8) - -#define FREQ_MUL (10000000 / 625) - -#define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0b10000000 & (status)) - -#define DRIVER_NAME "si476x-radio" -#define DRIVER_CARD "SI476x AM/FM Receiver" - -enum si476x_freq_bands { - SI476X_BAND_FM, - SI476X_BAND_AM, -}; - -static const struct v4l2_frequency_band si476x_bands[] = { - [SI476X_BAND_FM] = { - .type = V4L2_TUNER_RADIO, - .index = SI476X_BAND_FM, - .capability = V4L2_TUNER_CAP_LOW - | V4L2_TUNER_CAP_STEREO - | V4L2_TUNER_CAP_RDS - | V4L2_TUNER_CAP_RDS_BLOCK_IO - | V4L2_TUNER_CAP_FREQ_BANDS, - .rangelow = 64 * FREQ_MUL, - .rangehigh = 108 * FREQ_MUL, - .modulation = V4L2_BAND_MODULATION_FM, - }, - [SI476X_BAND_AM] = { - .type = V4L2_TUNER_RADIO, - .index = SI476X_BAND_AM, - .capability = V4L2_TUNER_CAP_LOW - | V4L2_TUNER_CAP_FREQ_BANDS, - .rangelow = 0.52 * FREQ_MUL, - .rangehigh = 30 * FREQ_MUL, - .modulation = V4L2_BAND_MODULATION_AM, - }, -}; - -static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band) -{ - return freq >= si476x_bands[band].rangelow && - freq <= si476x_bands[band].rangehigh; -} - -static inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high, - int band) -{ - return low >= si476x_bands[band].rangelow && - high <= si476x_bands[band].rangehigh; -} - -static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl); -static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); - -enum phase_diversity_modes_idx { - SI476X_IDX_PHDIV_DISABLED, - SI476X_IDX_PHDIV_PRIMARY_COMBINING, - SI476X_IDX_PHDIV_PRIMARY_ANTENNA, - SI476X_IDX_PHDIV_SECONDARY_ANTENNA, - SI476X_IDX_PHDIV_SECONDARY_COMBINING, -}; - -static const char * const phase_diversity_modes[] = { - [SI476X_IDX_PHDIV_DISABLED] = "Disabled", - [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary", - [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna", - [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna", - [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary", -}; - -static inline enum phase_diversity_modes_idx -si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode) -{ - switch (mode) { - default: /* FALLTHROUGH */ - case SI476X_PHDIV_DISABLED: - return SI476X_IDX_PHDIV_DISABLED; - case SI476X_PHDIV_PRIMARY_COMBINING: - return SI476X_IDX_PHDIV_PRIMARY_COMBINING; - case SI476X_PHDIV_PRIMARY_ANTENNA: - return SI476X_IDX_PHDIV_PRIMARY_ANTENNA; - case SI476X_PHDIV_SECONDARY_ANTENNA: - return SI476X_IDX_PHDIV_SECONDARY_ANTENNA; - case SI476X_PHDIV_SECONDARY_COMBINING: - return SI476X_IDX_PHDIV_SECONDARY_COMBINING; - } -} - -static inline enum si476x_phase_diversity_mode -si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx) -{ - static const int idx_to_value[] = { - [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED, - [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING, - [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA, - [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA, - [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING, - }; - - return idx_to_value[idx]; -} - -static const struct v4l2_ctrl_ops si476x_ctrl_ops = { - .g_volatile_ctrl = si476x_radio_g_volatile_ctrl, - .s_ctrl = si476x_radio_s_ctrl, -}; - - -enum si476x_ctrl_idx { - SI476X_IDX_RSSI_THRESHOLD, - SI476X_IDX_SNR_THRESHOLD, - SI476X_IDX_MAX_TUNE_ERROR, - SI476X_IDX_HARMONICS_COUNT, - SI476X_IDX_DIVERSITY_MODE, - SI476X_IDX_INTERCHIP_LINK, -}; -static struct v4l2_ctrl_config si476x_ctrls[] = { - - /** - * SI476X during its station seeking(or tuning) process uses several - * parameters to detrmine if "the station" is valid: - * - * - Signal's SNR(in dBuV) must be lower than - * #V4L2_CID_SI476X_SNR_THRESHOLD - * - Signal's RSSI(in dBuV) must be greater than - * #V4L2_CID_SI476X_RSSI_THRESHOLD - * - Signal's frequency deviation(in units of 2ppm) must not be - * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR - */ - [SI476X_IDX_RSSI_THRESHOLD] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_RSSI_THRESHOLD, - .name = "Valid RSSI Threshold", - .type = V4L2_CTRL_TYPE_INTEGER, - .min = -128, - .max = 127, - .step = 1, - }, - [SI476X_IDX_SNR_THRESHOLD] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_SNR_THRESHOLD, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Valid SNR Threshold", - .min = -128, - .max = 127, - .step = 1, - }, - [SI476X_IDX_MAX_TUNE_ERROR] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_MAX_TUNE_ERROR, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Max Tune Errors", - .min = 0, - .max = 126 * 2, - .step = 2, - }, - - /** - * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics - * built-in power-line noise supression filter is to reject - * during AM-mode operation. - */ - [SI476X_IDX_HARMONICS_COUNT] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_HARMONICS_COUNT, - .type = V4L2_CTRL_TYPE_INTEGER, - - .name = "Count of Harmonics to Reject", - .min = 0, - .max = 20, - .step = 1, - }, - - /** - * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which - * two tuners working in diversity mode are to work in. - * - * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled - * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is - * on, primary tuner's antenna is the main one. - * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is - * off, primary tuner's antenna is the main one. - * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is - * off, secondary tuner's antenna is the main one. - * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is - * on, secondary tuner's antenna is the main one. - */ - [SI476X_IDX_DIVERSITY_MODE] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_DIVERSITY_MODE, - .type = V4L2_CTRL_TYPE_MENU, - .name = "Phase Diversity Mode", - .qmenu = phase_diversity_modes, - .min = 0, - .max = ARRAY_SIZE(phase_diversity_modes) - 1, - }, - - /** - * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in - * diversity mode indicator. Allows user to determine if two - * chips working in diversity mode have established a link - * between each other and if the system as a whole uses - * signals from both antennas to receive FM radio. - */ - [SI476X_IDX_INTERCHIP_LINK] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_INTERCHIP_LINK, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, - .name = "Inter-Chip Link", - .min = 0, - .max = 1, - .step = 1, - }, -}; - -struct si476x_radio; - -/** - * struct si476x_radio_ops - vtable of tuner functions - * - * This table holds pointers to functions implementing particular - * operations depending on the mode in which the tuner chip was - * configured to start in. If the function is not supported - * corresponding element is set to #NULL. - * - * @tune_freq: Tune chip to a specific frequency - * @seek_start: Star station seeking - * @rsq_status: Get Recieved Signal Quality(RSQ) status - * @rds_blckcnt: Get recived RDS blocks count - * @phase_diversity: Change phase diversity mode of the tuner - * @phase_div_status: Get phase diversity mode status - * @acf_status: Get the status of Automatically Controlled - * Features(ACF) - * @agc_status: Get Automatic Gain Control(AGC) status - */ -struct si476x_radio_ops { - int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *); - int (*seek_start)(struct si476x_core *, bool, bool); - int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *, - struct si476x_rsq_status_report *); - int (*rds_blckcnt)(struct si476x_core *, bool, - struct si476x_rds_blockcount_report *); - - int (*phase_diversity)(struct si476x_core *, - enum si476x_phase_diversity_mode); - int (*phase_div_status)(struct si476x_core *); - int (*acf_status)(struct si476x_core *, - struct si476x_acf_status_report *); - int (*agc_status)(struct si476x_core *, - struct si476x_agc_status_report *); -}; - -/** - * struct si476x_radio - radio device - * - * @core: Pointer to underlying core device - * @videodev: Pointer to video device created by V4L2 subsystem - * @ops: Vtable of functions. See struct si476x_radio_ops for details - * @kref: Reference counter - * @core_lock: An r/w semaphore to brebvent the deletion of underlying - * core structure is the radio device is being used - */ -struct si476x_radio { - struct v4l2_device v4l2dev; - struct video_device videodev; - struct v4l2_ctrl_handler ctrl_handler; - - struct si476x_core *core; - /* This field should not be accesses unless core lock is held */ - const struct si476x_radio_ops *ops; - - struct dentry *debugfs; - u32 audmode; -}; - -static inline struct si476x_radio * -v4l2_dev_to_radio(struct v4l2_device *d) -{ - return container_of(d, struct si476x_radio, v4l2dev); -} - -static inline struct si476x_radio * -v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) -{ - return container_of(d, struct si476x_radio, ctrl_handler); -} - -/* - * si476x_vidioc_querycap - query device capabilities - */ -static int si476x_radio_querycap(struct file *file, void *priv, - struct v4l2_capability *capability) -{ - struct si476x_radio *radio = video_drvdata(file); - - strlcpy(capability->driver, radio->v4l2dev.name, - sizeof(capability->driver)); - strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); - snprintf(capability->bus_info, sizeof(capability->bus_info), - "platform:%s", radio->v4l2dev.name); - - capability->device_caps = V4L2_CAP_TUNER - | V4L2_CAP_RADIO - | V4L2_CAP_HW_FREQ_SEEK; - - si476x_core_lock(radio->core); - if (!si476x_core_is_a_secondary_tuner(radio->core)) - capability->device_caps |= V4L2_CAP_RDS_CAPTURE - | V4L2_CAP_READWRITE; - si476x_core_unlock(radio->core); - - capability->capabilities = capability->device_caps - | V4L2_CAP_DEVICE_CAPS; - return 0; -} - -static int si476x_radio_enum_freq_bands(struct file *file, void *priv, - struct v4l2_frequency_band *band) -{ - int err; - struct si476x_radio *radio = video_drvdata(file); - - if (band->tuner != 0) - return -EINVAL; - - switch (radio->core->chip_id) { - /* AM/FM tuners -- all bands are supported */ - case SI476X_CHIP_SI4761: - case SI476X_CHIP_SI4764: - if (band->index < ARRAY_SIZE(si476x_bands)) { - *band = si476x_bands[band->index]; - err = 0; - } else { - err = -EINVAL; - } - break; - /* FM companion tuner chips -- only FM bands are - * supported */ - case SI476X_CHIP_SI4768: - if (band->index == SI476X_BAND_FM) { - *band = si476x_bands[band->index]; - err = 0; - } else { - err = -EINVAL; - } - break; - default: - err = -EINVAL; - } - - return err; -} - -static int si476x_radio_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner) -{ - int err; - struct si476x_rsq_status_report report; - struct si476x_radio *radio = video_drvdata(file); - - struct si476x_rsq_status_args args = { - .primary = false, - .rsqack = false, - .attune = false, - .cancel = false, - .stcack = false, - }; - - if (tuner->index != 0) - return -EINVAL; - - tuner->type = V4L2_TUNER_RADIO; - tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies - * in multiples of - * 62.5 Hz */ - | V4L2_TUNER_CAP_STEREO - | V4L2_TUNER_CAP_HWSEEK_BOUNDED - | V4L2_TUNER_CAP_HWSEEK_WRAP - | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; - - si476x_core_lock(radio->core); - - if (si476x_core_is_a_secondary_tuner(radio->core)) { - strlcpy(tuner->name, "FM (secondary)", sizeof(tuner->name)); - tuner->rxsubchans = 0; - tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; - } else if (si476x_core_has_am(radio->core)) { - if (si476x_core_is_a_primary_tuner(radio->core)) - strlcpy(tuner->name, "AM/FM (primary)", - sizeof(tuner->name)); - else - strlcpy(tuner->name, "AM/FM", sizeof(tuner->name)); - - tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO - | V4L2_TUNER_SUB_RDS; - tuner->capability |= V4L2_TUNER_CAP_RDS - | V4L2_TUNER_CAP_RDS_BLOCK_IO - | V4L2_TUNER_CAP_FREQ_BANDS; - - tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow; - } else { - strlcpy(tuner->name, "FM", sizeof(tuner->name)); - tuner->rxsubchans = V4L2_TUNER_SUB_RDS; - tuner->capability |= V4L2_TUNER_CAP_RDS - | V4L2_TUNER_CAP_RDS_BLOCK_IO - | V4L2_TUNER_CAP_FREQ_BANDS; - tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; - } - - tuner->audmode = radio->audmode; - - tuner->afc = 1; - tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh; - - err = radio->ops->rsq_status(radio->core, - &args, &report); - if (err < 0) { - tuner->signal = 0; - } else { - /* - * tuner->signal value range: 0x0000 .. 0xFFFF, - * report.rssi: -128 .. 127 - */ - tuner->signal = (report.rssi + 128) * 257; - } - si476x_core_unlock(radio->core); - - return err; -} - -static int si476x_radio_s_tuner(struct file *file, void *priv, - const struct v4l2_tuner *tuner) -{ - struct si476x_radio *radio = video_drvdata(file); - - if (tuner->index != 0) - return -EINVAL; - - if (tuner->audmode == V4L2_TUNER_MODE_MONO || - tuner->audmode == V4L2_TUNER_MODE_STEREO) - radio->audmode = tuner->audmode; - else - radio->audmode = V4L2_TUNER_MODE_STEREO; - - return 0; -} - -static int si476x_radio_init_vtable(struct si476x_radio *radio, - enum si476x_func func) -{ - static const struct si476x_radio_ops fm_ops = { - .tune_freq = si476x_core_cmd_fm_tune_freq, - .seek_start = si476x_core_cmd_fm_seek_start, - .rsq_status = si476x_core_cmd_fm_rsq_status, - .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount, - .phase_diversity = si476x_core_cmd_fm_phase_diversity, - .phase_div_status = si476x_core_cmd_fm_phase_div_status, - .acf_status = si476x_core_cmd_fm_acf_status, - .agc_status = si476x_core_cmd_agc_status, - }; - - static const struct si476x_radio_ops am_ops = { - .tune_freq = si476x_core_cmd_am_tune_freq, - .seek_start = si476x_core_cmd_am_seek_start, - .rsq_status = si476x_core_cmd_am_rsq_status, - .rds_blckcnt = NULL, - .phase_diversity = NULL, - .phase_div_status = NULL, - .acf_status = si476x_core_cmd_am_acf_status, - .agc_status = NULL, - }; - - switch (func) { - case SI476X_FUNC_FM_RECEIVER: - radio->ops = &fm_ops; - return 0; - - case SI476X_FUNC_AM_RECEIVER: - radio->ops = &am_ops; - return 0; - default: - WARN(1, "Unexpected tuner function value\n"); - return -EINVAL; - } -} - -static int si476x_radio_pretune(struct si476x_radio *radio, - enum si476x_func func) -{ - int retval; - - struct si476x_tune_freq_args args = { - .zifsr = false, - .hd = false, - .injside = SI476X_INJSIDE_AUTO, - .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE, - .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO, - .antcap = 0, - }; - - switch (func) { - case SI476X_FUNC_FM_RECEIVER: - args.freq = v4l2_to_si476x(radio->core, - 92 * FREQ_MUL); - retval = radio->ops->tune_freq(radio->core, &args); - break; - case SI476X_FUNC_AM_RECEIVER: - args.freq = v4l2_to_si476x(radio->core, - 0.6 * FREQ_MUL); - retval = radio->ops->tune_freq(radio->core, &args); - break; - default: - WARN(1, "Unexpected tuner function value\n"); - retval = -EINVAL; - } - - return retval; -} -static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio, - enum si476x_func func) -{ - int err; - - /* regcache_mark_dirty(radio->core->regmap); */ - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE, - SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT); - if (err < 0) - return err; - - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_AUDIO_DEEMPHASIS, - SI476X_PROP_AUDIO_PWR_LINE_FILTER); - if (err < 0) - return err; - - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_INT_CTL_ENABLE, - SI476X_PROP_INT_CTL_ENABLE); - if (err < 0) - return err; - - /* - * Is there any point in restoring SNR and the like - * when switching between AM/FM? - */ - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_VALID_MAX_TUNE_ERROR, - SI476X_PROP_VALID_MAX_TUNE_ERROR); - if (err < 0) - return err; - - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_VALID_SNR_THRESHOLD, - SI476X_PROP_VALID_RSSI_THRESHOLD); - if (err < 0) - return err; - - if (func == SI476X_FUNC_FM_RECEIVER) { - if (si476x_core_has_diversity(radio->core)) { - err = si476x_core_cmd_fm_phase_diversity(radio->core, - radio->core->diversity_mode); - if (err < 0) - return err; - } - - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, - SI476X_PROP_FM_RDS_CONFIG); - if (err < 0) - return err; - } - - return si476x_radio_init_vtable(radio, func); - -} - -static int si476x_radio_change_func(struct si476x_radio *radio, - enum si476x_func func) -{ - int err; - bool soft; - /* - * Since power/up down is a very time consuming operation, - * try to avoid doing it if the requested mode matches the one - * the tuner is in - */ - if (func == radio->core->power_up_parameters.func) - return 0; - - soft = true; - err = si476x_core_stop(radio->core, soft); - if (err < 0) { - /* - * OK, if the chip does not want to play nice let's - * try to reset it in more brutal way - */ - soft = false; - err = si476x_core_stop(radio->core, soft); - if (err < 0) - return err; - } - /* - Set the desired radio tuner function - */ - radio->core->power_up_parameters.func = func; - - err = si476x_core_start(radio->core, soft); - if (err < 0) - return err; - - /* - * No need to do the rest of manipulations for the bootlader - * mode - */ - if (func != SI476X_FUNC_FM_RECEIVER && - func != SI476X_FUNC_AM_RECEIVER) - return err; - - return si476x_radio_do_post_powerup_init(radio, func); -} - -static int si476x_radio_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - int err; - struct si476x_radio *radio = video_drvdata(file); - - if (f->tuner != 0 || - f->type != V4L2_TUNER_RADIO) - return -EINVAL; - - si476x_core_lock(radio->core); - - if (radio->ops->rsq_status) { - struct si476x_rsq_status_report report; - struct si476x_rsq_status_args args = { - .primary = false, - .rsqack = false, - .attune = true, - .cancel = false, - .stcack = false, - }; - - err = radio->ops->rsq_status(radio->core, &args, &report); - if (!err) - f->frequency = si476x_to_v4l2(radio->core, - report.readfreq); - } else { - err = -EINVAL; - } - - si476x_core_unlock(radio->core); - - return err; -} - -static int si476x_radio_s_frequency(struct file *file, void *priv, - const struct v4l2_frequency *f) -{ - int err; - u32 freq = f->frequency; - struct si476x_tune_freq_args args; - struct si476x_radio *radio = video_drvdata(file); - - const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh + - si476x_bands[SI476X_BAND_FM].rangelow) / 2; - const int band = (freq > midrange) ? - SI476X_BAND_FM : SI476X_BAND_AM; - const enum si476x_func func = (band == SI476X_BAND_AM) ? - SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER; - - if (f->tuner != 0 || - f->type != V4L2_TUNER_RADIO) - return -EINVAL; - - si476x_core_lock(radio->core); - - freq = clamp(freq, - si476x_bands[band].rangelow, - si476x_bands[band].rangehigh); - - if (si476x_radio_freq_is_inside_of_the_band(freq, - SI476X_BAND_AM) && - (!si476x_core_has_am(radio->core) || - si476x_core_is_a_secondary_tuner(radio->core))) { - err = -EINVAL; - goto unlock; - } - - err = si476x_radio_change_func(radio, func); - if (err < 0) - goto unlock; - - args.zifsr = false; - args.hd = false; - args.injside = SI476X_INJSIDE_AUTO; - args.freq = v4l2_to_si476x(radio->core, freq); - args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE; - args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO; - args.antcap = 0; - - err = radio->ops->tune_freq(radio->core, &args); - -unlock: - si476x_core_unlock(radio->core); - return err; -} - -static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv, - const struct v4l2_hw_freq_seek *seek) -{ - int err; - enum si476x_func func; - u32 rangelow, rangehigh; - struct si476x_radio *radio = video_drvdata(file); - - if (file->f_flags & O_NONBLOCK) - return -EAGAIN; - - if (seek->tuner != 0 || - seek->type != V4L2_TUNER_RADIO) - return -EINVAL; - - si476x_core_lock(radio->core); - - if (!seek->rangelow) { - err = regmap_read(radio->core->regmap, - SI476X_PROP_SEEK_BAND_BOTTOM, - &rangelow); - if (!err) - rangelow = si476x_to_v4l2(radio->core, rangelow); - else - goto unlock; - } - if (!seek->rangehigh) { - err = regmap_read(radio->core->regmap, - SI476X_PROP_SEEK_BAND_TOP, - &rangehigh); - if (!err) - rangehigh = si476x_to_v4l2(radio->core, rangehigh); - else - goto unlock; - } - - if (rangelow > rangehigh) { - err = -EINVAL; - goto unlock; - } - - if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, - SI476X_BAND_FM)) { - func = SI476X_FUNC_FM_RECEIVER; - - } else if (si476x_core_has_am(radio->core) && - si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, - SI476X_BAND_AM)) { - func = SI476X_FUNC_AM_RECEIVER; - } else { - err = -EINVAL; - goto unlock; - } - - err = si476x_radio_change_func(radio, func); - if (err < 0) - goto unlock; - - if (seek->rangehigh) { - err = regmap_write(radio->core->regmap, - SI476X_PROP_SEEK_BAND_TOP, - v4l2_to_si476x(radio->core, - seek->rangehigh)); - if (err) - goto unlock; - } - if (seek->rangelow) { - err = regmap_write(radio->core->regmap, - SI476X_PROP_SEEK_BAND_BOTTOM, - v4l2_to_si476x(radio->core, - seek->rangelow)); - if (err) - goto unlock; - } - if (seek->spacing) { - err = regmap_write(radio->core->regmap, - SI476X_PROP_SEEK_FREQUENCY_SPACING, - v4l2_to_si476x(radio->core, - seek->spacing)); - if (err) - goto unlock; - } - - err = radio->ops->seek_start(radio->core, - seek->seek_upward, - seek->wrap_around); -unlock: - si476x_core_unlock(radio->core); - - - - return err; -} - -static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) -{ - int retval; - struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); - - si476x_core_lock(radio->core); - - switch (ctrl->id) { - case V4L2_CID_SI476X_INTERCHIP_LINK: - if (si476x_core_has_diversity(radio->core)) { - if (radio->ops->phase_diversity) { - retval = radio->ops->phase_div_status(radio->core); - if (retval < 0) - break; - - ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval); - retval = 0; - break; - } else { - retval = -ENOTTY; - break; - } - } - retval = -EINVAL; - break; - default: - retval = -EINVAL; - break; - } - si476x_core_unlock(radio->core); - return retval; - -} - -static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) -{ - int retval; - enum si476x_phase_diversity_mode mode; - struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); - - si476x_core_lock(radio->core); - - switch (ctrl->id) { - case V4L2_CID_SI476X_HARMONICS_COUNT: - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_AUDIO_PWR_LINE_FILTER, - SI476X_PROP_PWR_HARMONICS_MASK, - ctrl->val); - break; - case V4L2_CID_POWER_LINE_FREQUENCY: - switch (ctrl->val) { - case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_AUDIO_PWR_LINE_FILTER, - SI476X_PROP_PWR_ENABLE_MASK, - 0); - break; - case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_AUDIO_PWR_LINE_FILTER, - SI476X_PROP_PWR_GRID_MASK, - SI476X_PROP_PWR_GRID_50HZ); - break; - case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_AUDIO_PWR_LINE_FILTER, - SI476X_PROP_PWR_GRID_MASK, - SI476X_PROP_PWR_GRID_60HZ); - break; - default: - retval = -EINVAL; - break; - } - break; - case V4L2_CID_SI476X_RSSI_THRESHOLD: - retval = regmap_write(radio->core->regmap, - SI476X_PROP_VALID_RSSI_THRESHOLD, - ctrl->val); - break; - case V4L2_CID_SI476X_SNR_THRESHOLD: - retval = regmap_write(radio->core->regmap, - SI476X_PROP_VALID_SNR_THRESHOLD, - ctrl->val); - break; - case V4L2_CID_SI476X_MAX_TUNE_ERROR: - retval = regmap_write(radio->core->regmap, - SI476X_PROP_VALID_MAX_TUNE_ERROR, - ctrl->val); - break; - case V4L2_CID_RDS_RECEPTION: - /* - * It looks like RDS related properties are - * inaccesable when tuner is in AM mode, so cache the - * changes - */ - if (si476x_core_is_in_am_receiver_mode(radio->core)) - regcache_cache_only(radio->core->regmap, true); - - if (ctrl->val) { - retval = regmap_write(radio->core->regmap, - SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT, - radio->core->rds_fifo_depth); - if (retval < 0) - break; - - if (radio->core->client->irq) { - retval = regmap_write(radio->core->regmap, - SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, - SI476X_RDSRECV); - if (retval < 0) - break; - } - - /* Drain RDS FIFO before enabling RDS processing */ - retval = si476x_core_cmd_fm_rds_status(radio->core, - false, - true, - true, - NULL); - if (retval < 0) - break; - - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_FM_RDS_CONFIG, - SI476X_PROP_RDSEN_MASK, - SI476X_PROP_RDSEN); - } else { - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_FM_RDS_CONFIG, - SI476X_PROP_RDSEN_MASK, - !SI476X_PROP_RDSEN); - } - - if (si476x_core_is_in_am_receiver_mode(radio->core)) - regcache_cache_only(radio->core->regmap, false); - break; - case V4L2_CID_TUNE_DEEMPHASIS: - retval = regmap_write(radio->core->regmap, - SI476X_PROP_AUDIO_DEEMPHASIS, - ctrl->val); - break; - - case V4L2_CID_SI476X_DIVERSITY_MODE: - mode = si476x_phase_diversity_idx_to_mode(ctrl->val); - - if (mode == radio->core->diversity_mode) { - retval = 0; - break; - } - - if (si476x_core_is_in_am_receiver_mode(radio->core)) { - /* - * Diversity cannot be configured while tuner - * is in AM mode so save the changes and carry on. - */ - radio->core->diversity_mode = mode; - retval = 0; - } else { - retval = radio->ops->phase_diversity(radio->core, mode); - if (!retval) - radio->core->diversity_mode = mode; - } - break; - - default: - retval = -EINVAL; - break; - } - - si476x_core_unlock(radio->core); - - return retval; -} - -static int si476x_radio_g_chip_ident(struct file *file, void *fh, - struct v4l2_dbg_chip_ident *chip) -{ - if (chip->match.type == V4L2_CHIP_MATCH_HOST && - v4l2_chip_match_host(&chip->match)) - return 0; - return -EINVAL; -} - - -#ifdef CONFIG_VIDEO_ADV_DEBUG -static int si476x_radio_g_register(struct file *file, void *fh, - struct v4l2_dbg_register *reg) -{ - int err; - unsigned int value; - struct si476x_radio *radio = video_drvdata(file); - - si476x_core_lock(radio->core); - reg->size = 2; - err = regmap_read(radio->core->regmap, - (unsigned int)reg->reg, &value); - reg->val = value; - si476x_core_unlock(radio->core); - - return err; -} -static int si476x_radio_s_register(struct file *file, void *fh, - const struct v4l2_dbg_register *reg) -{ - - int err; - struct si476x_radio *radio = video_drvdata(file); - - si476x_core_lock(radio->core); - err = regmap_write(radio->core->regmap, - (unsigned int)reg->reg, - (unsigned int)reg->val); - si476x_core_unlock(radio->core); - - return err; -} -#endif - -static int si476x_radio_fops_open(struct file *file) -{ - struct si476x_radio *radio = video_drvdata(file); - int err; - - err = v4l2_fh_open(file); - if (err) - return err; - - if (v4l2_fh_is_singular_file(file)) { - si476x_core_lock(radio->core); - err = si476x_core_set_power_state(radio->core, - SI476X_POWER_UP_FULL); - if (err < 0) - goto done; - - err = si476x_radio_do_post_powerup_init(radio, - radio->core->power_up_parameters.func); - if (err < 0) - goto power_down; - - err = si476x_radio_pretune(radio, - radio->core->power_up_parameters.func); - if (err < 0) - goto power_down; - - si476x_core_unlock(radio->core); - /*Must be done after si476x_core_unlock to prevent a deadlock*/ - v4l2_ctrl_handler_setup(&radio->ctrl_handler); - } - - return err; - -power_down: - si476x_core_set_power_state(radio->core, - SI476X_POWER_DOWN); -done: - si476x_core_unlock(radio->core); - v4l2_fh_release(file); - - return err; -} - -static int si476x_radio_fops_release(struct file *file) -{ - int err; - struct si476x_radio *radio = video_drvdata(file); - - if (v4l2_fh_is_singular_file(file) && - atomic_read(&radio->core->is_alive)) - si476x_core_set_power_state(radio->core, - SI476X_POWER_DOWN); - - err = v4l2_fh_release(file); - - return err; -} - -static ssize_t si476x_radio_fops_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos) -{ - ssize_t rval; - size_t fifo_len; - unsigned int copied; - - struct si476x_radio *radio = video_drvdata(file); - - /* block if no new data available */ - if (kfifo_is_empty(&radio->core->rds_fifo)) { - if (file->f_flags & O_NONBLOCK) - return -EWOULDBLOCK; - - rval = wait_event_interruptible(radio->core->rds_read_queue, - (!kfifo_is_empty(&radio->core->rds_fifo) || - !atomic_read(&radio->core->is_alive))); - if (rval < 0) - return -EINTR; - - if (!atomic_read(&radio->core->is_alive)) - return -ENODEV; - } - - fifo_len = kfifo_len(&radio->core->rds_fifo); - - if (kfifo_to_user(&radio->core->rds_fifo, buf, - min(fifo_len, count), - &copied) != 0) { - dev_warn(&radio->videodev.dev, - "Error during FIFO to userspace copy\n"); - rval = -EIO; - } else { - rval = (ssize_t)copied; - } - - return rval; -} - -static unsigned int si476x_radio_fops_poll(struct file *file, - struct poll_table_struct *pts) -{ - struct si476x_radio *radio = video_drvdata(file); - unsigned long req_events = poll_requested_events(pts); - unsigned int err = v4l2_ctrl_poll(file, pts); - - if (req_events & (POLLIN | POLLRDNORM)) { - if (atomic_read(&radio->core->is_alive)) - poll_wait(file, &radio->core->rds_read_queue, pts); - - if (!atomic_read(&radio->core->is_alive)) - err = POLLHUP; - - if (!kfifo_is_empty(&radio->core->rds_fifo)) - err = POLLIN | POLLRDNORM; - } - - return err; -} - -static const struct v4l2_file_operations si476x_fops = { - .owner = THIS_MODULE, - .read = si476x_radio_fops_read, - .poll = si476x_radio_fops_poll, - .unlocked_ioctl = video_ioctl2, - .open = si476x_radio_fops_open, - .release = si476x_radio_fops_release, -}; - - -static const struct v4l2_ioctl_ops si4761_ioctl_ops = { - .vidioc_querycap = si476x_radio_querycap, - .vidioc_g_tuner = si476x_radio_g_tuner, - .vidioc_s_tuner = si476x_radio_s_tuner, - - .vidioc_g_frequency = si476x_radio_g_frequency, - .vidioc_s_frequency = si476x_radio_s_frequency, - .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek, - .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands, - - .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, - - .vidioc_g_chip_ident = si476x_radio_g_chip_ident, -#ifdef CONFIG_VIDEO_ADV_DEBUG - .vidioc_g_register = si476x_radio_g_register, - .vidioc_s_register = si476x_radio_s_register, -#endif -}; - - -static const struct video_device si476x_viddev_template = { - .fops = &si476x_fops, - .name = DRIVER_NAME, - .release = video_device_release_empty, -}; - - - -static ssize_t si476x_radio_read_acf_blob(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - int err; - struct si476x_radio *radio = file->private_data; - struct si476x_acf_status_report report; - - si476x_core_lock(radio->core); - if (radio->ops->acf_status) - err = radio->ops->acf_status(radio->core, &report); - else - err = -ENOENT; - si476x_core_unlock(radio->core); - - if (err < 0) - return err; - - return simple_read_from_buffer(user_buf, count, ppos, &report, - sizeof(report)); -} - -static const struct file_operations radio_acf_fops = { - .open = simple_open, - .llseek = default_llseek, - .read = si476x_radio_read_acf_blob, -}; - -static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - int err; - struct si476x_radio *radio = file->private_data; - struct si476x_rds_blockcount_report report; - - si476x_core_lock(radio->core); - if (radio->ops->rds_blckcnt) - err = radio->ops->rds_blckcnt(radio->core, true, - &report); - else - err = -ENOENT; - si476x_core_unlock(radio->core); - - if (err < 0) - return err; - - return simple_read_from_buffer(user_buf, count, ppos, &report, - sizeof(report)); -} - -static const struct file_operations radio_rds_blckcnt_fops = { - .open = simple_open, - .llseek = default_llseek, - .read = si476x_radio_read_rds_blckcnt_blob, -}; - -static ssize_t si476x_radio_read_agc_blob(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - int err; - struct si476x_radio *radio = file->private_data; - struct si476x_agc_status_report report; - - si476x_core_lock(radio->core); - if (radio->ops->rds_blckcnt) - err = radio->ops->agc_status(radio->core, &report); - else - err = -ENOENT; - si476x_core_unlock(radio->core); - - if (err < 0) - return err; - - return simple_read_from_buffer(user_buf, count, ppos, &report, - sizeof(report)); -} - -static const struct file_operations radio_agc_fops = { - .open = simple_open, - .llseek = default_llseek, - .read = si476x_radio_read_agc_blob, -}; - -static ssize_t si476x_radio_read_rsq_blob(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - int err; - struct si476x_radio *radio = file->private_data; - struct si476x_rsq_status_report report; - struct si476x_rsq_status_args args = { - .primary = false, - .rsqack = false, - .attune = false, - .cancel = false, - .stcack = false, - }; - - si476x_core_lock(radio->core); - if (radio->ops->rds_blckcnt) - err = radio->ops->rsq_status(radio->core, &args, &report); - else - err = -ENOENT; - si476x_core_unlock(radio->core); - - if (err < 0) - return err; - - return simple_read_from_buffer(user_buf, count, ppos, &report, - sizeof(report)); -} - -static const struct file_operations radio_rsq_fops = { - .open = simple_open, - .llseek = default_llseek, - .read = si476x_radio_read_rsq_blob, -}; - -static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - int err; - struct si476x_radio *radio = file->private_data; - struct si476x_rsq_status_report report; - struct si476x_rsq_status_args args = { - .primary = true, - .rsqack = false, - .attune = false, - .cancel = false, - .stcack = false, - }; - - si476x_core_lock(radio->core); - if (radio->ops->rds_blckcnt) - err = radio->ops->rsq_status(radio->core, &args, &report); - else - err = -ENOENT; - si476x_core_unlock(radio->core); - - if (err < 0) - return err; - - return simple_read_from_buffer(user_buf, count, ppos, &report, - sizeof(report)); -} - -static const struct file_operations radio_rsq_primary_fops = { - .open = simple_open, - .llseek = default_llseek, - .read = si476x_radio_read_rsq_primary_blob, -}; - - -static int si476x_radio_init_debugfs(struct si476x_radio *radio) -{ - struct dentry *dentry; - int ret; - - dentry = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto exit; - } - radio->debugfs = dentry; - - dentry = debugfs_create_file("acf", S_IRUGO, - radio->debugfs, radio, &radio_acf_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto cleanup; - } - - dentry = debugfs_create_file("rds_blckcnt", S_IRUGO, - radio->debugfs, radio, - &radio_rds_blckcnt_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto cleanup; - } - - dentry = debugfs_create_file("agc", S_IRUGO, - radio->debugfs, radio, &radio_agc_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto cleanup; - } - - dentry = debugfs_create_file("rsq", S_IRUGO, - radio->debugfs, radio, &radio_rsq_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto cleanup; - } - - dentry = debugfs_create_file("rsq_primary", S_IRUGO, - radio->debugfs, radio, - &radio_rsq_primary_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto cleanup; - } - - return 0; -cleanup: - debugfs_remove_recursive(radio->debugfs); -exit: - return ret; -} - - -static int si476x_radio_add_new_custom(struct si476x_radio *radio, - enum si476x_ctrl_idx idx) -{ - int rval; - struct v4l2_ctrl *ctrl; - - ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, - &si476x_ctrls[idx], - NULL); - rval = radio->ctrl_handler.error; - if (ctrl == NULL && rval) - dev_err(radio->v4l2dev.dev, - "Could not initialize '%s' control %d\n", - si476x_ctrls[idx].name, rval); - - return rval; -} - -static int si476x_radio_probe(struct platform_device *pdev) -{ - int rval; - struct si476x_radio *radio; - struct v4l2_ctrl *ctrl; - - static atomic_t instance = ATOMIC_INIT(0); - - radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); - if (!radio) - return -ENOMEM; - - radio->core = i2c_mfd_cell_to_core(&pdev->dev); - - v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); - - rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev); - if (rval) { - dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); - return rval; - } - - memcpy(&radio->videodev, &si476x_viddev_template, - sizeof(struct video_device)); - - radio->videodev.v4l2_dev = &radio->v4l2dev; - radio->videodev.ioctl_ops = &si4761_ioctl_ops; - - video_set_drvdata(&radio->videodev, radio); - platform_set_drvdata(pdev, radio); - - set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags); - - radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; - v4l2_ctrl_handler_init(&radio->ctrl_handler, - 1 + ARRAY_SIZE(si476x_ctrls)); - - if (si476x_core_has_am(radio->core)) { - ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, - &si476x_ctrl_ops, - V4L2_CID_POWER_LINE_FREQUENCY, - V4L2_CID_POWER_LINE_FREQUENCY_60HZ, - 0, 0); - rval = radio->ctrl_handler.error; - if (ctrl == NULL && rval) { - dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n", - rval); - goto exit; - } - - rval = si476x_radio_add_new_custom(radio, - SI476X_IDX_HARMONICS_COUNT); - if (rval < 0) - goto exit; - } - - rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD); - if (rval < 0) - goto exit; - - rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD); - if (rval < 0) - goto exit; - - rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR); - if (rval < 0) - goto exit; - - ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, - &si476x_ctrl_ops, - V4L2_CID_TUNE_DEEMPHASIS, - V4L2_DEEMPHASIS_75_uS, 0, 0); - rval = radio->ctrl_handler.error; - if (ctrl == NULL && rval) { - dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", - rval); - goto exit; - } - - ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, - V4L2_CID_RDS_RECEPTION, - 0, 1, 1, 1); - rval = radio->ctrl_handler.error; - if (ctrl == NULL && rval) { - dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n", - rval); - goto exit; - } - - if (si476x_core_has_diversity(radio->core)) { - si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = - si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); - si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE); - if (rval < 0) - goto exit; - - si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK); - if (rval < 0) - goto exit; - } - - /* register video device */ - rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); - if (rval < 0) { - dev_err(&pdev->dev, "Could not register video device\n"); - goto exit; - } - - rval = si476x_radio_init_debugfs(radio); - if (rval < 0) { - dev_err(&pdev->dev, "Could not creat debugfs interface\n"); - goto exit; - } - - return 0; -exit: - v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); - return rval; -} - -static int si476x_radio_remove(struct platform_device *pdev) -{ - struct si476x_radio *radio = platform_get_drvdata(pdev); - - v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); - video_unregister_device(&radio->videodev); - v4l2_device_unregister(&radio->v4l2dev); - debugfs_remove_recursive(radio->debugfs); - - return 0; -} - -MODULE_ALIAS("platform:si476x-radio"); - -static struct platform_driver si476x_radio_driver = { - .driver = { - .name = DRIVER_NAME, - .owner = THIS_MODULE, - }, - .probe = si476x_radio_probe, - .remove = si476x_radio_remove, -}; -module_platform_driver(si476x_radio_driver); - -MODULE_AUTHOR("Andrey Smirnov "); -MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell"); -MODULE_LICENSE("GPL"); diff --git a/include/media/si476x.h b/include/media/si476x.h deleted file mode 100644 index beb6433d6958..000000000000 --- a/include/media/si476x.h +++ /dev/null @@ -1,426 +0,0 @@ -/* - * include/media/si476x.h -- Common definitions for si476x driver - * - * Copyright (C) 2012 Innovative Converged Devices(ICD) - * Copyright (C) 2013 Andrey Smirnov - * - * Author: Andrey Smirnov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - */ - -#ifndef SI476X_H -#define SI476X_H - -#include -#include - -struct si476x_device; - -/* It is possible to select one of the four adresses using pins A0 - * and A1 on SI476x */ -#define SI476X_I2C_ADDR_1 0x60 -#define SI476X_I2C_ADDR_2 0x61 -#define SI476X_I2C_ADDR_3 0x62 -#define SI476X_I2C_ADDR_4 0x63 - -enum si476x_iqclk_config { - SI476X_IQCLK_NOOP = 0, - SI476X_IQCLK_TRISTATE = 1, - SI476X_IQCLK_IQ = 21, -}; -enum si476x_iqfs_config { - SI476X_IQFS_NOOP = 0, - SI476X_IQFS_TRISTATE = 1, - SI476X_IQFS_IQ = 21, -}; -enum si476x_iout_config { - SI476X_IOUT_NOOP = 0, - SI476X_IOUT_TRISTATE = 1, - SI476X_IOUT_OUTPUT = 22, -}; -enum si476x_qout_config { - SI476X_QOUT_NOOP = 0, - SI476X_QOUT_TRISTATE = 1, - SI476X_QOUT_OUTPUT = 22, -}; - -enum si476x_dclk_config { - SI476X_DCLK_NOOP = 0, - SI476X_DCLK_TRISTATE = 1, - SI476X_DCLK_DAUDIO = 10, -}; - -enum si476x_dfs_config { - SI476X_DFS_NOOP = 0, - SI476X_DFS_TRISTATE = 1, - SI476X_DFS_DAUDIO = 10, -}; - -enum si476x_dout_config { - SI476X_DOUT_NOOP = 0, - SI476X_DOUT_TRISTATE = 1, - SI476X_DOUT_I2S_OUTPUT = 12, - SI476X_DOUT_I2S_INPUT = 13, -}; - -enum si476x_xout_config { - SI476X_XOUT_NOOP = 0, - SI476X_XOUT_TRISTATE = 1, - SI476X_XOUT_I2S_INPUT = 13, - SI476X_XOUT_MODE_SELECT = 23, -}; - - -enum si476x_icin_config { - SI476X_ICIN_NOOP = 0, - SI476X_ICIN_TRISTATE = 1, - SI476X_ICIN_GPO1_HIGH = 2, - SI476X_ICIN_GPO1_LOW = 3, - SI476X_ICIN_IC_LINK = 30, -}; - -enum si476x_icip_config { - SI476X_ICIP_NOOP = 0, - SI476X_ICIP_TRISTATE = 1, - SI476X_ICIP_GPO2_HIGH = 2, - SI476X_ICIP_GPO2_LOW = 3, - SI476X_ICIP_IC_LINK = 30, -}; - -enum si476x_icon_config { - SI476X_ICON_NOOP = 0, - SI476X_ICON_TRISTATE = 1, - SI476X_ICON_I2S = 10, - SI476X_ICON_IC_LINK = 30, -}; - -enum si476x_icop_config { - SI476X_ICOP_NOOP = 0, - SI476X_ICOP_TRISTATE = 1, - SI476X_ICOP_I2S = 10, - SI476X_ICOP_IC_LINK = 30, -}; - - -enum si476x_lrout_config { - SI476X_LROUT_NOOP = 0, - SI476X_LROUT_TRISTATE = 1, - SI476X_LROUT_AUDIO = 2, - SI476X_LROUT_MPX = 3, -}; - - -enum si476x_intb_config { - SI476X_INTB_NOOP = 0, - SI476X_INTB_TRISTATE = 1, - SI476X_INTB_DAUDIO = 10, - SI476X_INTB_IRQ = 40, -}; - -enum si476x_a1_config { - SI476X_A1_NOOP = 0, - SI476X_A1_TRISTATE = 1, - SI476X_A1_IRQ = 40, -}; - -enum si476x_part_revisions { - SI476X_REVISION_A10 = 0, - SI476X_REVISION_A20 = 1, - SI476X_REVISION_A30 = 2, -}; - -struct si476x_pinmux { - enum si476x_dclk_config dclk; - enum si476x_dfs_config dfs; - enum si476x_dout_config dout; - enum si476x_xout_config xout; - - enum si476x_iqclk_config iqclk; - enum si476x_iqfs_config iqfs; - enum si476x_iout_config iout; - enum si476x_qout_config qout; - - enum si476x_icin_config icin; - enum si476x_icip_config icip; - enum si476x_icon_config icon; - enum si476x_icop_config icop; - - enum si476x_lrout_config lrout; - - enum si476x_intb_config intb; - enum si476x_a1_config a1; -}; - -/** - * enum si476x_phase_diversity_mode - possbile phase diversity modes - * for SI4764/5/6/7 chips. - * - * @SI476X_PHDIV_DISABLED: Phase diversity feature is - * disabled. - * @SI476X_PHDIV_PRIMARY_COMBINING: Tuner works as a primary tuner - * in combination with a - * secondary one. - * @SI476X_PHDIV_PRIMARY_ANTENNA: Tuner works as a primary tuner - * using only its own antenna. - * @SI476X_PHDIV_SECONDARY_ANTENNA: Tuner works as a primary tuner - * usning seconary tuner's antenna. - * @SI476X_PHDIV_SECONDARY_COMBINING: Tuner works as a secondary - * tuner in combination with the - * primary one. - */ -enum si476x_phase_diversity_mode { - SI476X_PHDIV_DISABLED = 0, - SI476X_PHDIV_PRIMARY_COMBINING = 1, - SI476X_PHDIV_PRIMARY_ANTENNA = 2, - SI476X_PHDIV_SECONDARY_ANTENNA = 3, - SI476X_PHDIV_SECONDARY_COMBINING = 5, -}; - -enum si476x_ibias6x { - SI476X_IBIAS6X_OTHER = 0, - SI476X_IBIAS6X_RCVR1_NON_4MHZ_CLK = 1, -}; - -enum si476x_xstart { - SI476X_XSTART_MULTIPLE_TUNER = 0x11, - SI476X_XSTART_NORMAL = 0x77, -}; - -enum si476x_freq { - SI476X_FREQ_4_MHZ = 0, - SI476X_FREQ_37P209375_MHZ = 1, - SI476X_FREQ_36P4_MHZ = 2, - SI476X_FREQ_37P8_MHZ = 3, -}; - -enum si476x_xmode { - SI476X_XMODE_CRYSTAL_RCVR1 = 1, - SI476X_XMODE_EXT_CLOCK = 2, - SI476X_XMODE_CRYSTAL_RCVR2_3 = 3, -}; - -enum si476x_xbiashc { - SI476X_XBIASHC_SINGLE_RECEIVER = 0, - SI476X_XBIASHC_MULTIPLE_RECEIVER = 1, -}; - -enum si476x_xbias { - SI476X_XBIAS_RCVR2_3 = 0, - SI476X_XBIAS_4MHZ_RCVR1 = 3, - SI476X_XBIAS_RCVR1 = 7, -}; - -enum si476x_func { - SI476X_FUNC_BOOTLOADER = 0, - SI476X_FUNC_FM_RECEIVER = 1, - SI476X_FUNC_AM_RECEIVER = 2, - SI476X_FUNC_WB_RECEIVER = 3, -}; - - -/** - * @xcload: Selects the amount of additional on-chip capacitance to - * be connected between XTAL1 and gnd and between XTAL2 and - * GND. One half of the capacitance value shown here is the - * additional load capacitance presented to the xtal. The - * minimum step size is 0.277 pF. Recommended value is 0x28 - * but it will be layout dependent. Range is 0–0x3F i.e. - * (0–16.33 pF) - * @ctsien: enable CTSINT(interrupt request when CTS condition - * arises) when set - * @intsel: when set A1 pin becomes the interrupt pin; otherwise, - * INTB is the interrupt pin - * @func: selects the boot function of the device. I.e. - * SI476X_BOOTLOADER - Boot loader - * SI476X_FM_RECEIVER - FM receiver - * SI476X_AM_RECEIVER - AM receiver - * SI476X_WB_RECEIVER - Weatherband receiver - * @freq: oscillator's crystal frequency: - * SI476X_XTAL_37P209375_MHZ - 37.209375 Mhz - * SI476X_XTAL_36P4_MHZ - 36.4 Mhz - * SI476X_XTAL_37P8_MHZ - 37.8 Mhz - */ -struct si476x_power_up_args { - enum si476x_ibias6x ibias6x; - enum si476x_xstart xstart; - u8 xcload; - bool fastboot; - enum si476x_xbiashc xbiashc; - enum si476x_xbias xbias; - enum si476x_func func; - enum si476x_freq freq; - enum si476x_xmode xmode; -}; - - -enum si476x_ctrl_id { - V4L2_CID_SI476X_RSSI_THRESHOLD = (V4L2_CID_USER_SI476X_BASE + 1), - V4L2_CID_SI476X_SNR_THRESHOLD = (V4L2_CID_USER_SI476X_BASE + 2), - V4L2_CID_SI476X_MAX_TUNE_ERROR = (V4L2_CID_USER_SI476X_BASE + 3), - V4L2_CID_SI476X_HARMONICS_COUNT = (V4L2_CID_USER_SI476X_BASE + 4), - V4L2_CID_SI476X_DIVERSITY_MODE = (V4L2_CID_USER_SI476X_BASE + 5), - V4L2_CID_SI476X_INTERCHIP_LINK = (V4L2_CID_USER_SI476X_BASE + 6), -}; - -/* - * Platform dependent definition - */ -struct si476x_platform_data { - int gpio_reset; /* < 0 if not used */ - - struct si476x_power_up_args power_up_parameters; - enum si476x_phase_diversity_mode diversity_mode; - - struct si476x_pinmux pinmux; -}; - -/** - * struct si476x_rsq_status - structure containing received signal - * quality - * @multhint: Multipath Detect High. - * true - Indicatedes that the value is below - * FM_RSQ_MULTIPATH_HIGH_THRESHOLD - * false - Indicatedes that the value is above - * FM_RSQ_MULTIPATH_HIGH_THRESHOLD - * @multlint: Multipath Detect Low. - * true - Indicatedes that the value is below - * FM_RSQ_MULTIPATH_LOW_THRESHOLD - * false - Indicatedes that the value is above - * FM_RSQ_MULTIPATH_LOW_THRESHOLD - * @snrhint: SNR Detect High. - * true - Indicatedes that the value is below - * FM_RSQ_SNR_HIGH_THRESHOLD - * false - Indicatedes that the value is above - * FM_RSQ_SNR_HIGH_THRESHOLD - * @snrlint: SNR Detect Low. - * true - Indicatedes that the value is below - * FM_RSQ_SNR_LOW_THRESHOLD - * false - Indicatedes that the value is above - * FM_RSQ_SNR_LOW_THRESHOLD - * @rssihint: RSSI Detect High. - * true - Indicatedes that the value is below - * FM_RSQ_RSSI_HIGH_THRESHOLD - * false - Indicatedes that the value is above - * FM_RSQ_RSSI_HIGH_THRESHOLD - * @rssilint: RSSI Detect Low. - * true - Indicatedes that the value is below - * FM_RSQ_RSSI_LOW_THRESHOLD - * false - Indicatedes that the value is above - * FM_RSQ_RSSI_LOW_THRESHOLD - * @bltf: Band Limit. - * Set if seek command hits the band limit or wrapped to - * the original frequency. - * @snr_ready: SNR measurement in progress. - * @rssiready: RSSI measurement in progress. - * @afcrl: Set if FREQOFF >= MAX_TUNE_ERROR - * @valid: Set if the channel is valid - * rssi < FM_VALID_RSSI_THRESHOLD - * snr < FM_VALID_SNR_THRESHOLD - * tune_error < FM_VALID_MAX_TUNE_ERROR - * @readfreq: Current tuned frequency. - * @freqoff: Signed frequency offset. - * @rssi: Received Signal Strength Indicator(dBuV). - * @snr: RF SNR Indicator(dB). - * @lassi: - * @hassi: Low/High side Adjacent(100 kHz) Channel Strength Indicator - * @mult: Multipath indicator - * @dev: Who knows? But values may vary. - * @readantcap: Antenna tuning capacity value. - * @assi: Adjacent Channel(+/- 200kHz) Strength Indicator - * @usn: Ultrasonic Noise Inticator in -DBFS - */ -struct si476x_rsq_status_report { - __u8 multhint, multlint; - __u8 snrhint, snrlint; - __u8 rssihint, rssilint; - __u8 bltf; - __u8 snr_ready; - __u8 rssiready; - __u8 injside; - __u8 afcrl; - __u8 valid; - - __u16 readfreq; - __s8 freqoff; - __s8 rssi; - __s8 snr; - __s8 issi; - __s8 lassi, hassi; - __s8 mult; - __u8 dev; - __u16 readantcap; - __s8 assi; - __s8 usn; - - __u8 pilotdev; - __u8 rdsdev; - __u8 assidev; - __u8 strongdev; - __u16 rdspi; -} __packed; - -/** - * si476x_acf_status_report - ACF report results - * - * @blend_int: If set, indicates that stereo separation has crossed - * below the blend threshold as set by FM_ACF_BLEND_THRESHOLD - * @hblend_int: If set, indicates that HiBlend cutoff frequency is - * lower than threshold as set by FM_ACF_HBLEND_THRESHOLD - * @hicut_int: If set, indicates that HiCut cutoff frequency is lower - * than the threshold set by ACF_ - - */ -struct si476x_acf_status_report { - __u8 blend_int; - __u8 hblend_int; - __u8 hicut_int; - __u8 chbw_int; - __u8 softmute_int; - __u8 smute; - __u8 smattn; - __u8 chbw; - __u8 hicut; - __u8 hiblend; - __u8 pilot; - __u8 stblend; -} __packed; - -enum si476x_fmagc { - SI476X_FMAGC_10K_OHM = 0, - SI476X_FMAGC_800_OHM = 1, - SI476X_FMAGC_400_OHM = 2, - SI476X_FMAGC_200_OHM = 4, - SI476X_FMAGC_100_OHM = 8, - SI476X_FMAGC_50_OHM = 16, - SI476X_FMAGC_25_OHM = 32, - SI476X_FMAGC_12P5_OHM = 64, - SI476X_FMAGC_6P25_OHM = 128, -}; - -struct si476x_agc_status_report { - __u8 mxhi; - __u8 mxlo; - __u8 lnahi; - __u8 lnalo; - __u8 fmagc1; - __u8 fmagc2; - __u8 pgagain; - __u8 fmwblang; -} __packed; - -struct si476x_rds_blockcount_report { - __u16 expected; - __u16 received; - __u16 uncorrectable; -} __packed; - -#endif /* SI476X_H*/ -- cgit v1.2.3 From b879a9c2a755d4ddf9e685258de6435710fd2f03 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 18 Apr 2013 20:46:08 -0300 Subject: [media] v4l2: Add a V4L2 driver for SI476X MFD This commit adds a driver that exposes all the radio related functionality of the Si476x series of chips via the V4L2 subsystem. [mchehab@redhat.com: change it to depends on MFD_SI476X_CORE instead of selecting it; vidioc_s_register now uses const struct] Acked-by: Hans Verkuil Signed-off-by: Andrey Smirnov Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/si476x.txt | 187 ++++ drivers/media/radio/Kconfig | 16 + drivers/media/radio/Makefile | 1 + drivers/media/radio/radio-si476x.c | 1599 ++++++++++++++++++++++++++++++++++ include/media/si476x.h | 37 + 5 files changed, 1840 insertions(+) create mode 100644 Documentation/video4linux/si476x.txt create mode 100644 drivers/media/radio/radio-si476x.c create mode 100644 include/media/si476x.h (limited to 'Documentation') diff --git a/Documentation/video4linux/si476x.txt b/Documentation/video4linux/si476x.txt new file mode 100644 index 000000000000..d1a08db2cbd9 --- /dev/null +++ b/Documentation/video4linux/si476x.txt @@ -0,0 +1,187 @@ +SI476x Driver Readme +------------------------------------------------ + Copyright (C) 2013 Andrey Smirnov + +TODO for the driver +------------------------------ + +- According to the SiLabs' datasheet it is possible to update the + firmware of the radio chip in the run-time, thus bringing it to the + most recent version. Unfortunately I couldn't find any mentioning of + the said firmware update for the old chips that I tested the driver + against, so for chips like that the driver only exposes the old + functionality. + + +Parameters exposed over debugfs +------------------------------- +SI476x allow user to get multiple characteristics that can be very +useful for EoL testing/RF performance estimation, parameters that have +very little to do with V4L2 subsystem. Such parameters are exposed via +debugfs and can be accessed via regular file I/O operations. + +The drivers exposes following files: + +* /sys/kernel/debug//acf + This file contains ACF(Automatically Controlled Features) status + information. The contents of the file is binary data of the + following layout: + + Offset | Name | Description + ==================================================================== + 0x00 | blend_int | Flag, set when stereo separation has + | | crossed below the blend threshold + -------------------------------------------------------------------- + 0x01 | hblend_int | Flag, set when HiBlend cutoff + | | frequency is lower than threshold + -------------------------------------------------------------------- + 0x02 | hicut_int | Flag, set when HiCut cutoff + | | frequency is lower than threshold + -------------------------------------------------------------------- + 0x03 | chbw_int | Flag, set when channel filter + | | bandwidth is less than threshold + -------------------------------------------------------------------- + 0x04 | softmute_int | Flag indicating that softmute + | | attenuation has increased above + | | softmute threshold + -------------------------------------------------------------------- + 0x05 | smute | 0 - Audio is not soft muted + | | 1 - Audio is soft muted + -------------------------------------------------------------------- + 0x06 | smattn | Soft mute attenuation level in dB + -------------------------------------------------------------------- + 0x07 | chbw | Channel filter bandwidth in kHz + -------------------------------------------------------------------- + 0x08 | hicut | HiCut cutoff frequency in units of + | | 100Hz + -------------------------------------------------------------------- + 0x09 | hiblend | HiBlend cutoff frequency in units + | | of 100 Hz + -------------------------------------------------------------------- + 0x10 | pilot | 0 - Stereo pilot is not present + | | 1 - Stereo pilot is present + -------------------------------------------------------------------- + 0x11 | stblend | Stereo blend in % + -------------------------------------------------------------------- + + +* /sys/kernel/debug//rds_blckcnt + This file contains statistics about RDS receptions. It's binary data + has the following layout: + + Offset | Name | Description + ==================================================================== + 0x00 | expected | Number of expected RDS blocks + -------------------------------------------------------------------- + 0x02 | received | Number of received RDS blocks + -------------------------------------------------------------------- + 0x04 | uncorrectable | Number of uncorrectable RDS blocks + -------------------------------------------------------------------- + +* /sys/kernel/debug//agc + This file contains information about parameters pertaining to + AGC(Automatic Gain Control) + + The layout is: + Offset | Name | Description + ==================================================================== + 0x00 | mxhi | 0 - FM Mixer PD high threshold is + | | not tripped + | | 1 - FM Mixer PD high threshold is + | | tripped + -------------------------------------------------------------------- + 0x01 | mxlo | ditto for FM Mixer PD low + -------------------------------------------------------------------- + 0x02 | lnahi | ditto for FM LNA PD high + -------------------------------------------------------------------- + 0x03 | lnalo | ditto for FM LNA PD low + -------------------------------------------------------------------- + 0x04 | fmagc1 | FMAGC1 attenuator resistance + | | (see datasheet for more detail) + -------------------------------------------------------------------- + 0x05 | fmagc2 | ditto for FMAGC2 + -------------------------------------------------------------------- + 0x06 | pgagain | PGA gain in dB + -------------------------------------------------------------------- + 0x07 | fmwblang | FM/WB LNA Gain in dB + -------------------------------------------------------------------- + +* /sys/kernel/debug//rsq + This file contains information about parameters pertaining to + RSQ(Received Signal Quality) + + The layout is: + Offset | Name | Description + ==================================================================== + 0x00 | multhint | 0 - multipath value has not crossed + | | the Multipath high threshold + | | 1 - multipath value has crossed + | | the Multipath high threshold + -------------------------------------------------------------------- + 0x01 | multlint | ditto for Multipath low threshold + -------------------------------------------------------------------- + 0x02 | snrhint | 0 - received signal's SNR has not + | | crossed high threshold + | | 1 - received signal's SNR has + | | crossed high threshold + -------------------------------------------------------------------- + 0x03 | snrlint | ditto for low threshold + -------------------------------------------------------------------- + 0x04 | rssihint | ditto for RSSI high threshold + -------------------------------------------------------------------- + 0x05 | rssilint | ditto for RSSI low threshold + -------------------------------------------------------------------- + 0x06 | bltf | Flag indicating if seek command + | | reached/wrapped seek band limit + -------------------------------------------------------------------- + 0x07 | snr_ready | Indicates that SNR metrics is ready + -------------------------------------------------------------------- + 0x08 | rssiready | ditto for RSSI metrics + -------------------------------------------------------------------- + 0x09 | injside | 0 - Low-side injection is being used + | | 1 - High-side injection is used + -------------------------------------------------------------------- + 0x10 | afcrl | Flag indicating if AFC rails + -------------------------------------------------------------------- + 0x11 | valid | Flag indicating if channel is valid + -------------------------------------------------------------------- + 0x12 | readfreq | Current tuned frequency + -------------------------------------------------------------------- + 0x14 | freqoff | Singed frequency offset in units of + | | 2ppm + -------------------------------------------------------------------- + 0x15 | rssi | Signed value of RSSI in dBuV + -------------------------------------------------------------------- + 0x16 | snr | Signed RF SNR in dB + -------------------------------------------------------------------- + 0x17 | issi | Signed Image Strength Signal + | | indicator + -------------------------------------------------------------------- + 0x18 | lassi | Signed Low side adjacent Channel + | | Strength indicator + -------------------------------------------------------------------- + 0x19 | hassi | ditto fpr High side + -------------------------------------------------------------------- + 0x20 | mult | Multipath indicator + -------------------------------------------------------------------- + 0x21 | dev | Frequency deviation + -------------------------------------------------------------------- + 0x24 | assi | Adjascent channel SSI + -------------------------------------------------------------------- + 0x25 | usn | Ultrasonic noise indicator + -------------------------------------------------------------------- + 0x26 | pilotdev | Pilot deviation in units of 100 Hz + -------------------------------------------------------------------- + 0x27 | rdsdev | ditto for RDS + -------------------------------------------------------------------- + 0x28 | assidev | ditto for ASSI + -------------------------------------------------------------------- + 0x29 | strongdev | Frequency deviation + -------------------------------------------------------------------- + 0x30 | rdspi | RDS PI code + -------------------------------------------------------------------- + +* /sys/kernel/debug//rsq_primary + This file contains information about parameters pertaining to + RSQ(Received Signal Quality) for primary tuner only. Layout is as + the one above. diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 24e64a09884c..c0beee2fa37c 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -18,6 +18,22 @@ config RADIO_SI470X source "drivers/media/radio/si470x/Kconfig" +config RADIO_SI476X + tristate "Silicon Laboratories Si476x I2C FM Radio" + depends on I2C && VIDEO_V4L2 + depends on MFD_SI476X_CORE + select SND_SOC_SI476X + ---help--- + Choose Y here if you have this FM radio chip. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux 2 API. Information on + this API and pointers to "v4l2" programs may be found at + . + + To compile this driver as a module, choose M here: the + module will be called radio-si476x. + config USB_MR800 tristate "AverMedia MR 800 USB FM radio support" depends on USB && VIDEO_V4L2 diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 303eaebdb85a..0dcdb320cfc7 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o obj-$(CONFIG_RADIO_TRUST) += radio-trust.o obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o +obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o obj-$(CONFIG_USB_DSBR) += dsbr100.o obj-$(CONFIG_RADIO_SI470X) += si470x/ diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c new file mode 100644 index 000000000000..9430c6a29937 --- /dev/null +++ b/drivers/media/radio/radio-si476x.c @@ -0,0 +1,1599 @@ +/* + * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FM_FREQ_RANGE_LOW 64000000 +#define FM_FREQ_RANGE_HIGH 108000000 + +#define AM_FREQ_RANGE_LOW 520000 +#define AM_FREQ_RANGE_HIGH 30000000 + +#define PWRLINEFLTR (1 << 8) + +#define FREQ_MUL (10000000 / 625) + +#define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0b10000000 & (status)) + +#define DRIVER_NAME "si476x-radio" +#define DRIVER_CARD "SI476x AM/FM Receiver" + +enum si476x_freq_bands { + SI476X_BAND_FM, + SI476X_BAND_AM, +}; + +static const struct v4l2_frequency_band si476x_bands[] = { + [SI476X_BAND_FM] = { + .type = V4L2_TUNER_RADIO, + .index = SI476X_BAND_FM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 64 * FREQ_MUL, + .rangehigh = 108 * FREQ_MUL, + .modulation = V4L2_BAND_MODULATION_FM, + }, + [SI476X_BAND_AM] = { + .type = V4L2_TUNER_RADIO, + .index = SI476X_BAND_AM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 0.52 * FREQ_MUL, + .rangehigh = 30 * FREQ_MUL, + .modulation = V4L2_BAND_MODULATION_AM, + }, +}; + +static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band) +{ + return freq >= si476x_bands[band].rangelow && + freq <= si476x_bands[band].rangehigh; +} + +static inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high, + int band) +{ + return low >= si476x_bands[band].rangelow && + high <= si476x_bands[band].rangehigh; +} + +static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl); +static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); + +enum phase_diversity_modes_idx { + SI476X_IDX_PHDIV_DISABLED, + SI476X_IDX_PHDIV_PRIMARY_COMBINING, + SI476X_IDX_PHDIV_PRIMARY_ANTENNA, + SI476X_IDX_PHDIV_SECONDARY_ANTENNA, + SI476X_IDX_PHDIV_SECONDARY_COMBINING, +}; + +static const char * const phase_diversity_modes[] = { + [SI476X_IDX_PHDIV_DISABLED] = "Disabled", + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary", + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna", + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna", + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary", +}; + +static inline enum phase_diversity_modes_idx +si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode) +{ + switch (mode) { + default: /* FALLTHROUGH */ + case SI476X_PHDIV_DISABLED: + return SI476X_IDX_PHDIV_DISABLED; + case SI476X_PHDIV_PRIMARY_COMBINING: + return SI476X_IDX_PHDIV_PRIMARY_COMBINING; + case SI476X_PHDIV_PRIMARY_ANTENNA: + return SI476X_IDX_PHDIV_PRIMARY_ANTENNA; + case SI476X_PHDIV_SECONDARY_ANTENNA: + return SI476X_IDX_PHDIV_SECONDARY_ANTENNA; + case SI476X_PHDIV_SECONDARY_COMBINING: + return SI476X_IDX_PHDIV_SECONDARY_COMBINING; + } +} + +static inline enum si476x_phase_diversity_mode +si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx) +{ + static const int idx_to_value[] = { + [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED, + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING, + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA, + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA, + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING, + }; + + return idx_to_value[idx]; +} + +static const struct v4l2_ctrl_ops si476x_ctrl_ops = { + .g_volatile_ctrl = si476x_radio_g_volatile_ctrl, + .s_ctrl = si476x_radio_s_ctrl, +}; + + +enum si476x_ctrl_idx { + SI476X_IDX_RSSI_THRESHOLD, + SI476X_IDX_SNR_THRESHOLD, + SI476X_IDX_MAX_TUNE_ERROR, + SI476X_IDX_HARMONICS_COUNT, + SI476X_IDX_DIVERSITY_MODE, + SI476X_IDX_INTERCHIP_LINK, +}; +static struct v4l2_ctrl_config si476x_ctrls[] = { + + /** + * SI476X during its station seeking(or tuning) process uses several + * parameters to detrmine if "the station" is valid: + * + * - Signal's SNR(in dBuV) must be lower than + * #V4L2_CID_SI476X_SNR_THRESHOLD + * - Signal's RSSI(in dBuV) must be greater than + * #V4L2_CID_SI476X_RSSI_THRESHOLD + * - Signal's frequency deviation(in units of 2ppm) must not be + * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR + */ + [SI476X_IDX_RSSI_THRESHOLD] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_RSSI_THRESHOLD, + .name = "Valid RSSI Threshold", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = -128, + .max = 127, + .step = 1, + }, + [SI476X_IDX_SNR_THRESHOLD] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_SNR_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Valid SNR Threshold", + .min = -128, + .max = 127, + .step = 1, + }, + [SI476X_IDX_MAX_TUNE_ERROR] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_MAX_TUNE_ERROR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Max Tune Errors", + .min = 0, + .max = 126 * 2, + .step = 2, + }, + + /** + * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics + * built-in power-line noise supression filter is to reject + * during AM-mode operation. + */ + [SI476X_IDX_HARMONICS_COUNT] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_HARMONICS_COUNT, + .type = V4L2_CTRL_TYPE_INTEGER, + + .name = "Count of Harmonics to Reject", + .min = 0, + .max = 20, + .step = 1, + }, + + /** + * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which + * two tuners working in diversity mode are to work in. + * + * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled + * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is + * on, primary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is + * off, primary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is + * off, secondary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is + * on, secondary tuner's antenna is the main one. + */ + [SI476X_IDX_DIVERSITY_MODE] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_DIVERSITY_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Phase Diversity Mode", + .qmenu = phase_diversity_modes, + .min = 0, + .max = ARRAY_SIZE(phase_diversity_modes) - 1, + }, + + /** + * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in + * diversity mode indicator. Allows user to determine if two + * chips working in diversity mode have established a link + * between each other and if the system as a whole uses + * signals from both antennas to receive FM radio. + */ + [SI476X_IDX_INTERCHIP_LINK] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_INTERCHIP_LINK, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, + .name = "Inter-Chip Link", + .min = 0, + .max = 1, + .step = 1, + }, +}; + +struct si476x_radio; + +/** + * struct si476x_radio_ops - vtable of tuner functions + * + * This table holds pointers to functions implementing particular + * operations depending on the mode in which the tuner chip was + * configured to start in. If the function is not supported + * corresponding element is set to #NULL. + * + * @tune_freq: Tune chip to a specific frequency + * @seek_start: Star station seeking + * @rsq_status: Get Recieved Signal Quality(RSQ) status + * @rds_blckcnt: Get recived RDS blocks count + * @phase_diversity: Change phase diversity mode of the tuner + * @phase_div_status: Get phase diversity mode status + * @acf_status: Get the status of Automatically Controlled + * Features(ACF) + * @agc_status: Get Automatic Gain Control(AGC) status + */ +struct si476x_radio_ops { + int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *); + int (*seek_start)(struct si476x_core *, bool, bool); + int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *, + struct si476x_rsq_status_report *); + int (*rds_blckcnt)(struct si476x_core *, bool, + struct si476x_rds_blockcount_report *); + + int (*phase_diversity)(struct si476x_core *, + enum si476x_phase_diversity_mode); + int (*phase_div_status)(struct si476x_core *); + int (*acf_status)(struct si476x_core *, + struct si476x_acf_status_report *); + int (*agc_status)(struct si476x_core *, + struct si476x_agc_status_report *); +}; + +/** + * struct si476x_radio - radio device + * + * @core: Pointer to underlying core device + * @videodev: Pointer to video device created by V4L2 subsystem + * @ops: Vtable of functions. See struct si476x_radio_ops for details + * @kref: Reference counter + * @core_lock: An r/w semaphore to brebvent the deletion of underlying + * core structure is the radio device is being used + */ +struct si476x_radio { + struct v4l2_device v4l2dev; + struct video_device videodev; + struct v4l2_ctrl_handler ctrl_handler; + + struct si476x_core *core; + /* This field should not be accesses unless core lock is held */ + const struct si476x_radio_ops *ops; + + struct dentry *debugfs; + u32 audmode; +}; + +static inline struct si476x_radio * +v4l2_dev_to_radio(struct v4l2_device *d) +{ + return container_of(d, struct si476x_radio, v4l2dev); +} + +static inline struct si476x_radio * +v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) +{ + return container_of(d, struct si476x_radio, ctrl_handler); +} + +/* + * si476x_vidioc_querycap - query device capabilities + */ +static int si476x_radio_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct si476x_radio *radio = video_drvdata(file); + + strlcpy(capability->driver, radio->v4l2dev.name, + sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", radio->v4l2dev.name); + + capability->device_caps = V4L2_CAP_TUNER + | V4L2_CAP_RADIO + | V4L2_CAP_HW_FREQ_SEEK; + + si476x_core_lock(radio->core); + if (!si476x_core_is_a_secondary_tuner(radio->core)) + capability->device_caps |= V4L2_CAP_RDS_CAPTURE + | V4L2_CAP_READWRITE; + si476x_core_unlock(radio->core); + + capability->capabilities = capability->device_caps + | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int si476x_radio_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (band->tuner != 0) + return -EINVAL; + + switch (radio->core->chip_id) { + /* AM/FM tuners -- all bands are supported */ + case SI476X_CHIP_SI4761: + case SI476X_CHIP_SI4764: + if (band->index < ARRAY_SIZE(si476x_bands)) { + *band = si476x_bands[band->index]; + err = 0; + } else { + err = -EINVAL; + } + break; + /* FM companion tuner chips -- only FM bands are + * supported */ + case SI476X_CHIP_SI4768: + if (band->index == SI476X_BAND_FM) { + *band = si476x_bands[band->index]; + err = 0; + } else { + err = -EINVAL; + } + break; + default: + err = -EINVAL; + } + + return err; +} + +static int si476x_radio_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + int err; + struct si476x_rsq_status_report report; + struct si476x_radio *radio = video_drvdata(file); + + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + if (tuner->index != 0) + return -EINVAL; + + tuner->type = V4L2_TUNER_RADIO; + tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies + * in multiples of + * 62.5 Hz */ + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_HWSEEK_BOUNDED + | V4L2_TUNER_CAP_HWSEEK_WRAP + | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; + + si476x_core_lock(radio->core); + + if (si476x_core_is_a_secondary_tuner(radio->core)) { + strlcpy(tuner->name, "FM (secondary)", sizeof(tuner->name)); + tuner->rxsubchans = 0; + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; + } else if (si476x_core_has_am(radio->core)) { + if (si476x_core_is_a_primary_tuner(radio->core)) + strlcpy(tuner->name, "AM/FM (primary)", + sizeof(tuner->name)); + else + strlcpy(tuner->name, "AM/FM", sizeof(tuner->name)); + + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO + | V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + + tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow; + } else { + strlcpy(tuner->name, "FM", sizeof(tuner->name)); + tuner->rxsubchans = V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; + } + + tuner->audmode = radio->audmode; + + tuner->afc = 1; + tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh; + + err = radio->ops->rsq_status(radio->core, + &args, &report); + if (err < 0) { + tuner->signal = 0; + } else { + /* + * tuner->signal value range: 0x0000 .. 0xFFFF, + * report.rssi: -128 .. 127 + */ + tuner->signal = (report.rssi + 128) * 257; + } + si476x_core_unlock(radio->core); + + return err; +} + +static int si476x_radio_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *tuner) +{ + struct si476x_radio *radio = video_drvdata(file); + + if (tuner->index != 0) + return -EINVAL; + + if (tuner->audmode == V4L2_TUNER_MODE_MONO || + tuner->audmode == V4L2_TUNER_MODE_STEREO) + radio->audmode = tuner->audmode; + else + radio->audmode = V4L2_TUNER_MODE_STEREO; + + return 0; +} + +static int si476x_radio_init_vtable(struct si476x_radio *radio, + enum si476x_func func) +{ + static const struct si476x_radio_ops fm_ops = { + .tune_freq = si476x_core_cmd_fm_tune_freq, + .seek_start = si476x_core_cmd_fm_seek_start, + .rsq_status = si476x_core_cmd_fm_rsq_status, + .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount, + .phase_diversity = si476x_core_cmd_fm_phase_diversity, + .phase_div_status = si476x_core_cmd_fm_phase_div_status, + .acf_status = si476x_core_cmd_fm_acf_status, + .agc_status = si476x_core_cmd_agc_status, + }; + + static const struct si476x_radio_ops am_ops = { + .tune_freq = si476x_core_cmd_am_tune_freq, + .seek_start = si476x_core_cmd_am_seek_start, + .rsq_status = si476x_core_cmd_am_rsq_status, + .rds_blckcnt = NULL, + .phase_diversity = NULL, + .phase_div_status = NULL, + .acf_status = si476x_core_cmd_am_acf_status, + .agc_status = NULL, + }; + + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + radio->ops = &fm_ops; + return 0; + + case SI476X_FUNC_AM_RECEIVER: + radio->ops = &am_ops; + return 0; + default: + WARN(1, "Unexpected tuner function value\n"); + return -EINVAL; + } +} + +static int si476x_radio_pretune(struct si476x_radio *radio, + enum si476x_func func) +{ + int retval; + + struct si476x_tune_freq_args args = { + .zifsr = false, + .hd = false, + .injside = SI476X_INJSIDE_AUTO, + .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE, + .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO, + .antcap = 0, + }; + + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + args.freq = v4l2_to_si476x(radio->core, + 92 * FREQ_MUL); + retval = radio->ops->tune_freq(radio->core, &args); + break; + case SI476X_FUNC_AM_RECEIVER: + args.freq = v4l2_to_si476x(radio->core, + 0.6 * FREQ_MUL); + retval = radio->ops->tune_freq(radio->core, &args); + break; + default: + WARN(1, "Unexpected tuner function value\n"); + retval = -EINVAL; + } + + return retval; +} +static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio, + enum si476x_func func) +{ + int err; + + /* regcache_mark_dirty(radio->core->regmap); */ + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE, + SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_AUDIO_DEEMPHASIS, + SI476X_PROP_AUDIO_PWR_LINE_FILTER); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_INT_CTL_ENABLE, + SI476X_PROP_INT_CTL_ENABLE); + if (err < 0) + return err; + + /* + * Is there any point in restoring SNR and the like + * when switching between AM/FM? + */ + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_VALID_MAX_TUNE_ERROR, + SI476X_PROP_VALID_MAX_TUNE_ERROR); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_VALID_SNR_THRESHOLD, + SI476X_PROP_VALID_RSSI_THRESHOLD); + if (err < 0) + return err; + + if (func == SI476X_FUNC_FM_RECEIVER) { + if (si476x_core_has_diversity(radio->core)) { + err = si476x_core_cmd_fm_phase_diversity(radio->core, + radio->core->diversity_mode); + if (err < 0) + return err; + } + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, + SI476X_PROP_FM_RDS_CONFIG); + if (err < 0) + return err; + } + + return si476x_radio_init_vtable(radio, func); + +} + +static int si476x_radio_change_func(struct si476x_radio *radio, + enum si476x_func func) +{ + int err; + bool soft; + /* + * Since power/up down is a very time consuming operation, + * try to avoid doing it if the requested mode matches the one + * the tuner is in + */ + if (func == radio->core->power_up_parameters.func) + return 0; + + soft = true; + err = si476x_core_stop(radio->core, soft); + if (err < 0) { + /* + * OK, if the chip does not want to play nice let's + * try to reset it in more brutal way + */ + soft = false; + err = si476x_core_stop(radio->core, soft); + if (err < 0) + return err; + } + /* + Set the desired radio tuner function + */ + radio->core->power_up_parameters.func = func; + + err = si476x_core_start(radio->core, soft); + if (err < 0) + return err; + + /* + * No need to do the rest of manipulations for the bootlader + * mode + */ + if (func != SI476X_FUNC_FM_RECEIVER && + func != SI476X_FUNC_AM_RECEIVER) + return err; + + return si476x_radio_do_post_powerup_init(radio, func); +} + +static int si476x_radio_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + if (radio->ops->rsq_status) { + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = true, + .cancel = false, + .stcack = false, + }; + + err = radio->ops->rsq_status(radio->core, &args, &report); + if (!err) + f->frequency = si476x_to_v4l2(radio->core, + report.readfreq); + } else { + err = -EINVAL; + } + + si476x_core_unlock(radio->core); + + return err; +} + +static int si476x_radio_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + int err; + u32 freq = f->frequency; + struct si476x_tune_freq_args args; + struct si476x_radio *radio = video_drvdata(file); + + const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh + + si476x_bands[SI476X_BAND_FM].rangelow) / 2; + const int band = (freq > midrange) ? + SI476X_BAND_FM : SI476X_BAND_AM; + const enum si476x_func func = (band == SI476X_BAND_AM) ? + SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER; + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + freq = clamp(freq, + si476x_bands[band].rangelow, + si476x_bands[band].rangehigh); + + if (si476x_radio_freq_is_inside_of_the_band(freq, + SI476X_BAND_AM) && + (!si476x_core_has_am(radio->core) || + si476x_core_is_a_secondary_tuner(radio->core))) { + err = -EINVAL; + goto unlock; + } + + err = si476x_radio_change_func(radio, func); + if (err < 0) + goto unlock; + + args.zifsr = false; + args.hd = false; + args.injside = SI476X_INJSIDE_AUTO; + args.freq = v4l2_to_si476x(radio->core, freq); + args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE; + args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO; + args.antcap = 0; + + err = radio->ops->tune_freq(radio->core, &args); + +unlock: + si476x_core_unlock(radio->core); + return err; +} + +static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + int err; + enum si476x_func func; + u32 rangelow, rangehigh; + struct si476x_radio *radio = video_drvdata(file); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (seek->tuner != 0 || + seek->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + if (!seek->rangelow) { + err = regmap_read(radio->core->regmap, + SI476X_PROP_SEEK_BAND_BOTTOM, + &rangelow); + if (!err) + rangelow = si476x_to_v4l2(radio->core, rangelow); + else + goto unlock; + } + if (!seek->rangehigh) { + err = regmap_read(radio->core->regmap, + SI476X_PROP_SEEK_BAND_TOP, + &rangehigh); + if (!err) + rangehigh = si476x_to_v4l2(radio->core, rangehigh); + else + goto unlock; + } + + if (rangelow > rangehigh) { + err = -EINVAL; + goto unlock; + } + + if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, + SI476X_BAND_FM)) { + func = SI476X_FUNC_FM_RECEIVER; + + } else if (si476x_core_has_am(radio->core) && + si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, + SI476X_BAND_AM)) { + func = SI476X_FUNC_AM_RECEIVER; + } else { + err = -EINVAL; + goto unlock; + } + + err = si476x_radio_change_func(radio, func); + if (err < 0) + goto unlock; + + if (seek->rangehigh) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_BAND_TOP, + v4l2_to_si476x(radio->core, + seek->rangehigh)); + if (err) + goto unlock; + } + if (seek->rangelow) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_BAND_BOTTOM, + v4l2_to_si476x(radio->core, + seek->rangelow)); + if (err) + goto unlock; + } + if (seek->spacing) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_FREQUENCY_SPACING, + v4l2_to_si476x(radio->core, + seek->spacing)); + if (err) + goto unlock; + } + + err = radio->ops->seek_start(radio->core, + seek->seek_upward, + seek->wrap_around); +unlock: + si476x_core_unlock(radio->core); + + + + return err; +} + +static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + int retval; + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + si476x_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_SI476X_INTERCHIP_LINK: + if (si476x_core_has_diversity(radio->core)) { + if (radio->ops->phase_diversity) { + retval = radio->ops->phase_div_status(radio->core); + if (retval < 0) + break; + + ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval); + retval = 0; + break; + } else { + retval = -ENOTTY; + break; + } + } + retval = -EINVAL; + break; + default: + retval = -EINVAL; + break; + } + si476x_core_unlock(radio->core); + return retval; + +} + +static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + int retval; + enum si476x_phase_diversity_mode mode; + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + si476x_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_SI476X_HARMONICS_COUNT: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_HARMONICS_MASK, + ctrl->val); + break; + case V4L2_CID_POWER_LINE_FREQUENCY: + switch (ctrl->val) { + case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_ENABLE_MASK, + 0); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_GRID_MASK, + SI476X_PROP_PWR_GRID_50HZ); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_GRID_MASK, + SI476X_PROP_PWR_GRID_60HZ); + break; + default: + retval = -EINVAL; + break; + } + break; + case V4L2_CID_SI476X_RSSI_THRESHOLD: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_RSSI_THRESHOLD, + ctrl->val); + break; + case V4L2_CID_SI476X_SNR_THRESHOLD: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_SNR_THRESHOLD, + ctrl->val); + break; + case V4L2_CID_SI476X_MAX_TUNE_ERROR: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_MAX_TUNE_ERROR, + ctrl->val); + break; + case V4L2_CID_RDS_RECEPTION: + /* + * It looks like RDS related properties are + * inaccesable when tuner is in AM mode, so cache the + * changes + */ + if (si476x_core_is_in_am_receiver_mode(radio->core)) + regcache_cache_only(radio->core->regmap, true); + + if (ctrl->val) { + retval = regmap_write(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT, + radio->core->rds_fifo_depth); + if (retval < 0) + break; + + if (radio->core->client->irq) { + retval = regmap_write(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, + SI476X_RDSRECV); + if (retval < 0) + break; + } + + /* Drain RDS FIFO before enabling RDS processing */ + retval = si476x_core_cmd_fm_rds_status(radio->core, + false, + true, + true, + NULL); + if (retval < 0) + break; + + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_FM_RDS_CONFIG, + SI476X_PROP_RDSEN_MASK, + SI476X_PROP_RDSEN); + } else { + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_FM_RDS_CONFIG, + SI476X_PROP_RDSEN_MASK, + !SI476X_PROP_RDSEN); + } + + if (si476x_core_is_in_am_receiver_mode(radio->core)) + regcache_cache_only(radio->core->regmap, false); + break; + case V4L2_CID_TUNE_DEEMPHASIS: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_AUDIO_DEEMPHASIS, + ctrl->val); + break; + + case V4L2_CID_SI476X_DIVERSITY_MODE: + mode = si476x_phase_diversity_idx_to_mode(ctrl->val); + + if (mode == radio->core->diversity_mode) { + retval = 0; + break; + } + + if (si476x_core_is_in_am_receiver_mode(radio->core)) { + /* + * Diversity cannot be configured while tuner + * is in AM mode so save the changes and carry on. + */ + radio->core->diversity_mode = mode; + retval = 0; + } else { + retval = radio->ops->phase_diversity(radio->core, mode); + if (!retval) + radio->core->diversity_mode = mode; + } + break; + + default: + retval = -EINVAL; + break; + } + + si476x_core_unlock(radio->core); + + return retval; +} + +static int si476x_radio_g_chip_ident(struct file *file, void *fh, + struct v4l2_dbg_chip_ident *chip) +{ + if (chip->match.type == V4L2_CHIP_MATCH_HOST && + v4l2_chip_match_host(&chip->match)) + return 0; + return -EINVAL; +} + + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int si476x_radio_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + int err; + unsigned int value; + struct si476x_radio *radio = video_drvdata(file); + + si476x_core_lock(radio->core); + reg->size = 2; + err = regmap_read(radio->core->regmap, + (unsigned int)reg->reg, &value); + reg->val = value; + si476x_core_unlock(radio->core); + + return err; +} +static int si476x_radio_s_register(struct file *file, void *fh, + const struct v4l2_dbg_register *reg) +{ + + int err; + struct si476x_radio *radio = video_drvdata(file); + + si476x_core_lock(radio->core); + err = regmap_write(radio->core->regmap, + (unsigned int)reg->reg, + (unsigned int)reg->val); + si476x_core_unlock(radio->core); + + return err; +} +#endif + +static int si476x_radio_fops_open(struct file *file) +{ + struct si476x_radio *radio = video_drvdata(file); + int err; + + err = v4l2_fh_open(file); + if (err) + return err; + + if (v4l2_fh_is_singular_file(file)) { + si476x_core_lock(radio->core); + err = si476x_core_set_power_state(radio->core, + SI476X_POWER_UP_FULL); + if (err < 0) + goto done; + + err = si476x_radio_do_post_powerup_init(radio, + radio->core->power_up_parameters.func); + if (err < 0) + goto power_down; + + err = si476x_radio_pretune(radio, + radio->core->power_up_parameters.func); + if (err < 0) + goto power_down; + + si476x_core_unlock(radio->core); + /*Must be done after si476x_core_unlock to prevent a deadlock*/ + v4l2_ctrl_handler_setup(&radio->ctrl_handler); + } + + return err; + +power_down: + si476x_core_set_power_state(radio->core, + SI476X_POWER_DOWN); +done: + si476x_core_unlock(radio->core); + v4l2_fh_release(file); + + return err; +} + +static int si476x_radio_fops_release(struct file *file) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (v4l2_fh_is_singular_file(file) && + atomic_read(&radio->core->is_alive)) + si476x_core_set_power_state(radio->core, + SI476X_POWER_DOWN); + + err = v4l2_fh_release(file); + + return err; +} + +static ssize_t si476x_radio_fops_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t rval; + size_t fifo_len; + unsigned int copied; + + struct si476x_radio *radio = video_drvdata(file); + + /* block if no new data available */ + if (kfifo_is_empty(&radio->core->rds_fifo)) { + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + rval = wait_event_interruptible(radio->core->rds_read_queue, + (!kfifo_is_empty(&radio->core->rds_fifo) || + !atomic_read(&radio->core->is_alive))); + if (rval < 0) + return -EINTR; + + if (!atomic_read(&radio->core->is_alive)) + return -ENODEV; + } + + fifo_len = kfifo_len(&radio->core->rds_fifo); + + if (kfifo_to_user(&radio->core->rds_fifo, buf, + min(fifo_len, count), + &copied) != 0) { + dev_warn(&radio->videodev.dev, + "Error during FIFO to userspace copy\n"); + rval = -EIO; + } else { + rval = (ssize_t)copied; + } + + return rval; +} + +static unsigned int si476x_radio_fops_poll(struct file *file, + struct poll_table_struct *pts) +{ + struct si476x_radio *radio = video_drvdata(file); + unsigned long req_events = poll_requested_events(pts); + unsigned int err = v4l2_ctrl_poll(file, pts); + + if (req_events & (POLLIN | POLLRDNORM)) { + if (atomic_read(&radio->core->is_alive)) + poll_wait(file, &radio->core->rds_read_queue, pts); + + if (!atomic_read(&radio->core->is_alive)) + err = POLLHUP; + + if (!kfifo_is_empty(&radio->core->rds_fifo)) + err = POLLIN | POLLRDNORM; + } + + return err; +} + +static const struct v4l2_file_operations si476x_fops = { + .owner = THIS_MODULE, + .read = si476x_radio_fops_read, + .poll = si476x_radio_fops_poll, + .unlocked_ioctl = video_ioctl2, + .open = si476x_radio_fops_open, + .release = si476x_radio_fops_release, +}; + + +static const struct v4l2_ioctl_ops si4761_ioctl_ops = { + .vidioc_querycap = si476x_radio_querycap, + .vidioc_g_tuner = si476x_radio_g_tuner, + .vidioc_s_tuner = si476x_radio_s_tuner, + + .vidioc_g_frequency = si476x_radio_g_frequency, + .vidioc_s_frequency = si476x_radio_s_frequency, + .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek, + .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_g_chip_ident = si476x_radio_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = si476x_radio_g_register, + .vidioc_s_register = si476x_radio_s_register, +#endif +}; + + +static const struct video_device si476x_viddev_template = { + .fops = &si476x_fops, + .name = DRIVER_NAME, + .release = video_device_release_empty, +}; + + + +static ssize_t si476x_radio_read_acf_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_acf_status_report report; + + si476x_core_lock(radio->core); + if (radio->ops->acf_status) + err = radio->ops->acf_status(radio->core, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_acf_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_acf_blob, +}; + +static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rds_blockcount_report report; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rds_blckcnt(radio->core, true, + &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rds_blckcnt_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rds_blckcnt_blob, +}; + +static ssize_t si476x_radio_read_agc_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_agc_status_report report; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->agc_status(radio->core, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_agc_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_agc_blob, +}; + +static ssize_t si476x_radio_read_rsq_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rsq_status(radio->core, &args, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rsq_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rsq_blob, +}; + +static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = true, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rsq_status(radio->core, &args, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rsq_primary_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rsq_primary_blob, +}; + + +static int si476x_radio_init_debugfs(struct si476x_radio *radio) +{ + struct dentry *dentry; + int ret; + + dentry = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto exit; + } + radio->debugfs = dentry; + + dentry = debugfs_create_file("acf", S_IRUGO, + radio->debugfs, radio, &radio_acf_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rds_blckcnt", S_IRUGO, + radio->debugfs, radio, + &radio_rds_blckcnt_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("agc", S_IRUGO, + radio->debugfs, radio, &radio_agc_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rsq", S_IRUGO, + radio->debugfs, radio, &radio_rsq_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rsq_primary", S_IRUGO, + radio->debugfs, radio, + &radio_rsq_primary_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + return 0; +cleanup: + debugfs_remove_recursive(radio->debugfs); +exit: + return ret; +} + + +static int si476x_radio_add_new_custom(struct si476x_radio *radio, + enum si476x_ctrl_idx idx) +{ + int rval; + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, + &si476x_ctrls[idx], + NULL); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) + dev_err(radio->v4l2dev.dev, + "Could not initialize '%s' control %d\n", + si476x_ctrls[idx].name, rval); + + return rval; +} + +static int si476x_radio_probe(struct platform_device *pdev) +{ + int rval; + struct si476x_radio *radio; + struct v4l2_ctrl *ctrl; + + static atomic_t instance = ATOMIC_INIT(0); + + radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); + if (!radio) + return -ENOMEM; + + radio->core = i2c_mfd_cell_to_core(&pdev->dev); + + v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); + + rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev); + if (rval) { + dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); + return rval; + } + + memcpy(&radio->videodev, &si476x_viddev_template, + sizeof(struct video_device)); + + radio->videodev.v4l2_dev = &radio->v4l2dev; + radio->videodev.ioctl_ops = &si4761_ioctl_ops; + + video_set_drvdata(&radio->videodev, radio); + platform_set_drvdata(pdev, radio); + + set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags); + + radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; + v4l2_ctrl_handler_init(&radio->ctrl_handler, + 1 + ARRAY_SIZE(si476x_ctrls)); + + if (si476x_core_has_am(radio->core)) { + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, + &si476x_ctrl_ops, + V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_60HZ, + 0, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n", + rval); + goto exit; + } + + rval = si476x_radio_add_new_custom(radio, + SI476X_IDX_HARMONICS_COUNT); + if (rval < 0) + goto exit; + } + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD); + if (rval < 0) + goto exit; + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD); + if (rval < 0) + goto exit; + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR); + if (rval < 0) + goto exit; + + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, + &si476x_ctrl_ops, + V4L2_CID_TUNE_DEEMPHASIS, + V4L2_DEEMPHASIS_75_uS, 0, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", + rval); + goto exit; + } + + ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, + V4L2_CID_RDS_RECEPTION, + 0, 1, 1, 1); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n", + rval); + goto exit; + } + + if (si476x_core_has_diversity(radio->core)) { + si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = + si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); + si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE); + if (rval < 0) + goto exit; + + si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK); + if (rval < 0) + goto exit; + } + + /* register video device */ + rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); + if (rval < 0) { + dev_err(&pdev->dev, "Could not register video device\n"); + goto exit; + } + + rval = si476x_radio_init_debugfs(radio); + if (rval < 0) { + dev_err(&pdev->dev, "Could not creat debugfs interface\n"); + goto exit; + } + + return 0; +exit: + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + return rval; +} + +static int si476x_radio_remove(struct platform_device *pdev) +{ + struct si476x_radio *radio = platform_get_drvdata(pdev); + + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + video_unregister_device(&radio->videodev); + v4l2_device_unregister(&radio->v4l2dev); + debugfs_remove_recursive(radio->debugfs); + + return 0; +} + +MODULE_ALIAS("platform:si476x-radio"); + +static struct platform_driver si476x_radio_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = si476x_radio_probe, + .remove = si476x_radio_remove, +}; +module_platform_driver(si476x_radio_driver); + +MODULE_AUTHOR("Andrey Smirnov "); +MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell"); +MODULE_LICENSE("GPL"); diff --git a/include/media/si476x.h b/include/media/si476x.h new file mode 100644 index 000000000000..e02e241e2d22 --- /dev/null +++ b/include/media/si476x.h @@ -0,0 +1,37 @@ +/* + * include/media/si476x.h -- Common definitions for si476x driver + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#ifndef SI476X_H +#define SI476X_H + +#include +#include + +#include + +enum si476x_ctrl_id { + V4L2_CID_SI476X_RSSI_THRESHOLD = (V4L2_CID_USER_SI476X_BASE + 1), + V4L2_CID_SI476X_SNR_THRESHOLD = (V4L2_CID_USER_SI476X_BASE + 2), + V4L2_CID_SI476X_MAX_TUNE_ERROR = (V4L2_CID_USER_SI476X_BASE + 3), + V4L2_CID_SI476X_HARMONICS_COUNT = (V4L2_CID_USER_SI476X_BASE + 4), + V4L2_CID_SI476X_DIVERSITY_MODE = (V4L2_CID_USER_SI476X_BASE + 5), + V4L2_CID_SI476X_INTERCHIP_LINK = (V4L2_CID_USER_SI476X_BASE + 6), +}; + +#endif /* SI476X_H*/ -- cgit v1.2.3