Hi all,
The eighth version of this patchset addresses comments on the mailing list and many changes due to the work I did on the cec-compliance and cec-ctl utilities and due to stress-testing that uncovered a serious core design problem that required a partial rewrite.
As far as I am concerned this version is code complete and the only thing missing is that the documentation is out-of-sync with the current implementation.
So there will be a v9 fixing this, but since this may take some time I decided to post a v8 so people can look at this.
Best regards,
Hans
Changes since v7 ================
- I thought that the core thread could handle out-of-order messages, but that turned out to be wrong. After careful analysis I realized that I had to rewrite this part in cec.c in order to make it work. - Added new CEC-specific keys to input.h and use them in the CEC rc keymap. Replaced KEY_PLAY/PAUSE/STOP with KEY_PLAYCD/PAUSECD/STOPCD to clarify that these are media operations and not the Pause key on the keyboard. - Added CEC_PHYS_ADDR_INVALID (0xffff) - Added monitor support to monitor CEC traffic - Replaced CAP_TRANSMIT and CAP_RECEIVE by a single CAP_IO. - Replaced CAP_CDC by CAP_CDC_HPD since this only applies to the HPD part of the CDC messages. - Add CAP_IS_SOURCE. - Add ninputs field to cec_caps to export the number of inputs of the device. - Drop CEC_LOG_ADDRS_FL_HANDLE_MSGS and the flags field (see next change for more info). - Add CEC_CLAIM and CEC_RELEASE to explicitly start/stop processing CEC messages. This also implies ownership of the CEC interface, so other filehandles can only receive but not transmit. - Reworked event handling: report adapter state changes, input changes and if the message receive queue is full. - cec-funcs.h: added CDC HEC support. - Renamed G/S_ADAP ioctls to ADAP_G/S: this made it clearer which ioctls deal with the adapter configuration and which deal with CEC messages/events. - Clarified which CEC messages are passed on to userspace and which aren't. Specifically if CAP_ARC is set, then all ARC messages are handled by the kernel. If CAP_CDC_HPD is set, then all CDC hotplug messages are handled by the kernel. Otherwise these messages are passed on to userspace.
Changes since v6 ================ - added cec-funcs.h to provide wrapper functions that fill in the cec_msg struct. This header is needed both by the kernel and by applications. - fix a missing rc_unregister_device call. - added CEC support for the adv7842 and cobalt drivers. - added CEC operand defines. Rename CEC message defines to CEC_MSG_ and operand defines now use CEC_OP_. - the CEC_VERSION defines are dropped since we now have the CEC_OP_VERSION defines. - ditto: CEC_PRIM_DEVTYPE_ is now CEC_OP_PRIM_DEVTYPE. - ditto: CEC_FL_ALL_DEVTYPE_ is now CEC_OP_ALL_DEVTYPE. - cec-ioc-g-adap-log-addrs.xml: document cec_versions field. - cec-ioc-g-caps.xml: drop vendor_id and version fields. - add MAINTAINERS entry. - add CDC support (not yet fully functional). - add a second debug level for message debugging. - fix a nasty kernel Oops in cec_transmit_msg while waiting for transmit completion (adap->tx_queue[idx].func wasn't set to NULL). - add support for CEC_MSG_REPORT_FEATURES (CEC 2.0 only). - correctly abort unsupported messages. - add support for the device power status feature. - add support for the audio return channel (preliminary). - add support for the CDC hotplug message (preliminary). - added osd_name to struct cec_log_addrs. - reported physical addresses are stored internally. - fix enabling/disabling the CEC adapter (internal fields weren't cleared correctly). - zero reserved fields. - return an error if you try to receive/transmit and the adapter isn't configured. - when creating the adapter provide the owner module and the parent device. - add a CEC_VENDOR_ID_NONE define to signal if no vendor ID was set. - add new capabilities: RC (remote control), ARC (audio return channel) and CDC (Capability Discovery and Control). - applications that want to handle messages for a logical address need to set the CEC_LOG_ADDRS_FL_HANDLE_MSGS flag. Otherwise the CEC core will be the one handling all messages. - Each logical address has its own all_device_types value. So this should be an array, not a single value. - I'm sure I've forgotten some changes...
Changes since v5 ================ - drop struct cec_timeval in favour of a __u64 that keeps the timestamp in ns - remove userspace documentation from Documentation/cec.txt as userspace API is described in the DocBook - add missing documentation for the passthrough mode to the DocBook - add information about the number of events that can be queued - fix misspelling of reply - fix behaviour of posting an event in cec_received_msg, such that the behaviour is consistent with the documentation
Changes since v4 ================ - add sequence numbering to transmitted messages - add sequence number handling to event hanlding - add passthrough mode - change reserved field sizes - fixed CEC version defines and addec CEC 2.0 commands - add DocBook documentation
Changes since v3 ================ - remove the promiscuous mode - rewrite the devicetree patches - fixes, expansion and partial rewrite of the documentation - reorder of API structures and addition of reserved fields - use own struct to report time (32/64 bit safe) - fix of handling events - add cec.h to include/uapi/linux/Kbuild - fixes in the adv76xx driver (add missing methods, change adv7604 to adv76xx) - cleanup of debug messages in s5p-cec driver - remove non necessary claiming of a gpio in the s5p-cec driver - cleanup headers of the s5p-cec driver
Changes since v2 ===============- - added promiscuous mode - added new key codes to the input framework - add vendor ID reporting - add the possibility to clear assigned logical addresses - cleanup of the rc cec map
Changes since v1 ================ - documentation edited and moved to the Documentation folder - added key up/down message handling - add missing CEC commands to the cec.h file
Background ==========
The work on a common CEC framework was started over three years ago by Hans Verkuil. Unfortunately the work has stalled. As I have received the task of creating a driver for the CEC interface module present on the Exynos range of SoCs, I got in touch with Hans. He replied that the work stalled due to his lack of time.
Original RFC by Hans Verkuil/Martin Bugge ========================================= https://www.mail-archive.com/linux-media@vger.kernel.org/msg28735.html
Hans Verkuil (9): input.h: add BUS_CEC type cec: add HDMI CEC framework cec.txt: add CEC framework documentation DocBook/media: add CEC documentation v4l2-subdev: add HDMI CEC ops cec: adv7604: add cec support. cec: adv7842: add cec support cec: adv7511: add cec support. cobalt: add cec support
Kamil Debski (6): dts: exynos4*: add HDMI CEC pin definition to pinctrl dts: exynos4: add node for the HDMI CEC device dts: exynos4412-odroid*: enable the HDMI CEC device HID: add HDMI CEC specific keycodes rc: Add HDMI CEC protocol handling cec: s5p-cec: Add s5p-cec driver
Documentation/DocBook/media/Makefile | 2 + Documentation/DocBook/media/v4l/biblio.xml | 10 + Documentation/DocBook/media/v4l/cec-api.xml | 75 + Documentation/DocBook/media/v4l/cec-func-close.xml | 59 + Documentation/DocBook/media/v4l/cec-func-ioctl.xml | 73 + Documentation/DocBook/media/v4l/cec-func-open.xml | 94 + Documentation/DocBook/media/v4l/cec-func-poll.xml | 89 + .../DocBook/media/v4l/cec-ioc-adap-g-caps.xml | 138 ++ .../DocBook/media/v4l/cec-ioc-adap-g-log-addrs.xml | 306 ++++ .../DocBook/media/v4l/cec-ioc-adap-g-phys-addr.xml | 78 + .../DocBook/media/v4l/cec-ioc-adap-g-state.xml | 87 + .../DocBook/media/v4l/cec-ioc-adap-g-vendor-id.xml | 70 + .../DocBook/media/v4l/cec-ioc-dqevent.xml | 125 ++ .../DocBook/media/v4l/cec-ioc-g-monitor.xml | 86 + .../DocBook/media/v4l/cec-ioc-g-passthrough.xml | 81 + .../DocBook/media/v4l/cec-ioc-receive.xml | 185 ++ Documentation/DocBook/media_api.tmpl | 8 +- Documentation/cec.txt | 166 ++ .../devicetree/bindings/media/s5p-cec.txt | 31 + MAINTAINERS | 12 + arch/arm/boot/dts/exynos4.dtsi | 12 + arch/arm/boot/dts/exynos4210-pinctrl.dtsi | 7 + arch/arm/boot/dts/exynos4210-universal_c210.dts | 4 + arch/arm/boot/dts/exynos4x12-pinctrl.dtsi | 7 + drivers/media/Kconfig | 6 + drivers/media/Makefile | 2 + drivers/media/cec.c | 1903 ++++++++++++++++++++ drivers/media/i2c/adv7511.c | 359 +++- drivers/media/i2c/adv7604.c | 236 ++- drivers/media/i2c/adv7842.c | 250 ++- drivers/media/pci/cobalt/cobalt-driver.c | 50 +- drivers/media/pci/cobalt/cobalt-driver.h | 2 + drivers/media/pci/cobalt/cobalt-irq.c | 3 + drivers/media/pci/cobalt/cobalt-v4l2.c | 107 +- drivers/media/platform/Kconfig | 10 + drivers/media/platform/Makefile | 1 + drivers/media/platform/s5p-cec/Makefile | 2 + drivers/media/platform/s5p-cec/exynos_hdmi_cec.h | 37 + .../media/platform/s5p-cec/exynos_hdmi_cecctrl.c | 208 +++ drivers/media/platform/s5p-cec/regs-cec.h | 96 + drivers/media/platform/s5p-cec/s5p_cec.c | 277 +++ drivers/media/platform/s5p-cec/s5p_cec.h | 76 + drivers/media/rc/keymaps/Makefile | 1 + drivers/media/rc/keymaps/rc-cec.c | 174 ++ drivers/media/rc/rc-main.c | 1 + include/media/adv7511.h | 6 +- include/media/cec.h | 178 ++ include/media/rc-core.h | 1 + include/media/rc-map.h | 5 +- include/media/v4l2-subdev.h | 9 + include/uapi/linux/Kbuild | 2 + include/uapi/linux/cec-funcs.h | 1771 ++++++++++++++++++ include/uapi/linux/cec.h | 781 ++++++++ include/uapi/linux/input.h | 29 + 54 files changed, 8341 insertions(+), 47 deletions(-) create mode 100644 Documentation/DocBook/media/v4l/cec-api.xml create mode 100644 Documentation/DocBook/media/v4l/cec-func-close.xml create mode 100644 Documentation/DocBook/media/v4l/cec-func-ioctl.xml create mode 100644 Documentation/DocBook/media/v4l/cec-func-open.xml create mode 100644 Documentation/DocBook/media/v4l/cec-func-poll.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-adap-g-caps.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-adap-g-log-addrs.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-adap-g-phys-addr.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-adap-g-state.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-adap-g-vendor-id.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-dqevent.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-g-monitor.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-g-passthrough.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-receive.xml create mode 100644 Documentation/cec.txt create mode 100644 Documentation/devicetree/bindings/media/s5p-cec.txt create mode 100644 drivers/media/cec.c create mode 100644 drivers/media/platform/s5p-cec/Makefile create mode 100644 drivers/media/platform/s5p-cec/exynos_hdmi_cec.h create mode 100644 drivers/media/platform/s5p-cec/exynos_hdmi_cecctrl.c create mode 100644 drivers/media/platform/s5p-cec/regs-cec.h create mode 100644 drivers/media/platform/s5p-cec/s5p_cec.c create mode 100644 drivers/media/platform/s5p-cec/s5p_cec.h create mode 100644 drivers/media/rc/keymaps/rc-cec.c create mode 100644 include/media/cec.h create mode 100644 include/uapi/linux/cec-funcs.h create mode 100644 include/uapi/linux/cec.h
From: Kamil Debski kamil@wypas.org
Add pinctrl nodes for the HDMI CEC device to the Exynos4210 and Exynos4x12 SoCs. These are required by the HDMI CEC device.
Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com Acked-by: Krzysztof Kozlowski k.kozlowski@samsung.com --- arch/arm/boot/dts/exynos4210-pinctrl.dtsi | 7 +++++++ arch/arm/boot/dts/exynos4x12-pinctrl.dtsi | 7 +++++++ 2 files changed, 14 insertions(+)
diff --git a/arch/arm/boot/dts/exynos4210-pinctrl.dtsi b/arch/arm/boot/dts/exynos4210-pinctrl.dtsi index a7c2128..9331c62 100644 --- a/arch/arm/boot/dts/exynos4210-pinctrl.dtsi +++ b/arch/arm/boot/dts/exynos4210-pinctrl.dtsi @@ -820,6 +820,13 @@ samsung,pin-pud = <1>; samsung,pin-drv = <0>; }; + + hdmi_cec: hdmi-cec { + samsung,pins = "gpx3-6"; + samsung,pin-function = <3>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; };
pinctrl@03860000 { diff --git a/arch/arm/boot/dts/exynos4x12-pinctrl.dtsi b/arch/arm/boot/dts/exynos4x12-pinctrl.dtsi index bac25c6..856b292 100644 --- a/arch/arm/boot/dts/exynos4x12-pinctrl.dtsi +++ b/arch/arm/boot/dts/exynos4x12-pinctrl.dtsi @@ -885,6 +885,13 @@ samsung,pin-pud = <0>; samsung,pin-drv = <0>; }; + + hdmi_cec: hdmi-cec { + samsung,pins = "gpx3-6"; + samsung,pin-function = <3>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; };
pinctrl_2: pinctrl@03860000 {
From: Kamil Debski kamil@wypas.org
This patch adds HDMI CEC node specific to the Exynos4210/4x12 SoC series.
Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com Acked-by: Krzysztof Kozlowski k.kozlowski@samsung.com --- arch/arm/boot/dts/exynos4.dtsi | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/arch/arm/boot/dts/exynos4.dtsi b/arch/arm/boot/dts/exynos4.dtsi index b0d52b1..0d5319e 100644 --- a/arch/arm/boot/dts/exynos4.dtsi +++ b/arch/arm/boot/dts/exynos4.dtsi @@ -719,6 +719,18 @@ status = "disabled"; };
+ hdmicec: cec@100B0000 { + compatible = "samsung,s5p-cec"; + reg = <0x100B0000 0x200>; + interrupts = <0 114 0>; + clocks = <&clock CLK_HDMI_CEC>; + clock-names = "hdmicec"; + samsung,syscon-phandle = <&pmu_system_controller>; + pinctrl-names = "default"; + pinctrl-0 = <&hdmi_cec>; + status = "disabled"; + }; + mixer: mixer@12C10000 { compatible = "samsung,exynos4210-mixer"; interrupts = <0 91 0>;
From: Kamil Debski kamil@wypas.org
Add a dts node entry and enable the HDMI CEC device present in the Exynos4 family of SoCs.
Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com Acked-by: Krzysztof Kozlowski k.kozlowski@samsung.com --- arch/arm/boot/dts/exynos4210-universal_c210.dts | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/arch/arm/boot/dts/exynos4210-universal_c210.dts b/arch/arm/boot/dts/exynos4210-universal_c210.dts index d4f2b11..06df693 100644 --- a/arch/arm/boot/dts/exynos4210-universal_c210.dts +++ b/arch/arm/boot/dts/exynos4210-universal_c210.dts @@ -515,6 +515,10 @@ enable-active-high; };
+ cec@100B0000 { + status = "okay"; + }; + hdmi_ddc: i2c-ddc { compatible = "i2c-gpio"; gpios = <&gpe4 2 0 &gpe4 3 0>;
Inputs can come in over the HDMI CEC bus, so add a new type for this.
Signed-off-by: Hans Verkuil hans.verkuil@cisco.com Acked-by: Dmitry Torokhov dmitry.torokhov@gmail.com --- include/uapi/linux/input.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index 731417c..a32bff1 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h @@ -972,6 +972,7 @@ struct input_keymap_entry { #define BUS_GSC 0x1A #define BUS_ATARI 0x1B #define BUS_SPI 0x1C +#define BUS_CEC 0x1D
/* * MT_TOOL types
From: Kamil Debski kamil@wypas.org
Add HDMI CEC specific keycodes to the keycodes definition.
Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- include/uapi/linux/input.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index a32bff1..5e7019a 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h @@ -752,6 +752,34 @@ struct input_keymap_entry { #define KEY_KBDINPUTASSIST_ACCEPT 0x264 #define KEY_KBDINPUTASSIST_CANCEL 0x265
+#define KEY_RIGHT_UP 0x266 +#define KEY_RIGHT_DOWN 0x267 +#define KEY_LEFT_UP 0x268 +#define KEY_LEFT_DOWN 0x269 +#define KEY_ROOT_MENU 0x26a /* Show Device's Root Menu */ +#define KEY_MEDIA_TOP_MENU 0x26b /* Show Top Menu of the Media (e.g. DVD) */ +#define KEY_NUMERIC_11 0x26c +#define KEY_NUMERIC_12 0x26d +/* + * Toggle Audio Description: refers to an audio service that helps blind and + * visually impaired consumers understand the action in a program. Note: in + * some countries this is referred to as "Video Description". + */ +#define KEY_AUDIO_DESC 0x26e +#define KEY_3D_MODE 0x26f +#define KEY_NEXT_FAVORITE 0x270 +#define KEY_STOP_RECORD 0x271 +#define KEY_PAUSE_RECORD 0x272 +#define KEY_VOD 0x273 /* Video on Demand */ +#define KEY_UNMUTE 0x274 +#define KEY_FASTREVERSE 0x275 +#define KEY_SLOWREVERSE 0x276 +/* + * Control a data application associated with the currently viewed channel, + * e.g. teletext or data broadcast application (MHEG, MHP, HbbTV, etc.) + */ +#define KEY_DATA 0x275 + #define BTN_TRIGGER_HAPPY 0x2c0 #define BTN_TRIGGER_HAPPY1 0x2c0 #define BTN_TRIGGER_HAPPY2 0x2c1
From: Kamil Debski kamil@wypas.org
Add handling of remote control events coming from the HDMI CEC bus. This patch includes a new keymap that maps values found in the CEC messages to the keys pressed and released. Also, a new protocol has been added to the core.
Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- drivers/media/rc/keymaps/Makefile | 1 + drivers/media/rc/keymaps/rc-cec.c | 174 ++++++++++++++++++++++++++++++++++++++ drivers/media/rc/rc-main.c | 1 + include/media/rc-core.h | 1 + include/media/rc-map.h | 5 +- 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 drivers/media/rc/keymaps/rc-cec.c
diff --git a/drivers/media/rc/keymaps/Makefile b/drivers/media/rc/keymaps/Makefile index fbbd3bb..9cffcc6 100644 --- a/drivers/media/rc/keymaps/Makefile +++ b/drivers/media/rc/keymaps/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_RC_MAP) += rc-adstech-dvb-t-pci.o \ rc-behold.o \ rc-behold-columbus.o \ rc-budget-ci-old.o \ + rc-cec.o \ rc-cinergy-1400.o \ rc-cinergy.o \ rc-delock-61959.o \ diff --git a/drivers/media/rc/keymaps/rc-cec.c b/drivers/media/rc/keymaps/rc-cec.c new file mode 100644 index 0000000..193cdca --- /dev/null +++ b/drivers/media/rc/keymaps/rc-cec.c @@ -0,0 +1,174 @@ +/* Keytable for the CEC remote control + * + * Copyright (c) 2015 by Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <media/rc-map.h> +#include <linux/module.h> + +/* CEC Spec "High-Definition Multimedia Interface Specification" can be obtained + * here: http://xtreamerdev.googlecode.com/files/CEC_Specs.pdf + * The list of control codes is listed in Table 27: User Control Codes p. 95 */ + +static struct rc_map_table cec[] = { + { 0x00, KEY_OK }, + { 0x01, KEY_UP }, + { 0x02, KEY_DOWN }, + { 0x03, KEY_LEFT }, + { 0x04, KEY_RIGHT }, + { 0x05, KEY_RIGHT_UP }, + { 0x06, KEY_RIGHT_DOWN }, + { 0x07, KEY_LEFT_UP }, + { 0x08, KEY_LEFT_DOWN }, + { 0x09, KEY_ROOT_MENU }, /* CEC Spec: Device Root Menu - see Note 2 */ + /* Note 2: This is the initial display that a device shows. It is + * device-dependent and can be, for example, a contents menu, setup + * menu, favorite menu or other menu. The actual menu displayed + * may also depend on the device's current state. */ + { 0x0a, KEY_SETUP }, + { 0x0b, KEY_MENU }, /* CEC Spec: Contents Menu */ + { 0x0c, KEY_FAVORITES }, /* CEC Spec: Favorite Menu */ + { 0x0d, KEY_EXIT }, + /* 0x0e-0x0f: Reserved */ + { 0x10, KEY_MEDIA_TOP_MENU }, + { 0x11, KEY_CONTEXT_MENU }, + /* 0x12-0x1c: Reserved */ + { 0x1d, KEY_DIGITS }, /* CEC Spec: select/toggle a Number Entry Mode */ + { 0x1e, KEY_NUMERIC_11 }, + { 0x1f, KEY_NUMERIC_12 }, + /* 0x20-0x29: Keys 0 to 9 */ + { 0x20, KEY_NUMERIC_0 }, + { 0x21, KEY_NUMERIC_1 }, + { 0x22, KEY_NUMERIC_2 }, + { 0x23, KEY_NUMERIC_3 }, + { 0x24, KEY_NUMERIC_4 }, + { 0x25, KEY_NUMERIC_5 }, + { 0x26, KEY_NUMERIC_6 }, + { 0x27, KEY_NUMERIC_7 }, + { 0x28, KEY_NUMERIC_8 }, + { 0x29, KEY_NUMERIC_9 }, + { 0x2a, KEY_DOT }, + { 0x2b, KEY_ENTER }, + { 0x2c, KEY_CLEAR }, + /* 0x2d-0x2e: Reserved */ + { 0x2f, KEY_NEXT_FAVORITE }, /* CEC Spec: Next Favorite */ + { 0x30, KEY_CHANNELUP }, + { 0x31, KEY_CHANNELDOWN }, + { 0x32, KEY_PREVIOUS }, /* CEC Spec: Previous Channel */ + { 0x33, KEY_SOUND }, /* CEC Spec: Sound Select */ + { 0x34, KEY_VIDEO }, /* 0x34: CEC Spec: Input Select */ + { 0x35, KEY_INFO }, /* CEC Spec: Display Information */ + { 0x36, KEY_HELP }, + { 0x37, KEY_PAGEUP }, + { 0x38, KEY_PAGEDOWN }, + /* 0x39-0x3f: Reserved */ + { 0x40, KEY_POWER }, + { 0x41, KEY_VOLUMEUP }, + { 0x42, KEY_VOLUMEDOWN }, + { 0x43, KEY_MUTE }, + { 0x44, KEY_PLAYCD }, + { 0x45, KEY_STOPCD }, + { 0x46, KEY_PAUSECD }, + { 0x47, KEY_RECORD }, + { 0x48, KEY_REWIND }, + { 0x49, KEY_FASTFORWARD }, + { 0x4a, KEY_EJECTCD }, /* CEC Spec: Eject */ + { 0x4b, KEY_FORWARD }, + { 0x4c, KEY_BACK }, + { 0x4d, KEY_STOP_RECORD }, /* CEC Spec: Stop-Record */ + { 0x4e, KEY_PAUSE_RECORD }, /* CEC Spec: Pause-Record */ + /* 0x4f: Reserved */ + { 0x50, KEY_ANGLE }, + { 0x51, KEY_TV2 }, + { 0x52, KEY_VOD }, /* CEC Spec: Video on Demand */ + { 0x53, KEY_EPG }, + { 0x54, KEY_TIME }, /* CEC Spec: Timer */ + { 0x55, KEY_CONFIG }, + /* The following codes are hard to implement at this moment, as they + * carry an additional additional argument. Most likely changes to RC + * framework are necessary. + * For now they are interpreted by the CEC framework as non keycodes + * and are passed as messages enabling user application to parse them. + * */ + /* 0x56: CEC Spec: Select Broadcast Type */ + /* 0x57: CEC Spec: Select Sound presentation */ + { 0x58, KEY_AUDIO_DESC }, /* CEC 2.0 and up */ + { 0x59, KEY_WWW }, /* CEC 2.0 and up */ + { 0x5a, KEY_3D_MODE }, /* CEC 2.0 and up */ + /* 0x5b-0x5f: Reserved */ + { 0x60, KEY_PLAYCD }, /* CEC Spec: Play Function */ + { 0x6005, KEY_FASTFORWARD }, + { 0x6006, KEY_FASTFORWARD }, + { 0x6007, KEY_FASTFORWARD }, + { 0x6015, KEY_SLOW }, + { 0x6016, KEY_SLOW }, + { 0x6017, KEY_SLOW }, + { 0x6009, KEY_FASTREVERSE }, + { 0x600a, KEY_FASTREVERSE }, + { 0x600b, KEY_FASTREVERSE }, + { 0x6019, KEY_SLOWREVERSE }, + { 0x601a, KEY_SLOWREVERSE }, + { 0x601b, KEY_SLOWREVERSE }, + { 0x6020, KEY_REWIND }, + { 0x6024, KEY_PLAYCD }, + { 0x6025, KEY_PAUSECD }, + { 0x61, KEY_PLAYPAUSE }, /* CEC Spec: Pause-Play Function */ + { 0x62, KEY_RECORD }, /* Spec: Record Function */ + { 0x63, KEY_PAUSE_RECORD }, /* CEC Spec: Pause-Record Function */ + { 0x64, KEY_STOPCD }, /* CEC Spec: Stop Function */ + { 0x65, KEY_MUTE }, /* CEC Spec: Mute Function */ + { 0x66, KEY_UNMUTE }, /* CEC Spec: Restore the volume */ + /* The following codes are hard to implement at this moment, as they + * carry an additional additional argument. Most likely changes to RC + * framework are necessary. + * For now they are interpreted by the CEC framework as non keycodes + * and are passed as messages enabling user application to parse them. + * */ + /* 0x67: CEC Spec: Tune Function */ + /* 0x68: CEC Spec: Seleect Media Function */ + /* 0x69: CEC Spec: Select A/V Input Function */ + /* 0x6a: CEC Spec: Select Audio Input Function */ + { 0x6b, KEY_POWER }, /* CEC Spec: Power Toggle Function */ + { 0x6c, KEY_SLEEP }, /* CEC Spec: Power Off Function */ + { 0x6d, KEY_WAKEUP }, /* CEC Spec: Power On Function */ + /* 0x6e-0x70: Reserved */ + { 0x71, KEY_BLUE }, /* CEC Spec: F1 (Blue) */ + { 0x72, KEY_RED }, /* CEC Spec: F2 (Red) */ + { 0x73, KEY_GREEN }, /* CEC Spec: F3 (Green) */ + { 0x74, KEY_YELLOW }, /* CEC Spec: F4 (Yellow) */ + { 0x75, KEY_F5 }, + { 0x76, KEY_DATA }, /* CEC Spec: Data - see Note 3 */ + /* Note 3: This is used, for example, to enter or leave a digital TV + * data broadcast application. */ + /* 0x77-0xff: Reserved */ +}; + +static struct rc_map_list cec_map = { + .map = { + .scan = cec, + .size = ARRAY_SIZE(cec), + .rc_type = RC_TYPE_CEC, + .name = RC_MAP_CEC, + } +}; + +static int __init init_rc_map_cec(void) +{ + return rc_map_register(&cec_map); +} + +static void __exit exit_rc_map_cec(void) +{ + rc_map_unregister(&cec_map); +} + +module_init(init_rc_map_cec); +module_exit(exit_rc_map_cec); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kamil Debski"); diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index d08cedb..b5949bc 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -801,6 +801,7 @@ static struct { { RC_BIT_SHARP, "sharp" }, { RC_BIT_MCE_KBD, "mce_kbd" }, { RC_BIT_XMP, "xmp" }, + { RC_BIT_CEC, "cec" }, };
/** diff --git a/include/media/rc-core.h b/include/media/rc-core.h index 5642fbe..e2ba912 100644 --- a/include/media/rc-core.h +++ b/include/media/rc-core.h @@ -32,6 +32,7 @@ do { \ enum rc_driver_type { RC_DRIVER_SCANCODE = 0, /* Driver or hardware generates a scancode */ RC_DRIVER_IR_RAW, /* Needs a Infra-Red pulse/space decoder */ + RC_DRIVER_CEC, };
/** diff --git a/include/media/rc-map.h b/include/media/rc-map.h index 7c4bbc4..d9f87d5 100644 --- a/include/media/rc-map.h +++ b/include/media/rc-map.h @@ -31,6 +31,7 @@ enum rc_type { RC_TYPE_RC6_MCE = 16, /* MCE (Philips RC6-6A-32 subtype) protocol */ RC_TYPE_SHARP = 17, /* Sharp protocol */ RC_TYPE_XMP = 18, /* XMP protocol */ + RC_TYPE_CEC = 19, /* CEC protocol */ };
#define RC_BIT_NONE 0 @@ -53,6 +54,7 @@ enum rc_type { #define RC_BIT_RC6_MCE (1 << RC_TYPE_RC6_MCE) #define RC_BIT_SHARP (1 << RC_TYPE_SHARP) #define RC_BIT_XMP (1 << RC_TYPE_XMP) +#define RC_BIT_CEC (1 << RC_TYPE_CEC)
#define RC_BIT_ALL (RC_BIT_UNKNOWN | RC_BIT_OTHER | \ RC_BIT_RC5 | RC_BIT_RC5X | RC_BIT_RC5_SZ | \ @@ -61,7 +63,7 @@ enum rc_type { RC_BIT_NEC | RC_BIT_SANYO | RC_BIT_MCE_KBD | \ RC_BIT_RC6_0 | RC_BIT_RC6_6A_20 | RC_BIT_RC6_6A_24 | \ RC_BIT_RC6_6A_32 | RC_BIT_RC6_MCE | RC_BIT_SHARP | \ - RC_BIT_XMP) + RC_BIT_XMP | RC_BIT_CEC)
#define RC_SCANCODE_UNKNOWN(x) (x) @@ -123,6 +125,7 @@ void rc_map_init(void); #define RC_MAP_BEHOLD_COLUMBUS "rc-behold-columbus" #define RC_MAP_BEHOLD "rc-behold" #define RC_MAP_BUDGET_CI_OLD "rc-budget-ci-old" +#define RC_MAP_CEC "rc-cec" #define RC_MAP_CINERGY_1400 "rc-cinergy-1400" #define RC_MAP_CINERGY "rc-cinergy" #define RC_MAP_DELOCK_61959 "rc-delock-61959"
The added HDMI CEC framework provides a generic kernel interface for HDMI CEC devices.
Signed-off-by: Hans Verkuil hansverk@cisco.com [k.debski@samsung.com: Merged CEC Updates commit by Hans Verkuil] [k.debski@samsung.com: Merged Update author commit by Hans Verkuil] [k.debski@samsung.com: change kthread handling when setting logical address] [k.debski@samsung.com: code cleanup and fixes] [k.debski@samsung.com: add missing CEC commands to match spec] [k.debski@samsung.com: add RC framework support] [k.debski@samsung.com: move and edit documentation] [k.debski@samsung.com: add vendor id reporting] [k.debski@samsung.com: add possibility to clear assigned logical addresses] [k.debski@samsung.com: documentation fixes, clenaup and expansion] [k.debski@samsung.com: reorder of API structs and add reserved fields] [k.debski@samsung.com: fix handling of events and fix 32/64bit timespec problem] [k.debski@samsung.com: add cec.h to include/uapi/linux/Kbuild] [k.debski@samsung.com: add sequence number handling] [k.debski@samsung.com: add passthrough mode] [k.debski@samsung.com: fix CEC defines, add missing CEC 2.0 commands] minor additions] Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- MAINTAINERS | 12 + drivers/media/Kconfig | 6 + drivers/media/Makefile | 2 + drivers/media/cec.c | 1903 ++++++++++++++++++++++++++++++++++++++++ include/media/cec.h | 178 ++++ include/uapi/linux/Kbuild | 2 + include/uapi/linux/cec-funcs.h | 1771 +++++++++++++++++++++++++++++++++++++ include/uapi/linux/cec.h | 781 +++++++++++++++++ 8 files changed, 4655 insertions(+) create mode 100644 drivers/media/cec.c create mode 100644 include/media/cec.h create mode 100644 include/uapi/linux/cec-funcs.h create mode 100644 include/uapi/linux/cec.h
diff --git a/MAINTAINERS b/MAINTAINERS index e0946a0..2904a66 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2553,6 +2553,18 @@ F: drivers/net/ieee802154/cc2520.c F: include/linux/spi/cc2520.h F: Documentation/devicetree/bindings/net/ieee802154/cc2520.txt
+CEC DRIVER +M: Hans Verkuil hans.verkuil@cisco.com +L: linux-media@vger.kernel.org +T: git git://linuxtv.org/media_tree.git +W: http://linuxtv.org +S: Supported +F: drivers/media/cec.c +F: drivers/media/rc/keymaps/rc-cec.c +F: include/media/cec.h +F: include/uapi/linux/cec.h +F: include/uapi/linux/cec-funcs.h + CELL BROADBAND ENGINE ARCHITECTURE M: Arnd Bergmann arnd@arndb.de L: linuxppc-dev@lists.ozlabs.org diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index 3ef3d6c..48e44f5 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -15,6 +15,12 @@ if MEDIA_SUPPORT
comment "Multimedia core support"
+config CEC + tristate "CEC API (EXPERIMENTAL)" + select RC_CORE + ---help--- + Enable the CEC API. + # # Multimedia support - automatically enable V4L2 and DVB core # diff --git a/drivers/media/Makefile b/drivers/media/Makefile index e608bbc..db66014 100644 --- a/drivers/media/Makefile +++ b/drivers/media/Makefile @@ -2,6 +2,8 @@ # Makefile for the kernel multimedia device drivers. #
+obj-$(CONFIG_CEC) += cec.o + media-objs := media-device.o media-devnode.o media-entity.o
# diff --git a/drivers/media/cec.c b/drivers/media/cec.c new file mode 100644 index 0000000..6c485d3 --- /dev/null +++ b/drivers/media/cec.c @@ -0,0 +1,1903 @@ +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/kmod.h> +#include <linux/ktime.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <media/cec.h> + +#define CEC_NUM_DEVICES 256 +#define CEC_NAME "cec" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +#define dprintk(lvl, fmt, arg...) \ + do { \ + if (lvl <= debug) \ + pr_info("cec-%s: " fmt, adap->name, ## arg); \ + } while (0) + +static dev_t cec_dev_t; + +/* Active devices */ +static DEFINE_MUTEX(cec_devnode_lock); +static DECLARE_BITMAP(cec_devnode_nums, CEC_NUM_DEVICES); + +/* dev to cec_devnode */ +#define to_cec_devnode(cd) container_of(cd, struct cec_devnode, dev) + +static inline struct cec_devnode *cec_devnode_data(struct file *filp) +{ + struct cec_fh *fh = filp->private_data; + + return &fh->adap->devnode; +} + +static bool cec_pa_are_adjacent(const struct cec_adapter *adap, u16 pa1, u16 pa2) +{ + u16 mask = 0xf000; + int i; + + if (pa1 == CEC_PHYS_ADDR_INVALID || pa2 == CEC_PHYS_ADDR_INVALID) + return false; + for (i = 0; i < 3; i++) { + if ((pa1 & mask) != (pa2 & mask)) + break; + mask = (mask >> 4) | 0xf000; + } + if ((pa1 & ~mask) || (pa2 & ~mask)) + return false; + if (!(pa1 & mask) ^ !(pa2 & mask)) + return true; + return false; +} + +static bool cec_la_are_adjacent(const struct cec_adapter *adap, u8 la1, u8 la2) +{ + u16 pa1 = adap->phys_addrs[la1]; + u16 pa2 = adap->phys_addrs[la2]; + + return cec_pa_are_adjacent(adap, pa1, pa2); +} + +static int cec_log_addr2idx(const struct cec_adapter *adap, u8 log_addr) +{ + int i; + + for (i = 0; i < adap->num_log_addrs; i++) + if (adap->log_addr[i] == log_addr) + return i; + return -1; +} + +static unsigned cec_log_addr2dev(const struct cec_adapter *adap, u8 log_addr) +{ + int i = cec_log_addr2idx(adap, log_addr); + + return adap->prim_device[i < 0 ? 0 : i]; +} + +/* Called when the last user of the cec device exits. */ +static void cec_devnode_release(struct device *cd) +{ + struct cec_devnode *cecdev = to_cec_devnode(cd); + + mutex_lock(&cec_devnode_lock); + + /* Delete the cdev on this minor as well */ + cdev_del(&cecdev->cdev); + + /* Mark device node number as free */ + clear_bit(cecdev->minor, cec_devnode_nums); + + mutex_unlock(&cec_devnode_lock); + + /* Release cec_devnode and perform other cleanups as needed. */ + if (cecdev->release) + cecdev->release(cecdev); +} + +static struct bus_type cec_bus_type = { + .name = CEC_NAME, +}; + +static void cec_queue_msg_fh(struct cec_fh *fh, const struct cec_msg *msg) +{ + struct cec_msg_entry *entry; + struct cec_event *ev = &fh->events[CEC_EVENT_LOST_MSGS - 1]; + + mutex_lock(&fh->lock); + if (fh->queued_msgs == CEC_MAX_MSG_QUEUE_SZ) + goto lost_msgs; + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (entry == NULL) + goto lost_msgs; + + entry->msg = *msg; + list_add(&entry->list, &fh->msgs); + fh->queued_msgs++; + mutex_unlock(&fh->lock); + wake_up_interruptible(&fh->wait); + return; + +lost_msgs: + if (ev->event == 0) { + ev->ts = ktime_get_ns(); + ev->event = CEC_EVENT_LOST_MSGS; + } + mutex_unlock(&fh->lock); + wake_up_interruptible(&fh->wait); +} + +static void cec_queue_msg_monitor(struct cec_adapter *adap, + const struct cec_msg *msg) +{ + struct cec_fh *fh; + + mutex_lock(&adap->devnode.fhs_lock); + list_for_each_entry(fh, &adap->devnode.fhs, list) { + if (fh->monitor) + cec_queue_msg_fh(fh, msg); + } + mutex_unlock(&adap->devnode.fhs_lock); +} + +static void cec_post_state_event_fh(struct cec_adapter *adap, + struct cec_fh *fh, u64 ts) +{ + struct cec_event *ev = &fh->events[CEC_EVENT_STATE_CHANGE - 1]; + struct cec_event_state_change *ch = &ev->state_change; + + mutex_lock(&fh->lock); + ev->ts = ts; + ev->event = CEC_EVENT_STATE_CHANGE; + if (!adap->is_enabled) + ch->state = CEC_EVENT_STATE_DISABLED; + else if (adap->is_configuring) + ch->state = CEC_EVENT_STATE_CONFIGURING; + else if (adap->is_configured) + ch->state = CEC_EVENT_STATE_CONFIGURED; + else + ch->state = CEC_EVENT_STATE_UNCONFIGURED; + mutex_unlock(&fh->lock); + wake_up_interruptible(&fh->wait); +} + +static void cec_post_state_event(struct cec_adapter *adap) +{ + u64 ts = ktime_get_ns(); + struct cec_fh *fh; + + mutex_lock(&adap->devnode.fhs_lock); + list_for_each_entry(fh, &adap->devnode.fhs, list) + cec_post_state_event_fh(adap, fh, ts); + mutex_unlock(&adap->devnode.fhs_lock); +} + +static void cec_data_completed(struct cec_data *data) +{ + if (data->blocking) { + /* + * Someone is blocking so mark the message as completed + * and call complete. + */ + data->completed = true; + complete(&data->c); + } else { + /* + * No blocking, so just queue the message if needed and + * free the memory. + */ + if (data->fh) + cec_queue_msg_fh(data->fh, &data->msg); + kfree(data); + } +} + +/* + * Main CEC state machine + * + * Wait until the thread should be stopped, or we're not transmitting and + * a new transmit message is queued up, in which case we start transmitting + * that message. When the adapter finished transmitting the message it will + * call cec_transmit_done(). + * + * If the adapter is disabled, then remove all queued messages instead. + */ +static int cec_thread_func(void *_adap) +{ + struct cec_adapter *adap = _adap; + + for (;;) { + struct cec_data *data; + u32 timeout; + + wait_event_interruptible(adap->kthread_waitq, + kthread_should_stop() || + (!adap->transmitting && + !list_empty(&adap->transmit_queue))); + + if (kthread_should_stop()) + break; + mutex_lock(&adap->lock); + + if (!adap->is_enabled) { + while (!list_empty(&adap->transmit_queue)) { + data = list_first_entry(&adap->transmit_queue, + struct cec_data, list); + list_del(&data->list); + data->msg.ts = ktime_get_ns(); + data->msg.status = CEC_TX_STATUS_RETRY_TIMEOUT; + data->msg.reply = 0; + cec_data_completed(data); + } + goto unlock; + } + + if (list_empty(&adap->transmit_queue)) + goto unlock; + + data = list_first_entry(&adap->transmit_queue, + struct cec_data, list); + list_del(&data->list); + adap->transmitting = data; + timeout = data->msg.len == 1 ? 200 : 1000; + adap->adap_transmit(adap, timeout, &data->msg); +unlock: + mutex_unlock(&adap->lock); + } + return 0; +} + +void cec_transmit_done(struct cec_adapter *adap, u32 status) +{ + dprintk(2, "cec_transmit_done\n"); + mutex_lock(&adap->lock); + if (WARN_ON(adap->transmitting == NULL)) { + dprintk(0, "cec_transmit_done without an ongoing transmit!\n"); + } else { + struct cec_data *data = adap->transmitting; + struct cec_msg *msg = &data->msg; + + msg->ts = ktime_get_ns(); + msg->status = status; + if (status || !adap->is_configured) + msg->reply = 0; + /* Queue transmitted message for monitoring purposes */ + cec_queue_msg_monitor(adap, msg); + adap->transmitting = NULL; + if (msg->reply) { + /* + * We want to wait for a reply, so queue the message to + * the wait_queue and schedule a timeout task. + */ + if (msg->timeout == 0) + msg->timeout = 1000; + list_add_tail(&data->list, &adap->wait_queue); + schedule_delayed_work(&data->work, + msecs_to_jiffies(msg->timeout)); + } else { + cec_data_completed(data); + } + /* + * Wake up the main thread to see if another message is ready + * for transmitting. + */ + wake_up_interruptible(&adap->kthread_waitq); + } + mutex_unlock(&adap->lock); +} +EXPORT_SYMBOL_GPL(cec_transmit_done); + +/* + * Called when waiting for a reply times out. + */ +static void cec_wait_timeout(struct work_struct *work) +{ + struct cec_data *data = container_of(work, struct cec_data, work.work); + struct cec_adapter *adap = data->adap; + + mutex_lock(&adap->lock); + if (list_empty(&data->list)) + goto unlock; + + list_del_init(&data->list); + data->msg.ts = ktime_get_ns(); + data->msg.status = CEC_TX_STATUS_REPLY_TIMEOUT; + cec_data_completed(data); +unlock: + mutex_unlock(&adap->lock); +} + +static int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, + struct cec_fh *fh, bool block) +{ + struct cec_data *data; + int res = 0; + + if (msg->len == 0 || msg->len > 16) { + dprintk(1, "cec_transmit_msg: invalid length %d\n", msg->len); + return -EINVAL; + } + if (msg->reply && (msg->len == 1 || (cec_msg_is_broadcast(msg)))) { + dprintk(1, "cec_transmit_msg: can't reply for poll or broadcast msg\n"); + return -EINVAL; + } + if (msg->len > 1 && !cec_msg_is_broadcast(msg) && + cec_msg_initiator(msg) == cec_msg_destination(msg)) { + dprintk(1, "cec_transmit_msg: initiator == destination\n"); + return -EINVAL; + } + if (cec_msg_initiator(msg) != 0xf && + cec_log_addr2idx(adap, cec_msg_initiator(msg)) < 0) { + dprintk(1, "cec_transmit_msg: initiator has unknown logical address\n"); + return -EINVAL; + } + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + if (msg->len == 1) + dprintk(2, "cec_transmit_msg: 0x%02x%s\n", + msg->msg[0], !block ? " nb" : ""); + else if (msg->reply) + dprintk(2, "cec_transmit_msg: 0x%02x 0x%02x (wait for 0x%02x)%s\n", + msg->msg[0], msg->msg[1], + msg->reply, !block ? " nb" : ""); + else + dprintk(2, "cec_transmit_msg: 0x%02x 0x%02x%s\n", + msg->msg[0], msg->msg[1], + !block ? " nb" : ""); + + if (msg->len > 1 && msg->msg[1] == CEC_MSG_CDC_MESSAGE) { + msg->msg[2] = adap->phys_addr >> 8; + msg->msg[3] = adap->phys_addr & 0xff; + } + data->msg = *msg; + data->fh = fh; + data->adap = adap; + data->blocking = block; + init_completion(&data->c); + INIT_DELAYED_WORK(&data->work, cec_wait_timeout); + + mutex_lock(&adap->lock); + if (adap->is_configured || adap->is_configuring) { + data->msg.sequence = adap->sequence++; + list_add_tail(&data->list, &adap->transmit_queue); + if (adap->transmitting == NULL) + wake_up_interruptible(&adap->kthread_waitq); + } else { + res = -ENONET; + kfree(data); + } + mutex_unlock(&adap->lock); + if (res || !block) + return res; + res = wait_for_completion_interruptible(&data->c); + mutex_lock(&adap->lock); + if (data->completed) { + *msg = data->msg; + kfree(data); + res = 0; + } else { + data->blocking = false; + data->fh = NULL; + } + mutex_unlock(&adap->lock); + return res; +} + +int cec_transmit_msg(struct cec_adapter *adap, struct cec_msg *msg, + bool block) +{ + return cec_transmit_msg_fh(adap, msg, NULL, block); +} +EXPORT_SYMBOL_GPL(cec_transmit_msg); + +static int cec_report_features(struct cec_adapter *adap, unsigned la_idx) +{ + struct cec_msg msg = { }; + u8 *features = adap->features[la_idx]; + bool op_is_dev_features = false; + unsigned idx; + + if (adap->cec_version < CEC_OP_CEC_VERSION_2_0) + return 0; + + /* Report Features */ + msg.msg[0] = (adap->log_addr[la_idx] << 4) | 0x0f; + msg.len = 4; + msg.msg[1] = CEC_MSG_REPORT_FEATURES; + msg.msg[2] = adap->cec_version; + msg.msg[3] = adap->all_device_types[la_idx]; + + /* Write RC Profiles first, then Device Features */ + for (idx = 0; idx < sizeof(adap->features[0]); idx++) { + msg.msg[msg.len++] = features[idx]; + if ((features[idx] & CEC_OP_FEAT_EXT) == 0) { + if (op_is_dev_features) + break; + op_is_dev_features = true; + } + } + return cec_transmit_msg(adap, &msg, false); +} + +static int cec_report_phys_addr(struct cec_adapter *adap, unsigned la_idx) +{ + struct cec_msg msg = { }; + + /* Report Physical Address */ + msg.msg[0] = (adap->log_addr[la_idx] << 4) | 0x0f; + cec_msg_report_physical_addr(&msg, adap->phys_addr, + adap->prim_device[la_idx]); + dprintk(2, "config: la %d pa %x.%x.%x.%x\n", + adap->log_addr[la_idx], + cec_phys_addr_exp(adap->phys_addr)); + return cec_transmit_msg(adap, &msg, false); +} + +static int cec_feature_abort_reason(struct cec_adapter *adap, + struct cec_msg *msg, u8 reason) +{ + struct cec_msg tx_msg = { }; + + /* + * Don't reply with CEC_MSG_FEATURE_ABORT to a CEC_MSG_FEATURE_ABORT + * message! + */ + if (msg->msg[1] == CEC_MSG_FEATURE_ABORT) + return 0; + cec_msg_set_reply_to(&tx_msg, msg); + cec_msg_feature_abort(&tx_msg, msg->msg[1], reason); + return cec_transmit_msg(adap, &tx_msg, false); +} + +static int cec_feature_abort(struct cec_adapter *adap, struct cec_msg *msg) +{ + return cec_feature_abort_reason(adap, msg, + CEC_OP_ABORT_UNRECOGNIZED_OP); +} + +static int cec_feature_refused(struct cec_adapter *adap, struct cec_msg *msg) +{ + return cec_feature_abort_reason(adap, msg, + CEC_OP_ABORT_REFUSED); +} + +/* + * Called when a CEC message is received. This function will do any + * necessary core processing. The is_reply bool is true if this message + * is a reply to an earlier transmit. + */ +static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, + bool is_reply) +{ + bool is_broadcast = cec_msg_is_broadcast(msg); + u8 dest_laddr = cec_msg_destination(msg); + u8 init_laddr = cec_msg_initiator(msg); + u8 devtype = cec_log_addr2dev(adap, dest_laddr); + int la_idx = cec_log_addr2idx(adap, dest_laddr); + bool is_directed = la_idx >= 0; + bool from_unregistered = init_laddr == 0xf; + u16 cdc_phys_addr; + struct cec_msg tx_cec_msg = { }; + u8 *tx_msg = tx_cec_msg.msg; + + dprintk(1, "cec_receive_notify: %02x %02x\n", msg->msg[0], msg->msg[1]); + + if (!is_directed && !is_broadcast) { + if (adap->passthrough) + goto skip_processing; + return 0; + } + + if (adap->received) { + /* Allow drivers to process the message first */ + if (adap->received(adap, msg) != -ENOMSG) + return 0; + } + + /* + * ARC, CDC and REPORT_PHYSICAL_ADDR, CEC_MSG_USER_CONTROL_PRESSED and + * CEC_MSG_USER_CONTROL_RELEASED messages always have to be + * handled by the CEC core, even if the passthrough mode is on. + * ARC and CDC messages will never be seen even if passthrough is + * on, but the others are just passed on normally. + */ + switch (msg->msg[1]) { + case CEC_MSG_INITIATE_ARC: + case CEC_MSG_TERMINATE_ARC: + case CEC_MSG_REQUEST_ARC_INITIATION: + case CEC_MSG_REQUEST_ARC_TERMINATION: + case CEC_MSG_REPORT_ARC_INITIATED: + case CEC_MSG_REPORT_ARC_TERMINATED: + /* ARC messages are never passed through if CAP_ARC is set */ + + /* Abort/ignore if ARC is not supported */ + if (!(adap->capabilities & CEC_CAP_ARC)) { + /* Just abort if nobody is listening */ + if (is_directed && !is_reply && !adap->cec_owner) + return cec_feature_abort(adap, msg); + goto skip_processing; + } + /* Ignore if addressing is wrong */ + if (is_broadcast || from_unregistered) + return 0; + break; + + case CEC_MSG_CDC_MESSAGE: + switch (msg->msg[4]) { + case CEC_MSG_CDC_HPD_REPORT_STATE: + case CEC_MSG_CDC_HPD_SET_STATE: + /* + * CDC_HPD messages are never passed through if + * CAP_CDC_HPD is set + */ + + /* Ignore if CDC_HPD is not supported */ + if (!(adap->capabilities & CEC_CAP_CDC_HPD)) + goto skip_processing; + /* or the addressing is wrong */ + if (!is_broadcast) + return 0; + break; + default: + /* Other CDC messages are ignored */ + goto skip_processing; + } + break; + + case CEC_MSG_GET_CEC_VERSION: + case CEC_MSG_GIVE_DEVICE_VENDOR_ID: + case CEC_MSG_ABORT: + case CEC_MSG_GIVE_DEVICE_POWER_STATUS: + case CEC_MSG_GIVE_PHYSICAL_ADDR: + case CEC_MSG_GIVE_OSD_NAME: + case CEC_MSG_GIVE_FEATURES: + /* + * Skip processing these messages if the passthrough mode + * is on. + */ + if (adap->passthrough) + goto skip_processing; + /* Ignore if addressing is wrong */ + if (is_broadcast || from_unregistered) + return 0; + break; + + case CEC_MSG_USER_CONTROL_PRESSED: + case CEC_MSG_USER_CONTROL_RELEASED: + /* Wrong addressing mode: don't process */ + if (is_broadcast || from_unregistered) + goto skip_processing; + break; + + case CEC_MSG_REPORT_PHYSICAL_ADDR: + /* + * This message is always processed, regardless of the + * passthrough setting. + * + * Exception: don't process if wrong addressing mode. + */ + if (!is_broadcast) + goto skip_processing; + break; + + default: + break; + } + + cec_msg_set_reply_to(&tx_cec_msg, msg); + + switch (msg->msg[1]) { + /* The following messages are processed but still passed through */ + case CEC_MSG_REPORT_PHYSICAL_ADDR: + adap->phys_addrs[init_laddr] = + (msg->msg[2] << 8) | msg->msg[3]; + dprintk(1, "Reported physical address %04x for logical address %d\n", + adap->phys_addrs[init_laddr], init_laddr); + break; + + case CEC_MSG_USER_CONTROL_PRESSED: + if (!(adap->capabilities & CEC_CAP_RC)) + break; + + switch (msg->msg[2]) { + /* Play function, this message can have variable length + * depending on the specific play function that is used. + */ + case 0x60: + if (msg->len == 2) + rc_keydown(adap->rc, RC_TYPE_CEC, + msg->msg[2], 0); + else + rc_keydown(adap->rc, RC_TYPE_CEC, + msg->msg[2] << 8 | msg->msg[3], 0); + break; + /* Other function messages that are not handled. + * Currently the RC framework does not allow to supply an + * additional parameter to a keypress. These "keys" contain + * other information such as channel number, an input number + * etc. + * For the time being these messages are not processed by the + * framework and are simply forwarded to the user space. + */ + case 0x56: case 0x57: + case 0x67: case 0x68: case 0x69: case 0x6a: + break; + default: + rc_keydown(adap->rc, RC_TYPE_CEC, msg->msg[2], 0); + break; + } + break; + + case CEC_MSG_USER_CONTROL_RELEASED: + if (!(adap->capabilities & CEC_CAP_RC)) + break; + rc_keyup(adap->rc); + break; + + /* + * The remaining messages are only processed if the passthrough mode + * is off. + */ + case CEC_MSG_GET_CEC_VERSION: + cec_msg_cec_version(&tx_cec_msg, adap->cec_version); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_GIVE_PHYSICAL_ADDR: + /* Do nothing for CEC switches using addr 15 */ + if (devtype == CEC_OP_PRIM_DEVTYPE_SWITCH && dest_laddr == 15) + return 0; + cec_msg_report_physical_addr(&tx_cec_msg, adap->phys_addr, devtype); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_GIVE_DEVICE_VENDOR_ID: + if (!(adap->capabilities & CEC_CAP_VENDOR_ID) || + adap->vendor_id == CEC_VENDOR_ID_NONE) + return cec_feature_abort(adap, msg); + cec_msg_device_vendor_id(&tx_cec_msg, adap->vendor_id); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_ABORT: + /* Do nothing for CEC switches */ + if (devtype == CEC_OP_PRIM_DEVTYPE_SWITCH) + return 0; + return cec_feature_refused(adap, msg); + + case CEC_MSG_GIVE_DEVICE_POWER_STATUS: + /* Do nothing for CEC switches */ + if (devtype == CEC_OP_PRIM_DEVTYPE_SWITCH) + return 0; + cec_msg_report_power_status(&tx_cec_msg, adap->pwr_state); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_GIVE_OSD_NAME: { + if (adap->osd_name[0] == 0) + return cec_feature_abort(adap, msg); + cec_msg_set_osd_name(&tx_cec_msg, adap->osd_name); + return cec_transmit_msg(adap, &tx_cec_msg, false); + } + + case CEC_MSG_GIVE_FEATURES: + if (adap->cec_version >= CEC_OP_CEC_VERSION_2_0) + return cec_report_features(adap, la_idx); + return 0; + + case CEC_MSG_REQUEST_ARC_INITIATION: + if (!adap->is_source || + !cec_la_are_adjacent(adap, dest_laddr, init_laddr)) + return cec_feature_refused(adap, msg); + cec_msg_initiate_arc(&tx_cec_msg, false); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_REQUEST_ARC_TERMINATION: + if (!adap->is_source || + !cec_la_are_adjacent(adap, dest_laddr, init_laddr)) + return cec_feature_refused(adap, msg); + cec_msg_terminate_arc(&tx_cec_msg, false); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_INITIATE_ARC: + if (!adap->ninputs || + !cec_la_are_adjacent(adap, dest_laddr, init_laddr)) + return cec_feature_refused(adap, msg); + if (adap->sink_initiate_arc && adap->sink_initiate_arc(adap)) + return 0; + cec_msg_report_arc_initiated(&tx_cec_msg); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_TERMINATE_ARC: + if (!adap->ninputs || + !cec_la_are_adjacent(adap, dest_laddr, init_laddr)) + return cec_feature_refused(adap, msg); + if (adap->sink_terminate_arc && adap->sink_terminate_arc(adap)) + return 0; + cec_msg_report_arc_terminated(&tx_cec_msg); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_REPORT_ARC_INITIATED: + if (!adap->is_source || + !cec_la_are_adjacent(adap, dest_laddr, init_laddr)) + return cec_feature_refused(adap, msg); + if (adap->source_arc_initiated) + adap->source_arc_initiated(adap); + return 0; + + case CEC_MSG_REPORT_ARC_TERMINATED: + if (!adap->is_source || + !cec_la_are_adjacent(adap, dest_laddr, init_laddr)) + return cec_feature_refused(adap, msg); + if (adap->source_arc_terminated) + adap->source_arc_terminated(adap); + return 0; + + case CEC_MSG_CDC_MESSAGE: { + unsigned shift; + unsigned input_port; + + cdc_phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + if (!cec_pa_are_adjacent(adap, cdc_phys_addr, adap->phys_addr)) + return 0; + + switch (msg->msg[4]) { + case CEC_MSG_CDC_HPD_REPORT_STATE: + /* + * Ignore if we're not a sink or the message comes from + * an upstream device. + */ + if (!adap->ninputs || cdc_phys_addr <= adap->phys_addr) + return 0; + adap->sink_cdc_hpd(adap, msg->msg[5] >> 4, msg->msg[5] & 0xf); + return 0; + case CEC_MSG_CDC_HPD_SET_STATE: + /* Ignore if we're not a source */ + if (!adap->is_source) + return 0; + break; + default: + return 0; + } + + input_port = msg->msg[5] >> 4; + for (shift = 0; shift < 16; shift += 4) { + if (cdc_phys_addr & (0xf000 >> shift)) + continue; + cdc_phys_addr |= input_port << (12 - shift); + break; + } + if (cdc_phys_addr != adap->phys_addr) + return 0; + + tx_cec_msg.len = 6; + /* broadcast reply */ + tx_msg[0] = (adap->log_addr[0] << 4) | 0xf; + cec_msg_cdc_hpd_report_state(&tx_cec_msg, + msg->msg[5] & 0xf, + adap->source_cdc_hpd(adap, msg->msg[5] & 0xf)); + return cec_transmit_msg(adap, &tx_cec_msg, false); + } + + default: + /* + * Unprocessed messages are aborted if userspace isn't doing + * any processing either. + */ + if (is_directed && !is_reply && !adap->cec_owner) + return cec_feature_abort(adap, msg); + break; + } + +skip_processing: + if (!is_reply && adap->cec_owner) + cec_queue_msg_fh(adap->cec_owner, msg); + return 0; +} + +void cec_received_msg(struct cec_adapter *adap, struct cec_msg *msg) +{ + struct cec_data *data; + bool is_reply = false; + + mutex_lock(&adap->lock); + msg->ts = ktime_get_ns(); + msg->status = CEC_RX_STATUS_READY; + msg->sequence = msg->reply = msg->timeout = 0; + memset(msg->reserved, 0, sizeof(msg->reserved)); + dprintk(2, "cec_received_msg: %02x %02x\n", msg->msg[0], msg->msg[1]); + if (msg->len > 1 && msg->msg[1] != CEC_MSG_CDC_MESSAGE) { + u8 cmd = msg->msg[1]; + + if (cmd == CEC_MSG_FEATURE_ABORT) + cmd = msg->msg[2]; + list_for_each_entry(data, &adap->wait_queue, list) { + struct cec_msg *dst = &data->msg; + + if (cec_msg_initiator(msg) != cec_msg_destination(dst) || + cmd != dst->reply) + continue; + msg->sequence = dst->sequence; + *dst = *msg; + if (msg->msg[1] == CEC_MSG_FEATURE_ABORT) { + dst->reply = 0; + dst->status = CEC_TX_STATUS_FEATURE_ABORT; + } + list_del_init(&data->list); + if (!cancel_delayed_work(&data->work)) { + mutex_unlock(&adap->lock); + flush_scheduled_work(); + mutex_lock(&adap->lock); + } + if (data->blocking || data->fh) + is_reply = true; + cec_data_completed(data); + break; + } + } + mutex_unlock(&adap->lock); + cec_queue_msg_monitor(adap, msg); + + if (msg->len <= 1) + return; + + cec_receive_notify(adap, msg, is_reply); +} +EXPORT_SYMBOL_GPL(cec_received_msg); + +static int cec_receive_msg(struct cec_fh *fh, struct cec_msg *msg, bool block) +{ + int res; + + do { + mutex_lock(&fh->lock); + if (fh->queued_msgs) { + struct cec_msg_entry *entry = + list_first_entry(&fh->msgs, + struct cec_msg_entry, list); + + list_del(&entry->list); + *msg = entry->msg; + kfree(entry); + fh->queued_msgs--; + res = 0; + } else { + res = -EAGAIN; + } + mutex_unlock(&fh->lock); + if (!block || !res) + break; + if (msg->timeout) { + res = wait_event_interruptible_timeout(fh->wait, + fh->queued_msgs, + msecs_to_jiffies(msg->timeout)); + if (res == 0) + res = -ETIMEDOUT; + else if (res > 0) + res = 0; + } else { + res = wait_event_interruptible(fh->wait, + fh->queued_msgs); + } + } while (!res); + return res; +} + +static void cec_post_inputs_event_fh(struct cec_adapter *adap, + struct cec_fh *fh, u64 ts) +{ + struct cec_event *ev = &fh->events[CEC_EVENT_INPUTS_CHANGE - 1]; + struct cec_event_inputs_change *ch = &ev->inputs_change; + + mutex_lock(&fh->lock); + if (ev->event == 0) { + ev->ts = ts; + ev->event = CEC_EVENT_INPUTS_CHANGE; + } + ch->changed_inputs |= + adap->connected_inputs ^ ch->connected_inputs; + ch->connected_inputs = adap->connected_inputs; + mutex_unlock(&fh->lock); + wake_up_interruptible(&fh->wait); +} + +static void cec_post_inputs_event(struct cec_adapter *adap) +{ + u64 ts = ktime_get_ns(); + struct cec_fh *fh; + + mutex_lock(&adap->devnode.fhs_lock); + list_for_each_entry(fh, &adap->devnode.fhs, list) + cec_post_inputs_event_fh(adap, fh, ts); + mutex_unlock(&adap->devnode.fhs_lock); +} + +int cec_enable(struct cec_adapter *adap, bool enable) +{ + int ret = 0; + + mutex_lock(&adap->lock); + if (enable == adap->is_enabled) + goto unlock; + ret = adap->adap_enable(adap, enable); + if (ret) + goto unlock; + adap->is_configured = false; + adap->is_enabled = enable; + if (!enable) { + adap->num_log_addrs = 0; + memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs)); + wake_up_interruptible(&adap->kthread_waitq); + } + cec_post_state_event(adap); +unlock: + mutex_unlock(&adap->lock); + return ret; +} +EXPORT_SYMBOL_GPL(cec_enable); + +struct cec_log_addrs_int { + struct cec_adapter *adap; + struct cec_log_addrs log_addrs; + struct completion c; + bool free_on_exit; + int err; +}; + +static int cec_config_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs *log_addrs) +{ + static const u8 tv_log_addrs[] = { + 0, CEC_LOG_ADDR_INVALID + }; + static const u8 record_log_addrs[] = { + 1, 2, 9, 12, 13, CEC_LOG_ADDR_INVALID + }; + static const u8 tuner_log_addrs[] = { + 3, 6, 7, 10, 12, 13, CEC_LOG_ADDR_INVALID + }; + static const u8 playback_log_addrs[] = { + 4, 8, 11, 12, 13, CEC_LOG_ADDR_INVALID + }; + static const u8 audiosystem_log_addrs[] = { + 5, 12, 13, CEC_LOG_ADDR_INVALID + }; + static const u8 specific_use_log_addrs[] = { + 14, 12, 13, CEC_LOG_ADDR_INVALID + }; + static const u8 unregistered_log_addrs[] = { + CEC_LOG_ADDR_INVALID + }; + static const u8 *type2addrs[7] = { + [CEC_LOG_ADDR_TYPE_TV] = tv_log_addrs, + [CEC_LOG_ADDR_TYPE_RECORD] = record_log_addrs, + [CEC_LOG_ADDR_TYPE_TUNER] = tuner_log_addrs, + [CEC_LOG_ADDR_TYPE_PLAYBACK] = playback_log_addrs, + [CEC_LOG_ADDR_TYPE_AUDIOSYSTEM] = audiosystem_log_addrs, + [CEC_LOG_ADDR_TYPE_SPECIFIC] = specific_use_log_addrs, + [CEC_LOG_ADDR_TYPE_UNREGISTERED] = unregistered_log_addrs, + }; + struct cec_msg msg = { }; + u32 claimed_addrs = 0; + int i, j; + int err; + + if (adap->phys_addr) { + /* The TV functionality can only map to physical address 0. + For any other address, try the Specific functionality + instead as per the spec. */ + for (i = 0; i < log_addrs->num_log_addrs; i++) + if (log_addrs->log_addr_type[i] == CEC_LOG_ADDR_TYPE_TV) + log_addrs->log_addr_type[i] = + CEC_LOG_ADDR_TYPE_SPECIFIC; + } + + dprintk(2, "physical address: %x.%x.%x.%x, claim %d logical addresses\n", + cec_phys_addr_exp(adap->phys_addr), + log_addrs->num_log_addrs); + strlcpy(adap->osd_name, log_addrs->osd_name, sizeof(adap->osd_name)); + adap->num_log_addrs = 0; + cec_post_state_event(adap); + + /* TODO: remember last used logical addr type to achieve + faster logical address polling by trying that one first. + */ + for (i = 0; i < log_addrs->num_log_addrs; i++) { + const u8 *la_list = type2addrs[log_addrs->log_addr_type[i]]; + + if (kthread_should_stop()) { + err = -EINTR; + goto unconfigure; + } + + for (j = 0; la_list[j] != CEC_LOG_ADDR_INVALID; j++) { + u8 log_addr = la_list[j]; + + if (claimed_addrs & (1 << log_addr)) + continue; + + /* Send polling message */ + msg.len = 1; + msg.msg[0] = 0xf0 | log_addr; + msg.reply = 0; + err = cec_transmit_msg(adap, &msg, true); + if (err) + goto unconfigure; + + if (msg.status == CEC_TX_STATUS_RETRY_TIMEOUT) { + unsigned idx = adap->num_log_addrs++; + + /* Message not acknowledged, so this logical + address is free to use. */ + claimed_addrs |= 1 << log_addr; + adap->log_addr[idx] = log_addr; + log_addrs->log_addr[i] = log_addr; + adap->log_addr_type[idx] = + log_addrs->log_addr_type[i]; + adap->prim_device[idx] = + log_addrs->primary_device_type[i]; + adap->all_device_types[idx] = + log_addrs->all_device_types[i]; + adap->phys_addrs[log_addr] = adap->phys_addr; + memcpy(adap->features[idx], log_addrs->features[i], + sizeof(adap->features[idx])); + err = adap->adap_log_addr(adap, log_addr); + dprintk(2, "claim addr %d (%d)\n", log_addr, + adap->prim_device[idx]); + if (err) + goto unconfigure; + + /* + * Report Features must come first according + * to CEC 2.0 + */ + cec_report_features(adap, idx); + cec_report_phys_addr(adap, idx); + break; + } + } + } + if (adap->num_log_addrs == 0) { + if (log_addrs->num_log_addrs > 1) + dprintk(2, "could not claim last %d addresses\n", + log_addrs->num_log_addrs - 1); + adap->log_addr[0] = 15; + adap->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED; + adap->prim_device[0] = CEC_OP_PRIM_DEVTYPE_SWITCH; + adap->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH; + err = adap->adap_log_addr(adap, 15); + dprintk(2, "claim addr %d (%d)\n", 15, adap->prim_device[0]); + if (err) + goto unconfigure; + + adap->num_log_addrs = 1; + /* TODO: do we need to do this for an unregistered device? */ + cec_report_phys_addr(adap, 0); + } + mutex_lock(&adap->lock); + adap->is_configured = true; + adap->is_configuring = false; + cec_post_state_event(adap); + mutex_unlock(&adap->lock); + return 0; + +unconfigure: + mutex_lock(&adap->lock); + adap->num_log_addrs = 0; + adap->is_configuring = false; + cec_post_state_event(adap); + mutex_unlock(&adap->lock); + return err; +} + +static int cec_config_thread_func(void *arg) +{ + struct cec_log_addrs_int *cla_int = arg; + int err; + + cla_int->err = err = cec_config_log_addrs(cla_int->adap, + &cla_int->log_addrs); + cla_int->adap->kthread_config = NULL; + if (cla_int->free_on_exit) + kfree(cla_int); + else + complete(&cla_int->c); + + return err; +} + +int cec_claim_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs *log_addrs, bool block) +{ + struct cec_log_addrs_int *cla_int; + int i; + + if (!adap->is_enabled) + return -ENONET; + + if (log_addrs->num_log_addrs > CEC_MAX_LOG_ADDRS) { + dprintk(1, "num_log_addrs > %d\n", CEC_MAX_LOG_ADDRS); + return -EINVAL; + } + if (log_addrs->num_log_addrs == 0) { + int err = adap->adap_log_addr(adap, CEC_LOG_ADDR_INVALID); + + if (err) + return err; + mutex_lock(&adap->lock); + adap->is_configured = false; + adap->num_log_addrs = 0; + wake_up_interruptible(&adap->kthread_waitq); + cec_post_state_event(adap); + mutex_unlock(&adap->lock); + return 0; + } + if (log_addrs->cec_version != CEC_OP_CEC_VERSION_1_4 && + log_addrs->cec_version != CEC_OP_CEC_VERSION_2_0) { + dprintk(1, "unsupported CEC version\n"); + return -EINVAL; + } + if (log_addrs->num_log_addrs > 1) + for (i = 0; i < log_addrs->num_log_addrs; i++) + if (log_addrs->log_addr_type[i] == + CEC_LOG_ADDR_TYPE_UNREGISTERED) { + dprintk(1, "can't claim unregistered logical address\n"); + return -EINVAL; + } + for (i = 0; i < log_addrs->num_log_addrs; i++) { + u8 *features = log_addrs->features[i]; + bool op_is_dev_features = false; + + if (log_addrs->primary_device_type[i] > + CEC_OP_PRIM_DEVTYPE_PROCESSOR) { + dprintk(1, "unknown primary device type\n"); + return -EINVAL; + } + if (log_addrs->primary_device_type[i] == 2) { + dprintk(1, "invalid primary device type\n"); + return -EINVAL; + } + if (log_addrs->log_addr_type[i] > CEC_LOG_ADDR_TYPE_UNREGISTERED) { + dprintk(1, "unknown logical address type\n"); + return -EINVAL; + } + if (log_addrs->cec_version < CEC_OP_CEC_VERSION_2_0) + continue; + + for (i = 0; i < sizeof(adap->features[0]); i++) { + if ((features[i] & 0x80) == 0) { + if (op_is_dev_features) + break; + op_is_dev_features = true; + } + } + if (!op_is_dev_features || i == sizeof(adap->features[0])) { + dprintk(1, "malformed features\n"); + return -EINVAL; + } + } + + /* For phys addr 0xffff only the Unregistered functionality is + allowed. */ + if (adap->phys_addr == CEC_PHYS_ADDR_INVALID && + (log_addrs->num_log_addrs > 1 || + log_addrs->log_addr_type[0] != CEC_LOG_ADDR_TYPE_UNREGISTERED)) { + dprintk(1, "physical addr 0xffff only allows unregistered logical address\n"); + return -EINVAL; + } + + cla_int = kzalloc(sizeof(*cla_int), GFP_KERNEL); + if (cla_int == NULL) + return -ENOMEM; + mutex_lock(&adap->lock); + if (adap->is_configuring || adap->is_configured) { + mutex_unlock(&adap->lock); + kfree(cla_int); + return -EBUSY; + } + adap->is_configuring = true; + mutex_unlock(&adap->lock); + init_completion(&cla_int->c); + cla_int->free_on_exit = !block; + cla_int->adap = adap; + cla_int->log_addrs = *log_addrs; + adap->kthread_config = kthread_run(cec_config_thread_func, cla_int, + "ceccfg-%s", adap->name); + if (block) { + wait_for_completion(&cla_int->c); + *log_addrs = cla_int->log_addrs; + kfree(cla_int); + } + return 0; +} +EXPORT_SYMBOL_GPL(cec_claim_log_addrs); + +void cec_log_status(struct cec_adapter *adap) +{ + dprintk(0, "enabled: %d\n", adap->is_enabled); + dprintk(0, "configured: %d\n", adap->is_configured); + dprintk(0, "configuring: %d\n", adap->is_configuring); + dprintk(0, "phys_addr: %04x\n", adap->phys_addr); + dprintk(0, "number of LAs: %d\n", adap->num_log_addrs); + if (adap->cec_owner) + dprintk(0, "has owner\n"); + if (adap->passthrough) + dprintk(0, "has passthrough\n"); + if (mutex_is_locked(&adap->lock)) + dprintk(0, "is locked\n"); +} +EXPORT_SYMBOL_GPL(cec_log_status); + +void cec_connected_inputs(struct cec_adapter *adap, u16 connected_inputs) +{ + if (adap->connected_inputs != connected_inputs) { + adap->connected_inputs = connected_inputs; + if (cec_devnode_is_registered(&adap->devnode)) + cec_post_inputs_event(adap); + } +} +EXPORT_SYMBOL_GPL(cec_connected_inputs); + +u8 cec_sink_cdc_hpd(struct cec_adapter *adap, u8 input_port, u8 cdc_hpd_state) +{ + struct cec_msg msg = { }; + int err; + + if (!adap->is_configured) + return CEC_OP_HPD_ERROR_INITIATOR_WRONG_STATE; + + msg.msg[0] = (adap->log_addr[0] << 4) | 0xf; + cec_msg_cdc_hpd_set_state(&msg, input_port, cdc_hpd_state); + err = cec_transmit_msg(adap, &msg, false); + if (err) + return CEC_OP_HPD_ERROR_OTHER; + return CEC_OP_HPD_ERROR_NONE; +} +EXPORT_SYMBOL_GPL(cec_sink_cdc_hpd); + +static unsigned int cec_poll(struct file *filp, + struct poll_table_struct *poll) +{ + struct cec_fh *fh = filp->private_data; + struct cec_adapter *adap = fh->adap; + unsigned res = 0; + + if (!cec_devnode_is_registered(&adap->devnode)) + return POLLERR | POLLHUP; + mutex_lock(&adap->lock); + if (adap->is_configured) + res |= POLLOUT | POLLWRNORM; + if (fh->queued_msgs) + res |= POLLIN | POLLRDNORM; + if (fh->events) + res |= POLLPRI; + poll_wait(filp, &fh->wait, poll); + mutex_unlock(&adap->lock); + return res; +} + +static long cec_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct cec_fh *fh = filp->private_data; + struct cec_adapter *adap = fh->adap; + bool block = !(filp->f_flags & O_NONBLOCK); + void __user *parg = (void __user *)arg; + int err; + + if (!cec_devnode_is_registered(&adap->devnode)) + return -EIO; + + switch (cmd) { + case CEC_ADAP_G_CAPS: { + struct cec_caps caps; + + caps.available_log_addrs = adap->available_log_addrs; + caps.capabilities = adap->capabilities; + caps.ninputs = adap->ninputs; + memset(caps.reserved, 0, sizeof(caps.reserved)); + if (copy_to_user(parg, &caps, sizeof(caps))) + return -EFAULT; + break; + } + + case CEC_TRANSMIT: { + struct cec_msg msg; + + if (!(adap->capabilities & CEC_CAP_IO)) + return -ENOTTY; + if (copy_from_user(&msg, parg, sizeof(msg))) + return -EFAULT; + memset(msg.reserved, 0, sizeof(msg.reserved)); + if (!adap->is_configured) + return -ENONET; + if (fh->monitor) + return -EPERM; + if (adap->cec_owner && adap->cec_owner != fh) + return -EBUSY; + if (block || !msg.reply) + fh = NULL; + + err = cec_transmit_msg_fh(adap, &msg, fh, block); + if (err) + return err; + if (copy_to_user(parg, &msg, sizeof(msg))) + return -EFAULT; + break; + } + + case CEC_RECEIVE: { + struct cec_msg msg; + + if (!(adap->capabilities & CEC_CAP_IO)) + return -ENOTTY; + if (copy_from_user(&msg, parg, sizeof(msg))) + return -EFAULT; + memset(msg.reserved, 0, sizeof(msg.reserved)); + if (!adap->is_configured) + return -ENONET; + if (!fh->monitor && adap->cec_owner != fh) + return -EPERM; + + err = cec_receive_msg(fh, &msg, block); + if (err) + return err; + if (copy_to_user(parg, &msg, sizeof(msg))) + return -EFAULT; + break; + } + + case CEC_DQEVENT: { + struct cec_event *ev = NULL; + u64 ts = ~0ULL; + unsigned i; + + mutex_lock(&fh->lock); + for (i = 0; i < CEC_NUM_EVENTS; i++) { + if (fh->events[i].event && + fh->events[i].ts <= ts) { + ev = &fh->events[i]; + ts = ev->ts; + } + } + err = -EAGAIN; + if (ev) { + if (copy_to_user((void __user *)arg, ev, sizeof(*ev))) { + err = -EFAULT; + } else { + if (ev->event == CEC_EVENT_INPUTS_CHANGE) + ev->inputs_change.changed_inputs = 0; + ev->event = 0; + err = 0; + } + } + mutex_unlock(&fh->lock); + return err; + } + + case CEC_ADAP_G_STATE: { + u32 state = adap->is_enabled; + + if (copy_to_user(parg, &state, sizeof(state))) + return -EFAULT; + break; + } + + case CEC_ADAP_S_STATE: { + u32 state; + + if (!(adap->capabilities & CEC_CAP_STATE)) + return -ENOTTY; + if (copy_from_user(&state, parg, sizeof(state))) + return -EFAULT; + if (state > CEC_ADAP_ENABLED) + return -EINVAL; + if (adap->is_configuring) + return -EBUSY; + if (state == adap->is_enabled) + return 0; + if (adap->cec_owner && adap->cec_owner != fh) + return -EBUSY; + cec_enable(adap, state); + break; + } + + case CEC_ADAP_G_PHYS_ADDR: { + u16 phys_addr = adap->is_enabled ? adap->phys_addr : + CEC_PHYS_ADDR_INVALID; + + if (copy_to_user(parg, &phys_addr, sizeof(adap->phys_addr))) + return -EFAULT; + break; + } + + case CEC_ADAP_S_PHYS_ADDR: { + u16 phys_addr; + + if (!(adap->capabilities & CEC_CAP_PHYS_ADDR)) + return -ENOTTY; + if (copy_from_user(&phys_addr, parg, sizeof(phys_addr))) + return -EFAULT; + if (adap->phys_addr == phys_addr) + return 0; + if (!adap->is_enabled) + return -ENONET; + if (adap->is_configuring || adap->is_configured) + return -EBUSY; + if (adap->cec_owner && adap->cec_owner != fh) + return -EBUSY; + adap->phys_addr = phys_addr; + break; + } + + case CEC_ADAP_S_LOG_ADDRS: { + struct cec_log_addrs log_addrs; + + if (!(adap->capabilities & CEC_CAP_LOG_ADDRS)) + return -ENOTTY; + if (copy_from_user(&log_addrs, parg, sizeof(log_addrs))) + return -EFAULT; + if (adap->is_configuring) + return -EBUSY; + if (log_addrs.num_log_addrs && adap->is_configured) + return -EBUSY; + if (adap->cec_owner && adap->cec_owner != fh) + return -EBUSY; + + memset(log_addrs.reserved, 0, sizeof(log_addrs.reserved)); + err = cec_claim_log_addrs(adap, &log_addrs, block); + if (err) + return err; + + if (filp->f_flags & O_NONBLOCK) { + if (copy_to_user(parg, &log_addrs, sizeof(log_addrs))) + return -EFAULT; + break; + } + + /* fall through */ + } + + case CEC_ADAP_G_LOG_ADDRS: { + struct cec_log_addrs log_addrs = { adap->cec_version }; + unsigned i; + + mutex_lock(&adap->lock); + log_addrs.num_log_addrs = adap->num_log_addrs; + strlcpy(log_addrs.osd_name, adap->osd_name, + sizeof(log_addrs.osd_name)); + for (i = 0; i < adap->num_log_addrs; i++) { + log_addrs.primary_device_type[i] = adap->prim_device[i]; + log_addrs.log_addr_type[i] = adap->log_addr_type[i]; + log_addrs.log_addr[i] = adap->log_addr[i]; + log_addrs.all_device_types[i] = adap->all_device_types[i]; + memcpy(log_addrs.features[i], adap->features[i], + sizeof(log_addrs.features[i])); + } + mutex_unlock(&adap->lock); + + if (copy_to_user(parg, &log_addrs, sizeof(log_addrs))) + return -EFAULT; + break; + } + + case CEC_ADAP_G_VENDOR_ID: + if (copy_to_user(parg, &adap->vendor_id, + sizeof(adap->vendor_id))) + return -EFAULT; + break; + + case CEC_ADAP_S_VENDOR_ID: { + u32 vendor_id; + + if (!(adap->capabilities & CEC_CAP_VENDOR_ID)) + return -ENOTTY; + if (copy_from_user(&vendor_id, parg, sizeof(vendor_id))) + return -EFAULT; + /* Vendor ID is a 24 bit number, so check if the value is + * within the correct range. */ + if (vendor_id != CEC_VENDOR_ID_NONE && + (vendor_id & 0xff000000) != 0) + return -EINVAL; + if (adap->vendor_id == vendor_id) + return 0; + if (adap->is_configuring || adap->is_configured) + return -EBUSY; + if (adap->cec_owner && adap->cec_owner != fh) + return -EBUSY; + adap->vendor_id = vendor_id; + break; + } + + case CEC_G_PASSTHROUGH: { + u32 passthrough = CEC_PASSTHROUGH_DISABLED; + + if (!(adap->capabilities & CEC_CAP_PASSTHROUGH)) + return -ENOTTY; + if (adap->cec_owner == fh) + passthrough = adap->passthrough; + if (copy_to_user(parg, &passthrough, sizeof(passthrough))) + return -EFAULT; + break; + } + + case CEC_G_MONITOR: { + u32 monitor = fh->monitor; + + if (copy_to_user(parg, &monitor, sizeof(monitor))) + return -EFAULT; + break; + } + + case CEC_S_MONITOR: { + u32 monitor; + + if (copy_from_user(&monitor, parg, sizeof(monitor))) + return -EFAULT; + if (monitor > CEC_MONITOR_ENABLED) + return -EINVAL; + if (fh->monitor == monitor) + break; + if (adap->cec_owner == fh) + return -EBUSY; + fh->monitor = monitor; + break; + } + + case CEC_CLAIM: { + u32 passthrough; + + if (copy_from_user(&passthrough, parg, sizeof(passthrough))) + return -EFAULT; + if (passthrough > CEC_PASSTHROUGH_ENABLED) + return -EINVAL; + if (passthrough && + !(adap->capabilities & CEC_CAP_PASSTHROUGH)) + return -EPERM; + if (adap->cec_owner && adap->cec_owner != fh) + return -EBUSY; + if (fh->monitor) + return -EBUSY; + mutex_lock(&adap->lock); + adap->passthrough = passthrough; + adap->cec_owner = fh; + mutex_unlock(&adap->lock); + break; + } + + case CEC_RELEASE: { + if (adap->cec_owner && adap->cec_owner != fh) + return -EBUSY; + mutex_lock(&adap->lock); + adap->cec_owner = NULL; + adap->passthrough = 0; + mutex_unlock(&adap->lock); + break; + } + + default: + return -ENOTTY; + } + return 0; +} + +/* Override for the open function */ +static int cec_open(struct inode *inode, struct file *filp) +{ + struct cec_devnode *cecdev; + struct cec_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL); + + if (fh == NULL) + return -ENOMEM; + + INIT_LIST_HEAD(&fh->msgs); + mutex_init(&fh->lock); + init_waitqueue_head(&fh->wait); + + /* Check if the cec device is available. This needs to be done with + * the cec_devnode_lock held to prevent an open/unregister race: + * without the lock, the device could be unregistered and freed between + * the cec_devnode_is_registered() and get_device() calls, leading to + * a crash. + */ + mutex_lock(&cec_devnode_lock); + cecdev = container_of(inode->i_cdev, struct cec_devnode, cdev); + /* return ENXIO if the cec device has been removed + already or if it is not registered anymore. */ + if (!cec_devnode_is_registered(cecdev)) { + mutex_unlock(&cec_devnode_lock); + kfree(fh); + return -ENXIO; + } + /* and increase the device refcount */ + get_device(&cecdev->dev); + mutex_unlock(&cec_devnode_lock); + + fh->adap = to_cec_adapter(cecdev); + filp->private_data = fh; + mutex_lock(&cecdev->fhs_lock); + list_add(&fh->list, &cecdev->fhs); + mutex_unlock(&cecdev->fhs_lock); + if (fh->adap->ninputs) + cec_post_inputs_event_fh(fh->adap, fh, ktime_get_ns()); + cec_post_state_event_fh(fh->adap, fh, ktime_get_ns()); + + return 0; +} + +/* Override for the release function */ +static int cec_release(struct inode *inode, struct file *filp) +{ + struct cec_devnode *cecdev = cec_devnode_data(filp); + struct cec_adapter *adap = to_cec_adapter(cecdev); + struct cec_fh *fh = filp->private_data; + int ret = 0; + + if (adap->cec_owner == fh) + adap->cec_owner = NULL; + + mutex_lock(&cecdev->fhs_lock); + list_del(&fh->list); + mutex_unlock(&cecdev->fhs_lock); + + while (!list_empty(&fh->msgs)) { + struct cec_msg_entry *entry = + list_first_entry(&fh->msgs, struct cec_msg_entry, list); + + list_del(&entry->list); + kfree(entry); + } + kfree(fh); + + /* decrease the refcount unconditionally since the release() + return value is ignored. */ + put_device(&cecdev->dev); + filp->private_data = NULL; + return ret; +} + +static const struct file_operations cec_devnode_fops = { + .owner = THIS_MODULE, + .open = cec_open, + .unlocked_ioctl = cec_ioctl, + .release = cec_release, + .poll = cec_poll, + .llseek = no_llseek, +}; + +/** + * cec_devnode_register - register a cec device node + * @cecdev: cec device node structure we want to register + * + * The registration code assigns minor numbers and registers the new device node + * with the kernel. An error is returned if no free minor number can be found, + * or if the registration of the device node fails. + * + * Zero is returned on success. + * + * Note that if the cec_devnode_register call fails, the release() callback of + * the cec_devnode structure is *not* called, so the caller is responsible for + * freeing any data. + */ +static int __must_check cec_devnode_register(struct cec_devnode *cecdev, + struct module *owner) +{ + int minor; + int ret; + + /* Initialization */ + INIT_LIST_HEAD(&cecdev->fhs); + mutex_init(&cecdev->fhs_lock); + + /* Part 1: Find a free minor number */ + mutex_lock(&cec_devnode_lock); + minor = find_next_zero_bit(cec_devnode_nums, CEC_NUM_DEVICES, 0); + if (minor == CEC_NUM_DEVICES) { + mutex_unlock(&cec_devnode_lock); + pr_err("could not get a free minor\n"); + return -ENFILE; + } + + set_bit(minor, cec_devnode_nums); + mutex_unlock(&cec_devnode_lock); + + cecdev->minor = minor; + + /* Part 2: Initialize and register the character device */ + cdev_init(&cecdev->cdev, &cec_devnode_fops); + cecdev->cdev.owner = owner; + + ret = cdev_add(&cecdev->cdev, MKDEV(MAJOR(cec_dev_t), cecdev->minor), + 1); + if (ret < 0) { + pr_err("%s: cdev_add failed\n", __func__); + goto error; + } + + /* Part 3: Register the cec device */ + cecdev->dev.bus = &cec_bus_type; + cecdev->dev.devt = MKDEV(MAJOR(cec_dev_t), cecdev->minor); + cecdev->dev.release = cec_devnode_release; + if (cecdev->parent) + cecdev->dev.parent = cecdev->parent; + dev_set_name(&cecdev->dev, "cec%d", cecdev->minor); + ret = device_register(&cecdev->dev); + if (ret < 0) { + pr_err("%s: device_register failed\n", __func__); + goto error; + } + + /* Part 4: Activate this minor. The char device can now be used. */ + set_bit(CEC_FLAG_REGISTERED, &cecdev->flags); + + return 0; + +error: + cdev_del(&cecdev->cdev); + clear_bit(cecdev->minor, cec_devnode_nums); + return ret; +} + +/** + * cec_devnode_unregister - unregister a cec device node + * @cecdev: the device node to unregister + * + * This unregisters the passed device. Future open calls will be met with + * errors. + * + * This function can safely be called if the device node has never been + * registered or has already been unregistered. + */ +static void cec_devnode_unregister(struct cec_devnode *cecdev) +{ + /* Check if cecdev was ever registered at all */ + if (!cec_devnode_is_registered(cecdev)) + return; + + mutex_lock(&cec_devnode_lock); + clear_bit(CEC_FLAG_REGISTERED, &cecdev->flags); + mutex_unlock(&cec_devnode_lock); + device_unregister(&cecdev->dev); +} + +int cec_create_adapter(struct cec_adapter *adap, const char *name, u32 caps, + u8 ninputs, struct module *owner, struct device *parent) +{ + int res = 0; + + adap->owner = owner; + if (WARN_ON(!owner)) + return -ENXIO; + adap->devnode.parent = parent; + if (WARN_ON(!parent)) + return -ENXIO; + adap->name = name; + adap->phys_addr = CEC_PHYS_ADDR_INVALID; + adap->capabilities = caps; + adap->ninputs = ninputs; + adap->is_source = caps & CEC_CAP_IS_SOURCE; + if (WARN_ON(!adap->ninputs && !adap->is_source)) + return -ENXIO; + adap->cec_version = CEC_OP_CEC_VERSION_2_0; + adap->vendor_id = CEC_VENDOR_ID_NONE; + adap->available_log_addrs = 1; + adap->sequence = 0; + memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs)); + mutex_init(&adap->lock); + INIT_LIST_HEAD(&adap->transmit_queue); + INIT_LIST_HEAD(&adap->wait_queue); + adap->kthread = kthread_run(cec_thread_func, adap, "cec-%s", name); + init_waitqueue_head(&adap->kthread_waitq); + if (IS_ERR(adap->kthread)) { + pr_err("cec-%s: kernel_thread() failed\n", name); + return PTR_ERR(adap->kthread); + } + if (caps) { + res = cec_devnode_register(&adap->devnode, adap->owner); + if (res) { + kthread_stop(adap->kthread); + return res; + } + } + + if (!(caps & CEC_CAP_RC)) + return 0; + + /* Prepare the RC input device */ + adap->rc = rc_allocate_device(); + if (!adap->rc) { + pr_err("cec-%s: failed to allocate memory for rc_dev\n", name); + cec_devnode_unregister(&adap->devnode); + kthread_stop(adap->kthread); + return -ENOMEM; + } + + snprintf(adap->input_name, sizeof(adap->input_name), "RC for %s", name); + snprintf(adap->input_phys, sizeof(adap->input_phys), "%s/input0", name); + strlcpy(adap->input_drv, name, sizeof(adap->input_drv)); + + adap->rc->input_name = adap->input_name; + adap->rc->input_phys = adap->input_phys; + adap->rc->input_id.bustype = BUS_CEC; + adap->rc->input_id.vendor = 0; + adap->rc->input_id.product = 0; + adap->rc->input_id.version = 1; + adap->rc->dev.parent = adap->devnode.parent; + adap->rc->driver_name = adap->input_drv; + adap->rc->driver_type = RC_DRIVER_CEC; + adap->rc->allowed_protocols = RC_BIT_CEC; + adap->rc->priv = adap; + adap->rc->map_name = RC_MAP_CEC; + adap->rc->timeout = MS_TO_NS(100); + + res = rc_register_device(adap->rc); + + if (res) { + pr_err("cec-%s: failed to prepare input device\n", name); + cec_devnode_unregister(&adap->devnode); + rc_free_device(adap->rc); + kthread_stop(adap->kthread); + } + + return res; +} +EXPORT_SYMBOL_GPL(cec_create_adapter); + +void cec_delete_adapter(struct cec_adapter *adap) +{ + if (adap->kthread == NULL) + return; + kthread_stop(adap->kthread); + if (adap->kthread_config) + kthread_stop(adap->kthread_config); + if (adap->is_enabled) + cec_enable(adap, false); + rc_unregister_device(adap->rc); + if (cec_devnode_is_registered(&adap->devnode)) + cec_devnode_unregister(&adap->devnode); +} +EXPORT_SYMBOL_GPL(cec_delete_adapter); + +/* + * Initialise cec for linux + */ +static int __init cec_devnode_init(void) +{ + int ret; + + pr_info("Linux cec interface: v0.10\n"); + ret = alloc_chrdev_region(&cec_dev_t, 0, CEC_NUM_DEVICES, + CEC_NAME); + if (ret < 0) { + pr_warn("cec: unable to allocate major\n"); + return ret; + } + + ret = bus_register(&cec_bus_type); + if (ret < 0) { + unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES); + pr_warn("cec: bus_register failed\n"); + return -EIO; + } + + return 0; +} + +static void __exit cec_devnode_exit(void) +{ + bus_unregister(&cec_bus_type); + unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES); +} + +subsys_initcall(cec_devnode_init); +module_exit(cec_devnode_exit) + +MODULE_AUTHOR("Hans Verkuil hans.verkuil@cisco.com"); +MODULE_DESCRIPTION("Device node registration for cec drivers"); +MODULE_LICENSE("GPL"); diff --git a/include/media/cec.h b/include/media/cec.h new file mode 100644 index 0000000..8f01c5d --- /dev/null +++ b/include/media/cec.h @@ -0,0 +1,178 @@ +#ifndef _CEC_DEVNODE_H +#define _CEC_DEVNODE_H + +#include <linux/poll.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/cdev.h> +#include <linux/kthread.h> +#include <linux/timer.h> +#include <linux/cec-funcs.h> +#include <media/rc-core.h> + +#define cec_phys_addr_exp(pa) \ + ((pa) >> 12), ((pa) >> 8) & 0xf, ((pa) >> 4) & 0xf, (pa) & 0xf + +/* + * Flag to mark the cec_devnode struct as registered. Drivers must not touch + * this flag directly, it will be set and cleared by cec_devnode_register and + * cec_devnode_unregister. + */ +#define CEC_FLAG_REGISTERED 0 + +/** + * struct cec_devnode - cec device node + * @parent: parent device + * @minor: device node minor number + * @flags: flags, combination of the CEC_FLAG_* constants + * + * This structure represents a cec-related device node. + * + * The @parent is a physical device. It must be set by core or device drivers + * before registering the node. + */ +struct cec_devnode { + /* sysfs */ + struct device dev; /* cec device */ + struct cdev cdev; /* character device */ + struct device *parent; /* device parent */ + + /* device info */ + int minor; + unsigned long flags; /* Use bitops to access flags */ + struct mutex fhs_lock; + struct list_head fhs; /* cec_fh list */ + + /* callbacks */ + void (*release)(struct cec_devnode *cecdev); +}; + +static inline int cec_devnode_is_registered(struct cec_devnode *cecdev) +{ + return test_bit(CEC_FLAG_REGISTERED, &cecdev->flags); +} + +struct cec_adapter; +struct cec_data; + +struct cec_data { + struct list_head list; + struct cec_adapter *adap; + struct cec_msg msg; + struct cec_fh *fh; + struct delayed_work work; + struct completion c; + bool blocking; + bool completed; +}; + +struct cec_msg_entry { + struct list_head list; + struct cec_msg msg; +}; + +#define CEC_NUM_EVENTS CEC_EVENT_LOST_MSGS + +struct cec_fh { + struct list_head list; + struct cec_adapter *adap; + bool monitor; + + /* Events */ + wait_queue_head_t wait; + struct cec_event events[CEC_NUM_EVENTS]; + struct mutex lock; + struct list_head msgs; /* queued messages */ + unsigned int queued_msgs; +}; + +/* + * The minimum message length you can receive (excepting poll messages) is 2. + * With a transfer rate of at most 36 bytes per second this makes 18 messages + * per second worst case. + * + * We queue at most 10 seconds worth of messages. + */ +#define CEC_MAX_MSG_QUEUE_SZ (18 * 10) + +struct cec_adapter { + struct module *owner; + const char *name; + struct cec_devnode devnode; + struct mutex lock; + struct rc_dev *rc; + + struct list_head transmit_queue; + struct list_head wait_queue; + struct cec_data *transmitting; + + struct task_struct *kthread_config; + + struct task_struct *kthread; + wait_queue_head_t kthread_waitq; + wait_queue_head_t waitq; + + /* Can be set by the main driver: */ + u32 capabilities; + u8 available_log_addrs; + u8 pwr_state; + u16 phys_addr; + u32 vendor_id; + u8 cec_version; + + u8 ninputs; + u16 connected_inputs; + bool is_source; + bool is_enabled; + bool is_configuring; + bool is_configured; + u8 num_log_addrs; + struct cec_fh *cec_owner; + u8 prim_device[CEC_MAX_LOG_ADDRS]; + u8 log_addr_type[CEC_MAX_LOG_ADDRS]; + u8 log_addr[CEC_MAX_LOG_ADDRS]; + u8 all_device_types[CEC_MAX_LOG_ADDRS]; + u8 features[CEC_MAX_LOG_ADDRS][12]; + u16 phys_addrs[15]; + char osd_name[15]; + u8 passthrough; + u32 sequence; + + char input_name[32]; + char input_phys[32]; + char input_drv[32]; + + int (*adap_enable)(struct cec_adapter *adap, bool enable); + int (*adap_log_addr)(struct cec_adapter *adap, u8 logical_addr); + int (*adap_transmit)(struct cec_adapter *adap, u32 timeout_ms, struct cec_msg *msg); + + int (*received)(struct cec_adapter *adap, struct cec_msg *msg); + + /* High-level callbacks */ + u8 (*source_cdc_hpd)(struct cec_adapter *adap, u8 cdc_hpd_state); + u8 (*sink_cdc_hpd)(struct cec_adapter *adap, u8 cdc_hpd_state, u8 cdc_hpd_error); + int (*sink_initiate_arc)(struct cec_adapter *adap); + int (*sink_terminate_arc)(struct cec_adapter *adap); + int (*source_arc_initiated)(struct cec_adapter *adap); + int (*source_arc_terminated)(struct cec_adapter *adap); +}; + +#define to_cec_adapter(node) container_of(node, struct cec_adapter, devnode) + +int cec_create_adapter(struct cec_adapter *adap, const char *name, u32 caps, + u8 ninputs, struct module *owner, struct device *parent); +void cec_delete_adapter(struct cec_adapter *adap); +int cec_transmit_msg(struct cec_adapter *adap, struct cec_msg *msg, + bool block); +int cec_claim_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs *log_addrs, bool block); +int cec_enable(struct cec_adapter *adap, bool enable); +u8 cec_sink_cdc_hpd(struct cec_adapter *adap, u8 input_port, u8 cdc_hpd_state); +void cec_log_status(struct cec_adapter *adap); + +/* Called by the adapter */ +void cec_transmit_done(struct cec_adapter *adap, u32 status); +void cec_received_msg(struct cec_adapter *adap, struct cec_msg *msg); +void cec_connected_inputs(struct cec_adapter *adap, u16 connected_inputs); + +#endif /* _CEC_DEVNODE_H */ diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index 1ff9942..fef06f9 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -81,6 +81,8 @@ header-y += capi.h header-y += cciss_defs.h header-y += cciss_ioctl.h header-y += cdrom.h +header-y += cec.h +header-y += cec-funcs.h header-y += cgroupstats.h header-y += chio.h header-y += cm4000_cs.h diff --git a/include/uapi/linux/cec-funcs.h b/include/uapi/linux/cec-funcs.h new file mode 100644 index 0000000..b27b045 --- /dev/null +++ b/include/uapi/linux/cec-funcs.h @@ -0,0 +1,1771 @@ +#ifndef _CEC_FUNCS_H +#define _CEC_FUNCS_H + +#include <linux/cec.h> + +/* One Touch Play Feature */ +static inline void cec_msg_active_source(struct cec_msg *msg, __u16 phys_addr) +{ + msg->len = 4; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_ACTIVE_SOURCE; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; +} + +static inline void cec_ops_active_source(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_image_view_on(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_IMAGE_VIEW_ON; +} + +static inline void cec_msg_text_view_on(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_TEXT_VIEW_ON; +} + + +/* Routing Control Feature */ +static inline void cec_msg_inactive_source(struct cec_msg *msg, + __u16 phys_addr) +{ + msg->len = 4; + msg->msg[1] = CEC_MSG_INACTIVE_SOURCE; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; +} + +static inline void cec_ops_inactive_source(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_request_active_source(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_REQUEST_ACTIVE_SOURCE; + msg->reply = reply ? CEC_MSG_ACTIVE_SOURCE : 0; +} + +static inline void cec_msg_routing_information(struct cec_msg *msg, + __u16 phys_addr) +{ + msg->len = 4; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_ROUTING_INFORMATION; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; +} + +static inline void cec_ops_routing_information(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_routing_change(struct cec_msg *msg, + bool reply, + __u16 orig_phys_addr, + __u16 new_phys_addr) +{ + msg->len = 6; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_ROUTING_CHANGE; + msg->msg[2] = orig_phys_addr >> 8; + msg->msg[3] = orig_phys_addr & 0xff; + msg->msg[4] = new_phys_addr >> 8; + msg->msg[5] = new_phys_addr & 0xff; + msg->reply = reply ? CEC_MSG_ROUTING_INFORMATION : 0; +} + +static inline void cec_ops_routing_change(const struct cec_msg *msg, + __u16 *orig_phys_addr, + __u16 *new_phys_addr) +{ + *orig_phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *new_phys_addr = (msg->msg[4] << 8) | msg->msg[5]; +} + +static inline void cec_msg_set_stream_path(struct cec_msg *msg, __u16 phys_addr) +{ + msg->len = 4; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_SET_STREAM_PATH; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; +} + +static inline void cec_ops_set_stream_path(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + + +/* Standby Feature */ +static inline void cec_msg_standby(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_STANDBY; +} + + +/* One Touch Record Feature */ +static inline void cec_msg_record_off(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_RECORD_OFF; +} + +struct cec_op_arib_data { + __u16 transport_id; + __u16 service_id; + __u16 orig_network_id; +}; + +struct cec_op_atsc_data { + __u16 transport_id; + __u16 program_number; +}; + +struct cec_op_dvb_data { + __u16 transport_id; + __u16 service_id; + __u16 orig_network_id; +}; + +struct cec_op_channel_data { + __u8 channel_number_fmt; + __u16 major; + __u16 minor; +}; + +struct cec_op_digital_service_id { + __u8 service_id_method; + __u8 dig_bcast_system; + union { + struct cec_op_arib_data arib; + struct cec_op_atsc_data atsc; + struct cec_op_dvb_data dvb; + struct cec_op_channel_data channel; + }; +}; + +struct cec_op_record_src { + __u8 type; + union { + struct cec_op_digital_service_id digital; + struct { + __u8 ana_bcast_type; + __u16 ana_freq; + __u8 bcast_system; + } analog; + struct { + __u8 plug; + } ext_plug; + struct { + __u16 phys_addr; + } ext_phys_addr; + }; +}; + +static inline void cec_set_digital_service_id(__u8 *msg, + const struct cec_op_digital_service_id *digital) +{ + *msg++ = (digital->service_id_method << 7) | digital->dig_bcast_system; + if (digital->service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) { + *msg++ = (digital->channel.channel_number_fmt << 2) | + (digital->channel.major >> 8); + *msg++ = digital->channel.major && 0xff; + *msg++ = digital->channel.minor >> 8; + *msg++ = digital->channel.minor & 0xff; + *msg++ = 0; + *msg++ = 0; + return; + } + switch (digital->dig_bcast_system) { + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: + *msg++ = digital->atsc.transport_id >> 8; + *msg++ = digital->atsc.transport_id & 0xff; + *msg++ = digital->atsc.program_number >> 8; + *msg++ = digital->atsc.program_number & 0xff; + *msg++ = 0; + *msg++ = 0; + break; + default: + *msg++ = digital->dvb.transport_id >> 8; + *msg++ = digital->dvb.transport_id & 0xff; + *msg++ = digital->dvb.service_id >> 8; + *msg++ = digital->dvb.service_id & 0xff; + *msg++ = digital->dvb.orig_network_id >> 8; + *msg++ = digital->dvb.orig_network_id & 0xff; + break; + } +} + +static inline void cec_get_digital_service_id(const __u8 *msg, + struct cec_op_digital_service_id *digital) +{ + digital->service_id_method = msg[0] >> 7; + digital->dig_bcast_system = msg[0] & 0x7f; + if (digital->service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) { + digital->channel.channel_number_fmt = msg[1] >> 2; + digital->channel.major = ((msg[1] & 3) << 6) | msg[2]; + digital->channel.minor = (msg[3] << 8) | msg[4]; + return; + } + digital->dvb.transport_id = (msg[1] << 8) | msg[2]; + digital->dvb.service_id = (msg[3] << 8) | msg[4]; + digital->dvb.orig_network_id = (msg[5] << 8) | msg[6]; +} + +static inline void cec_msg_record_on_own(struct cec_msg *msg) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_RECORD_ON; + msg->msg[2] = CEC_OP_RECORD_SRC_OWN; +} + +static inline void cec_msg_record_on_digital(struct cec_msg *msg, + const struct cec_op_digital_service_id *digital) +{ + msg->len = 10; + msg->msg[1] = CEC_MSG_RECORD_ON; + msg->msg[2] = CEC_OP_RECORD_SRC_DIGITAL; + cec_set_digital_service_id(msg->msg + 3, digital); +} + +static inline void cec_msg_record_on_analog(struct cec_msg *msg, + __u8 ana_bcast_type, + __u16 ana_freq, + __u8 bcast_system) +{ + msg->len = 7; + msg->msg[1] = CEC_MSG_RECORD_ON; + msg->msg[2] = CEC_OP_RECORD_SRC_ANALOG; + msg->msg[3] = ana_bcast_type; + msg->msg[4] = ana_freq >> 8; + msg->msg[5] = ana_freq & 0xff; + msg->msg[6] = bcast_system; +} + +static inline void cec_msg_record_on_plug(struct cec_msg *msg, + __u8 plug) +{ + msg->len = 4; + msg->msg[1] = CEC_MSG_RECORD_ON; + msg->msg[2] = CEC_OP_RECORD_SRC_EXT_PLUG; + msg->msg[3] = plug; +} + +static inline void cec_msg_record_on_phys_addr(struct cec_msg *msg, + __u16 phys_addr) +{ + msg->len = 5; + msg->msg[1] = CEC_MSG_RECORD_ON; + msg->msg[2] = CEC_OP_RECORD_SRC_EXT_PHYS_ADDR; + msg->msg[3] = phys_addr >> 8; + msg->msg[4] = phys_addr & 0xff; +} + +static inline void cec_msg_record_on(struct cec_msg *msg, + const struct cec_op_record_src *rec_src) +{ + switch (rec_src->type) { + case CEC_OP_RECORD_SRC_OWN: + cec_msg_record_on_own(msg); + break; + case CEC_OP_RECORD_SRC_DIGITAL: + cec_msg_record_on_digital(msg, &rec_src->digital); + break; + case CEC_OP_RECORD_SRC_ANALOG: + cec_msg_record_on_analog(msg, + rec_src->analog.ana_bcast_type, + rec_src->analog.ana_freq, + rec_src->analog.bcast_system); + break; + case CEC_OP_RECORD_SRC_EXT_PLUG: + cec_msg_record_on_plug(msg, rec_src->ext_plug.plug); + break; + case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR: + cec_msg_record_on_phys_addr(msg, + rec_src->ext_phys_addr.phys_addr); + break; + } +} + +static inline void cec_ops_record_on(const struct cec_msg *msg, + struct cec_op_record_src *rec_src) +{ + rec_src->type = msg->msg[2]; + switch (rec_src->type) { + case CEC_OP_RECORD_SRC_OWN: + break; + case CEC_OP_RECORD_SRC_DIGITAL: + cec_get_digital_service_id(msg->msg + 3, &rec_src->digital); + break; + case CEC_OP_RECORD_SRC_ANALOG: + rec_src->analog.ana_bcast_type = msg->msg[3]; + rec_src->analog.ana_freq = + (msg->msg[4] << 8) | msg->msg[5]; + rec_src->analog.bcast_system = msg->msg[6]; + break; + case CEC_OP_RECORD_SRC_EXT_PLUG: + rec_src->ext_plug.plug = msg->msg[3]; + break; + case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR: + rec_src->ext_phys_addr.phys_addr = + (msg->msg[3] << 8) | msg->msg[4]; + break; + } +} + +static inline void cec_msg_record_status(struct cec_msg *msg, __u8 rec_status) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_RECORD_STATUS; + msg->msg[2] = rec_status; +} + +static inline void cec_ops_record_status(const struct cec_msg *msg, + __u8 *rec_status) +{ + *rec_status = msg->msg[2]; +} + +static inline void cec_msg_record_tv_screen(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_RECORD_TV_SCREEN; + msg->reply = reply ? CEC_MSG_RECORD_ON : 0; +} + + +/* Timer Programming Feature */ +static inline void cec_msg_timer_status(struct cec_msg *msg, + __u8 timer_overlap_warning, + __u8 media_info, + __u8 prog_info, + __u8 prog_error, + __u8 duration_hr, + __u8 duration_min) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_TIMER_STATUS; + msg->msg[2] = (timer_overlap_warning << 7) | + (media_info << 5) | + (prog_info ? 0x10 : 0) | + (prog_info ? prog_info : prog_error); + if (prog_info == CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE || + prog_info == CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE || + prog_error == CEC_OP_PROG_ERROR_DUPLICATE) { + msg->len += 2; + msg->msg[3] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[4] = ((duration_min / 10) << 4) | (duration_min % 10); + } +} + +static inline void cec_ops_timer_status(struct cec_msg *msg, + __u8 *timer_overlap_warning, + __u8 *media_info, + __u8 *prog_info, + __u8 *prog_error, + __u8 *duration_hr, + __u8 *duration_min) +{ + *timer_overlap_warning = msg->msg[2] >> 7; + *media_info = (msg->msg[2] >> 5) & 3; + if (msg->msg[2] & 0x10) { + *prog_info = msg->msg[2] & 0xf; + *prog_error = 0; + } else { + *prog_info = 0; + *prog_error = msg->msg[2] & 0xf; + } + if (*prog_info == CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE || + *prog_info == CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE || + *prog_error == CEC_OP_PROG_ERROR_DUPLICATE) { + *duration_hr = (msg->msg[3] >> 4) * 10 + (msg->msg[3] & 0xf); + *duration_min = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + } else { + *duration_hr = *duration_min = 0; + } +} + +static inline void cec_msg_timer_cleared_status(struct cec_msg *msg, + __u8 timer_cleared_status) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_TIMER_CLEARED_STATUS; + msg->msg[2] = timer_cleared_status; +} + +static inline void cec_ops_timer_cleared_status(struct cec_msg *msg, + __u8 *timer_cleared_status) +{ + *timer_cleared_status = msg->msg[2]; +} + +static inline void cec_msg_clear_analogue_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + __u8 ana_bcast_type, + __u16 ana_freq, + __u8 bcast_system) +{ + msg->len = 13; + msg->msg[1] = CEC_MSG_CLEAR_ANALOGUE_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + msg->msg[9] = ana_bcast_type; + msg->msg[10] = ana_freq >> 8; + msg->msg[11] = ana_freq & 0xff; + msg->msg[12] = bcast_system; + msg->reply = reply ? CEC_MSG_TIMER_CLEARED_STATUS : 0; +} + +static inline void cec_ops_clear_analogue_timer(struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + __u8 *ana_bcast_type, + __u16 *ana_freq, + __u8 *bcast_system) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + *ana_bcast_type = msg->msg[9]; + *ana_freq = (msg->msg[10] << 8) | msg->msg[11]; + *bcast_system = msg->msg[12]; +} + +static inline void cec_msg_clear_digital_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + const struct cec_op_digital_service_id *digital) +{ + msg->len = 16; + msg->reply = reply ? CEC_MSG_TIMER_CLEARED_STATUS : 0; + msg->msg[1] = CEC_MSG_CLEAR_DIGITAL_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + cec_set_digital_service_id(msg->msg + 9, digital); +} + +static inline void cec_ops_clear_digital_timer(struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + struct cec_op_digital_service_id *digital) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + cec_get_digital_service_id(msg->msg + 9, digital); +} + +static inline void cec_msg_clear_ext_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + __u8 ext_src_spec, + __u8 plug, + __u16 phys_addr) +{ + msg->len = 13; + msg->msg[1] = CEC_MSG_CLEAR_EXT_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + msg->msg[9] = ext_src_spec; + msg->msg[10] = plug; + msg->msg[11] = phys_addr >> 8; + msg->msg[12] = phys_addr & 0xff; + msg->reply = reply ? CEC_MSG_TIMER_CLEARED_STATUS : 0; +} + +static inline void cec_ops_clear_ext_timer(struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + __u8 *ext_src_spec, + __u8 *plug, + __u16 *phys_addr) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + *ext_src_spec = msg->msg[9]; + *plug = msg->msg[10]; + *phys_addr = (msg->msg[11] << 8) | msg->msg[12]; +} + +static inline void cec_msg_set_analogue_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + __u8 ana_bcast_type, + __u16 ana_freq, + __u8 bcast_system) +{ + msg->len = 13; + msg->msg[1] = CEC_MSG_SET_ANALOGUE_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + msg->msg[9] = ana_bcast_type; + msg->msg[10] = ana_freq >> 8; + msg->msg[11] = ana_freq & 0xff; + msg->msg[12] = bcast_system; + msg->reply = reply ? CEC_MSG_TIMER_STATUS : 0; +} + +static inline void cec_ops_set_analogue_timer(struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + __u8 *ana_bcast_type, + __u16 *ana_freq, + __u8 *bcast_system) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + *ana_bcast_type = msg->msg[9]; + *ana_freq = (msg->msg[10] << 8) | msg->msg[11]; + *bcast_system = msg->msg[12]; +} + +static inline void cec_msg_set_digital_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + const struct cec_op_digital_service_id *digital) +{ + msg->len = 16; + msg->reply = reply ? CEC_MSG_TIMER_STATUS : 0; + msg->msg[1] = CEC_MSG_SET_DIGITAL_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + cec_set_digital_service_id(msg->msg + 9, digital); +} + +static inline void cec_ops_set_digital_timer(struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + struct cec_op_digital_service_id *digital) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + cec_get_digital_service_id(msg->msg + 9, digital); +} + +static inline void cec_msg_set_ext_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + __u8 ext_src_spec, + __u8 plug, + __u16 phys_addr) +{ + msg->len = 13; + msg->msg[1] = CEC_MSG_SET_EXT_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + msg->msg[9] = ext_src_spec; + msg->msg[10] = plug; + msg->msg[11] = phys_addr >> 8; + msg->msg[12] = phys_addr & 0xff; + msg->reply = reply ? CEC_MSG_TIMER_STATUS : 0; +} + +static inline void cec_ops_set_ext_timer(struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + __u8 *ext_src_spec, + __u8 *plug, + __u16 *phys_addr) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + *ext_src_spec = msg->msg[9]; + *plug = msg->msg[10]; + *phys_addr = (msg->msg[11] << 8) | msg->msg[12]; +} + +static inline void cec_msg_set_timer_program_title(struct cec_msg *msg, + const char *prog_title) +{ + unsigned len = strlen(prog_title); + + if (len > 14) + len = 14; + msg->len = 2 + len; + msg->msg[1] = CEC_MSG_SET_TIMER_PROGRAM_TITLE; + memcpy(msg->msg + 2, prog_title, len); +} + +static inline void cec_ops_set_timer_program_title(const struct cec_msg *msg, + char *prog_title) +{ + unsigned len = msg->len - 2; + + if (len > 14) + len = 14; + memcpy(prog_title, msg->msg + 2, len); + prog_title[len] = '\0'; +} + +/* System Information Feature */ +static inline void cec_msg_cec_version(struct cec_msg *msg, __u8 cec_version) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_CEC_VERSION; + msg->msg[2] = cec_version; +} + +static inline void cec_ops_cec_version(const struct cec_msg *msg, + __u8 *cec_version) +{ + *cec_version = msg->msg[2]; +} + +static inline void cec_msg_get_cec_version(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GET_CEC_VERSION; + msg->reply = reply ? CEC_MSG_CEC_VERSION : 0; +} + +static inline void cec_msg_report_physical_addr(struct cec_msg *msg, + __u16 phys_addr, __u8 prim_devtype) +{ + msg->len = 5; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_REPORT_PHYSICAL_ADDR; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; + msg->msg[4] = prim_devtype; +} + +static inline void cec_ops_report_physical_addr(const struct cec_msg *msg, + __u16 *phys_addr, __u8 *prim_devtype) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *prim_devtype = msg->msg[4]; +} + +static inline void cec_msg_give_physical_addr(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_PHYSICAL_ADDR; + msg->reply = reply ? CEC_MSG_REPORT_PHYSICAL_ADDR : 0; +} + +static inline void cec_msg_set_menu_language(struct cec_msg *msg, + const char *language) +{ + msg->len = 5; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_SET_MENU_LANGUAGE; + memcpy(msg->msg + 2, language, 3); +} + +static inline void cec_ops_set_menu_language(struct cec_msg *msg, + char *language) +{ + memcpy(language, msg->msg + 2, 3); +} + +static inline void cec_msg_get_menu_language(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GET_MENU_LANGUAGE; + msg->reply = reply ? CEC_MSG_SET_MENU_LANGUAGE : 0; +} + +/* + * Assumes a single RC Profile byte and a single Device Features byte, + * i.e. no extended features are supported by this helper function. + */ +static inline void cec_msg_report_features(struct cec_msg *msg, + __u8 cec_version, __u8 all_device_types, + __u8 rc_profile, __u8 dev_features) +{ + msg->len = 6; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_REPORT_FEATURES; + msg->msg[2] = cec_version; + msg->msg[3] = all_device_types; + msg->msg[4] = rc_profile; + msg->msg[5] = dev_features; +} + +static inline void cec_ops_report_features(const struct cec_msg *msg, + __u8 *cec_version, __u8 *all_device_types, + const __u8 **rc_profile, const __u8 **dev_features) +{ + const __u8 *p = &msg->msg[4]; + + *cec_version = msg->msg[2]; + *all_device_types = msg->msg[3]; + *rc_profile = p; + while (p < &msg->msg[14] && (*p & CEC_OP_FEAT_EXT)) + p++; + if (!(*p & CEC_OP_FEAT_EXT)) { + *dev_features = p + 1; + while (p < &msg->msg[15] && (*p & CEC_OP_FEAT_EXT)) + p++; + } + if (*p & CEC_OP_FEAT_EXT) + *rc_profile = *dev_features = NULL; +} + +static inline void cec_msg_give_features(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_FEATURES; + msg->reply = reply ? CEC_MSG_REPORT_FEATURES : 0; +} + +/* Deck Control Feature */ +static inline void cec_msg_deck_control(struct cec_msg *msg, + __u8 deck_control_mode) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_DECK_CONTROL; + msg->msg[2] = deck_control_mode; +} + +static inline void cec_ops_deck_control(struct cec_msg *msg, + __u8 *deck_control_mode) +{ + *deck_control_mode = msg->msg[2]; +} + +static inline void cec_msg_deck_status(struct cec_msg *msg, + __u8 deck_info) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_DECK_STATUS; + msg->msg[2] = deck_info; +} + +static inline void cec_ops_deck_status(struct cec_msg *msg, + __u8 *deck_info) +{ + *deck_info = msg->msg[2]; +} + +static inline void cec_msg_give_deck_status(struct cec_msg *msg, + bool reply, + __u8 status_req) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_GIVE_DECK_STATUS; + msg->msg[2] = status_req; + msg->reply = reply ? CEC_MSG_DECK_STATUS : 0; +} + +static inline void cec_ops_give_deck_status(struct cec_msg *msg, + __u8 *status_req) +{ + *status_req = msg->msg[2]; +} + +static inline void cec_msg_play(struct cec_msg *msg, + __u8 play_mode) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_PLAY; + msg->msg[2] = play_mode; +} + +static inline void cec_ops_play(struct cec_msg *msg, + __u8 *play_mode) +{ + *play_mode = msg->msg[2]; +} + + +/* Tuner Control Feature */ +struct cec_op_tuner_device_info { + __u8 rec_flag; + __u8 tuner_display_info; + bool is_analog; + union { + struct cec_op_digital_service_id digital; + struct { + __u8 ana_bcast_type; + __u16 ana_freq; + __u8 bcast_system; + } analog; + }; +}; + +static inline void cec_msg_tuner_device_status_analog(struct cec_msg *msg, + __u8 rec_flag, + __u8 tuner_display_info, + __u8 ana_bcast_type, + __u16 ana_freq, + __u8 bcast_system) +{ + msg->len = 7; + msg->msg[1] = CEC_MSG_TUNER_DEVICE_STATUS; + msg->msg[2] = (rec_flag << 7) | tuner_display_info; + msg->msg[3] = ana_bcast_type; + msg->msg[4] = ana_freq >> 8; + msg->msg[5] = ana_freq & 0xff; + msg->msg[6] = bcast_system; +} + +static inline void cec_msg_tuner_device_status_digital(struct cec_msg *msg, + __u8 rec_flag, __u8 tuner_display_info, + const struct cec_op_digital_service_id *digital) +{ + msg->len = 10; + msg->msg[1] = CEC_MSG_TUNER_DEVICE_STATUS; + msg->msg[2] = (rec_flag << 7) | tuner_display_info; + cec_set_digital_service_id(msg->msg + 3, digital); +} + +static inline void cec_msg_tuner_device_status(struct cec_msg *msg, + const struct cec_op_tuner_device_info *tuner_dev_info) +{ + if (tuner_dev_info->is_analog) + cec_msg_tuner_device_status_analog(msg, + tuner_dev_info->rec_flag, + tuner_dev_info->tuner_display_info, + tuner_dev_info->analog.ana_bcast_type, + tuner_dev_info->analog.ana_freq, + tuner_dev_info->analog.bcast_system); + else + cec_msg_tuner_device_status_digital(msg, + tuner_dev_info->rec_flag, + tuner_dev_info->tuner_display_info, + &tuner_dev_info->digital); +} + +static inline void cec_ops_tuner_device_status(struct cec_msg *msg, + struct cec_op_tuner_device_info *tuner_dev_info) +{ + tuner_dev_info->is_analog = msg->len < 10; + tuner_dev_info->rec_flag = msg->msg[2] >> 7; + tuner_dev_info->tuner_display_info = msg->msg[2] & 0x7f; + if (tuner_dev_info->is_analog) { + tuner_dev_info->analog.ana_bcast_type = msg->msg[3]; + tuner_dev_info->analog.ana_freq = (msg->msg[4] << 8) | msg->msg[5]; + tuner_dev_info->analog.bcast_system = msg->msg[6]; + return; + } + cec_get_digital_service_id(msg->msg + 3, &tuner_dev_info->digital); +} + +static inline void cec_msg_give_tuner_device_status(struct cec_msg *msg, + bool reply, + __u8 status_req) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_GIVE_TUNER_DEVICE_STATUS; + msg->msg[2] = status_req; + msg->reply = reply ? CEC_MSG_TUNER_DEVICE_STATUS : 0; +} + +static inline void cec_ops_give_tuner_device_status(struct cec_msg *msg, + __u8 *status_req) +{ + *status_req = msg->msg[2]; +} + +static inline void cec_msg_select_analogue_service(struct cec_msg *msg, + __u8 ana_bcast_type, + __u16 ana_freq, + __u8 bcast_system) +{ + msg->len = 6; + msg->msg[1] = CEC_MSG_SELECT_ANALOGUE_SERVICE; + msg->msg[2] = ana_bcast_type; + msg->msg[3] = ana_freq >> 8; + msg->msg[4] = ana_freq & 0xff; + msg->msg[5] = bcast_system; +} + +static inline void cec_ops_select_analogue_service(struct cec_msg *msg, + __u8 *ana_bcast_type, + __u16 *ana_freq, + __u8 *bcast_system) +{ + *ana_bcast_type = msg->msg[2]; + *ana_freq = (msg->msg[3] << 8) | msg->msg[4]; + *bcast_system = msg->msg[5]; +} + +static inline void cec_msg_select_digital_service(struct cec_msg *msg, + const struct cec_op_digital_service_id *digital) +{ + msg->len = 9; + msg->msg[1] = CEC_MSG_SELECT_DIGITAL_SERVICE; + cec_set_digital_service_id(msg->msg + 2, digital); +} + +static inline void cec_ops_select_digital_service(struct cec_msg *msg, + struct cec_op_digital_service_id *digital) +{ + cec_get_digital_service_id(msg->msg + 2, digital); +} + +static inline void cec_msg_tuner_step_decrement(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_TUNER_STEP_DECREMENT; +} + +static inline void cec_msg_tuner_step_increment(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_TUNER_STEP_INCREMENT; +} + + +/* Vendor Specific Commands Feature */ +static inline void cec_msg_device_vendor_id(struct cec_msg *msg, __u32 vendor_id) +{ + msg->len = 5; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_DEVICE_VENDOR_ID; + msg->msg[2] = vendor_id >> 16; + msg->msg[3] = (vendor_id >> 8) & 0xff; + msg->msg[4] = vendor_id & 0xff; +} + +static inline void cec_ops_device_vendor_id(const struct cec_msg *msg, + __u32 *vendor_id) +{ + *vendor_id = (msg->msg[2] << 16) | (msg->msg[3] << 8) | msg->msg[4]; +} + +static inline void cec_msg_give_device_vendor_id(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_DEVICE_VENDOR_ID; + msg->reply = reply ? CEC_MSG_DEVICE_VENDOR_ID : 0; +} + +static inline void cec_msg_vendor_remote_button_up(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_VENDOR_REMOTE_BUTTON_UP; +} + + +/* OSD Display Feature */ +static inline void cec_msg_set_osd_string(struct cec_msg *msg, + __u8 disp_ctl, + const char *osd) +{ + unsigned len = strlen(osd); + + if (len > 13) + len = 13; + msg->len = 3 + len; + msg->msg[1] = CEC_MSG_SET_OSD_STRING; + msg->msg[2] = disp_ctl; + memcpy(msg->msg + 3, osd, len); +} + +static inline void cec_ops_set_osd_string(const struct cec_msg *msg, + __u8 *disp_ctl, + char *osd) +{ + unsigned len = msg->len - 3; + + *disp_ctl = msg->msg[2]; + if (len > 13) + len = 13; + memcpy(osd, msg->msg + 3, len); + osd[len] = '\0'; +} + + +/* Device OSD Transfer Feature */ +static inline void cec_msg_set_osd_name(struct cec_msg *msg, const char *name) +{ + unsigned len = strlen(name); + + if (len > 14) + len = 14; + msg->len = 2 + len; + msg->msg[1] = CEC_MSG_SET_OSD_NAME; + memcpy(msg->msg + 2, name, len); +} + +static inline void cec_ops_set_osd_name(const struct cec_msg *msg, + char *name) +{ + unsigned len = msg->len - 2; + + if (len > 14) + len = 14; + memcpy(name, msg->msg + 2, len); + name[len] = '\0'; +} + +static inline void cec_msg_give_osd_name(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_OSD_NAME; + msg->reply = reply ? CEC_MSG_SET_OSD_NAME : 0; +} + + +/* Device Menu Control Feature */ +static inline void cec_msg_menu_status(struct cec_msg *msg, + __u8 menu_state) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_MENU_STATUS; + msg->msg[2] = menu_state; +} + +static inline void cec_ops_menu_status(struct cec_msg *msg, + __u8 *menu_state) +{ + *menu_state = msg->msg[2]; +} + +static inline void cec_msg_menu_request(struct cec_msg *msg, + bool reply, + __u8 menu_req) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_MENU_REQUEST; + msg->msg[2] = menu_req; + msg->reply = reply ? CEC_MSG_MENU_STATUS : 0; +} + +static inline void cec_ops_menu_request(struct cec_msg *msg, + __u8 *menu_req) +{ + *menu_req = msg->msg[2]; +} + +static inline void cec_msg_user_control_pressed(struct cec_msg *msg, + __u8 ui_cmd) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_USER_CONTROL_PRESSED; + msg->msg[2] = ui_cmd; +} + +static inline void cec_ops_user_control_pressed(struct cec_msg *msg, + __u8 *ui_cmd) +{ + *ui_cmd = msg->msg[2]; +} + +static inline void cec_msg_user_control_released(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_USER_CONTROL_RELEASED; +} + +/* Remote Control Passthrough Feature */ + +/* Power Status Feature */ +static inline void cec_msg_report_power_status(struct cec_msg *msg, + __u8 pwr_state) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_REPORT_POWER_STATUS; + msg->msg[2] = pwr_state; +} + +static inline void cec_ops_report_power_status(const struct cec_msg *msg, + __u8 *pwr_state) +{ + *pwr_state = msg->msg[2]; +} + +static inline void cec_msg_give_device_power_status(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_DEVICE_POWER_STATUS; + msg->reply = reply ? CEC_MSG_REPORT_POWER_STATUS : 0; +} + +/* General Protocol Messages */ +static inline void cec_msg_feature_abort(struct cec_msg *msg, + __u8 abort_msg, __u8 reason) +{ + msg->len = 4; + msg->msg[1] = CEC_MSG_FEATURE_ABORT; + msg->msg[2] = abort_msg; + msg->msg[3] = reason; +} + +static inline void cec_ops_feature_abort(const struct cec_msg *msg, + __u8 *abort_msg, __u8 *reason) +{ + *abort_msg = msg->msg[2]; + *reason = msg->msg[3]; +} + +/* This changes the current message into an abort message */ +static inline void cec_msg_reply_abort(struct cec_msg *msg, __u8 reason) +{ + cec_msg_set_reply_to(msg, msg); + msg->len = 4; + msg->msg[2] = msg->msg[1]; + msg->msg[3] = reason; + msg->msg[1] = CEC_MSG_FEATURE_ABORT; +} + +static inline void cec_msg_abort(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_ABORT; +} + + +/* System Audio Control Feature */ +static inline void cec_msg_report_audio_status(struct cec_msg *msg, + __u8 aud_mute_status, + __u8 aud_vol_status) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_REPORT_AUDIO_STATUS; + msg->msg[2] = (aud_mute_status << 7) | (aud_vol_status & 0x7f); +} + +static inline void cec_ops_report_audio_status(const struct cec_msg *msg, + __u8 *aud_mute_status, + __u8 *aud_vol_status) +{ + *aud_mute_status = msg->msg[2] >> 7; + *aud_vol_status = msg->msg[2] & 0x7f; +} + +static inline void cec_msg_give_audio_status(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_AUDIO_STATUS; + msg->reply = reply ? CEC_MSG_REPORT_AUDIO_STATUS : 0; +} + +static inline void cec_msg_set_system_audio_mode(struct cec_msg *msg, + __u8 sys_aud_status) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_SET_SYSTEM_AUDIO_MODE; + msg->msg[2] = sys_aud_status; +} + +static inline void cec_ops_set_system_audio_mode(const struct cec_msg *msg, + __u8 *sys_aud_status) +{ + *sys_aud_status = msg->msg[2]; +} + +static inline void cec_msg_system_audio_mode_request(struct cec_msg *msg, + bool reply, + __u16 phys_addr) +{ + msg->len = phys_addr == 0xffff ? 2 : 4; + msg->msg[1] = CEC_MSG_SYSTEM_AUDIO_MODE_REQUEST; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; + msg->reply = reply ? CEC_MSG_SET_SYSTEM_AUDIO_MODE : 0; + +} + +static inline void cec_ops_system_audio_mode_request(const struct cec_msg *msg, + __u16 *phys_addr) +{ + if (msg->len < 4) + *phys_addr = 0xffff; + else + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_system_audio_mode_status(struct cec_msg *msg, + __u8 sys_aud_status) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_SYSTEM_AUDIO_MODE_STATUS; + msg->msg[2] = sys_aud_status; +} + +static inline void cec_ops_system_audio_mode_status(const struct cec_msg *msg, + __u8 *sys_aud_status) +{ + *sys_aud_status = msg->msg[2]; +} + +static inline void cec_msg_give_system_audio_mode_status(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS; + msg->reply = reply ? CEC_MSG_SYSTEM_AUDIO_MODE_STATUS : 0; +} + +static inline void cec_msg_report_short_audio_descriptor(struct cec_msg *msg, + __u8 num_descriptors, + const __u32 *descriptors) +{ + unsigned i; + + msg->len = 2 + num_descriptors * 3; + msg->msg[1] = CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR; + for (i = 0; i < num_descriptors; i++) { + msg->msg[2 + i * 3] = (descriptors[i] >> 16) & 0xff; + msg->msg[3 + i * 3] = (descriptors[i] >> 8) & 0xff; + msg->msg[4 + i * 3] = descriptors[i] & 0xff; + } +} + +static inline void cec_ops_report_short_audio_descriptor(const struct cec_msg *msg, + __u8 *num_descriptors, + __u32 *descriptors) +{ + unsigned i; + + *num_descriptors = (msg->len - 2) / 3; + for (i = 0; i < *num_descriptors; i++) + descriptors[i] = (msg->msg[2 + i * 3] << 16) | + (msg->msg[3 + i * 3] << 8) | + msg->msg[4 + i * 3]; +} + +static inline void cec_msg_request_short_audio_descriptor(struct cec_msg *msg, + __u8 audio_format_id, + __u8 audio_format_code) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_REQUEST_SHORT_AUDIO_DESCRIPTOR; + msg->msg[2] = (audio_format_id << 6) | (audio_format_code & 0x3f); +} + +static inline void cec_ops_request_short_audio_descriptor(const struct cec_msg *msg, + __u8 *audio_format_id, + __u8 *audio_format_code) +{ + *audio_format_id = msg->msg[2] >> 6; + *audio_format_code = msg->msg[2] & 0x3f; +} + + +/* Audio Rate Control Feature */ +static inline void cec_msg_set_audio_rate(struct cec_msg *msg, + __u8 audio_rate) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_SET_AUDIO_RATE; + msg->msg[2] = audio_rate; +} + +static inline void cec_ops_set_audio_rate(const struct cec_msg *msg, + __u8 *audio_rate) +{ + *audio_rate = msg->msg[2]; +} + + +/* Audio Return Channel Control Feature */ +static inline void cec_msg_report_arc_initiated(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_REPORT_ARC_INITIATED; +} + +static inline void cec_msg_initiate_arc(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_INITIATE_ARC; + msg->reply = reply ? CEC_MSG_REPORT_ARC_INITIATED : 0; +} + +static inline void cec_msg_request_arc_initiation(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_REQUEST_ARC_INITIATION; + msg->reply = reply ? CEC_MSG_INITIATE_ARC : 0; +} + +static inline void cec_msg_report_arc_terminated(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_REPORT_ARC_TERMINATED; +} + +static inline void cec_msg_terminate_arc(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_TERMINATE_ARC; + msg->reply = reply ? CEC_MSG_REPORT_ARC_TERMINATED : 0; +} + +static inline void cec_msg_request_arc_termination(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_REQUEST_ARC_TERMINATION; + msg->reply = reply ? CEC_MSG_TERMINATE_ARC : 0; +} + + +/* Dynamic Audio Lipsync Feature */ +/* Only for CEC 2.0 and up */ +static inline void cec_msg_report_current_latency(struct cec_msg *msg, + __u16 phys_addr, + __u8 video_latency, + __u8 low_latency_mode, + __u8 audio_out_compensated, + __u8 audio_out_delay) +{ + msg->len = 7; + msg->msg[1] = CEC_MSG_REPORT_CURRENT_LATENCY; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; + msg->msg[4] = video_latency; + msg->msg[5] = (low_latency_mode << 2) | audio_out_compensated; + msg->msg[6] = audio_out_delay; +} + +static inline void cec_ops_report_current_latency(const struct cec_msg *msg, + __u16 *phys_addr, + __u8 *video_latency, + __u8 *low_latency_mode, + __u8 *audio_out_compensated, + __u8 *audio_out_delay) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *video_latency = msg->msg[4]; + *low_latency_mode = (msg->msg[5] >> 2) & 1; + *audio_out_compensated = msg->msg[5] & 3; + *audio_out_delay = msg->msg[6]; +} + +static inline void cec_msg_request_current_latency(struct cec_msg *msg, + bool reply, + __u16 phys_addr) +{ + msg->len = 4; + msg->msg[1] = CEC_MSG_REQUEST_CURRENT_LATENCY; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; + msg->reply = reply ? CEC_MSG_REPORT_CURRENT_LATENCY : 0; +} + +static inline void cec_ops_request_current_latency(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + + +/* Capability Discovery and Control Feature */ +static inline void cec_msg_cdc_hec_inquire_state(struct cec_msg *msg, + __u16 phys_addr1, + __u16 phys_addr2) +{ + msg->len = 9; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_INQUIRE_STATE; + msg->msg[5] = phys_addr1 >> 8; + msg->msg[6] = phys_addr1 & 0xff; + msg->msg[7] = phys_addr2 >> 8; + msg->msg[8] = phys_addr2 & 0xff; +} + +static inline void cec_ops_cdc_hec_inquire_state(const struct cec_msg *msg, + __u16 *phys_addr, + __u16 *phys_addr1, + __u16 *phys_addr2) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; + *phys_addr2 = (msg->msg[7] << 8) | msg->msg[8]; +} + +static inline void cec_msg_cdc_hec_report_state(struct cec_msg *msg, + __u16 target_phys_addr, + __u8 hec_func_state, + __u8 host_func_state, + __u8 enc_func_state, + __u8 cdc_errcode, + __u8 has_field, + __u16 hec_field) +{ + msg->len = has_field ? 10 : 8; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_REPORT_STATE; + msg->msg[5] = target_phys_addr >> 8; + msg->msg[6] = target_phys_addr & 0xff; + msg->msg[7] = (hec_func_state << 6) | + (host_func_state << 4) | + (enc_func_state << 2) | + cdc_errcode; + if (has_field) { + msg->msg[8] = hec_field >> 8; + msg->msg[9] = hec_field & 0xff; + } +} + +static inline void cec_ops_cdc_hec_report_state(const struct cec_msg *msg, + __u16 *phys_addr, + __u16 *target_phys_addr, + __u8 *hec_func_state, + __u8 *host_func_state, + __u8 *enc_func_state, + __u8 *cdc_errcode, + __u8 *has_field, + __u16 *hec_field) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *target_phys_addr = (msg->msg[5] << 8) | msg->msg[6]; + *hec_func_state = msg->msg[7] >> 6; + *host_func_state = (msg->msg[7] >> 4) & 3; + *enc_func_state = (msg->msg[7] >> 4) & 3; + *cdc_errcode = msg->msg[7] & 3; + *has_field = msg->len >= 10; + *hec_field = *has_field ? ((msg->msg[8] << 8) | msg->msg[9]) : 0; +} + +static inline void cec_msg_cdc_hec_set_state(struct cec_msg *msg, + __u16 phys_addr1, + __u16 phys_addr2, + __u8 hec_set_state, + __u16 phys_addr3, + __u16 phys_addr4, + __u16 phys_addr5) +{ + msg->len = 10; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_INQUIRE_STATE; + msg->msg[5] = phys_addr1 >> 8; + msg->msg[6] = phys_addr1 & 0xff; + msg->msg[7] = phys_addr2 >> 8; + msg->msg[8] = phys_addr2 & 0xff; + msg->msg[9] = hec_set_state; + if (phys_addr3 != CEC_PHYS_ADDR_INVALID) { + msg->msg[msg->len++] = phys_addr3 >> 8; + msg->msg[msg->len++] = phys_addr3 & 0xff; + if (phys_addr4 != CEC_PHYS_ADDR_INVALID) { + msg->msg[msg->len++] = phys_addr4 >> 8; + msg->msg[msg->len++] = phys_addr4 & 0xff; + if (phys_addr5 != CEC_PHYS_ADDR_INVALID) { + msg->msg[msg->len++] = phys_addr5 >> 8; + msg->msg[msg->len++] = phys_addr5 & 0xff; + } + } + } +} + +static inline void cec_ops_cdc_hec_set_state(const struct cec_msg *msg, + __u16 *phys_addr, + __u16 *phys_addr1, + __u16 *phys_addr2, + __u8 *hec_set_state, + __u16 *phys_addr3, + __u16 *phys_addr4, + __u16 *phys_addr5) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; + *phys_addr2 = (msg->msg[7] << 8) | msg->msg[8]; + *hec_set_state = msg->msg[9]; + *phys_addr3 = *phys_addr4 = *phys_addr5 = CEC_PHYS_ADDR_INVALID; + if (msg->len >= 12) + *phys_addr3 = (msg->msg[10] << 8) | msg->msg[11]; + if (msg->len >= 14) + *phys_addr4 = (msg->msg[12] << 8) | msg->msg[13]; + if (msg->len >= 16) + *phys_addr5 = (msg->msg[14] << 8) | msg->msg[15]; +} + +static inline void cec_msg_cdc_hec_set_state_adjacent(struct cec_msg *msg, + __u16 phys_addr1, + __u8 hec_set_state) +{ + msg->len = 8; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_SET_STATE_ADJACENT; + msg->msg[5] = phys_addr1 >> 8; + msg->msg[6] = phys_addr1 & 0xff; + msg->msg[7] = hec_set_state; +} + +static inline void cec_ops_cdc_hec_set_state_adjacent(const struct cec_msg *msg, + __u16 *phys_addr, + __u16 *phys_addr1, + __u8 *hec_set_state) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; + *hec_set_state = msg->msg[7]; +} + +static inline void cec_msg_cdc_hec_request_deactivation(struct cec_msg *msg, + __u16 phys_addr1, + __u16 phys_addr2, + __u16 phys_addr3) +{ + msg->len = 11; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_REQUEST_DEACTIVATION; + msg->msg[5] = phys_addr1 >> 8; + msg->msg[6] = phys_addr1 & 0xff; + msg->msg[7] = phys_addr2 >> 8; + msg->msg[8] = phys_addr2 & 0xff; + msg->msg[9] = phys_addr3 >> 8; + msg->msg[10] = phys_addr3 & 0xff; +} + +static inline void cec_ops_cdc_hec_request_deactivation(const struct cec_msg *msg, + __u16 *phys_addr, + __u16 *phys_addr1, + __u16 *phys_addr2, + __u16 *phys_addr3) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; + *phys_addr2 = (msg->msg[7] << 8) | msg->msg[8]; + *phys_addr3 = (msg->msg[9] << 8) | msg->msg[10]; +} + +static inline void cec_msg_cdc_hec_notify_alive(struct cec_msg *msg) +{ + msg->len = 5; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_NOTIFY_ALIVE; +} + +static inline void cec_ops_cdc_hec_notify_alive(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_cdc_hec_discover(struct cec_msg *msg) +{ + msg->len = 5; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_DISCOVER; +} + +static inline void cec_ops_cdc_hec_discover(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_cdc_hpd_set_state(struct cec_msg *msg, + __u8 input_port, + __u8 hpd_state) +{ + msg->len = 6; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HPD_SET_STATE; + msg->msg[5] = (input_port << 4) | hpd_state; +} + +static inline void cec_ops_cdc_hpd_set_state(const struct cec_msg *msg, + __u16 *phys_addr, + __u8 *input_port, + __u8 *hpd_state) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *input_port = msg->msg[5] >> 4; + *hpd_state = msg->msg[5] & 0xf; +} + +static inline void cec_msg_cdc_hpd_report_state(struct cec_msg *msg, + __u8 hpd_state, + __u8 hpd_error) +{ + msg->len = 6; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HPD_REPORT_STATE; + msg->msg[5] = (hpd_state << 4) | hpd_error; +} + +static inline void cec_ops_cdc_hpd_report_state(const struct cec_msg *msg, + __u16 *phys_addr, + __u8 *hpd_state, + __u8 *hpd_error) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *hpd_state = msg->msg[5] >> 4; + *hpd_error = msg->msg[5] & 0xf; +} + +#endif diff --git a/include/uapi/linux/cec.h b/include/uapi/linux/cec.h new file mode 100644 index 0000000..3cc4b98 --- /dev/null +++ b/include/uapi/linux/cec.h @@ -0,0 +1,781 @@ +#ifndef _CEC_H +#define _CEC_H + +#include <linux/types.h> + +struct cec_msg { + __u64 ts; + __u32 len; + __u32 status; + /* + * timeout (in ms) is used to timeout CEC_RECEIVE. + * Set to 0 if you want to wait forever. + */ + __u32 timeout; + /* + * The framework assigns a sequence number to messages that are sent. + * This can be used to track replies to previously sent messages. + */ + __u32 sequence; + __u8 msg[16]; + /* + * If non-zero, then wait for a reply with this opcode. + * If there was an error when sending the msg or FeatureAbort + * was returned, then reply is set to 0. + * If reply is non-zero upon return, then len/msg are set to + * the received message. + * If reply is zero upon return and status has the + * CEC_TX_STATUS_FEATURE_ABORT bit set, then len/msg are set to the + * received feature abort message. + * If reply is zero upon return and status has the + * CEC_TX_STATUS_REPLY_TIMEOUT + * bit set, then no reply was seen at all. + * This field is ignored with CEC_RECEIVE. + * If reply is non-zero for CEC_TRANSMIT and the message is a broadcast, + * then -EINVAL is returned. + * if reply is non-zero, then timeout is set to 1000 (the required + * maximum response time). + */ + __u8 reply; + __u8 reserved[35]; +}; + +static inline __u8 cec_msg_initiator(const struct cec_msg *msg) +{ + return msg->msg[0] >> 4; +} + +static inline __u8 cec_msg_destination(const struct cec_msg *msg) +{ + return msg->msg[0] & 0xf; +} + +static inline bool cec_msg_is_broadcast(const struct cec_msg *msg) +{ + return (msg->msg[0] & 0xf) == 0xf; +} + +static inline void cec_msg_init(struct cec_msg *msg, + __u8 initiator, __u8 destination) +{ + memset(msg, 0, sizeof(*msg)); + msg->msg[0] = (initiator << 4) | destination; + msg->len = 1; +} + +/* + * Set the msg destination to the orig initiator and the msg initiator to the + * orig destination. Note that msg and orig may be the same pointer, in which + * case the change is done in place. + */ +static inline void cec_msg_set_reply_to(struct cec_msg *msg, struct cec_msg *orig) +{ + /* The destination becomes the initiator and vice versa */ + msg->msg[0] = (cec_msg_destination(orig) << 4) | cec_msg_initiator(orig); +} + +/* cec status field */ +#define CEC_TX_STATUS_OK (0) +#define CEC_TX_STATUS_ARB_LOST (1 << 0) +#define CEC_TX_STATUS_RETRY_TIMEOUT (1 << 1) +#define CEC_TX_STATUS_FEATURE_ABORT (1 << 2) +#define CEC_TX_STATUS_REPLY_TIMEOUT (1 << 3) +#define CEC_RX_STATUS_READY (0) + +#define CEC_LOG_ADDR_INVALID 0xff +#define CEC_PHYS_ADDR_INVALID 0xffff + +/* The maximum number of logical addresses one device can be assigned to. + * The CEC 2.0 spec allows for only 2 logical addresses at the moment. The + * Analog Devices CEC hardware supports 3. So let's go wild and go for 4. */ +#define CEC_MAX_LOG_ADDRS 4 + +/* The logical address types that the CEC device wants to claim */ +#define CEC_LOG_ADDR_TYPE_TV 0 +#define CEC_LOG_ADDR_TYPE_RECORD 1 +#define CEC_LOG_ADDR_TYPE_TUNER 2 +#define CEC_LOG_ADDR_TYPE_PLAYBACK 3 +#define CEC_LOG_ADDR_TYPE_AUDIOSYSTEM 4 +#define CEC_LOG_ADDR_TYPE_SPECIFIC 5 +#define CEC_LOG_ADDR_TYPE_UNREGISTERED 6 +/* Switches should use UNREGISTERED. + * Processors should use SPECIFIC. */ + +/* + * Use this if there is no vendor ID (CEC_G_VENDOR_ID) or if the vendor ID + * should be disabled (CEC_S_VENDOR_ID) + */ +#define CEC_VENDOR_ID_NONE 0xffffffff + +/* The CEC adapter state */ +#define CEC_ADAP_DISABLED 0 +#define CEC_ADAP_ENABLED 1 + +/* The passthrough mode state */ +#define CEC_PASSTHROUGH_DISABLED 0 +#define CEC_PASSTHROUGH_ENABLED 1 + +/* The monitor state */ +#define CEC_MONITOR_DISABLED 0 +#define CEC_MONITOR_ENABLED 1 + +/* Userspace has to configure the adapter state (enable/disable) */ +#define CEC_CAP_STATE (1 << 0) +/* Userspace has to configure the physical address */ +#define CEC_CAP_PHYS_ADDR (1 << 1) +/* Userspace has to configure the logical addresses */ +#define CEC_CAP_LOG_ADDRS (1 << 2) +/* Userspace can transmit and receive messages */ +#define CEC_CAP_IO (1 << 3) +/* Userspace has to configure the vendor id */ +#define CEC_CAP_VENDOR_ID (1 << 4) +/* + * Passthrough all messages instead of processing them. + * Note: ARC and CDC messages are always processed. + */ +#define CEC_CAP_PASSTHROUGH (1 << 5) +/* Supports remote control */ +#define CEC_CAP_RC (1 << 6) +/* Supports ARC */ +#define CEC_CAP_ARC (1 << 7) +/* Supports CDC HPD */ +#define CEC_CAP_CDC_HPD (1 << 8) +/* Is a source */ +#define CEC_CAP_IS_SOURCE (1 << 9) + +struct cec_caps { + __u32 available_log_addrs; + __u32 capabilities; + __u8 ninputs; + __u8 reserved[39]; +}; + +struct cec_log_addrs { + __u8 cec_version; + __u8 num_log_addrs; + __u8 primary_device_type[CEC_MAX_LOG_ADDRS]; + __u8 log_addr_type[CEC_MAX_LOG_ADDRS]; + __u8 log_addr[CEC_MAX_LOG_ADDRS]; + char osd_name[15]; + + /* CEC 2.0 */ + __u8 all_device_types[CEC_MAX_LOG_ADDRS]; + __u8 features[CEC_MAX_LOG_ADDRS][12]; + + __u8 reserved[63]; +}; + +/* Events */ + +/* Event that occurs when the adapter state changes */ +#define CEC_EVENT_STATE_CHANGE 1 +/* Event that occurs when inputs are connected/disconnected */ +#define CEC_EVENT_INPUTS_CHANGE 2 +/* This event is sent when messages are lost because the application + * didn't empty the message queue in time */ +#define CEC_EVENT_LOST_MSGS 3 + +#define CEC_EVENT_STATE_DISABLED 0 +#define CEC_EVENT_STATE_UNCONFIGURED 1 +#define CEC_EVENT_STATE_CONFIGURING 2 +#define CEC_EVENT_STATE_CONFIGURED 3 + +struct cec_event_state_change { + /* current CEC adapter state */ + __u8 state; +}; + +struct cec_event_inputs_change { + /* bit 0 == input port 0, bit 15 == input port 15 */ + /* currently connected input ports */ + __u16 connected_inputs; + /* input ports that changed state since last event */ + __u16 changed_inputs; +}; + +struct cec_event { + __u64 ts; + __u32 event; + __u32 reserved[7]; + union { + struct cec_event_state_change state_change; + struct cec_event_inputs_change inputs_change; + __u32 raw[16]; + }; +}; + +/* ioctls */ + +/* Adapter capabilities */ +#define CEC_ADAP_G_CAPS _IOWR('a', 0, struct cec_caps) + +/* + Configure the CEC adapter. It sets the device type and which + logical types it will try to claim. It will return which + logical addresses it could actually claim. + An error is returned if the adapter is disabled or if there + is no physical address assigned. + */ + +#define CEC_ADAP_G_LOG_ADDRS _IOR ('a', 1, struct cec_log_addrs) +#define CEC_ADAP_S_LOG_ADDRS _IOWR('a', 2, struct cec_log_addrs) + +/* + Enable/disable the adapter. The Set state ioctl may not + be available if that is handled internally. + */ +#define CEC_ADAP_G_STATE _IOR ('a', 3, __u32) +#define CEC_ADAP_S_STATE _IOW ('a', 4, __u32) + +/* + phys_addr is either 0 (if this is the CEC root device) + or a valid physical address obtained from the sink's EDID + as read by this CEC device (if this is a source device) + or a physical address obtained and modified from a sink + EDID and used for a sink CEC device. + If nothing is connected, then phys_addr is 0xffff. + See HDMI 1.4b, section 8.7 (Physical Address). + + The Set ioctl may not be available if that is handled + internally. + */ +#define CEC_ADAP_G_PHYS_ADDR _IOR ('a', 5, __u16) +#define CEC_ADAP_S_PHYS_ADDR _IOW ('a', 6, __u16) + +/* + Read and set the vendor ID of the CEC adapter. + */ +#define CEC_ADAP_G_VENDOR_ID _IOR ('a', 7, __u32) +#define CEC_ADAP_S_VENDOR_ID _IOW ('a', 8, __u32) + +/* + Read and set the monitor state of the filehandle. + */ +#define CEC_G_MONITOR _IOR ('a', 9, __u32) +#define CEC_S_MONITOR _IOW ('a', 10, __u32) + +/* + Claim message handling and set passthrough mode, + release message handling and get passthrough mode for + this filehandle. + */ +#define CEC_CLAIM _IOW ('a', 11, __u32) +#define CEC_RELEASE _IO ('a', 12) +#define CEC_G_PASSTHROUGH _IOR ('a', 13, __u32) + +/* Transmit/receive a CEC command */ +#define CEC_TRANSMIT _IOWR('a', 14, struct cec_msg) +#define CEC_RECEIVE _IOWR('a', 15, struct cec_msg) + +/* Dequeue CEC events */ +#define CEC_DQEVENT _IOWR('a', 16, struct cec_event) + +/* Commands */ + +/* One Touch Play Feature */ +#define CEC_MSG_ACTIVE_SOURCE 0x82 +#define CEC_MSG_IMAGE_VIEW_ON 0x04 +#define CEC_MSG_TEXT_VIEW_ON 0x0d + + +/* Routing Control Feature */ + +/* + * Has also: + * CEC_MSG_ACTIVE_SOURCE + */ + +#define CEC_MSG_INACTIVE_SOURCE 0x9d +#define CEC_MSG_REQUEST_ACTIVE_SOURCE 0x85 +#define CEC_MSG_ROUTING_CHANGE 0x80 +#define CEC_MSG_ROUTING_INFORMATION 0x81 +#define CEC_MSG_SET_STREAM_PATH 0x86 + + +/* Standby Feature */ +#define CEC_MSG_STANDBY 0x36 + + +/* One Touch Record Feature */ +#define CEC_MSG_RECORD_OFF 0x0b +#define CEC_MSG_RECORD_ON 0x09 +/* Record Source Type Operand (rec_src_type) */ +#define CEC_OP_RECORD_SRC_OWN 1 +#define CEC_OP_RECORD_SRC_DIGITAL 2 +#define CEC_OP_RECORD_SRC_ANALOG 3 +#define CEC_OP_RECORD_SRC_EXT_PLUG 4 +#define CEC_OP_RECORD_SRC_EXT_PHYS_ADDR 5 +/* Service Identification Method Operand (service_id_method) */ +#define CEC_OP_SERVICE_ID_METHOD_BY_DIG_ID 0 +#define CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL 1 +/* Digital Service Broadcast System Operand (dig_bcast_system) */ +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN 0x00 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN 0x01 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN 0x02 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS 0x08 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS 0x09 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T 0x0a +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE 0x10 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT 0x11 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T 0x12 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C 0x18 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S 0x19 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2 0x1a +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T 0x1b +/* Analogue Broadcast Type Operand (ana_bcast_type) */ +#define CEC_OP_ANA_BCAST_TYPE_CABLE 0 +#define CEC_OP_ANA_BCAST_TYPE_SATELLITE 1 +#define CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL 2 +/* Broadcast System Operand (bcast_system) */ +#define CEC_OP_BCAST_SYSTEM_PAL_BG 0x00 +#define CEC_OP_BCAST_SYSTEM_SECAM_LQ 0x01 /* SECAM L' */ +#define CEC_OP_BCAST_SYSTEM_PAL_M 0x02 +#define CEC_OP_BCAST_SYSTEM_NTSC_M 0x03 +#define CEC_OP_BCAST_SYSTEM_PAL_I 0x04 +#define CEC_OP_BCAST_SYSTEM_SECAM_DK 0x05 +#define CEC_OP_BCAST_SYSTEM_SECAM_BG 0x06 +#define CEC_OP_BCAST_SYSTEM_SECAM_L 0x07 +#define CEC_OP_BCAST_SYSTEM_PAL_DK 0x08 +#define CEC_OP_BCAST_SYSTEM_OTHER 0x1f +/* Channel Number Format Operand (channel_number_fmt) */ +#define CEC_OP_CHANNEL_NUMBER_FMT_1_PART 0x01 +#define CEC_OP_CHANNEL_NUMBER_FMT_2_PART 0x02 + +#define CEC_MSG_RECORD_STATUS 0x0a +/* Record Status Operand (rec_status) */ +#define CEC_OP_RECORD_STATUS_CUR_SRC 0x01 +#define CEC_OP_RECORD_STATUS_DIG_SERVICE 0x02 +#define CEC_OP_RECORD_STATUS_ANA_SERVICE 0x03 +#define CEC_OP_RECORD_STATUS_EXT_INPUT 0x04 +#define CEC_OP_RECORD_STATUS_NO_DIG_SERVICE 0x05 +#define CEC_OP_RECORD_STATUS_NO_ANA_SERVICE 0x06 +#define CEC_OP_RECORD_STATUS_NO_SERVICE 0x07 +#define CEC_OP_RECORD_STATUS_INVALID_EXT_PLUG 0x09 +#define CEC_OP_RECORD_STATUS_INVALID_EXT_PHYS_ADDR 0x0a +#define CEC_OP_RECORD_STATUS_UNSUP_CA 0x0b +#define CEC_OP_RECORD_STATUS_NO_CA_ENTITLEMENTS 0x0c +#define CEC_OP_RECORD_STATUS_CANT_COPY_SRC 0x0d +#define CEC_OP_RECORD_STATUS_NO_MORE_COPIES 0x0e +#define CEC_OP_RECORD_STATUS_NO_MEDIA 0x10 +#define CEC_OP_RECORD_STATUS_PLAYING 0x11 +#define CEC_OP_RECORD_STATUS_ALREADY_RECORDING 0x12 +#define CEC_OP_RECORD_STATUS_MEDIA_PROT 0x13 +#define CEC_OP_RECORD_STATUS_NO_SIGNAL 0x14 +#define CEC_OP_RECORD_STATUS_MEDIA_PROBLEM 0x15 +#define CEC_OP_RECORD_STATUS_NO_SPACE 0x16 +#define CEC_OP_RECORD_STATUS_PARENTAL_LOCK 0x17 +#define CEC_OP_RECORD_STATUS_TERMINATED_OK 0x1a +#define CEC_OP_RECORD_STATUS_ALREADY_TERM 0x1b +#define CEC_OP_RECORD_STATUS_OTHER 0x1f + +#define CEC_MSG_RECORD_TV_SCREEN 0x0f + + +/* Timer Programming Feature */ +#define CEC_MSG_CLEAR_ANALOGUE_TIMER 0x33 +/* Recording Sequence Operand (recording_seq) */ +#define CEC_OP_REC_SEQ_SUNDAY 0x01 +#define CEC_OP_REC_SEQ_MONDAY 0x02 +#define CEC_OP_REC_SEQ_TUESDAY 0x04 +#define CEC_OP_REC_SEQ_WEDNESDAY 0x08 +#define CEC_OP_REC_SEQ_THURSDAY 0x10 +#define CEC_OP_REC_SEQ_FRIDAY 0x20 +#define CEC_OP_REC_SEQ_SATERDAY 0x40 +#define CEC_OP_REC_SEQ_ONCE_ONLY 0x00 + +#define CEC_MSG_CLEAR_DIGITAL_TIMER 0x99 + +#define CEC_MSG_CLEAR_EXT_TIMER 0xa1 +/* External Source Specifier Operand (ext_src_spec) */ +#define CEC_OP_EXT_SRC_PLUG 0x04 +#define CEC_OP_EXT_SRC_PHYS_ADDR 0x05 + +#define CEC_MSG_SET_ANALOGUE_TIMER 0x34 +#define CEC_MSG_SET_DIGITAL_TIMER 0x97 +#define CEC_MSG_SET_EXT_TIMER 0xa2 + +#define CEC_MSG_SET_TIMER_PROGRAM_TITLE 0x67 +#define CEC_MSG_TIMER_CLEARED_STATUS 0x43 +/* Timer Cleared Status Data Operand (timer_cleared_status) */ +#define CEC_OP_TIMER_CLR_STAT_RECORDING 0x00 +#define CEC_OP_TIMER_CLR_STAT_NO_MATCHING 0x01 +#define CEC_OP_TIMER_CLR_STAT_NO_INFO 0x02 +#define CEC_OP_TIMER_CLR_STAT_CLEARED 0x80 + +#define CEC_MSG_TIMER_STATUS 0x35 +/* Timer Overlap Warning Operand (timer_overlap_warning) */ +#define CEC_OP_TIMER_OVERLAP_WARNING_NO_OVERLAP 0 +#define CEC_OP_TIMER_OVERLAP_WARNING_OVERLAP 1 +/* Media Info Operand (media_info) */ +#define CEC_OP_MEDIA_INFO_UNPROT_MEDIA 0 +#define CEC_OP_MEDIA_INFO_PROT_MEDIA 1 +#define CEC_OP_MEDIA_INFO_NO_MEDIA 2 +/* Programmed Indicator Operand (prog_indicator) */ +#define CEC_OP_PROG_IND_NOT_PROGRAMMED 0 +#define CEC_OP_PROG_IND_PROGRAMMED 1 +/* Programmed Info Operand (prog_info) */ +#define CEC_OP_PROG_INFO_ENOUGH_SPACE 0x08 +#define CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE 0x09 +#define CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE 0x0b +#define CEC_OP_PROG_INFO_NONE_AVAILABLE 0x0a +/* Not Programmed Error Info Operand (prog_error) */ +#define CEC_OP_PROG_ERROR_NO_FREE_TIMER 0x01 +#define CEC_OP_PROG_ERROR_DATE_OUT_OF_RANGE 0x02 +#define CEC_OP_PROG_ERROR_REC_SEQ_ERROR 0x03 +#define CEC_OP_PROG_ERROR_INV_EXT_PLUG 0x04 +#define CEC_OP_PROG_ERROR_INV_EXT_PHYS_ADDR 0x05 +#define CEC_OP_PROG_ERROR_CA_UNSUPP 0x06 +#define CEC_OP_PROG_ERROR_INSUF_CA_ENTITLEMENTS 0x07 +#define CEC_OP_PROG_ERROR_RESOLUTION_UNSUPP 0x08 +#define CEC_OP_PROG_ERROR_PARENTAL_LOCK 0x09 +#define CEC_OP_PROG_ERROR_CLOCK_FAILURE 0x0a +#define CEC_OP_PROG_ERROR_DUPLICATE 0x0e + + +/* System Information Feature */ +#define CEC_MSG_CEC_VERSION 0x9e +/* CEC Version Operand (cec_version) */ +#define CEC_OP_CEC_VERSION_1_3A 4 +#define CEC_OP_CEC_VERSION_1_4 5 +#define CEC_OP_CEC_VERSION_2_0 6 + +#define CEC_MSG_GET_CEC_VERSION 0x9f +#define CEC_MSG_GIVE_PHYSICAL_ADDR 0x83 +#define CEC_MSG_GET_MENU_LANGUAGE 0x91 +#define CEC_MSG_REPORT_PHYSICAL_ADDR 0x84 +/* Primary Device Type Operand (prim_devtype) */ +#define CEC_OP_PRIM_DEVTYPE_TV 0 +#define CEC_OP_PRIM_DEVTYPE_RECORD 1 +#define CEC_OP_PRIM_DEVTYPE_TUNER 3 +#define CEC_OP_PRIM_DEVTYPE_PLAYBACK 4 +#define CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM 5 +#define CEC_OP_PRIM_DEVTYPE_SWITCH 6 +#define CEC_OP_PRIM_DEVTYPE_PROCESSOR 7 + +#define CEC_MSG_SET_MENU_LANGUAGE 0x32 +#define CEC_MSG_REPORT_FEATURES 0xa6 /* HDMI 2.0 */ +/* All Device Types Operand (all_device_types) */ +#define CEC_OP_ALL_DEVTYPE_TV 0x80 +#define CEC_OP_ALL_DEVTYPE_RECORD 0x40 +#define CEC_OP_ALL_DEVTYPE_TUNER 0x20 +#define CEC_OP_ALL_DEVTYPE_PLAYBACK 0x10 +#define CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM 0x08 +#define CEC_OP_ALL_DEVTYPE_SWITCH 0x04 +/* And if you wondering what happened to PROCESSOR devices: those should + * be mapped to a SWITCH. */ + +/* Valid for RC Profile and Device Feature operands */ +#define CEC_OP_FEAT_EXT 0x80 /* Extension bit */ +/* RC Profile Operand (rc_profile) */ +#define CEC_OP_FEAT_RC_TV_PROFILE_NONE 0x00 +#define CEC_OP_FEAT_RC_TV_PROFILE_1 0x02 +#define CEC_OP_FEAT_RC_TV_PROFILE_2 0x06 +#define CEC_OP_FEAT_RC_TV_PROFILE_3 0x0a +#define CEC_OP_FEAT_RC_TV_PROFILE_4 0x0e +#define CEC_OP_FEAT_RC_SRC_HAS_DEV_ROOT_MENU 0x50 +#define CEC_OP_FEAT_RC_SRC_HAS_DEV_SETUP_MENU 0x48 +#define CEC_OP_FEAT_RC_SRC_HAS_CONTENTS_MENU 0x44 +#define CEC_OP_FEAT_RC_SRC_HAS_MEDIA_TOP_MENU 0x42 +#define CEC_OP_FEAT_RC_SRC_HAS_MEDIA_CONTEXT_MENU 0x41 +/* Device Feature Operand (dev_features) */ +#define CEC_OP_FEAT_DEV_HAS_RECORD_TV_SCREEN 0x40 +#define CEC_OP_FEAT_DEV_HAS_SET_OSD_STRING 0x20 +#define CEC_OP_FEAT_DEV_HAS_DECK_CONTROL 0x10 +#define CEC_OP_FEAT_DEV_HAS_SET_AUDIO_RATE 0x08 +#define CEC_OP_FEAT_DEV_SINK_HAS_ARC_TX 0x04 +#define CEC_OP_FEAT_DEV_SOURCE_HAS_ARC_RX 0x02 + +#define CEC_MSG_GIVE_FEATURES 0xa5 /* HDMI 2.0 */ + + +/* Deck Control Feature */ +#define CEC_MSG_DECK_CONTROL 0x42 +/* Deck Control Mode Operand (deck_control_mode) */ +#define CEC_OP_DECK_CTL_MODE_SKIP_FWD 1 +#define CEC_OP_DECK_CTL_MODE_SKIP_REV 2 +#define CEC_OP_DECK_CTL_MODE_STOP 3 +#define CEC_OP_DECK_CTL_MODE_EJECT 4 + +#define CEC_MSG_DECK_STATUS 0x1b +/* Deck Info Operand (deck_info) */ +#define CEC_OP_DECK_INFO_PLAY 0x11 +#define CEC_OP_DECK_INFO_RECORD 0x12 +#define CEC_OP_DECK_INFO_PLAY_REV 0x13 +#define CEC_OP_DECK_INFO_STILL 0x14 +#define CEC_OP_DECK_INFO_SLOW 0x15 +#define CEC_OP_DECK_INFO_SLOW_REV 0x16 +#define CEC_OP_DECK_INFO_FAST_FWD 0x17 +#define CEC_OP_DECK_INFO_FAST_REV 0x18 +#define CEC_OP_DECK_INFO_NO_MEDIA 0x19 +#define CEC_OP_DECK_INFO_STOP 0x1a +#define CEC_OP_DECK_INFO_SKIP_FWD 0x1b +#define CEC_OP_DECK_INFO_SKIP_REV 0x1c +#define CEC_OP_DECK_INFO_INDEX_SEARCH_FWD 0x1d +#define CEC_OP_DECK_INFO_INDEX_SEARCH_REV 0x1e +#define CEC_OP_DECK_INFO_OTHER 0x1f + +#define CEC_MSG_GIVE_DECK_STATUS 0x1a +/* Status Request Operand (status_req) */ +#define CEC_OP_STATUS_REQ_ON 1 +#define CEC_OP_STATUS_REQ_OFF 2 +#define CEC_OP_STATUS_REQ_ONCE 3 + +#define CEC_MSG_PLAY 0x41 +/* Play Mode Operand (play_mode) */ +#define CEC_OP_PLAY_MODE_PLAY_FWD 0x24 +#define CEC_OP_PLAY_MODE_PLAY_REV 0x20 +#define CEC_OP_PLAY_MODE_PLAY_STILL 0x25 +#define CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MIN 0x05 +#define CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MED 0x06 +#define CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MAX 0x07 +#define CEC_OP_PLAY_MODE_PLAY_FAST_REV_MIN 0x09 +#define CEC_OP_PLAY_MODE_PLAY_FAST_REV_MED 0x0a +#define CEC_OP_PLAY_MODE_PLAY_FAST_REV_MAX 0x0b +#define CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MIN 0x15 +#define CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MED 0x16 +#define CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MAX 0x17 +#define CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MIN 0x19 +#define CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MED 0x1a +#define CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MAX 0x1b + + +/* Tuner Control Feature */ +#define CEC_MSG_GIVE_TUNER_DEVICE_STATUS 0x08 +#define CEC_MSG_SELECT_ANALOGUE_SERVICE 0x92 +#define CEC_MSG_SELECT_DIGITAL_SERVICE 0x93 +#define CEC_MSG_TUNER_DEVICE_STATUS 0x07 +/* Recording Flag Operand (rec_flag) */ +#define CEC_OP_REC_FLAG_USED 0 +#define CEC_OP_REC_FLAG_NOT_USED 1 +/* Tuner Display Info Operand (tuner_display_info) */ +#define CEC_OP_TUNER_DISPLAY_INFO_DIGITAL 0 +#define CEC_OP_TUNER_DISPLAY_INFO_NONE 1 +#define CEC_OP_TUNER_DISPLAY_INFO_ANALOGUE 2 + +#define CEC_MSG_TUNER_STEP_DECREMENT 0x06 +#define CEC_MSG_TUNER_STEP_INCREMENT 0x05 + + +/* Vendor Specific Commands Feature */ + +/* + * Has also: + * CEC_MSG_CEC_VERSION + * CEC_MSG_GET_CEC_VERSION + */ +#define CEC_MSG_DEVICE_VENDOR_ID 0x87 +#define CEC_MSG_GIVE_DEVICE_VENDOR_ID 0x8c +#define CEC_MSG_VENDOR_COMMAND 0x89 +#define CEC_MSG_VENDOR_COMMAND_WITH_ID 0xa0 +#define CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN 0x8a +#define CEC_MSG_VENDOR_REMOTE_BUTTON_UP 0x8b + + +/* OSD Display Feature */ +#define CEC_MSG_SET_OSD_STRING 0x64 +/* Display Control Operand (disp_ctl) */ +#define CEC_OP_DISP_CTL_DEFAULT 0x00 +#define CEC_OP_DISP_CTL_UNTIL_CLEARED 0x40 +#define CEC_OP_DISP_CTL_CLEAR 0x80 + + +/* Device OSD Transfer Feature */ +#define CEC_MSG_GIVE_OSD_NAME 0x46 +#define CEC_MSG_SET_OSD_NAME 0x47 + + +/* Device Menu Control Feature */ +#define CEC_MSG_MENU_REQUEST 0x8d +/* Menu Request Type Operand (menu_req) */ +#define CEC_OP_MENU_REQUEST_ACTIVATE 0x00 +#define CEC_OP_MENU_REQUEST_DEACTIVATE 0x01 +#define CEC_OP_MENU_REQUEST_QUERY 0x02 + +#define CEC_MSG_MENU_STATUS 0x8e +/* Menu State Operand (menu_state) */ +#define CEC_OP_MENU_STATE_ACTIVATED 0x00 +#define CEC_OP_MENU_STATE_DEACTIVATED 0x01 + +#define CEC_MSG_USER_CONTROL_PRESSED 0x44 +/* UI Broadcast Type Operand (ui_bcast_type) */ +#define CEC_OP_UI_BCAST_TYPE_TOGGLE_ALL 0x00 +#define CEC_OP_UI_BCAST_TYPE_TOGGLE_DIG_ANA 0x01 +#define CEC_OP_UI_BCAST_TYPE_ANALOGUE 0x10 +#define CEC_OP_UI_BCAST_TYPE_ANALOGUE_T 0x20 +#define CEC_OP_UI_BCAST_TYPE_ANALOGUE_CABLE 0x30 +#define CEC_OP_UI_BCAST_TYPE_ANALOGUE_SAT 0x40 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL 0x50 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL_T 0x60 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL_CABLE 0x70 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL_SAT 0x80 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL_COM_SAT 0x90 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL_COM_SAT2 0x91 +#define CEC_OP_UI_BCAST_TYPE_IP 0xa0 +/* UI Sound Presentation Control Operand (ui_snd_pres_ctl) */ +#define CEC_OP_UI_SND_PRES_CTL_DUAL_MONO 0x10 +#define CEC_OP_UI_SND_PRES_CTL_KARAOKE 0x20 +#define CEC_OP_UI_SND_PRES_CTL_DOWNMIX 0x80 +#define CEC_OP_UI_SND_PRES_CTL_REVERB 0x90 +#define CEC_OP_UI_SND_PRES_CTL_EQUALIZER 0xa0 +#define CEC_OP_UI_SND_PRES_CTL_BASS_UP 0xb1 +#define CEC_OP_UI_SND_PRES_CTL_BASS_NEUTRAL 0xb2 +#define CEC_OP_UI_SND_PRES_CTL_BASS_DOWN 0xb3 +#define CEC_OP_UI_SND_PRES_CTL_TREBLE_UP 0xc1 +#define CEC_OP_UI_SND_PRES_CTL_TREBLE_NEUTRAL 0xc2 +#define CEC_OP_UI_SND_PRES_CTL_TREBLE_DOWN 0xc3 + +#define CEC_MSG_USER_CONTROL_RELEASED 0x45 + + +/* Remote Control Passthrough Feature */ + +/* + * Has also: + * CEC_MSG_USER_CONTROL_PRESSED + * CEC_MSG_USER_CONTROL_RELEASED + */ + + +/* Power Status Feature */ +#define CEC_MSG_GIVE_DEVICE_POWER_STATUS 0x8f +#define CEC_MSG_REPORT_POWER_STATUS 0x90 +/* Power Status Operand (pwr_state) */ +#define CEC_OP_POWER_STATUS_ON 0 +#define CEC_OP_POWER_STATUS_STANDBY 1 +#define CEC_OP_POWER_STATUS_TO_ON 2 +#define CEC_OP_POWER_STATUS_TO_STANDBY 3 + + +/* General Protocol Messages */ +#define CEC_MSG_FEATURE_ABORT 0x00 +/* Abort Reason Operand (reason) */ +#define CEC_OP_ABORT_UNRECOGNIZED_OP 0 +#define CEC_OP_ABORT_INCORRECT_MODE 1 +#define CEC_OP_ABORT_NO_SOURCE 2 +#define CEC_OP_ABORT_INVALID_OP 3 +#define CEC_OP_ABORT_REFUSED 4 +#define CEC_OP_ABORT_UNDETERMINED 5 + +#define CEC_MSG_ABORT 0xff + + +/* System Audio Control Feature */ + +/* + * Has also: + * CEC_MSG_USER_CONTROL_PRESSED + * CEC_MSG_USER_CONTROL_RELEASED + */ +#define CEC_MSG_GIVE_AUDIO_STATUS 0x71 +#define CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS 0x7d +#define CEC_MSG_REPORT_AUDIO_STATUS 0x7a +/* Audio Mute Status Operand (aud_mute_status) */ +#define CEC_OP_AUD_MUTE_STATUS_OFF 0 +#define CEC_OP_AUD_MUTE_STATUS_ON 1 + +#define CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR 0xa3 +#define CEC_MSG_REQUEST_SHORT_AUDIO_DESCRIPTOR 0xa4 +#define CEC_MSG_SET_SYSTEM_AUDIO_MODE 0x72 +/* System Audio Status Operand (sys_aud_status) */ +#define CEC_OP_SYS_AUD_STATUS_OFF 0 +#define CEC_OP_SYS_AUD_STATUS_ON 1 + +#define CEC_MSG_SYSTEM_AUDIO_MODE_REQUEST 0x70 +#define CEC_MSG_SYSTEM_AUDIO_MODE_STATUS 0x7e +/* Audio Format ID Operand (audio_format_id) */ +#define CEC_OP_AUD_FMT_ID_CEA861 0 +#define CEC_OP_AUD_FMT_ID_CEA861_CXT 1 + + +/* Audio Rate Control Feature */ +#define CEC_MSG_SET_AUDIO_RATE 0x9a +/* Audio Rate Operand (audio_rate) */ +#define CEC_OP_AUD_RATE_OFF 0 +#define CEC_OP_AUD_RATE_WIDE_STD 1 +#define CEC_OP_AUD_RATE_WIDE_FAST 2 +#define CEC_OP_AUD_RATE_WIDE_SLOW 3 +#define CEC_OP_AUD_RATE_NARROW_STD 4 +#define CEC_OP_AUD_RATE_NARROW_FAST 5 +#define CEC_OP_AUD_RATE_NARROW_SLOW 6 + + +/* Audio Return Channel Control Feature */ +#define CEC_MSG_INITIATE_ARC 0xc0 +#define CEC_MSG_REPORT_ARC_INITIATED 0xc1 +#define CEC_MSG_REPORT_ARC_TERMINATED 0xc2 +#define CEC_MSG_REQUEST_ARC_INITIATION 0xc3 +#define CEC_MSG_REQUEST_ARC_TERMINATION 0xc4 +#define CEC_MSG_TERMINATE_ARC 0xc5 + + +/* Dynamic Audio Lipsync Feature */ +/* Only for CEC 2.0 and up */ +#define CEC_MSG_REQUEST_CURRENT_LATENCY 0xa7 +#define CEC_MSG_REPORT_CURRENT_LATENCY 0xa8 +/* Low Latency Mode Operand (low_latency_mode) */ +#define CEC_OP_LOW_LATENCY_MODE_OFF 0 +#define CEC_OP_LOW_LATENCY_MODE_ON 1 +/* Audio Output Compensated Operand (audio_out_compensated) */ +#define CEC_OP_AUD_OUT_COMPENSATED_NA 0 +#define CEC_OP_AUD_OUT_COMPENSATED_DELAY 1 +#define CEC_OP_AUD_OUT_COMPENSATED_NO_DELAY 2 +#define CEC_OP_AUD_OUT_COMPENSATED_PARTIAL_DELAY 3 + + +/* Capability Discovery and Control Feature */ +#define CEC_MSG_CDC_MESSAGE 0xf8 +/* Ethernet-over-HDMI: nobody ever does this... */ +#define CEC_MSG_CDC_HEC_INQUIRE_STATE 0x00 +#define CEC_MSG_CDC_HEC_REPORT_STATE 0x01 +/* HEC Functionality State Operand (hec_func_state) */ +#define CEC_OP_HEC_FUNC_STATE_NOT_SUPPORTED 0 +#define CEC_OP_HEC_FUNC_STATE_INACTIVE 1 +#define CEC_OP_HEC_FUNC_STATE_ACTIVE 2 +#define CEC_OP_HEC_FUNC_STATE_ACTIVATION_FIELD 3 +/* Host Functionality State Operand (host_func_state) */ +#define CEC_OP_HOST_FUNC_STATE_NOT_SUPPORTED 0 +#define CEC_OP_HOST_FUNC_STATE_INACTIVE 1 +#define CEC_OP_HOST_FUNC_STATE_ACTIVE 2 +/* ENC Functionality State Operand (enc_func_state) */ +#define CEC_OP_ENC_FUNC_STATE_EXT_CON_NOT_SUPPORTED 0 +#define CEC_OP_ENC_FUNC_STATE_EXT_CON_INACTIVE 1 +#define CEC_OP_ENC_FUNC_STATE_EXT_CON_ACTIVE 2 +/* CDC Error Code Operand (cdc_errcode) */ +#define CEC_OP_CDC_ERROR_CODE_NONE 0 +#define CEC_OP_CDC_ERROR_CODE_CAP_UNSUPPORTED 1 +#define CEC_OP_CDC_ERROR_CODE_WRONG_STATE 2 +#define CEC_OP_CDC_ERROR_CODE_OTHER 3 +/* HEC Support Operand (hec_support) */ +#define CEC_OP_HEC_SUPPORT_NO 0 +#define CEC_OP_HEC_SUPPORT_YES 1 +/* HEC Activation Operand (hec_activation) */ +#define CEC_OP_HEC_ACTIVATION_ON 0 +#define CEC_OP_HEC_ACTIVATION_OFF 1 + +#define CEC_MSG_CDC_HEC_SET_STATE_ADJACENT 0x02 +#define CEC_MSG_CDC_HEC_SET_STATE 0x03 +/* HEC Set State Operand (hec_set_state) */ +#define CEC_OP_HEC_SET_STATE_DEACTIVATE 0 +#define CEC_OP_HEC_SET_STATE_ACTIVATE 1 + +#define CEC_MSG_CDC_HEC_REQUEST_DEACTIVATION 0x04 +#define CEC_MSG_CDC_HEC_NOTIFY_ALIVE 0x05 +#define CEC_MSG_CDC_HEC_DISCOVER 0x06 +/* Hotplug Detect messages */ +#define CEC_MSG_CDC_HPD_SET_STATE 0x10 +/* HPD State Operand (hpd_state) */ +#define CEC_OP_HPD_STATE_CP_EDID_DISABLE 0 +#define CEC_OP_HPD_STATE_CP_EDID_ENABLE 1 +#define CEC_OP_HPD_STATE_CP_EDID_DISABLE_ENABLE 2 +#define CEC_OP_HPD_STATE_EDID_DISABLE 3 +#define CEC_OP_HPD_STATE_EDID_ENABLE 4 +#define CEC_OP_HPD_STATE_EDID_DISABLE_ENABLE 5 +#define CEC_MSG_CDC_HPD_REPORT_STATE 0x11 +/* HPD Error Code Operand (hpd_error) */ +#define CEC_OP_HPD_ERROR_NONE 0 +#define CEC_OP_HPD_ERROR_INITIATOR_NOT_CAPABLE 1 +#define CEC_OP_HPD_ERROR_INITIATOR_WRONG_STATE 2 +#define CEC_OP_HPD_ERROR_OTHER 3 +#define CEC_OP_HPD_ERROR_NONE_NO_VIDEO 4 + +#endif
On Tue, Aug 18, 2015 at 10:26:32AM +0200, Hans Verkuil wrote:
- /* Part 2: Initialize and register the character device */
- cdev_init(&cecdev->cdev, &cec_devnode_fops);
- cecdev->cdev.owner = owner;
- ret = cdev_add(&cecdev->cdev, MKDEV(MAJOR(cec_dev_t), cecdev->minor),
1);
- if (ret < 0) {
pr_err("%s: cdev_add failed\n", __func__);
goto error;
- }
- /* Part 3: Register the cec device */
- cecdev->dev.bus = &cec_bus_type;
- cecdev->dev.devt = MKDEV(MAJOR(cec_dev_t), cecdev->minor);
- cecdev->dev.release = cec_devnode_release;
- if (cecdev->parent)
cecdev->dev.parent = cecdev->parent;
- dev_set_name(&cecdev->dev, "cec%d", cecdev->minor);
- ret = device_register(&cecdev->dev);
It's worth pointing out that you can greatly simplify the lifetime handling (you don't need to get and put cecdev->dev) if you make the cdev a child of the cecdev->dev.
If you grep for kobj.parent in drivers/ you'll see many drivers are doing this.
cecdev->cdev.kobj.parent = &cecdev->dev.kobj;
but you will need to call device_initialize() on cecdev->dev first, and use device_add() here.
- if (ret < 0) {
pr_err("%s: device_register failed\n", __func__);
goto error;
- }
- /* Part 4: Activate this minor. The char device can now be used. */
- set_bit(CEC_FLAG_REGISTERED, &cecdev->flags);
Having flags to indicate whether userspace can open something is racy. I don't see any other uses of cecdev->flags. I think you should kill this, and replace it with a cecdev->dead flag which indicates when the cecdev is going away, and causes any pre-existing users to fail.
- return 0;
+error:
- cdev_del(&cecdev->cdev);
- clear_bit(cecdev->minor, cec_devnode_nums);
- return ret;
+}
+/**
- cec_devnode_unregister - unregister a cec device node
- @cecdev: the device node to unregister
- This unregisters the passed device. Future open calls will be met with
- errors.
- This function can safely be called if the device node has never been
- registered or has already been unregistered.
- */
+static void cec_devnode_unregister(struct cec_devnode *cecdev) +{
- /* Check if cecdev was ever registered at all */
- if (!cec_devnode_is_registered(cecdev))
return;
Just make it a programming error if someone unregisters something that they haven't registered... that's pretty standard kernel programming.
- mutex_lock(&cec_devnode_lock);
- clear_bit(CEC_FLAG_REGISTERED, &cecdev->flags);
This should wake up the poll waitqueue so that users get to hear about the device going away in a timely manner.
- mutex_unlock(&cec_devnode_lock);
- device_unregister(&cecdev->dev);
+}
+int cec_create_adapter(struct cec_adapter *adap, const char *name, u32 caps,
u8 ninputs, struct module *owner, struct device *parent)
+{
- int res = 0;
- adap->owner = owner;
- if (WARN_ON(!owner))
return -ENXIO;
- adap->devnode.parent = parent;
- if (WARN_ON(!parent))
return -ENXIO;
- adap->name = name;
- adap->phys_addr = CEC_PHYS_ADDR_INVALID;
- adap->capabilities = caps;
- adap->ninputs = ninputs;
- adap->is_source = caps & CEC_CAP_IS_SOURCE;
- if (WARN_ON(!adap->ninputs && !adap->is_source))
return -ENXIO;
- adap->cec_version = CEC_OP_CEC_VERSION_2_0;
- adap->vendor_id = CEC_VENDOR_ID_NONE;
- adap->available_log_addrs = 1;
- adap->sequence = 0;
- memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs));
- mutex_init(&adap->lock);
- INIT_LIST_HEAD(&adap->transmit_queue);
- INIT_LIST_HEAD(&adap->wait_queue);
- adap->kthread = kthread_run(cec_thread_func, adap, "cec-%s", name);
- init_waitqueue_head(&adap->kthread_waitq);
- if (IS_ERR(adap->kthread)) {
pr_err("cec-%s: kernel_thread() failed\n", name);
return PTR_ERR(adap->kthread);
- }
- if (caps) {
res = cec_devnode_register(&adap->devnode, adap->owner);
Okay, so adap->devnode contains a struct device. That struct device controls the lifetime of adap->devnode, and because adap->devnode is part of adap, this also defines the lifetime of adap as well. adap must _never_ be freed until cec_devnode_release() has been called.
Looking at patch 15, the adapter structure is part of the cobalt streams. This makes that structure also have a lifetime controlled by this struct device. There is no release method implemented in there, and indeed cec_devnode_release() shows that the release node is optional, which suggests a misunderstanding in this area.
Far too many nested data structures are involved here. This needs fixing - with the code in its present form, it contains serious data structure lifetime issues, and therefore is not ready for merging, sorry.
On Tue, Aug 18, 2015 at 11:00:20AM +0100, Russell King - ARM Linux wrote:
On Tue, Aug 18, 2015 at 10:26:32AM +0200, Hans Verkuil wrote:
- /* Part 2: Initialize and register the character device */
- cdev_init(&cecdev->cdev, &cec_devnode_fops);
- cecdev->cdev.owner = owner;
- ret = cdev_add(&cecdev->cdev, MKDEV(MAJOR(cec_dev_t), cecdev->minor),
1);
- if (ret < 0) {
pr_err("%s: cdev_add failed\n", __func__);
goto error;
- }
- /* Part 3: Register the cec device */
- cecdev->dev.bus = &cec_bus_type;
- cecdev->dev.devt = MKDEV(MAJOR(cec_dev_t), cecdev->minor);
- cecdev->dev.release = cec_devnode_release;
- if (cecdev->parent)
cecdev->dev.parent = cecdev->parent;
- dev_set_name(&cecdev->dev, "cec%d", cecdev->minor);
- ret = device_register(&cecdev->dev);
It's worth pointing out that you can greatly simplify the lifetime handling (you don't need to get and put cecdev->dev) if you make the cdev a child of the cecdev->dev.
If you grep for kobj.parent in drivers/ you'll see many drivers are doing this.
cecdev->cdev.kobj.parent = &cecdev->dev.kobj;
but you will need to call device_initialize() on cecdev->dev first, and use device_add() here.
This is basically a requirement if one embeds both device and a cdev into the same structure. Trying to do get/put in the driver is racy, you need to let framework know (by setting cdve's parent to the device structure).
Thanks.
From: Hans Verkuil hansverk@cisco.com
Document the new HDMI CEC framework.
Signed-off-by: Hans Verkuil hansverk@cisco.com [k.debski@samsung.com: add DocBook documentation by Hans Verkuil, with Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- Documentation/cec.txt | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 Documentation/cec.txt
diff --git a/Documentation/cec.txt b/Documentation/cec.txt new file mode 100644 index 0000000..b298249 --- /dev/null +++ b/Documentation/cec.txt @@ -0,0 +1,166 @@ +CEC Kernel Support +================== + +The CEC framework provides a unified kernel interface for use with HDMI CEC +hardware. It is designed to handle a multiple variants of hardware. Adding to +the flexibility of the framework it enables to set which parts of the CEC +protocol processing is handled by the hardware, by the driver and by the +userspace application. + + +The CEC Protocol +---------------- + +The CEC protocol enables consumer electronic devices to communicate with each +other through the HDMI connection. The protocol uses logical addresses in the +communication. The logical address is strictly connected with the functionality +provided by the device. The TV acting as the communication hub is always +assigned address 0. The physical address is determined by the physical +connection between devices. + +The protocol enables control of compatible devices with a single remote. +Synchronous power on/standby, instant playback with changing the content source +on the TV. + +The Kernel Interface +==================== + +CEC Adapter +----------- + +#define CEC_LOG_ADDR_INVALID 0xff + +/* The maximum number of logical addresses one device can be assigned to. + * The CEC 2.0 spec allows for only 2 logical addresses at the moment. The + * Analog Devices CEC hardware supports 3. So let's go wild and go for 4. */ +#define CEC_MAX_LOG_ADDRS 4 + +/* The "Primary Device Type" */ +#define CEC_OP_PRIM_DEVTYPE_TV 0 +#define CEC_OP_PRIM_DEVTYPE_RECORD 1 +#define CEC_OP_PRIM_DEVTYPE_TUNER 3 +#define CEC_OP_PRIM_DEVTYPE_PLAYBACK 4 +#define CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM 5 +#define CEC_OP_PRIM_DEVTYPE_SWITCH 6 +#define CEC_OP_PRIM_DEVTYPE_VIDEOPROC 7 + +/* The "All Device Types" flags (CEC 2.0) */ +#define CEC_OP_ALL_DEVTYPE_TV (1 << 7) +#define CEC_OP_ALL_DEVTYPE_RECORD (1 << 6) +#define CEC_OP_ALL_DEVTYPE_TUNER (1 << 5) +#define CEC_OP_ALL_DEVTYPE_PLAYBACK (1 << 4) +#define CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM (1 << 3) +#define CEC_OP_ALL_DEVTYPE_SWITCH (1 << 2) +/* And if you wondering what happened to VIDEOPROC devices: those should + * be mapped to a SWITCH. */ + +/* The logical address types that the CEC device wants to claim */ +#define CEC_LOG_ADDR_TYPE_TV 0 +#define CEC_LOG_ADDR_TYPE_RECORD 1 +#define CEC_LOG_ADDR_TYPE_TUNER 2 +#define CEC_LOG_ADDR_TYPE_PLAYBACK 3 +#define CEC_LOG_ADDR_TYPE_AUDIOSYSTEM 4 +#define CEC_LOG_ADDR_TYPE_SPECIFIC 5 +#define CEC_LOG_ADDR_TYPE_UNREGISTERED 6 +/* Switches should use UNREGISTERED. + * Video processors should use SPECIFIC. */ + +/* The CEC version */ +#define CEC_OP_CEC_VERSION_1_3A 4 +#define CEC_OP_CEC_VERSION_1_4 5 +#define CEC_OP_CEC_VERSION_2_0 6 + +struct cec_adapter { + /* internal fields removed */ + + u16 phys_addr; + u32 capabilities; + u8 version; + u8 num_log_addrs; + u8 prim_device[CEC_MAX_LOG_ADDRS]; + u8 log_addr_type[CEC_MAX_LOG_ADDRS]; + u8 log_addr[CEC_MAX_LOG_ADDRS]; + + int (*adap_enable)(struct cec_adapter *adap, bool enable); + int (*adap_log_addr)(struct cec_adapter *adap, u8 logical_addr); + int (*adap_transmit)(struct cec_adapter *adap, struct cec_msg *msg); + void (*adap_transmit_timed_out)(struct cec_adapter *adap); + + void (*claimed_log_addr)(struct cec_adapter *adap, u8 idx); + int (*received)(struct cec_adapter *adap, struct cec_msg *msg); +}; + +int cec_create_adapter(struct cec_adapter *adap, u32 caps); +void cec_delete_adapter(struct cec_adapter *adap); +int cec_transmit_msg(struct cec_adapter *adap, struct cec_data *data, bool block); + +/* Called by the adapter */ +void cec_transmit_done(struct cec_adapter *adap, u32 status); +void cec_received_msg(struct cec_adapter *adap, struct cec_msg *msg); + +int cec_receive_msg(struct cec_adapter *adap, struct cec_msg *msg, bool block); +int cec_claim_log_addrs(struct cec_adapter *adap, struct cec_log_addrs *log_addrs, bool block); + +The device type defines are defined by the CEC standard. + +The cec_adapter structure represents the adapter. It has a number of +operations that have to be implemented in the driver: adap_enable() enables +or disables the physical adapter, adap_log_addr() tells the driver which +logical address should be configured. This may be called multiple times +to configure multiple logical addresses. Calling adap_enable(false) or +adap_log_addr(CEC_LOG_ADDR_INVALID) will clear all configured logical +addresses. + +The adap_transmit op will setup the hardware to send out the given CEC message. +This will return without waiting for the transmission to finish. The +adap_transmit_timed_out() function is called when the current transmission timed +out and the hardware needs to be informed of this (the hardware should go back +from transmitter to receiver mode). + +The adapter driver will also call into the adapter: it should call +cec_transmit_done() when a cec transfer was finalized and cec_received_msg() +when a new message was received. + +When a message is received the received() op is called. + +The driver has to call cec_create_adapter to initialize the structure. If +the 'caps' argument is non-zero, then it will also create a /dev/cecX +device node to allow userspace to interact with the CEC device. Userspace +can request those capabilities with the CEC_G_CAPS ioctl. + +In order for a CEC adapter to be configured it needs a physical address. +This is normally assigned by the driver. It is either 0.0.0.0 for a TV (aka +video receiver) or it is derived from the EDID that the source received +from the sink. This is normally set by the driver before enabling the CEC +adapter, or it is set from userspace in the case of CEC USB dongles (although +embedded systems might also want to set this manually). + +After enabling the CEC adapter it has to be configured. + +The userspace has to inform the CEC adapter of which type of device it requests +the adapter to identify itself. After this information is set by userspace, the +CEC framework will attempt to to find and claim a logical addresses matching the +requested device type. If none are found, then it will fall back to logical +address Unregistered (15). To clear the logical addresses list from the list the +userspace application should set the num_log_addrs field of struct cec_log_addr +to 0. + +The type of device is set from the userspace with the CEC_S_ADAP_LOG_ADDRS. In +addition, claiming logical addresses can be initiated from the kernel side by +calling the cec_claim_log_addrs function. + +Before the addresses are claimed it is possible to send and receive messages. +Sending all messages is possible as it is up to the userspace to the source +and destination addresses in the message payload. However, only broadcast +messages can be received until a regular logical address is claimed. + +When a CEC message is received the CEC framework will take care of the CEC +core messages CEC_MSG_GET_CEC_VERSION, CEC_MSG_GIVE_PHYS_ADDR and CEC_MSG_ABORT. +Then it will call the received() op (if set), and finally it will queue it +for handling by userspace if create_devnode was true, or send back +FEATURE_ABORT if create_devnode was false. + +Drivers can also use the cec_transmit_msg() call to transmit a message. This +can either be fire-and-forget (the CEC framework will queue up messages in a +transmit queue), or a blocking wait until there is either an error or a +reply to the message.
From: Hans Verkuil hverkuil@xs4all.nl
Add DocBook documentation for the CEC API.
Signed-off-by: Hans Verkuil hansverk@cisco.com [k.debski@samsung.com: add documentation for passthrough mode] [k.debski@samsung.com: minor fixes and change of reserved field sizes] Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- Documentation/DocBook/media/Makefile | 2 + Documentation/DocBook/media/v4l/biblio.xml | 10 + Documentation/DocBook/media/v4l/cec-api.xml | 75 +++++ Documentation/DocBook/media/v4l/cec-func-close.xml | 59 ++++ Documentation/DocBook/media/v4l/cec-func-ioctl.xml | 73 +++++ Documentation/DocBook/media/v4l/cec-func-open.xml | 94 +++++++ Documentation/DocBook/media/v4l/cec-func-poll.xml | 89 ++++++ .../DocBook/media/v4l/cec-ioc-adap-g-caps.xml | 138 ++++++++++ .../DocBook/media/v4l/cec-ioc-adap-g-log-addrs.xml | 306 +++++++++++++++++++++ .../DocBook/media/v4l/cec-ioc-adap-g-phys-addr.xml | 78 ++++++ .../DocBook/media/v4l/cec-ioc-adap-g-state.xml | 87 ++++++ .../DocBook/media/v4l/cec-ioc-adap-g-vendor-id.xml | 70 +++++ .../DocBook/media/v4l/cec-ioc-dqevent.xml | 125 +++++++++ .../DocBook/media/v4l/cec-ioc-g-monitor.xml | 86 ++++++ .../DocBook/media/v4l/cec-ioc-g-passthrough.xml | 81 ++++++ .../DocBook/media/v4l/cec-ioc-receive.xml | 185 +++++++++++++ Documentation/DocBook/media_api.tmpl | 8 +- 17 files changed, 1564 insertions(+), 2 deletions(-) create mode 100644 Documentation/DocBook/media/v4l/cec-api.xml create mode 100644 Documentation/DocBook/media/v4l/cec-func-close.xml create mode 100644 Documentation/DocBook/media/v4l/cec-func-ioctl.xml create mode 100644 Documentation/DocBook/media/v4l/cec-func-open.xml create mode 100644 Documentation/DocBook/media/v4l/cec-func-poll.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-adap-g-caps.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-adap-g-log-addrs.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-adap-g-phys-addr.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-adap-g-state.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-adap-g-vendor-id.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-dqevent.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-g-monitor.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-g-passthrough.xml create mode 100644 Documentation/DocBook/media/v4l/cec-ioc-receive.xml
diff --git a/Documentation/DocBook/media/Makefile b/Documentation/DocBook/media/Makefile index 23996f8..5c89f4b 100644 --- a/Documentation/DocBook/media/Makefile +++ b/Documentation/DocBook/media/Makefile @@ -64,6 +64,7 @@ IOCTLS = \ $(shell perl -ne 'print "$$1 " if /#define\s+([A-Z][^\s]+)\s+_IO/' $(srctree)/include/uapi/linux/dvb/net.h) \ $(shell perl -ne 'print "$$1 " if /#define\s+([^\s]+)\s+_IO/' $(srctree)/include/uapi/linux/dvb/video.h) \ $(shell perl -ne 'print "$$1 " if /#define\s+([^\s]+)\s+_IO/' $(srctree)/include/uapi/linux/media.h) \ + $(shell perl -ne 'print "$$1 " if /#define\s+([^\s]+)\s+_IO/' $(srctree)/include/uapi/linux/cec.h) \ $(shell perl -ne 'print "$$1 " if /#define\s+([^\s]+)\s+_IO/' $(srctree)/include/uapi/linux/v4l2-subdev.h) \
DEFINES = \ @@ -100,6 +101,7 @@ STRUCTS = \ $(shell perl -ne 'print "$$1 " if (/^struct\s+([^\s]+)\s+/ && !/_old/)' $(srctree)/include/uapi/linux/dvb/net.h) \ $(shell perl -ne 'print "$$1 " if (/^struct\s+([^\s]+)\s+/)' $(srctree)/include/uapi/linux/dvb/video.h) \ $(shell perl -ne 'print "$$1 " if /^struct\s+([^\s]+)\s+/' $(srctree)/include/uapi/linux/media.h) \ + $(shell perl -ne 'print "$$1 " if /^struct\s+([^\s]+)\s+/' $(srctree)/include/uapi/linux/cec.h) \ $(shell perl -ne 'print "$$1 " if /^struct\s+([^\s]+)\s+/' $(srctree)/include/uapi/linux/v4l2-subdev.h) \ $(shell perl -ne 'print "$$1 " if /^struct\s+([^\s]+)\s+/' $(srctree)/include/uapi/linux/v4l2-mediabus.h)
diff --git a/Documentation/DocBook/media/v4l/biblio.xml b/Documentation/DocBook/media/v4l/biblio.xml index fdee6b3..bed940b 100644 --- a/Documentation/DocBook/media/v4l/biblio.xml +++ b/Documentation/DocBook/media/v4l/biblio.xml @@ -324,6 +324,16 @@ in the frequency range from 87,5 to 108,0 MHz</title> <subtitle>Specification Version 1.4a</subtitle> </biblioentry>
+ <biblioentry id="hdmi2"> + <abbrev>HDMI2</abbrev> + <authorgroup> + <corpauthor>HDMI Licensing LLC +(<ulink url="http://www.hdmi.org">http://www.hdmi.org</ulink>)</corpauthor> + </authorgroup> + <title>High-Definition Multimedia Interface</title> + <subtitle>Specification Version 2.0</subtitle> + </biblioentry> + <biblioentry id="dp"> <abbrev>DP</abbrev> <authorgroup> diff --git a/Documentation/DocBook/media/v4l/cec-api.xml b/Documentation/DocBook/media/v4l/cec-api.xml new file mode 100644 index 0000000..7394116 --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-api.xml @@ -0,0 +1,75 @@ +<partinfo> + <authorgroup> + <author> + <firstname>Hans</firstname> + <surname>Verkuil</surname> + <affiliation><address><email>hans.verkuil@cisco.com</email></address></affiliation> + <contrib>Initial version.</contrib> + </author> + </authorgroup> + <copyright> + <year>2015</year> + <holder>Hans Verkuil</holder> + </copyright> + + <revhistory> + <!-- Put document revisions here, newest first. --> + <revision> + <revnumber>1.0.0</revnumber> + <date>2015-04-27</date> + <authorinitials>hv</authorinitials> + <revremark>Initial revision</revremark> + </revision> + </revhistory> +</partinfo> + +<title>CEC API</title> + +<chapter id="cec-api"> + <title>CEC: Consumer Electronics Control</title> + + <section id="cec-intro"> + <title>Introduction</title> + <para>HDMI connectors provide a single pin for use by the Consumer Electronics + Control protocol. This protocol allows different devices connected by an HDMI cable + to communicate. The protocol for CEC version 1.4 is defined in supplements 1 (CEC) + and 2 (HEAC or HDMI Ethernet and Audio Return Channel) of the HDMI 1.4a + (<xref linkend="hdmi" />) specification and the extensions added to CEC version 2.0 + are defined in chapter 11 of the HDMI 2.0 (<xref linkend="hdmi2" />) specification. + </para> + + <para>The bitrate is very slow (effectively no more than 36 bytes per second) and + is based on the ancient AV.link protocol used in old SCART connectors. The protocol + closely resembles a crazy Rube Goldberg contraption and is an unholy mix of low and + high level messages. Some messages, especially those part of the HEAC protocol layered + on top of CEC, need to be handled by the kernel, others can be handled either by the + kernel or by userspace.</para> + + <para>In addition, CEC can be implemented in HDMI receivers, transmitters and in USB + devices that have an HDMI input and an HDMI output and that control just the CEC pin.</para> + + <para>Drivers that support CEC and that allow (or require) userspace to handle CEC + messages and/or configure the CEC adapter will create a CEC device node (/dev/cecX) + to give userspace access to the CEC adapter. The &CEC-ADAP-G-CAPS; ioctl will tell userspace + what it is allowed to do.</para> + </section> +</chapter> + +<appendix id="cec-user-func"> + <title>Function Reference</title> + <!-- Keep this alphabetically sorted. --> + &sub-cec-func-open; + &sub-cec-func-close; + &sub-cec-func-ioctl; + &sub-cec-func-poll; + <!-- All ioctls go here. --> + &sub-cec-ioc-adap-g-caps; + &sub-cec-ioc-adap-g-log-addrs; + &sub-cec-ioc-adap-g-phys-addr; + &sub-cec-ioc-adap-g-state; + &sub-cec-ioc-adap-g-vendor-id; + &sub-cec-ioc-dqevent; + &sub-cec-ioc-g-monitor; + &sub-cec-ioc-g-passthrough; + &sub-cec-ioc-receive; +</appendix> diff --git a/Documentation/DocBook/media/v4l/cec-func-close.xml b/Documentation/DocBook/media/v4l/cec-func-close.xml new file mode 100644 index 0000000..c3978af --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-func-close.xml @@ -0,0 +1,59 @@ +<refentry id="cec-func-close"> + <refmeta> + <refentrytitle>cec close()</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>cec-close</refname> + <refpurpose>Close a cec device</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcsynopsisinfo>#include <unistd.h></funcsynopsisinfo> + <funcprototype> + <funcdef>int <function>close</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>&fd;</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + + <para>Closes the cec device. Resources associated with the file descriptor + are freed. The device configuration remain unchanged.</para> + </refsect1> + + <refsect1> + <title>Return Value</title> + + <para><function>close</function> returns 0 on success. On error, -1 is + returned, and <varname>errno</varname> is set appropriately. Possible error + codes are:</para> + + <variablelist> + <varlistentry> + <term><errorcode>EBADF</errorcode></term> + <listitem> + <para><parameter>fd</parameter> is not a valid open file descriptor. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-func-ioctl.xml b/Documentation/DocBook/media/v4l/cec-func-ioctl.xml new file mode 100644 index 0000000..ad85973 --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-func-ioctl.xml @@ -0,0 +1,73 @@ +<refentry id="cec-func-ioctl"> + <refmeta> + <refentrytitle>cec ioctl()</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>cec-ioctl</refname> + <refpurpose>Control a cec device</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcsynopsisinfo>#include <sys/ioctl.h></funcsynopsisinfo> + <funcprototype> + <funcdef>int <function>ioctl</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>int <parameter>request</parameter></paramdef> + <paramdef>void *<parameter>argp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>&fd;</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>request</parameter></term> + <listitem> + <para>CEC ioctl request code as defined in the cec.h header file, + for example CEC_G_CAPS.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>argp</parameter></term> + <listitem> + <para>Pointer to a request-specific structure.</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + <para>The <function>ioctl()</function> function manipulates cec device + parameters. The argument <parameter>fd</parameter> must be an open file + descriptor.</para> + <para>The ioctl <parameter>request</parameter> code specifies the cec + function to be called. It has encoded in it whether the argument is an + input, output or read/write parameter, and the size of the argument + <parameter>argp</parameter> in bytes.</para> + <para>Macros and structures definitions specifying cec ioctl requests and + their parameters are located in the cec.h header file. All cec ioctl + requests, their respective function and parameters are specified in + <xref linkend="cec-user-func" />.</para> + </refsect1> + + <refsect1> + &return-value; + + <para>Request-specific error codes are listed in the + individual requests descriptions.</para> + <para>When an ioctl that takes an output or read/write parameter fails, + the parameter remains unmodified.</para> + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-func-open.xml b/Documentation/DocBook/media/v4l/cec-func-open.xml new file mode 100644 index 0000000..d814847 --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-func-open.xml @@ -0,0 +1,94 @@ +<refentry id="cec-func-open"> + <refmeta> + <refentrytitle>cec open()</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>cec-open</refname> + <refpurpose>Open a cec device</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcsynopsisinfo>#include <fcntl.h></funcsynopsisinfo> + <funcprototype> + <funcdef>int <function>open</function></funcdef> + <paramdef>const char *<parameter>device_name</parameter></paramdef> + <paramdef>int <parameter>flags</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>device_name</parameter></term> + <listitem> + <para>Device to be opened.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>flags</parameter></term> + <listitem> + <para>Open flags. Access mode must be either <constant>O_RDONLY</constant> + or <constant>O_RDWR</constant>. Other flags have no effect.</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + <refsect1> + <title>Description</title> + <para>To open a cec device applications call <function>open()</function> + with the desired device name. The function has no side effects; the device + configuration remain unchanged.</para> + <para>When the device is opened in read-only mode, attempts to modify its + configuration will result in an error, and <varname>errno</varname> will be + set to <errorcode>EBADF</errorcode>.</para> + </refsect1> + <refsect1> + <title>Return Value</title> + + <para><function>open</function> returns the new file descriptor on success. + On error, -1 is returned, and <varname>errno</varname> is set appropriately. + Possible error codes are:</para> + + <variablelist> + <varlistentry> + <term><errorcode>EACCES</errorcode></term> + <listitem> + <para>The requested access to the file is not allowed.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><errorcode>EMFILE</errorcode></term> + <listitem> + <para>The process already has the maximum number of files open. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><errorcode>ENFILE</errorcode></term> + <listitem> + <para>The system limit on the total number of open files has been + reached.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><errorcode>ENOMEM</errorcode></term> + <listitem> + <para>Insufficient kernel memory was available.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><errorcode>ENXIO</errorcode></term> + <listitem> + <para>No device corresponding to this device special file exists. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-func-poll.xml b/Documentation/DocBook/media/v4l/cec-func-poll.xml new file mode 100644 index 0000000..6853817 --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-func-poll.xml @@ -0,0 +1,89 @@ +<refentry id="cec-func-poll"> + <refmeta> + <refentrytitle>cec poll()</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>cec-poll</refname> + <refpurpose>Wait for some event on a file descriptor</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcsynopsisinfo>#include <sys/poll.h></funcsynopsisinfo> + <funcprototype> + <funcdef>int <function>poll</function></funcdef> + <paramdef>struct pollfd *<parameter>ufds</parameter></paramdef> + <paramdef>unsigned int <parameter>nfds</parameter></paramdef> + <paramdef>int <parameter>timeout</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>With the <function>poll()</function> function applications +can wait for CEC events.</para> + + <para>On success <function>poll()</function> returns the number of +file descriptors that have been selected (that is, file descriptors +for which the <structfield>revents</structfield> field of the +respective <structname>pollfd</structname> structure is non-zero). +CEC devices set the <constant>POLLIN</constant> and +<constant>POLLRDNORM</constant> flags in the +<structfield>revents</structfield> field if there are messages in the +receive queue. If the transmit queue has room for new messages, the +<constant>POLLOUT</constant> and <constant>POLLWRNORM</constant> +flags are set. If there are events in the event queue, then the +<constant>POLLPRI</constant> flag is set. +When the function timed out it returns a value of zero, on +failure it returns <returnvalue>-1</returnvalue> and the +<varname>errno</varname> variable is set appropriately. +</para> + + <para>For more details see the +<function>poll()</function> manual page.</para> + </refsect1> + + <refsect1> + <title>Return Value</title> + + <para>On success, <function>poll()</function> returns the number +structures which have non-zero <structfield>revents</structfield> +fields, or zero if the call timed out. On error +<returnvalue>-1</returnvalue> is returned, and the +<varname>errno</varname> variable is set appropriately:</para> + + <variablelist> + <varlistentry> + <term><errorcode>EBADF</errorcode></term> + <listitem> + <para>One or more of the <parameter>ufds</parameter> members +specify an invalid file descriptor.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><errorcode>EFAULT</errorcode></term> + <listitem> + <para><parameter>ufds</parameter> references an inaccessible +memory area.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><errorcode>EINTR</errorcode></term> + <listitem> + <para>The call was interrupted by a signal.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><errorcode>EINVAL</errorcode></term> + <listitem> + <para>The <parameter>nfds</parameter> argument is greater +than <constant>OPEN_MAX</constant>.</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-ioc-adap-g-caps.xml b/Documentation/DocBook/media/v4l/cec-ioc-adap-g-caps.xml new file mode 100644 index 0000000..db20619 --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-ioc-adap-g-caps.xml @@ -0,0 +1,138 @@ +<refentry id="cec-ioc-adap-g-caps"> + <refmeta> + <refentrytitle>ioctl CEC_ADAP_G_CAPS</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>CEC_ADAP_G_CAPS</refname> + <refpurpose>Query device capabilities</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcprototype> + <funcdef>int <function>ioctl</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>int <parameter>request</parameter></paramdef> + <paramdef>struct cec_caps *<parameter>argp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>File descriptor returned by + <link linkend='cec-func-open'><function>open()</function></link>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>request</parameter></term> + <listitem> + <para>CEC_ADAP_G_CAPS</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>argp</parameter></term> + <listitem> + <para></para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + + <para>All cec devices must support the <constant>CEC_ADAP_G_CAPS</constant> + ioctl. To query device information, applications call the ioctl with a + pointer to a &cec-caps;. The driver fills the structure and returns + the information to the application. + The ioctl never fails.</para> + + <table pgwide="1" frame="none" id="cec-caps"> + <title>struct <structname>cec_caps</structname></title> + <tgroup cols="3"> + &cs-str; + <tbody valign="top"> + <row> + <entry>__u32</entry> + <entry><structfield>available_log_addrs</structfield></entry> + <entry>How many logical addresses does the CEC adapter support. This will + be at most <constant>CEC_MAX_LOG_ADDRS</constant>.</entry> + </row> + <row> + <entry>__u32</entry> + <entry><structfield>capabilities</structfield></entry> + <entry>The capabilities of the CEC adapter, see <xref + linkend="cec-capabilities" />.</entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>reserved</structfield>[40]</entry> + <entry>Reserved for future extensions. Drivers must + set this array to zero.</entry> + </row> + </tbody> + </tgroup> + </table> + + <table pgwide="1" frame="none" id="cec-capabilities"> + <title>CEC Capabilities Flags</title> + <tgroup cols="3"> + &cs-def; + <tbody valign="top"> + <row> + <entry><constant>CEC_CAP_STATE</constant></entry> + <entry>0x00000001</entry> + <entry>Userspace has to configure the adapter state (enable or disable it) by + calling &CEC-ADAP-S-STATE;.</entry> + </row> + <row> + <entry><constant>CEC_CAP_PHYS_ADDR</constant></entry> + <entry>0x00000002</entry> + <entry>Userspace has to configure the physical address by + calling &CEC-ADAP-S-PHYS-ADDR;.</entry> + </row> + <row> + <entry><constant>CEC_CAP_LOG_ADDRS</constant></entry> + <entry>0x00000004</entry> + <entry>Userspace has to configure the logical addresses by + calling &CEC-ADAP-S-LOG-ADDRS;.</entry> + </row> + <row> + <entry><constant>CEC_CAP_TRANSMIT</constant></entry> + <entry>0x00000008</entry> + <entry>Userspace can transmit messages by calling &CEC-TRANSMIT;.</entry> + </row> + <row> + <entry><constant>CEC_CAP_RECEIVE</constant></entry> + <entry>0x00000010</entry> + <entry>Userspace can receive messages by calling &CEC-RECEIVE;.</entry> + </row> + <row> + <entry><constant>CEC_CAP_VENDOR_ID</constant></entry> + <entry>0x00000020</entry> + <entry>Userspace has to configure the vendor ID by + calling &CEC-ADAP-S-VENDOR-ID;.</entry> + </row> + <row> + <entry><constant>CEC_CAP_PASSTHROUGH</constant></entry> + <entry>0x00000040</entry> + <entry>Userspace can use the passthrough mode by + calling &CEC-G-PASSTHROUGH;.</entry> + </row> + </tbody> + </tgroup> + </table> + </refsect1> + + <refsect1> + &return-value; + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-ioc-adap-g-log-addrs.xml b/Documentation/DocBook/media/v4l/cec-ioc-adap-g-log-addrs.xml new file mode 100644 index 0000000..4d6575c --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-ioc-adap-g-log-addrs.xml @@ -0,0 +1,306 @@ +<refentry id="cec-ioc-adap-g-log-addrs"> + <refmeta> + <refentrytitle>ioctl CEC_ADAP_G_LOG_ADDRS, CEC_ADAP_S_LOG_ADDRS</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>CEC_ADAP_G_LOG_ADDRS</refname> + <refname>CEC_ADAP_S_LOG_ADDRS</refname> + <refpurpose>Get or set the logical addresses</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcprototype> + <funcdef>int <function>ioctl</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>int <parameter>request</parameter></paramdef> + <paramdef>struct cec_log_addrs *<parameter>argp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>File descriptor returned by + <link linkend='cec-func-open'><function>open()</function></link>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>request</parameter></term> + <listitem> + <para>CEC_ADAP_G_LOG_ADDRS, CEC_ADAP_S_LOG_ADDRS</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>argp</parameter></term> + <listitem> + <para></para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + + <para>To query the current CEC logical addresses applications call the +<constant>CEC_ADAP_G_LOG_ADDRS</constant> ioctl with a pointer to a +<structname>cec_log_addrs</structname> structure where the drivers stores the +logical addresses.</para> + + <para>To set new logical addresses applications fill in struct <structname>cec_log_addrs</structname> +and call the <constant>CEC_ADAP_S_LOG_ADDRS</constant> ioctl with a pointer to this struct. +The <constant>CEC_ADAP_S_LOG_ADDRS</constant> ioctl is only available if +<constant>CEC_CAP_LOG_ADDRS</constant> is set. This ioctl will block until all +requested logical addresses have been claimed.</para> + + <table pgwide="1" frame="none" id="cec-log-addrs"> + <title>struct <structname>cec_log_addrs</structname></title> + <tgroup cols="3"> + &cs-str; + <tbody valign="top"> + <row> + <entry>__u8</entry> + <entry><structfield>cec_version</structfield></entry> + <entry>The CEC version that this adapter shall use. See + <xref linkend="cec-versions" />. + Used to implement the <constant>CEC_MSG_CEC_VERSION</constant> and + <constant>CEC_MSG_REPORT_FEATURES</constant> messages. Note that + <constant>CEC_OP_CEC_VERSION_1_3A</constant> is not allowed + by the CEC framework. + </entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>num_log_addrs</structfield></entry> + <entry>Number of logical addresses to set up. Must be ≤ + <structfield>available_log_addrs</structfield> as returned by + &CEC-ADAP-G-CAPS;. All arrays in this structure are only filled up to + index <structfield>available_log_addrs</structfield>-1. The remaining + array elements will be ignored. Note that the CEC 2.0 standard allows + for a maximum of 2 logical addresses, although some hardware has support + for more. <constant>CEC_MAX_LOG_ADDRS</constant> is 4. The driver will + return the actual number of logical addresses it could claim, which may + be less than what was requested. If this field is set to 0, then the + CEC adapter shall clear all claimed logical addresses and all other + fields will be ignored.</entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>primary_device_type</structfield>[CEC_MAX_LOG_ADDRS]</entry> + <entry>Primary device type for each logical address. See + <xref linkend="cec-prim-dev-types" /> for possible types.</entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>log_addr_type</structfield>[CEC_MAX_LOG_ADDRS]</entry> + <entry>Logical address types. See <xref linkend="cec-log-addr-types" /> for + possible types. The driver will update this with the actual logical address + type that it claimed (e.g. it may have to fallback to + <constant>CEC_LOG_ADDR_TYPE_UNREGISTERED</constant>).</entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>log_addr</structfield>[CEC_MAX_LOG_ADDRS]</entry> + <entry>The actual logical addresses that were claimed. This is set by the + driver. If no logical address could be claimed, then it is set to + <constant>CEC_LOG_ADDR_INVALID</constant>.</entry> + </row> + <row> + <entry>char</entry> + <entry><structfield>osd_name</structfield>[15]</entry> + <entry>The actual logical addresses that were claimed. This is set by the + driver. If no logical address could be claimed, then it is set to + <constant>CEC_LOG_ADDR_INVALID</constant>.</entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>all_device_types</structfield>[CEC_MAX_LOG_ADDRS]</entry> + <entry>CEC 2.0 specific: all device types. See <xref linkend="cec-all-dev-types-flags" />. + Used to implement the <constant>CEC_MSG_REPORT_FEATURES</constant> message. + This field is ignored if <structfield>cec_version</structfield> < + <constant>CEC_OP_CEC_VERSION_2_0</constant>.</entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>features</structfield>[CEC_MAX_LOG_ADDRS][12]</entry> + <entry>Features for each logical address. Used to implement the + <constant>CEC_MSG_REPORT_FEATURES</constant> message. The 12 bytes include + both the RC Profile and the Device Features. + This field is ignored if <structfield>cec_version</structfield> < + <constant>CEC_OP_CEC_VERSION_2_0</constant>.</entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>reserved</structfield>[63]</entry> + <entry>Reserved for future extensions. Drivers and applications must + set this array to zero.</entry> + </row> + </tbody> + </tgroup> + </table> + + <table pgwide="1" frame="none" id="cec-versions"> + <title>CEC Versions</title> + <tgroup cols="3"> + &cs-def; + <tbody valign="top"> + <row> + <entry><constant>CEC_OP_CEC_VERSION_1_3A</constant></entry> + <entry>4</entry> + <entry>CEC version according to the HDMI 1.3a standard.</entry> + </row> + <row> + <entry><constant>CEC_OP_CEC_VERSION_1_4B</constant></entry> + <entry>5</entry> + <entry>CEC version according to the HDMI 1.4b standard.</entry> + </row> + <row> + <entry><constant>CEC_OP_CEC_VERSION_2_0</constant></entry> + <entry>6</entry> + <entry>CEC version according to the HDMI 2.0 standard.</entry> + </row> + </tbody> + </tgroup> + </table> + + <table pgwide="1" frame="none" id="cec-prim-dev-types"> + <title>CEC Primary Device Types</title> + <tgroup cols="3"> + &cs-def; + <tbody valign="top"> + <row> + <entry><constant>CEC_OP_PRIM_DEVTYPE_TV</constant></entry> + <entry>0</entry> + <entry>Use for a TV.</entry> + </row> + <row> + <entry><constant>CEC_OP_PRIM_DEVTYPE_RECORD</constant></entry> + <entry>1</entry> + <entry>Use for a recording device.</entry> + </row> + <row> + <entry><constant>CEC_OP_PRIM_DEVTYPE_TUNER</constant></entry> + <entry>3</entry> + <entry>Use for a device with a tuner.</entry> + </row> + <row> + <entry><constant>CEC_OP_PRIM_DEVTYPE_PLAYBACK</constant></entry> + <entry>4</entry> + <entry>Use for a playback device.</entry> + </row> + <row> + <entry><constant>CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM</constant></entry> + <entry>5</entry> + <entry>Use for an audio system (e.g. an audio/video receiver).</entry> + </row> + <row> + <entry><constant>CEC_OP_PRIM_DEVTYPE_SWITCH</constant></entry> + <entry>6</entry> + <entry>Use for a CEC switch.</entry> + </row> + <row> + <entry><constant>CEC_OP_PRIM_DEVTYPE_VIDEOPROC</constant></entry> + <entry>7</entry> + <entry>Use for a video processor device.</entry> + </row> + </tbody> + </tgroup> + </table> + + <table pgwide="1" frame="none" id="cec-log-addr-types"> + <title>CEC Logical Address Types</title> + <tgroup cols="3"> + &cs-def; + <tbody valign="top"> + <row> + <entry><constant>CEC_LOG_ADDR_TYPE_TV</constant></entry> + <entry>0</entry> + <entry>Use for a TV.</entry> + </row> + <row> + <entry><constant>CEC_LOG_ADDR_TYPE_RECORD</constant></entry> + <entry>1</entry> + <entry>Use for a recording device.</entry> + </row> + <row> + <entry><constant>CEC_LOG_ADDR_TYPE_TUNER</constant></entry> + <entry>2</entry> + <entry>Use for a tuner device.</entry> + </row> + <row> + <entry><constant>CEC_LOG_ADDR_TYPE_PLAYBACK</constant></entry> + <entry>3</entry> + <entry>Use for a playback device.</entry> + </row> + <row> + <entry><constant>CEC_LOG_ADDR_TYPE_AUDIOSYSTEM</constant></entry> + <entry>4</entry> + <entry>Use for an audio system device.</entry> + </row> + <row> + <entry><constant>CEC_LOG_ADDR_TYPE_SPECIFIC</constant></entry> + <entry>5</entry> + <entry>Use for a second TV or for a video processor device.</entry> + </row> + <row> + <entry><constant>CEC_LOG_ADDR_TYPE_UNREGISTERED</constant></entry> + <entry>6</entry> + <entry>Fallback if all relevant logical addresses are claimed, or for + pure CEC switches or CDC-only devices (CDC: Capability Discovery and Control).</entry> + </row> + </tbody> + </tgroup> + </table> + + <table pgwide="1" frame="none" id="cec-all-dev-types-flags"> + <title>CEC All Device Types Flags</title> + <tgroup cols="3"> + &cs-def; + <tbody valign="top"> + <row> + <entry><constant>CEC_OP_ALL_DEVTYPE_TV</constant></entry> + <entry>0x80</entry> + <entry>This supports the TV type.</entry> + </row> + <row> + <entry><constant>CEC_OP_ALL_DEVTYPE_RECORD</constant></entry> + <entry>0x40</entry> + <entry>This supports the Recording type.</entry> + </row> + <row> + <entry><constant>CEC_OP_ALL_DEVTYPE_TUNER</constant></entry> + <entry>0x20</entry> + <entry>This supports the Tuner type.</entry> + </row> + <row> + <entry><constant>CEC_OP_ALL_DEVTYPE_PLAYBACK</constant></entry> + <entry>0x10</entry> + <entry>This supports the Playback type.</entry> + </row> + <row> + <entry><constant>CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM</constant></entry> + <entry>0x08</entry> + <entry>This supports the Audio System type.</entry> + </row> + <row> + <entry><constant>CEC_OP_ALL_DEVTYPE_SWITCH</constant></entry> + <entry>0x04</entry> + <entry>This supports the CEC Switch or Video Processing type.</entry> + </row> + </tbody> + </tgroup> + </table> + </refsect1> + + <refsect1> + &return-value; + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-ioc-adap-g-phys-addr.xml b/Documentation/DocBook/media/v4l/cec-ioc-adap-g-phys-addr.xml new file mode 100644 index 0000000..4d2827c --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-ioc-adap-g-phys-addr.xml @@ -0,0 +1,78 @@ +<refentry id="cec-ioc-adap-g-phys-addr"> + <refmeta> + <refentrytitle>ioctl CEC_ADAP_G_PHYS_ADDR, CEC_ADAP_S_PHYS_ADDR</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>CEC_ADAP_G_PHYS_ADDR</refname> + <refname>CEC_ADAP_S_PHYS_ADDR</refname> + <refpurpose>Get or set the physical address</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcprototype> + <funcdef>int <function>ioctl</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>int <parameter>request</parameter></paramdef> + <paramdef>__u16 *<parameter>argp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>File descriptor returned by + <link linkend='cec-func-open'><function>open()</function></link>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>request</parameter></term> + <listitem> + <para>CEC_ADAP_G_PHYS_ADDR, CEC_ADAP_S_PHYS_ADDR</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>argp</parameter></term> + <listitem> + <para></para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + + <para>To query the current physical address applications call the +<constant>CEC_ADAP_G_PHYS_ADDR</constant> ioctl with a pointer to an __u16 +where the driver stores the physical address.</para> + + <para>To set a new physical address applications store the physical address in +an __u16 and call the <constant>CEC_ADAP_S_PHYS_ADDR</constant> ioctl with a +pointer to this integer. <constant>CEC_ADAP_S_PHYS_ADDR</constant> is only +available if <constant>CEC_CAP_PHYS_ADDR</constant> is set.</para> + + <para>The physical address is a 16-bit number where each group of 4 bits +represent a digit of the physical address a.b.c.d where the most significant +4 bits represent 'a'. The CEC root device (usually the TV) has address 0.0.0.0. +Every device that is hooked up to an input of the TV has address a.0.0.0 (where +'a' is ≥ 1), devices hooked up to those in turn have addresses a.b.0.0, etc. +So a topology of up to 5 devices deep is supported. The physical address a +device shall use is stored in the EDID of the sink.</para> + +<para>For example, the EDID for each HDMI input of the TV will have a different +physical address of the form a.0.0.0 that the sources will read out and use as +their physical address.</para> + </refsect1> + + <refsect1> + &return-value; + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-ioc-adap-g-state.xml b/Documentation/DocBook/media/v4l/cec-ioc-adap-g-state.xml new file mode 100644 index 0000000..19d1d45 --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-ioc-adap-g-state.xml @@ -0,0 +1,87 @@ +<refentry id="cec-ioc-adap-g-state"> + <refmeta> + <refentrytitle>ioctl CEC_ADAP_G_STATE, CEC_ADAP_S_STATE</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>CEC_ADAP_G_STATE</refname> + <refname>CEC_ADAP_S_STATE</refname> + <refpurpose>Get or set the adapter state</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcprototype> + <funcdef>int <function>ioctl</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>int <parameter>request</parameter></paramdef> + <paramdef>__u32 *<parameter>argp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>File descriptor returned by + <link linkend='cec-func-open'><function>open()</function></link>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>request</parameter></term> + <listitem> + <para>CEC_ADAP_G_STATE, CEC_ADAP_S_STATE</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>argp</parameter></term> + <listitem> + <para></para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + + <para>To query the current adapter state applications call the +<constant>CEC_ADAP_G_STATE</constant> ioctl with a pointer to an __u32 +where the driver stores the state.</para> + + <para>To set the adapter state applications store the CEC adapter state +in an __u32 and call the <constant>CEC_ADAP_S_STATE</constant> ioctl with a +pointer to this integer. <constant>CEC_ADAP_S_STATE</constant> is only +available if <constant>CEC_CAP_STATE</constant> is set.</para> + + <para>Available states are:</para> + + <table pgwide="1" frame="none" id="cec-adap-states"> + <title>Adapter States</title> + <tgroup cols="3"> + &cs-def; + <tbody valign="top"> + <row> + <entry><constant>CEC_ADAP_DISABLED</constant></entry> + <entry>0</entry> + <entry>The adapter is disabled.</entry> + </row> + <row> + <entry><constant>CEC_ADAP_ENABLED</constant></entry> + <entry>1</entry> + <entry>The adapter is enabled.</entry> + </row> + </tbody> + </tgroup> + </table> + </refsect1> + + <refsect1> + &return-value; + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-ioc-adap-g-vendor-id.xml b/Documentation/DocBook/media/v4l/cec-ioc-adap-g-vendor-id.xml new file mode 100644 index 0000000..0bf7731 --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-ioc-adap-g-vendor-id.xml @@ -0,0 +1,70 @@ +<refentry id="cec-ioc-adap-g-vendor-id"> + <refmeta> + <refentrytitle>ioctl CEC_ADAP_G_VENDOR_ID, CEC_ADAP_S_VENDOR_ID</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>CEC_ADAP_G_VENDOR_ID</refname> + <refname>CEC_ADAP_S_VENDOR_ID</refname> + <refpurpose>Get or set adapter vendor ID</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcprototype> + <funcdef>int <function>ioctl</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>int <parameter>request</parameter></paramdef> + <paramdef>__u32 *<parameter>argp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>File descriptor returned by + <link linkend='cec-func-open'><function>open()</function></link>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>request</parameter></term> + <listitem> + <para>CEC_ADAP_G_VENDOR_ID, CEC_ADAP_S_VENDOR_ID</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>argp</parameter></term> + <listitem> + <para></para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + + <para>To query the current CEC adapter vendor ID applications call the +<constant>CEC_ADAP_G_VENDOR_ID</constant> ioctl with a pointer to an __u32 +where the driver stores the vendor ID.</para> + + <para>To set a new CEC adapter vendor ID applications store the vendor ID in +an __u32 and call the <constant>CEC_ADAP_S_VENDOR_ID</constant> ioctl with a +pointer to this integer. <constant>CEC_ADAP_S_VENDOR_ID</constant> is only +available if <constant>CEC_CAP_VENDOR_ID</constant> is set.</para> + + <para>The vendor ID is a 24-bit number that identifies the specific +vendor or entity. Based on this ID vendor specific commands may be +defined.</para> + </refsect1> + + <refsect1> + &return-value; + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-ioc-dqevent.xml b/Documentation/DocBook/media/v4l/cec-ioc-dqevent.xml new file mode 100644 index 0000000..cbde320 --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-ioc-dqevent.xml @@ -0,0 +1,125 @@ +<refentry id="cec-ioc-g-event"> + <refmeta> + <refentrytitle>ioctl CEC_G_EVENT</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>CEC_G_EVENT</refname> + <refpurpose>Get a CEC event</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcprototype> + <funcdef>int <function>ioctl</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>int <parameter>request</parameter></paramdef> + <paramdef>struct cec_event *<parameter>argp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>File descriptor returned by + <link linkend='cec-func-open'><function>open()</function></link>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>request</parameter></term> + <listitem> + <para>CEC_G_EVENT</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>argp</parameter></term> + <listitem> + <para></para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + + <para>CEC devices can send asynchronous events. These can be retrieved by calling + the <constant>CEC_G_EVENT</constant> ioctl. If the file descriptor is in non-blocking + mode and no event is pending, then it will return -1 and set errno to the &EAGAIN;.</para> + + <para>There can be up to 40 events queued up. If more events are added, then the oldest event will be discarded.</para> + + <table pgwide="1" frame="none" id="cec-event"> + <title>struct <structname>cec_event</structname></title> + <tgroup cols="3"> + &cs-str; + <tbody valign="top"> + <row> + <entry>__u64</entry> + <entry><structfield>ts</structfield></entry> + <entry>Timestamp of the event in ns.</entry> + </row> + <row> + <entry>__u32</entry> + <entry><structfield>event</structfield></entry> + <entry>The event, see <xref linkend="cec-events" />.</entry> + </row> + <row> + <entry>__u32</entry> + <entry><structfield>sequence</structfield></entry> + <entry>For the <constant>CEC_EVENT_GOT_REPLY</constant> event this + holds the sequence number of message that requested the reply. + </entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>reserved</structfield>[8]</entry> + <entry>Reserved for future extensions. Drivers must + set this array to zero.</entry> + </row> + </tbody> + </tgroup> + </table> + + <table pgwide="1" frame="none" id="cec-events"> + <title>CEC Events</title> + <tgroup cols="3"> + &cs-def; + <tbody valign="top"> + <row> + <entry><constant>CEC_EVENT_CONNECT</constant></entry> + <entry>1</entry> + <entry>Generated when the HDMI cable is connected.</entry> + </row> + <row> + <entry><constant>CEC_EVENT_READY</constant></entry> + <entry>2</entry> + <entry>Generated when all logical addresses are claimed.</entry> + </row> + <row> + <entry><constant>CEC_EVENT_DISCONNECT</constant></entry> + <entry>3</entry> + <entry>Generated when the HDMI cable is disconnected.</entry> + </row> + <row> + <entry><constant>CEC_EVENT_GOT_REPLY</constant></entry> + <entry>4</entry> + <entry>Generated when a reply is received for a previously sent + message. Generated only if a reply was requested and only if the + message was sent in non-blocking mode.</entry> + </row> + </tbody> + </tgroup> + </table> + </refsect1> + + <refsect1> + &return-value; + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-ioc-g-monitor.xml b/Documentation/DocBook/media/v4l/cec-ioc-g-monitor.xml new file mode 100644 index 0000000..6883003 --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-ioc-g-monitor.xml @@ -0,0 +1,86 @@ +<refentry id="cec-ioc-g-monitor"> + <refmeta> + <refentrytitle>ioctl CEC_G_MONITOR, CEC_S_MONITOR</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>CEC_G_MONITOR</refname> + <refname>CEC_S_MONITOR</refname> + <refpurpose>Get or set the monitor mode</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcprototype> + <funcdef>int <function>ioctl</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>int <parameter>request</parameter></paramdef> + <paramdef>__u32 *<parameter>argp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>File descriptor returned by + <link linkend='cec-func-open'><function>open()</function></link>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>request</parameter></term> + <listitem> + <para>CEC_G_MONITOR, CEC_S_MONITOR</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>argp</parameter></term> + <listitem> + <para></para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + + <para>To query the current state of the monitor mode of the current file + descriptor applications call the <constant>CEC_G_MONITOR</constant> ioctl + with a pointer to an __u32 where the driver stores the state.</para> + + <para>To set the state of the monitor mode of the current file descriptor + applications store the monitor mode state in an __u32 and call the + <constant>CEC_S_MONITOR</constant> ioctl with a pointer to this integer.</para> + + <para>Available states are:</para> + + <table pgwide="1" frame="none" id="cec-monitor-states"> + <title>Monitor States</title> + <tgroup cols="3"> + &cs-def; + <tbody valign="top"> + <row> + <entry><constant>CEC_MONITOR_DISABLED</constant></entry> + <entry>0</entry> + <entry>The monitor mode is disabled.</entry> + </row> + <row> + <entry><constant>CEC_MONITOR_ENABLED</constant></entry> + <entry>1</entry> + <entry>The monitor mode is enabled.</entry> + </row> + </tbody> + </tgroup> + </table> + </refsect1> + + <refsect1> + &return-value; + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-ioc-g-passthrough.xml b/Documentation/DocBook/media/v4l/cec-ioc-g-passthrough.xml new file mode 100644 index 0000000..127d65e --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-ioc-g-passthrough.xml @@ -0,0 +1,81 @@ +<refentry id="cec-ioc-g-passthrough"> + <refmeta> + <refentrytitle>ioctl CEC_G_PASSTHROUGH</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>CEC_G_PASSTHROUGH</refname> + <refpurpose>Get the passthrough mode</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcprototype> + <funcdef>int <function>ioctl</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>int <parameter>request</parameter></paramdef> + <paramdef>__u32 *<parameter>argp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>File descriptor returned by + <link linkend='cec-func-open'><function>open()</function></link>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>request</parameter></term> + <listitem> + <para>CEC_G_PASSTHROUGH</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>argp</parameter></term> + <listitem> + <para></para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + + <para>To query the current state of the passthrough mode of the current file + descriptor the applications call the <constant>CEC_G_PASSTHROUGH</constant> ioctl + with a pointer to an __u32 where the driver stores the state.</para> + + <para>Available states are:</para> + + <table pgwide="1" frame="none" id="cec-passthrough-states"> + <title>Passthrough States</title> + <tgroup cols="3"> + &cs-def; + <tbody valign="top"> + <row> + <entry><constant>CEC_PASSTHROUGH_DISABLED</constant></entry> + <entry>0</entry> + <entry>The passthrough mode is disabled.</entry> + </row> + <row> + <entry><constant>CEC_PASSTHROUGH_ENABLED</constant></entry> + <entry>1</entry> + <entry>The passthrough is enabled.</entry> + </row> + </tbody> + </tgroup> + </table> + </refsect1> + + <refsect1> + &return-value; + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media/v4l/cec-ioc-receive.xml b/Documentation/DocBook/media/v4l/cec-ioc-receive.xml new file mode 100644 index 0000000..4b793df --- /dev/null +++ b/Documentation/DocBook/media/v4l/cec-ioc-receive.xml @@ -0,0 +1,185 @@ +<refentry id="cec-ioc-receive"> + <refmeta> + <refentrytitle>ioctl CEC_RECEIVE, CEC_TRANSMIT</refentrytitle> + &manvol; + </refmeta> + + <refnamediv> + <refname>CEC_RECEIVE</refname> + <refname>CEC_TRANSMIT</refname> + <refpurpose>Receive or transmit a CEC message</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcprototype> + <funcdef>int <function>ioctl</function></funcdef> + <paramdef>int <parameter>fd</parameter></paramdef> + <paramdef>int <parameter>request</parameter></paramdef> + <paramdef>struct cec_msg *<parameter>argp</parameter></paramdef> + </funcprototype> + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Arguments</title> + + <variablelist> + <varlistentry> + <term><parameter>fd</parameter></term> + <listitem> + <para>File descriptor returned by + <link linkend='cec-func-open'><function>open()</function></link>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>request</parameter></term> + <listitem> + <para>CEC_RECEIVE, CEC_TRANSMIT</para> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>argp</parameter></term> + <listitem> + <para></para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Description</title> + + <para>To receive a CEC message the application has to fill in the + <structname>cec_msg</structname> structure and pass it to the + <constant>CEC_RECEIVE</constant> ioctl. <constant>CEC_RECEIVE</constant> is + only available if <constant>CEC_CAP_RECEIVE</constant> is set. If the + file descriptor is in non-blocking mode and there are no received + messages pending, then it will return -1 and set errno to the &EAGAIN;. + If the file descriptor is in blocking mode and <structfield>timeout</structfield> + is non-zero and no message arrived within <structfield>timeout</structfield> + milliseconds, then it will return -1 and set errno to the &ETIMEDOUT;.</para> + + <para>To send a CEC message the application has to fill in the + <structname>cec_msg</structname> structure and pass it to the + <constant>CEC_TRANSMIT</constant> ioctl. <constant>CEC_TRANSMIT</constant> is + only available if <constant>CEC_CAP_TRANSMIT</constant> is set. + If there is no more room in the transmit queue, then it will return + -1 and set errno to the &EBUSY;.</para> + + <table pgwide="1" frame="none" id="cec-msg"> + <title>struct <structname>cec_msg</structname></title> + <tgroup cols="3"> + &cs-str; + <tbody valign="top"> + <row> + <entry>__u64</entry> + <entry><structfield>ts</structfield></entry> + <entry>Timestamp of when the message was transmitted in ns in the case + of <constant>CEC_TRANSMIT</constant> with <structfield>reply</structfield> + set to 0, or the timestamp of the received message in all other cases.</entry> + </row> + <row> + <entry>__u32</entry> + <entry><structfield>len</structfield></entry> + <entry>The length of the message. For <constant>CEC_TRANSMIT</constant> this + is filled in by the application. The driver will fill this in for + <constant>CEC_RECEIVE</constant> and for <constant>CEC_TRANSMIT</constant> + it will be filled in with the length of the reply message if + <structfield>reply</structfield> was set.</entry> + </row> + <row> + <entry>__u32</entry> + <entry><structfield>status</structfield></entry> + <entry>The status of the message. When used with <constant>CEC_RECEIVE</constant> + this is always set to <constant>CEC_RX_STATUS_READY</constant>. When + used with <constant>CEC_TRANSMIT</constant> see <xref linkend="cec-tx-status" /> + for the possible status values.</entry> + </row> + <row> + <entry>__u32</entry> + <entry><structfield>timeout</structfield></entry> + <entry>The timeout in milliseconds. This is the time we wait for a message to + be received. If it is set to 0, then we wait indefinitely. + It is ignored by <constant>CEC_TRANSMIT</constant>.</entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>msg</structfield>[16]</entry> + <entry>The message payload. For <constant>CEC_TRANSMIT</constant> this + is filled in by the application. The driver will fill this in for + <constant>CEC_RECEIVE</constant> and for <constant>CEC_TRANSMIT</constant> + it will be filled in with the payload of the reply message if + <structfield>reply</structfield> was set.</entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>reply</structfield></entry> + <entry>Wait until this message is replied. If <structfield>reply</structfield> + is 0, then don't wait for a reply but return after transmitting the + message. If there was an error as indicated by a non-zero <structfield>status</structfield> + field, then <structfield>reply</structfield> is set to 0 by the driver. + Ignored by <constant>CEC_RECEIVE</constant>.</entry> + </row> + <row> + <entry>__u32</entry> + <entry><structfield>sequence</structfield></entry> + <entry>The sequence number is automatically assigned by the CEC + framework for all transmitted messages. It can be later used by the + framework to generate an event if a reply for a message was + requested and the message was transmitted in a non-blocking mode. + </entry> + </row> + <row> + <entry>__u8</entry> + <entry><structfield>reserved</structfield>[35]</entry> + <entry>Reserved for future extensions. Drivers and applications must + set this array to zero.</entry> + </row> + </tbody> + </tgroup> + </table> + + <table pgwide="1" frame="none" id="cec-tx-status"> + <title>CEC Transmit Status</title> + <tgroup cols="3"> + &cs-def; + <tbody valign="top"> + <row> + <entry><constant>CEC_TX_STATUS_OK</constant></entry> + <entry>0x00</entry> + <entry>The message was transmitted successfully.</entry> + </row> + <row> + <entry><constant>CEC_TX_STATUS_ARB_LOST</constant></entry> + <entry>0x01</entry> + <entry>CEC line arbitration was lost.</entry> + </row> + <row> + <entry><constant>CEC_TX_STATUS_RETRY_TIMEOUT</constant></entry> + <entry>0x02</entry> + <entry>The transmit timed out. The timeout period is 200 ms for a polling + message and 1 second for other messages as specified by the CEC protocol.</entry> + </row> + <row> + <entry><constant>CEC_TX_STATUS_FEATURE_ABORT</constant></entry> + <entry>0x04</entry> + <entry>The message was transmitted successfully but the reply was + <constant>CEC_MSG_FEATURE_ABORT</constant>.</entry> + </row> + <row> + <entry><constant>CEC_TX_STATUS_REPLY_TIMEOUT</constant></entry> + <entry>0x08</entry> + <entry>The message was transmitted successfully but the reply was never + received within the 1 second timeout period (this timeout value is + specified by the CEC protocol).</entry> + </row> + </tbody> + </tgroup> + </table> + </refsect1> + + <refsect1> + &return-value; + </refsect1> +</refentry> diff --git a/Documentation/DocBook/media_api.tmpl b/Documentation/DocBook/media_api.tmpl index f3f5fe5..3f76d41 100644 --- a/Documentation/DocBook/media_api.tmpl +++ b/Documentation/DocBook/media_api.tmpl @@ -38,7 +38,7 @@ <title>LINUX MEDIA INFRASTRUCTURE API</title>
<copyright> - <year>2009-2014</year> + <year>2009-2015</year> <holder>LinuxTV Developers</holder> </copyright>
@@ -75,7 +75,7 @@ </mediaobject> </figure> <para>The media infrastructure API was designed to control such - devices. It is divided into four parts.</para> + devices. It is divided into five parts.</para> <para>The first part covers radio, video capture and output, cameras, analog TV devices and codecs.</para> <para>The second part covers the @@ -87,6 +87,7 @@ <xref linkend="fe-delivery-system-t" />.</para> <para>The third part covers the Remote Controller API.</para> <para>The fourth part covers the Media Controller API.</para> + <para>The fifth part covers the CEC (Consumer Electronics Control) API.</para> <para>It should also be noted that a media device may also have audio components, like mixers, PCM capture, PCM playback, etc, which are controlled via ALSA API.</para> @@ -107,6 +108,9 @@ <part id="media_common"> &sub-media-controller; </part> +<part id="cec"> +&sub-cec-api; +</part>
<chapter id="gen_errors"> &sub-gen-errors;
Add CEC callbacks to the v4l2_subdev_video_ops. These are the low-level CEC ops that subdevs that support CEC have to implement.
Signed-off-by: Hans Verkuil hansverk@cisco.com [k.debski@samsung.com: Merged changes from CEC Updates commit by Hans Verkuil] Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- include/media/v4l2-subdev.h | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index 370fc38..1b04d94 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -42,6 +42,10 @@
#define V4L2_DEVICE_NOTIFY_EVENT _IOW('v', 2, struct v4l2_event)
+#define V4L2_SUBDEV_CEC_TX_DONE _IOW('v', 3, u32) +#define V4L2_SUBDEV_CEC_RX_MSG _IOW('v', 4, struct cec_msg) +#define V4L2_SUBDEV_CEC_CONN_INPUTS _IOW('v', 5, u16) + struct v4l2_device; struct v4l2_ctrl_handler; struct v4l2_event; @@ -51,6 +55,7 @@ struct v4l2_subdev; struct v4l2_subdev_fh; struct tuner_setup; struct v4l2_mbus_frame_desc; +struct cec_msg;
/* decode_vbi_line */ struct v4l2_decode_vbi_line { @@ -339,6 +344,10 @@ struct v4l2_subdev_video_ops { const struct v4l2_mbus_config *cfg); int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf, unsigned int *size); + unsigned (*cec_available_log_addrs)(struct v4l2_subdev *sd); + int (*cec_enable)(struct v4l2_subdev *sd, bool enable); + int (*cec_log_addr)(struct v4l2_subdev *sd, u8 logical_addr); + int (*cec_transmit)(struct v4l2_subdev *sd, u32 timeout_ms, struct cec_msg *msg); };
/*
From: Hans Verkuil hverkuil@xs4all.nl
Add CEC support to the adv7604 driver.
Signed-off-by: Hans Verkuil hansverk@cisco.com [k.debski@samsung.com: Merged changes from CEC Updates commit by Hans Verkuil] [k.debski@samsung.com: add missing methods cec/io_write_and_or] [k.debski@samsung.com: change adv7604 to adv76xx in added functions] [hansverk@cisco.com: use _clr_set instead of _and_or] Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- drivers/media/i2c/adv7604.c | 236 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 232 insertions(+), 4 deletions(-)
diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index 5631ec0..20b0838 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -40,6 +40,7 @@ #include <linux/regmap.h>
#include <media/adv7604.h> +#include <media/cec.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> #include <media/v4l2-event.h> @@ -80,6 +81,8 @@ MODULE_LICENSE("GPL");
#define ADV76XX_OP_SWAP_CB_CR (1 << 0)
+#define ADV76XX_MAX_ADDRS (3) + enum adv76xx_type { ADV7604, ADV7611, @@ -184,6 +187,10 @@ struct adv76xx_state { u16 spa_port_a[2]; struct v4l2_fract aspect_ratio; u32 rgb_quantization_range; + u8 cec_addr[ADV76XX_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + struct workqueue_struct *work_queues; struct delayed_work delayed_work_enable_hotplug; bool restart_stdi_once; @@ -430,7 +437,8 @@ static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val) return regmap_write(state->regmap[ADV76XX_PAGE_IO], reg, val); }
-static inline int io_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +static inline int io_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) { return io_write(sd, reg, (io_read(sd, reg) & ~mask) | val); } @@ -463,6 +471,12 @@ static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) return regmap_write(state->regmap[ADV76XX_PAGE_CEC], reg, val); }
+static inline int cec_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return cec_write(sd, reg, (cec_read(sd, reg) & ~mask) | val); +} + static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) { struct adv76xx_state *state = to_state(sd); @@ -550,8 +564,13 @@ static inline int edid_write_block(struct v4l2_subdev *sd,
static void adv76xx_set_hpd(struct adv76xx_state *state, unsigned int hpd) { + u16 connected_inputs; unsigned int i;
+ connected_inputs = hpd & state->info->read_cable_det(&state->sd); + v4l2_subdev_notify(&state->sd, V4L2_SUBDEV_CEC_CONN_INPUTS, + &connected_inputs); + for (i = 0; i < state->info->num_dv_ports; ++i) gpiod_set_value_cansleep(state->hpd_gpio[i], hpd & BIT(i));
@@ -891,9 +910,12 @@ static int adv76xx_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) { struct adv76xx_state *state = to_state(sd); const struct adv76xx_chip_info *info = state->info; + u16 cable_det = info->read_cable_det(sd); + u16 connected_inputs = state->edid.present & cable_det; + + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_CONN_INPUTS, &connected_inputs);
- return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, - info->read_cable_det(sd)); + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, cable_det); }
static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, @@ -1914,6 +1936,186 @@ static int adv76xx_set_format(struct v4l2_subdev *sd, return 0; }
+static void adv76xx_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + if ((cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x02) { + v4l2_dbg(1, debug, sd, "%s: tx raw: arbitration lost\n", + __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_ARB_LOST); + return; + } + if (tx_raw_status & 0x04) { + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_RETRY_TIMEOUT); + return; + } + if (tx_raw_status & 0x01) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_OK); + return; + } +} + +static void adv76xx_cec_isr(struct v4l2_subdev *sd, bool *handled) +{ + u8 cec_irq; + + /* cec controller */ + cec_irq = io_read(sd, 0x4d) & 0x0f; + if (!cec_irq) + return; + + v4l2_dbg(1, debug, sd, "%s: cec: irq 0x%x\n", __func__, cec_irq); + adv76xx_cec_tx_raw_status(sd, cec_irq); + if (cec_irq & 0x08) { + struct cec_msg msg; + + msg.len = cec_read(sd, 0x25) & 0x1f; + if (msg.len > 16) + msg.len = 16; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = cec_read(sd, i + 0x15); + cec_write(sd, 0x26, 0x01); /* re-enable rx */ + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_RX_MSG, &msg); + } + } + + /* note: the bit order is swapped between 0x4d and 0x4e */ + cec_irq = ((cec_irq & 0x08) >> 3) | ((cec_irq & 0x04) >> 1) | + ((cec_irq & 0x02) << 1) | ((cec_irq & 0x01) << 3); + io_write(sd, 0x4e, cec_irq); + + if (handled) + *handled = true; +} + +static unsigned adv76xx_cec_available_log_addrs(struct v4l2_subdev *sd) +{ + return ADV76XX_MAX_ADDRS; +} + +static int adv76xx_cec_enable(struct v4l2_subdev *sd, bool enable) +{ + struct adv76xx_state *state = to_state(sd); + + if (!state->cec_enabled_adap && enable) { + cec_write_clr_set(sd, 0x2a, 0x01, 0x01); /* power up cec */ + cec_write(sd, 0x2c, 0x01); /* cec soft reset */ + cec_write_clr_set(sd, 0x11, 0x01, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready */ + io_write_clr_set(sd, 0x50, 0x0f, 0x0f); + cec_write(sd, 0x26, 0x01); /* enable rx */ + } else if (state->cec_enabled_adap && !enable) { + /* disable cec interrupts */ + io_write_clr_set(sd, 0x50, 0x0f, 0x00); + /* disable address mask 1-3 */ + cec_write_clr_set(sd, 0x27, 0x70, 0x00); + /* power down cec section */ + cec_write_clr_set(sd, 0x2a, 0x01, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + adv76xx_s_detect_tx_5v_ctrl(sd); + return 0; +} + +static int adv76xx_cec_log_addr(struct v4l2_subdev *sd, u8 addr) +{ + struct adv76xx_state *state = to_state(sd); + unsigned i, free_idx = ADV76XX_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + cec_write_clr_set(sd, 0x27, 0x70, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV76XX_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV76XX_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV76XX_MAX_ADDRS) { + i = free_idx; + if (i == ADV76XX_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + cec_write_clr_set(sd, 0x27, 0x10, 0x10); + /* set address for mask 0 */ + cec_write_clr_set(sd, 0x28, 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + cec_write_clr_set(sd, 0x27, 0x20, 0x20); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x28, 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + cec_write_clr_set(sd, 0x27, 0x40, 0x40); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x29, 0x0f, addr); + break; + } + return 0; +} + +static int adv76xx_cec_transmit(struct v4l2_subdev *sd, u32 timeout_ms, struct cec_msg *msg) +{ + u8 len = msg->len; + unsigned i; + + if (len == 1) + /* allow for one retry for polling */ + cec_write_clr_set(sd, 0x12, 0x07, 1); + else + /* allow for three retries */ + cec_write_clr_set(sd, 0x12, 0x07, 3); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* write data */ + for (i = 0; i < len; i++) + cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + cec_write(sd, 0x11, 0x01); + return 0; +} + static int adv76xx_isr(struct v4l2_subdev *sd, u32 status, bool *handled) { struct adv76xx_state *state = to_state(sd); @@ -1959,6 +2161,9 @@ static int adv76xx_isr(struct v4l2_subdev *sd, u32 status, bool *handled) *handled = true; }
+ /* cec */ + adv76xx_cec_isr(sd, handled); + /* tx 5v detect */ tx_5v = io_read(sd, 0x70) & info->cable_det_mask; if (tx_5v) { @@ -2266,8 +2471,19 @@ static int adv76xx_log_status(struct v4l2_subdev *sd) ((edid_enabled & 0x02) ? "Yes" : "No"), ((edid_enabled & 0x04) ? "Yes" : "No"), ((edid_enabled & 0x08) ? "Yes" : "No")); - v4l2_info(sd, "CEC: %s\n", !!(cec_read(sd, 0x2a) & 0x01) ? + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? "enabled" : "disabled"); + if (state->cec_enabled_adap) { + int i; + + for (i = 0; i < ADV76XX_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + }
v4l2_info(sd, "-----Signal status-----\n"); cable_det = info->read_cable_det(sd); @@ -2400,6 +2616,10 @@ static const struct v4l2_subdev_video_ops adv76xx_video_ops = { .s_dv_timings = adv76xx_s_dv_timings, .g_dv_timings = adv76xx_g_dv_timings, .query_dv_timings = adv76xx_query_dv_timings, + .cec_available_log_addrs = adv76xx_cec_available_log_addrs, + .cec_enable = adv76xx_cec_enable, + .cec_log_addr = adv76xx_cec_log_addr, + .cec_transmit = adv76xx_cec_transmit, };
static const struct v4l2_subdev_pad_ops adv76xx_pad_ops = { @@ -3249,6 +3469,14 @@ static int adv76xx_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct adv76xx_state *state = to_state(sd);
+ /* disable interrupts */ + io_write(sd, 0x40, 0); + io_write(sd, 0x41, 0); + io_write(sd, 0x46, 0); + io_write(sd, 0x6e, 0); + io_write(sd, 0x73, 0); + adv76xx_cec_enable(sd, false); + cancel_delayed_work(&state->delayed_work_enable_hotplug); destroy_workqueue(state->work_queues); v4l2_async_unregister_subdev(sd);
Add CEC support to the adv7842 driver.
Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- drivers/media/i2c/adv7842.c | 250 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 233 insertions(+), 17 deletions(-)
diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c index b7269b8..73c67c4 100644 --- a/drivers/media/i2c/adv7842.c +++ b/drivers/media/i2c/adv7842.c @@ -39,6 +39,7 @@ #include <linux/workqueue.h> #include <linux/v4l2-dv-timings.h> #include <linux/hdmi.h> +#include <media/cec.h> #include <media/v4l2-device.h> #include <media/v4l2-event.h> #include <media/v4l2-ctrls.h> @@ -79,6 +80,8 @@ MODULE_LICENSE("GPL");
#define ADV7842_OP_SWAP_CB_CR (1 << 0)
+#define ADV7842_MAX_ADDRS (3) + /* ********************************************************************** * @@ -142,6 +145,10 @@ struct adv7842_state { struct v4l2_ctrl *free_run_color_ctrl_manual; struct v4l2_ctrl *free_run_color_ctrl; struct v4l2_ctrl *rgb_quantization_range_ctrl; + + u8 cec_addr[ADV7842_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; };
/* Unsupported timings. This device cannot support 720p30. */ @@ -418,9 +425,9 @@ static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) return adv_smbus_write_byte_data(state->i2c_cec, reg, val); }
-static inline int cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +static inline int cec_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) { - return cec_write(sd, reg, (cec_read(sd, reg) & mask) | val); + return cec_write(sd, reg, (cec_read(sd, reg) & ~mask) | val); }
static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) @@ -696,6 +703,18 @@ adv7842_get_dv_timings_cap(struct v4l2_subdev *sd)
/* ----------------------------------------------------------------------- */
+static u16 adv7842_read_cable_det(struct v4l2_subdev *sd) +{ + u8 reg = io_read(sd, 0x6f); + u16 val = 0; + + if (reg & 0x02) + val |= 1; /* port A */ + if (reg & 0x01) + val |= 2; /* port B */ + return val; +} + static void adv7842_delayed_work_enable_hotplug(struct work_struct *work) { struct delayed_work *dwork = to_delayed_work(work); @@ -703,8 +722,13 @@ static void adv7842_delayed_work_enable_hotplug(struct work_struct *work) struct adv7842_state, delayed_work_enable_hotplug); struct v4l2_subdev *sd = &state->sd; int present = state->hdmi_edid.present; + u16 connected_inputs; u8 mask = 0;
+ connected_inputs = present & adv7842_read_cable_det(sd); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_CONN_INPUTS, + &connected_inputs); + v4l2_dbg(2, debug, sd, "%s: enable hotplug on ports: 0x%x\n", __func__, present);
@@ -983,20 +1007,16 @@ static int adv7842_s_register(struct v4l2_subdev *sd, static int adv7842_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) { struct adv7842_state *state = to_state(sd); - int prev = v4l2_ctrl_g_ctrl(state->detect_tx_5v_ctrl); - u8 reg_io_6f = io_read(sd, 0x6f); - int val = 0; + u16 cable_det = adv7842_read_cable_det(sd); + u16 connected_inputs;
- if (reg_io_6f & 0x02) - val |= 1; /* port A */ - if (reg_io_6f & 0x01) - val |= 2; /* port B */ + v4l2_dbg(1, debug, sd, "%s: 0x%x\n", __func__, cable_det);
- v4l2_dbg(1, debug, sd, "%s: 0x%x -> 0x%x\n", __func__, prev, val); + connected_inputs = state->hdmi_edid.present & cable_det; + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_CONN_INPUTS, + &connected_inputs);
- if (val != prev) - return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, val); - return 0; + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, cable_det); }
static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, @@ -2157,6 +2177,182 @@ static void adv7842_irq_enable(struct v4l2_subdev *sd, bool enable) } }
+static void adv7842_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + if ((cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x02) { + v4l2_dbg(1, debug, sd, "%s: tx raw: arbitration lost\n", + __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_ARB_LOST); + return; + } + if (tx_raw_status & 0x04) { + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_RETRY_TIMEOUT); + return; + } + if (tx_raw_status & 0x01) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_OK); + return; + } +} + +static void adv7842_cec_isr(struct v4l2_subdev *sd, bool *handled) +{ + u8 cec_irq; + + /* cec controller */ + cec_irq = io_read(sd, 0x93) & 0x0f; + if (!cec_irq) + return; + + v4l2_dbg(1, debug, sd, "%s: cec: irq 0x%x\n", __func__, cec_irq); + adv7842_cec_tx_raw_status(sd, cec_irq); + if (cec_irq & 0x08) { + struct cec_msg msg; + + msg.len = cec_read(sd, 0x25) & 0x1f; + if (msg.len > 16) + msg.len = 16; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = cec_read(sd, i + 0x15); + cec_write(sd, 0x26, 0x01); /* re-enable rx */ + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_RX_MSG, &msg); + } + } + + io_write(sd, 0x94, cec_irq); + + if (handled) + *handled = true; +} + +static unsigned adv7842_cec_available_log_addrs(struct v4l2_subdev *sd) +{ + return ADV7842_MAX_ADDRS; +} + +static int adv7842_cec_enable(struct v4l2_subdev *sd, bool enable) +{ + struct adv7842_state *state = to_state(sd); + + if (!state->cec_enabled_adap && enable) { + cec_write_clr_set(sd, 0x2a, 0x01, 0x01); /* power up cec */ + cec_write(sd, 0x2c, 0x01); /* cec soft reset */ + cec_write_clr_set(sd, 0x11, 0x01, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready */ + io_write_clr_set(sd, 0x96, 0x0f, 0x0f); + cec_write(sd, 0x26, 0x01); /* enable rx */ + } else if (state->cec_enabled_adap && !enable) { + /* disable cec interrupts */ + io_write_clr_set(sd, 0x96, 0x0f, 0x00); + /* disable address mask 1-3 */ + cec_write_clr_set(sd, 0x27, 0x70, 0x00); + /* power down cec section */ + cec_write_clr_set(sd, 0x2a, 0x01, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + return 0; +} + +static int adv7842_cec_log_addr(struct v4l2_subdev *sd, u8 addr) +{ + struct adv7842_state *state = to_state(sd); + unsigned i, free_idx = ADV7842_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + cec_write_clr_set(sd, 0x27, 0x70, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7842_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7842_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV7842_MAX_ADDRS) { + i = free_idx; + if (i == ADV7842_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + cec_write_clr_set(sd, 0x27, 0x10, 0x10); + /* set address for mask 0 */ + cec_write_clr_set(sd, 0x28, 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + cec_write_clr_set(sd, 0x27, 0x20, 0x20); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x28, 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + cec_write_clr_set(sd, 0x27, 0x40, 0x40); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x29, 0x0f, addr); + break; + } + return 0; +} + +static int adv7842_cec_transmit(struct v4l2_subdev *sd, u32 timeout_ms, struct cec_msg *msg) +{ + u8 len = msg->len; + unsigned i; + + if (len == 1) + /* allow for one retry for polling */ + cec_write_clr_set(sd, 0x12, 0x07, 1); + else + /* allow for three retries */ + cec_write_clr_set(sd, 0x12, 0x07, 3); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* write data */ + for (i = 0; i < len; i++) + cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + cec_write(sd, 0x11, 0x01); + return 0; +} + static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled) { struct adv7842_state *state = to_state(sd); @@ -2228,6 +2424,9 @@ static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled) *handled = true; }
+ /* cec */ + adv7842_cec_isr(sd, handled); + /* tx 5v detect */ if (irq_status[2] & 0x3) { v4l2_dbg(1, debug, sd, "%s: irq tx_5v\n", __func__); @@ -2308,10 +2507,12 @@ static int adv7842_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *e) case ADV7842_EDID_PORT_A: case ADV7842_EDID_PORT_B: memset(&state->hdmi_edid.edid, 0, 256); - if (e->blocks) + if (e->blocks) { state->hdmi_edid.present |= 0x04 << e->pad; - else + } else { state->hdmi_edid.present &= ~(0x04 << e->pad); + adv7842_s_detect_tx_5v_ctrl(sd); + } memcpy(&state->hdmi_edid.edid, e->edid, 128 * e->blocks); err = edid_write_hdmi_segment(sd, e->pad); break; @@ -2496,8 +2697,19 @@ static int adv7842_cp_log_status(struct v4l2_subdev *sd) v4l2_info(sd, "HPD A %s, B %s\n", reg_io_0x21 & 0x02 ? "enabled" : "disabled", reg_io_0x21 & 0x01 ? "enabled" : "disabled"); - v4l2_info(sd, "CEC %s\n", !!(cec_read(sd, 0x2a) & 0x01) ? + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? "enabled" : "disabled"); + if (state->cec_enabled_adap) { + int i; + + for (i = 0; i < ADV7842_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + }
v4l2_info(sd, "-----Signal status-----\n"); if (state->hdmi_port_a) { @@ -3045,6 +3257,10 @@ static const struct v4l2_subdev_video_ops adv7842_video_ops = { .s_dv_timings = adv7842_s_dv_timings, .g_dv_timings = adv7842_g_dv_timings, .query_dv_timings = adv7842_query_dv_timings, + .cec_available_log_addrs = adv7842_cec_available_log_addrs, + .cec_enable = adv7842_cec_enable, + .cec_log_addr = adv7842_cec_log_addr, + .cec_transmit = adv7842_cec_transmit, };
static const struct v4l2_subdev_pad_ops adv7842_pad_ops = { @@ -3341,7 +3557,7 @@ static int adv7842_remove(struct i2c_client *client) struct adv7842_state *state = to_state(sd);
adv7842_irq_enable(sd, false); - + adv7842_cec_enable(sd, false); cancel_delayed_work(&state->delayed_work_enable_hotplug); destroy_workqueue(state->work_queues); v4l2_device_unregister_subdev(sd);
Add CEC support to the adv7511 driver.
Signed-off-by: Hans Verkuil hansverk@cisco.com [k.debski@samsung.com: Merged changes from CEC Updates commit by Hans Verkuil] Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- drivers/media/i2c/adv7511.c | 359 +++++++++++++++++++++++++++++++++++++++++++- include/media/adv7511.h | 6 +- 2 files changed, 353 insertions(+), 12 deletions(-)
diff --git a/drivers/media/i2c/adv7511.c b/drivers/media/i2c/adv7511.c index e4900df..645d4ba 100644 --- a/drivers/media/i2c/adv7511.c +++ b/drivers/media/i2c/adv7511.c @@ -33,6 +33,7 @@ #include <media/v4l2-ctrls.h> #include <media/v4l2-dv-timings.h> #include <media/adv7511.h> +#include <media/cec.h>
static int debug; module_param(debug, int, 0644); @@ -59,6 +60,8 @@ MODULE_LICENSE("GPL v2"); #define ADV7511_MIN_PIXELCLOCK 20000000 #define ADV7511_MAX_PIXELCLOCK 225000000
+#define ADV7511_MAX_ADDRS (3) + /* ********************************************************************** * @@ -90,12 +93,19 @@ struct adv7511_state { struct v4l2_ctrl_handler hdl; int chip_revision; u8 i2c_edid_addr; - u8 i2c_cec_addr; u8 i2c_pktmem_addr; + u8 i2c_cec_addr; + + struct i2c_client *i2c_cec; + u8 cec_addr[ADV7511_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + /* Is the adv7511 powered on? */ bool power_on; /* Did we receive hotplug and rx-sense signals? */ bool have_monitor; + bool enabled_irq; /* timings from s_dv_timings */ struct v4l2_dv_timings dv_timings; u32 fmt_code; @@ -225,7 +235,7 @@ static int adv_smbus_read_i2c_block_data(struct i2c_client *client, return ret; }
-static inline void adv7511_edid_rd(struct v4l2_subdev *sd, u16 len, u8 *buf) +static void adv7511_edid_rd(struct v4l2_subdev *sd, uint16_t len, uint8_t *buf) { struct adv7511_state *state = get_adv7511_state(sd); int i; @@ -240,6 +250,34 @@ static inline void adv7511_edid_rd(struct v4l2_subdev *sd, u16 len, u8 *buf) v4l2_err(sd, "%s: i2c read error\n", __func__); }
+static inline int adv7511_cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + return i2c_smbus_read_byte_data(state->i2c_cec, reg); +} + +static int adv7511_cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7511_state *state = get_adv7511_state(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(state->i2c_cec, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "%s: I2C Write Problem\n", __func__); + return ret; +} + +static inline int adv7511_cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return adv7511_cec_write(sd, reg, (adv7511_cec_read(sd, reg) & mask) | val); +} + static int adv7511_pktmem_rd(struct v4l2_subdev *sd, u8 reg) { struct adv7511_state *state = get_adv7511_state(sd); @@ -413,16 +451,28 @@ static const struct v4l2_ctrl_ops adv7511_ctrl_ops = { #ifdef CONFIG_VIDEO_ADV_DEBUG static void adv7511_inv_register(struct v4l2_subdev *sd) { + struct adv7511_state *state = get_adv7511_state(sd); + v4l2_info(sd, "0x000-0x0ff: Main Map\n"); + if (state->i2c_cec) + v4l2_info(sd, "0x100-0x1ff: CEC Map\n"); }
static int adv7511_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) { + struct adv7511_state *state = get_adv7511_state(sd); + reg->size = 1; switch (reg->reg >> 8) { case 0: reg->val = adv7511_rd(sd, reg->reg & 0xff); break; + case 1: + if (state->i2c_cec) { + reg->val = adv7511_cec_read(sd, reg->reg & 0xff); + break; + } + /* fall through */ default: v4l2_info(sd, "Register %03llx not supported\n", reg->reg); adv7511_inv_register(sd); @@ -433,10 +483,18 @@ static int adv7511_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *
static int adv7511_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) { + struct adv7511_state *state = get_adv7511_state(sd); + switch (reg->reg >> 8) { case 0: adv7511_wr(sd, reg->reg & 0xff, reg->val & 0xff); break; + case 1: + if (state->i2c_cec) { + adv7511_cec_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + } + /* fall through */ default: v4l2_info(sd, "Register %03llx not supported\n", reg->reg); adv7511_inv_register(sd); @@ -524,6 +582,7 @@ static int adv7511_log_status(struct v4l2_subdev *sd) { struct adv7511_state *state = get_adv7511_state(sd); struct adv7511_state_edid *edid = &state->edid; + int i;
static const char * const states[] = { "in reset", @@ -593,7 +652,23 @@ static int adv7511_log_status(struct v4l2_subdev *sd) else v4l2_info(sd, "no timings set\n"); v4l2_info(sd, "i2c edid addr: 0x%x\n", state->i2c_edid_addr); + + if (state->i2c_cec == NULL) + return 0; + v4l2_info(sd, "i2c cec addr: 0x%x\n", state->i2c_cec_addr); + + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? + "enabled" : "disabled"); + if (state->cec_enabled_adap) { + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + } v4l2_info(sd, "i2c pktmem addr: 0x%x\n", state->i2c_pktmem_addr); return 0; } @@ -651,15 +726,138 @@ static int adv7511_s_power(struct v4l2_subdev *sd, int on) return true; }
+static unsigned adv7511_cec_available_log_addrs(struct v4l2_subdev *sd) +{ + return ADV7511_MAX_ADDRS; +} + +static int adv7511_cec_enable(struct v4l2_subdev *sd, bool enable) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + if (state->i2c_cec == NULL) + return -EIO; + + if (!state->cec_enabled_adap && enable) { + /* power up cec section */ + adv7511_cec_write_and_or(sd, 0x4e, 0xfc, 0x01); + /* legacy mode and clear all rx buffers */ + adv7511_cec_write(sd, 0x4a, 0x07); + adv7511_cec_write(sd, 0x4a, 0); + adv7511_cec_write_and_or(sd, 0x11, 0xfe, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready 1 */ + if (state->enabled_irq) + adv7511_wr_and_or(sd, 0x95, 0xc0, 0x39); + } else if (state->cec_enabled_adap && !enable) { + if (state->enabled_irq) + adv7511_wr_and_or(sd, 0x95, 0xc0, 0x00); + /* disable address mask 1-3 */ + adv7511_cec_write_and_or(sd, 0x4b, 0x8f, 0x00); + /* power down cec section */ + adv7511_cec_write_and_or(sd, 0x4e, 0xfc, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + return 0; +} + +static int adv7511_cec_log_addr(struct v4l2_subdev *sd, u8 addr) +{ + struct adv7511_state *state = get_adv7511_state(sd); + unsigned i, free_idx = ADV7511_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + adv7511_cec_write_and_or(sd, 0x4b, 0x8f, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7511_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV7511_MAX_ADDRS) { + i = free_idx; + if (i == ADV7511_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xef, 0x10); + /* set address for mask 0 */ + adv7511_cec_write_and_or(sd, 0x4c, 0xf0, addr); + break; + case 1: + /* enable address mask 1 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xdf, 0x20); + /* set address for mask 1 */ + adv7511_cec_write_and_or(sd, 0x4c, 0x0f, addr << 4); + break; + case 2: + /* enable address mask 2 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xbf, 0x40); + /* set address for mask 1 */ + adv7511_cec_write_and_or(sd, 0x4d, 0xf0, addr); + break; + } + return 0; +} + +static int adv7511_cec_transmit(struct v4l2_subdev *sd, u32 timeout_ms, struct cec_msg *msg) +{ + u8 len = msg->len; + unsigned i; + + v4l2_dbg(1, debug, sd, "%s: len %d\n", __func__, len); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* blocking, clear cec tx irq status */ + adv7511_wr_and_or(sd, 0x97, 0xc7, 0x38); + + /* write data */ + for (i = 0; i < len; i++) + adv7511_cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + adv7511_cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + adv7511_cec_write(sd, 0x11, 0x01); + return 0; +} + /* Enable interrupts */ static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable) { + struct adv7511_state *state = get_adv7511_state(sd); u8 irqs = MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT; u8 irqs_rd; int retries = 100;
v4l2_dbg(2, debug, sd, "%s: %s\n", __func__, enable ? "enable" : "disable");
+ if (state->enabled_irq == enable) + return; + state->enabled_irq = enable; + /* The datasheet says that the EDID ready interrupt should be disabled if there is no hotplug. */ if (!enable) @@ -667,6 +865,9 @@ static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable) else if (adv7511_have_hotplug(sd)) irqs |= MASK_ADV7511_EDID_RDY_INT;
+ if (state->cec_enabled_adap) + adv7511_wr_and_or(sd, 0x95, 0xc0, enable ? 0x39 : 0x00); + /* * This i2c write can fail (approx. 1 in 1000 writes). But it * is essential that this register is correct, so retry it @@ -685,24 +886,82 @@ static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable) v4l2_err(sd, "Could not set interrupts: hw failure?\n"); }
+static void adv_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + if ((adv7511_cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x10) { + v4l2_dbg(1, debug, sd, + "%s: tx raw: arbitration lost\n", __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_ARB_LOST); + return; + } + if (tx_raw_status & 0x08) { + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_RETRY_TIMEOUT); + return; + } + if (tx_raw_status & 0x20) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_OK); + return; + } +} + /* Interrupt handler */ static int adv7511_isr(struct v4l2_subdev *sd, u32 status, bool *handled) { u8 irq_status; + u8 cec_irq;
/* disable interrupts to prevent a race condition */ adv7511_set_isr(sd, false); irq_status = adv7511_rd(sd, 0x96); + cec_irq = adv7511_rd(sd, 0x97); /* clear detected interrupts */ adv7511_wr(sd, 0x96, irq_status); + adv7511_wr(sd, 0x97, cec_irq);
- v4l2_dbg(1, debug, sd, "%s: irq 0x%x\n", __func__, irq_status); + v4l2_dbg(1, debug, sd, "%s: irq 0x%x, cec-irq 0x%x\n", __func__, + irq_status, cec_irq);
if (irq_status & (MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT)) adv7511_check_monitor_present_status(sd); if (irq_status & MASK_ADV7511_EDID_RDY_INT) adv7511_check_edid_status(sd);
+ if (cec_irq & 0x38) + adv_cec_tx_raw_status(sd, cec_irq); + + if (cec_irq & 1) { + struct cec_msg msg; + + msg.len = adv7511_cec_read(sd, 0x25) & 0x1f; + + v4l2_dbg(1, debug, sd, "%s: cec msg len %d\n", __func__, + msg.len); + + if (msg.len > 16) + msg.len = 16; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = adv7511_cec_read(sd, i + 0x15); + + adv7511_cec_write(sd, 0x4a, 1); /* toggle to re-enable rx 1 */ + adv7511_cec_write(sd, 0x4a, 0); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_RX_MSG, &msg); + } + } + /* enable interrupts */ adv7511_set_isr(sd, true);
@@ -806,6 +1065,10 @@ static const struct v4l2_subdev_video_ops adv7511_video_ops = { .s_stream = adv7511_s_stream, .s_dv_timings = adv7511_s_dv_timings, .g_dv_timings = adv7511_g_dv_timings, + .cec_available_log_addrs = adv7511_cec_available_log_addrs, + .cec_enable = adv7511_cec_enable, + .cec_log_addr = adv7511_cec_log_addr, + .cec_transmit = adv7511_cec_transmit, };
/* ------------------------------ AUDIO OPS ------------------------------ */ @@ -1193,6 +1456,7 @@ static void adv7511_edid_handler(struct work_struct *work) /* We failed to read the EDID, so send an event for this. */ ed.present = false; ed.segment = adv7511_rd(sd, 0xc4); + ed.phys_addr = 0xffff; v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed); v4l2_dbg(1, debug, sd, "%s: no edid found\n", __func__); } @@ -1333,10 +1597,36 @@ static bool edid_verify_header(struct v4l2_subdev *sd, u32 segment) return !memcmp(data, hdmi_header, sizeof(hdmi_header)); }
+static int get_edid_spa_location(const u8 *edid) +{ + u8 d; + + if (edid[0x7e] != 1 || edid[0x80] != 0x02 || edid[0x81] != 0x03) + return -1; + + /* search Vendor Specific Data Block (tag 3) */ + d = edid[0x82] & 0x7f; + if (d > 4) { + int i = 0x84; + int end = 0x80 + d; + + do { + u8 tag = edid[i] >> 5; + u8 len = edid[i] & 0x1f; + + if (tag == 3 && len >= 5) + return i + 4; + i += len + 1; + } while (i < end); + } + return -1; +} + static bool adv7511_check_edid_status(struct v4l2_subdev *sd) { struct adv7511_state *state = get_adv7511_state(sd); u8 edidRdy = adv7511_rd(sd, 0xc5); + int offset;
v4l2_dbg(1, debug, sd, "%s: edid ready (retries: %d)\n", __func__, EDID_MAX_RETRIES - state->edid.read_retries); @@ -1382,6 +1672,12 @@ static bool adv7511_check_edid_status(struct v4l2_subdev *sd)
v4l2_dbg(1, debug, sd, "%s: edid complete with %d segment(s)\n", __func__, state->edid.segments); state->edid.complete = true; + offset = get_edid_spa_location(state->edid.data); + if (offset > 0) + ed.phys_addr = (state->edid.data[offset] << 8) | + state->edid.data[offset + 1]; + else + ed.phys_addr = 0xffff;
/* report when we have all segments but report only for segment 0 @@ -1403,11 +1699,14 @@ static void adv7511_init_setup(struct v4l2_subdev *sd) { struct adv7511_state *state = get_adv7511_state(sd); struct adv7511_state_edid *edid = &state->edid; + u32 cec_clk = state->pdata.cec_clk; + u8 ratio;
v4l2_dbg(1, debug, sd, "%s\n", __func__);
/* clear all interrupts */ adv7511_wr(sd, 0x96, 0xff); + adv7511_wr(sd, 0x97, 0xff); /* * Stop HPD from resetting a lot of registers. * It might leave the chip in a partly un-initialized state, @@ -1419,6 +1718,25 @@ static void adv7511_init_setup(struct v4l2_subdev *sd) adv7511_set_isr(sd, false); adv7511_s_stream(sd, false); adv7511_s_audio_stream(sd, false); + + if (state->i2c_cec == NULL) + return; + + v4l2_dbg(1, debug, sd, "%s: cec_clk %d\n", __func__, cec_clk); + + /* cec soft reset */ + adv7511_cec_write(sd, 0x50, 0x01); + adv7511_cec_write(sd, 0x50, 0x00); + + /* legacy mode */ + adv7511_cec_write(sd, 0x4a, 0x00); + + if (cec_clk % 750000 != 0) + v4l2_err(sd, "%s: cec_clk %d, not multiple of 750 Khz\n", + __func__, cec_clk); + + ratio = (cec_clk / 750000) - 1; + adv7511_cec_write(sd, 0x4e, ratio << 2); }
static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *id) @@ -1495,26 +1813,47 @@ static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id * chip_id[0] = adv7511_rd(sd, 0xf5); chip_id[1] = adv7511_rd(sd, 0xf6); if (chip_id[0] != 0x75 || chip_id[1] != 0x11) { - v4l2_err(sd, "chip_id != 0x7511, read 0x%02x%02x\n", chip_id[0], chip_id[1]); + v4l2_err(sd, "chip_id != 0x7511, read 0x%02x%02x\n", chip_id[0], + chip_id[1]); err = -EIO; goto err_entity; }
- state->i2c_edid = i2c_new_dummy(client->adapter, state->i2c_edid_addr >> 1); + state->i2c_edid = i2c_new_dummy(client->adapter, + state->i2c_edid_addr >> 1); if (state->i2c_edid == NULL) { v4l2_err(sd, "failed to register edid i2c client\n"); err = -ENOMEM; goto err_entity; }
+ adv7511_wr(sd, 0xe1, state->i2c_cec_addr); + if (state->pdata.cec_clk < 3000000 || + state->pdata.cec_clk > 100000000) { + v4l2_err(sd, "%s: cec_clk %u outside range, disabling cec\n", + __func__, state->pdata.cec_clk); + state->pdata.cec_clk = 0; + } + + if (state->pdata.cec_clk) { + state->i2c_cec = i2c_new_dummy(client->adapter, + state->i2c_cec_addr >> 1); + if (state->i2c_cec == NULL) { + v4l2_err(sd, "failed to register cec i2c client\n"); + goto err_unreg_edid; + } + adv7511_wr(sd, 0xe2, 0x00); /* power up cec section */ + } else { + adv7511_wr(sd, 0xe2, 0x01); /* power down cec section */ + } + state->i2c_pktmem = i2c_new_dummy(client->adapter, state->i2c_pktmem_addr >> 1); if (state->i2c_pktmem == NULL) { v4l2_err(sd, "failed to register pktmem i2c client\n"); err = -ENOMEM; - goto err_unreg_edid; + goto err_unreg_cec; }
- adv7511_wr(sd, 0xe2, 0x01); /* power down cec section */ state->work_queue = create_singlethread_workqueue(sd->name); if (state->work_queue == NULL) { v4l2_err(sd, "could not create workqueue\n"); @@ -1534,6 +1873,9 @@ static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *
err_unreg_pktmem: i2c_unregister_device(state->i2c_pktmem); +err_unreg_cec: + if (state->i2c_cec) + i2c_unregister_device(state->i2c_cec); err_unreg_edid: i2c_unregister_device(state->i2c_edid); err_entity: @@ -1555,9 +1897,12 @@ static int adv7511_remove(struct i2c_client *client) v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name, client->addr << 1, client->adapter->name);
+ adv7511_set_isr(sd, false); adv7511_init_setup(sd); cancel_delayed_work(&state->edid_handler); i2c_unregister_device(state->i2c_edid); + if (state->i2c_cec) + i2c_unregister_device(state->i2c_cec); i2c_unregister_device(state->i2c_pktmem); destroy_workqueue(state->work_queue); v4l2_device_unregister_subdev(sd); diff --git a/include/media/adv7511.h b/include/media/adv7511.h index d83b91d..61c3d71 100644 --- a/include/media/adv7511.h +++ b/include/media/adv7511.h @@ -32,11 +32,7 @@ struct adv7511_monitor_detect { struct adv7511_edid_detect { int present; int segment; -}; - -struct adv7511_cec_arg { - void *arg; - u32 f_flags; + uint16_t phys_addr; };
struct adv7511_platform_data {
From: Kamil Debski kamil@wypas.org
Add CEC interface driver present in the Samsung Exynos range of SoCs.
The following files were based on work by SangPil Moon: - exynos_hdmi_cec.h - exynos_hdmi_cecctl.c
Signed-off-by: Kamil Debski kamil@wypas.org Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- .../devicetree/bindings/media/s5p-cec.txt | 31 +++ drivers/media/platform/Kconfig | 10 + drivers/media/platform/Makefile | 1 + drivers/media/platform/s5p-cec/Makefile | 2 + drivers/media/platform/s5p-cec/exynos_hdmi_cec.h | 37 +++ .../media/platform/s5p-cec/exynos_hdmi_cecctrl.c | 208 ++++++++++++++++ drivers/media/platform/s5p-cec/regs-cec.h | 96 +++++++ drivers/media/platform/s5p-cec/s5p_cec.c | 277 +++++++++++++++++++++ drivers/media/platform/s5p-cec/s5p_cec.h | 76 ++++++ 9 files changed, 738 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/s5p-cec.txt create mode 100644 drivers/media/platform/s5p-cec/Makefile create mode 100644 drivers/media/platform/s5p-cec/exynos_hdmi_cec.h create mode 100644 drivers/media/platform/s5p-cec/exynos_hdmi_cecctrl.c create mode 100644 drivers/media/platform/s5p-cec/regs-cec.h create mode 100644 drivers/media/platform/s5p-cec/s5p_cec.c create mode 100644 drivers/media/platform/s5p-cec/s5p_cec.h
diff --git a/Documentation/devicetree/bindings/media/s5p-cec.txt b/Documentation/devicetree/bindings/media/s5p-cec.txt new file mode 100644 index 0000000..925ab4d --- /dev/null +++ b/Documentation/devicetree/bindings/media/s5p-cec.txt @@ -0,0 +1,31 @@ +* Samsung HDMI CEC driver + +The HDMI CEC module is present is Samsung SoCs and its purpose is to +handle communication between HDMI connected devices over the CEC bus. + +Required properties: + - compatible : value should be following + "samsung,s5p-cec" + + - reg : Physical base address of the IP registers and length of memory + mapped region. + + - interrupts : HDMI CEC interrupt number to the CPU. + - clocks : from common clock binding: handle to HDMI CEC clock. + - clock-names : from common clock binding: must contain "hdmicec", + corresponding to entry in the clocks property. + - samsung,syscon-phandle - phandle to the PMU system controller + +Example: + +hdmicec: cec@100B0000 { + compatible = "samsung,s5p-cec"; + reg = <0x100B0000 0x200>; + interrupts = <0 114 0>; + clocks = <&clock CLK_HDMI_CEC>; + clock-names = "hdmicec"; + samsung,syscon-phandle = <&pmu_system_controller>; + pinctrl-names = "default"; + pinctrl-0 = <&hdmi_cec>; + status = "okay"; +}; diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index dc75694..c5828de 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -157,6 +157,16 @@ config VIDEO_MEM2MEM_DEINTERLACE help Generic deinterlacing V4L2 driver.
+config VIDEO_SAMSUNG_S5P_CEC + tristate "Samsung S5P CEC driver" + depends on CEC && VIDEO_DEV && VIDEO_V4L2 && (PLAT_S5P || ARCH_EXYNOS) + default n + ---help--- + This is a driver for Samsung S5P HDMI CEC interface. It uses the + generic CEC framework interface. + CEC bus is present in the HDMI connector and enables communication + between compatible devices. + config VIDEO_SAMSUNG_S5P_G2D tristate "Samsung S5P and EXYNOS4 G2D 2d graphics accelerator driver" depends on VIDEO_DEV && VIDEO_V4L2 diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index efa0295..957af5f 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_VIDEO_MEM2MEM_DEINTERLACE) += m2m-deinterlace.o
obj-$(CONFIG_VIDEO_S3C_CAMIF) += s3c-camif/ obj-$(CONFIG_VIDEO_SAMSUNG_EXYNOS4_IS) += exynos4-is/ +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_CEC) += s5p-cec/ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_JPEG) += s5p-jpeg/ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_MFC) += s5p-mfc/ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_TV) += s5p-tv/ diff --git a/drivers/media/platform/s5p-cec/Makefile b/drivers/media/platform/s5p-cec/Makefile new file mode 100644 index 0000000..0e2cf45 --- /dev/null +++ b/drivers/media/platform/s5p-cec/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_CEC) += s5p-cec.o +s5p-cec-y += s5p_cec.o exynos_hdmi_cecctrl.o diff --git a/drivers/media/platform/s5p-cec/exynos_hdmi_cec.h b/drivers/media/platform/s5p-cec/exynos_hdmi_cec.h new file mode 100644 index 0000000..d008695 --- /dev/null +++ b/drivers/media/platform/s5p-cec/exynos_hdmi_cec.h @@ -0,0 +1,37 @@ +/* drivers/media/platform/s5p-cec/exynos_hdmi_cec.h + * + * Copyright (c) 2010, 2014 Samsung Electronics + * http://www.samsung.com/ + * + * Header file for interface of Samsung Exynos hdmi cec hardware + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _EXYNOS_HDMI_CEC_H_ +#define _EXYNOS_HDMI_CEC_H_ __FILE__ + +#include <linux/regmap.h> +#include <linux/miscdevice.h> +#include "s5p_cec.h" + +void s5p_cec_set_divider(struct s5p_cec_dev *cec); +void s5p_cec_enable_rx(struct s5p_cec_dev *cec); +void s5p_cec_mask_rx_interrupts(struct s5p_cec_dev *cec); +void s5p_cec_unmask_rx_interrupts(struct s5p_cec_dev *cec); +void s5p_cec_mask_tx_interrupts(struct s5p_cec_dev *cec); +void s5p_cec_unmask_tx_interrupts(struct s5p_cec_dev *cec); +void s5p_cec_reset(struct s5p_cec_dev *cec); +void s5p_cec_tx_reset(struct s5p_cec_dev *cec); +void s5p_cec_rx_reset(struct s5p_cec_dev *cec); +void s5p_cec_threshold(struct s5p_cec_dev *cec); +void s5p_cec_copy_packet(struct s5p_cec_dev *cec, char *data, size_t count); +void s5p_cec_set_addr(struct s5p_cec_dev *cec, u32 addr); +u32 s5p_cec_get_status(struct s5p_cec_dev *cec); +void s5p_clr_pending_tx(struct s5p_cec_dev *cec); +void s5p_clr_pending_rx(struct s5p_cec_dev *cec); +void s5p_cec_get_rx_buf(struct s5p_cec_dev *cec, u32 size, u8 *buffer); + +#endif /* _EXYNOS_HDMI_CEC_H_ */ diff --git a/drivers/media/platform/s5p-cec/exynos_hdmi_cecctrl.c b/drivers/media/platform/s5p-cec/exynos_hdmi_cecctrl.c new file mode 100644 index 0000000..134e50b --- /dev/null +++ b/drivers/media/platform/s5p-cec/exynos_hdmi_cecctrl.c @@ -0,0 +1,208 @@ +/* drivers/media/platform/s5p-cec/exynos_hdmi_cecctrl.c + * + * Copyright (c) 2009, 2014 Samsung Electronics + * http://www.samsung.com/ + * + * cec ftn file for Samsung TVOUT driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/io.h> +#include <linux/device.h> + +#include "exynos_hdmi_cec.h" +#include "regs-cec.h" + +#define S5P_HDMI_FIN 24000000 +#define CEC_DIV_RATIO 320000 + +#define CEC_MESSAGE_BROADCAST_MASK 0x0F +#define CEC_MESSAGE_BROADCAST 0x0F +#define CEC_FILTER_THRESHOLD 0x15 + +void s5p_cec_set_divider(struct s5p_cec_dev *cec) +{ + u32 div_ratio, div_val; + unsigned int reg; + + div_ratio = S5P_HDMI_FIN / CEC_DIV_RATIO - 1; + + if (regmap_read(cec->pmu, EXYNOS_HDMI_PHY_CONTROL, ®)) { + dev_err(cec->dev, "failed to read phy control\n"); + return; + } + + reg = (reg & ~(0x3FF << 16)) | (div_ratio << 16); + + if (regmap_write(cec->pmu, EXYNOS_HDMI_PHY_CONTROL, reg)) { + dev_err(cec->dev, "failed to write phy control\n"); + return; + } + + div_val = CEC_DIV_RATIO * 0.00005 - 1; + + writeb(0x0, cec->reg + S5P_CEC_DIVISOR_3); + writeb(0x0, cec->reg + S5P_CEC_DIVISOR_2); + writeb(0x0, cec->reg + S5P_CEC_DIVISOR_1); + writeb(div_val, cec->reg + S5P_CEC_DIVISOR_0); +} + +void s5p_cec_enable_rx(struct s5p_cec_dev *cec) +{ + u8 reg; + + reg = readb(cec->reg + S5P_CEC_RX_CTRL); + reg |= S5P_CEC_RX_CTRL_ENABLE; + writeb(reg, cec->reg + S5P_CEC_RX_CTRL); +} + +void s5p_cec_mask_rx_interrupts(struct s5p_cec_dev *cec) +{ + u8 reg; + + reg = readb(cec->reg + S5P_CEC_IRQ_MASK); + reg |= S5P_CEC_IRQ_RX_DONE; + reg |= S5P_CEC_IRQ_RX_ERROR; + writeb(reg, cec->reg + S5P_CEC_IRQ_MASK); +} + +void s5p_cec_unmask_rx_interrupts(struct s5p_cec_dev *cec) +{ + u8 reg; + + reg = readb(cec->reg + S5P_CEC_IRQ_MASK); + reg &= ~S5P_CEC_IRQ_RX_DONE; + reg &= ~S5P_CEC_IRQ_RX_ERROR; + writeb(reg, cec->reg + S5P_CEC_IRQ_MASK); +} + +void s5p_cec_mask_tx_interrupts(struct s5p_cec_dev *cec) +{ + u8 reg; + + reg = readb(cec->reg + S5P_CEC_IRQ_MASK); + reg |= S5P_CEC_IRQ_TX_DONE; + reg |= S5P_CEC_IRQ_TX_ERROR; + writeb(reg, cec->reg + S5P_CEC_IRQ_MASK); + +} + +void s5p_cec_unmask_tx_interrupts(struct s5p_cec_dev *cec) +{ + u8 reg; + + reg = readb(cec->reg + S5P_CEC_IRQ_MASK); + reg &= ~S5P_CEC_IRQ_TX_DONE; + reg &= ~S5P_CEC_IRQ_TX_ERROR; + writeb(reg, cec->reg + S5P_CEC_IRQ_MASK); +} + +void s5p_cec_reset(struct s5p_cec_dev *cec) +{ + u8 reg; + + writeb(S5P_CEC_RX_CTRL_RESET, cec->reg + S5P_CEC_RX_CTRL); + writeb(S5P_CEC_TX_CTRL_RESET, cec->reg + S5P_CEC_TX_CTRL); + + reg = readb(cec->reg + 0xc4); + reg &= ~0x1; + writeb(reg, cec->reg + 0xc4); +} + +void s5p_cec_tx_reset(struct s5p_cec_dev *cec) +{ + writeb(S5P_CEC_TX_CTRL_RESET, cec->reg + S5P_CEC_TX_CTRL); +} + +void s5p_cec_rx_reset(struct s5p_cec_dev *cec) +{ + u8 reg; + + writeb(S5P_CEC_RX_CTRL_RESET, cec->reg + S5P_CEC_RX_CTRL); + + reg = readb(cec->reg + 0xc4); + reg &= ~0x1; + writeb(reg, cec->reg + 0xc4); +} + +void s5p_cec_threshold(struct s5p_cec_dev *cec) +{ + writeb(CEC_FILTER_THRESHOLD, cec->reg + S5P_CEC_RX_FILTER_TH); + writeb(0, cec->reg + S5P_CEC_RX_FILTER_CTRL); +} + +void s5p_cec_copy_packet(struct s5p_cec_dev *cec, char *data, size_t count) +{ + char debug[40]; + int i = 0; + u8 reg; + + while (i < count) { + writeb(data[i], cec->reg + (S5P_CEC_TX_BUFF0 + (i * 4))); + sprintf(debug + i * 2, "%02x ", data[i]); + i++; + } + + writeb(count, cec->reg + S5P_CEC_TX_BYTES); + reg = readb(cec->reg + S5P_CEC_TX_CTRL); + reg |= S5P_CEC_TX_CTRL_START; + + if ((data[0] & CEC_MESSAGE_BROADCAST_MASK) == CEC_MESSAGE_BROADCAST) { + dev_dbg(cec->dev, "Broadcast"); + reg |= S5P_CEC_TX_CTRL_BCAST; + } else { + dev_dbg(cec->dev, "No Broadcast"); + reg &= ~S5P_CEC_TX_CTRL_BCAST; + } + + reg |= 0x50; + writeb(reg, cec->reg + S5P_CEC_TX_CTRL); + dev_dbg(cec->dev, "cec-tx: cec count(%d): %s", count, debug); +} + +void s5p_cec_set_addr(struct s5p_cec_dev *cec, u32 addr) +{ + writeb(addr & 0x0F, cec->reg + S5P_CEC_LOGIC_ADDR); +} + +u32 s5p_cec_get_status(struct s5p_cec_dev *cec) +{ + u32 status = 0; + + status = readb(cec->reg + S5P_CEC_STATUS_0); + status |= readb(cec->reg + S5P_CEC_STATUS_1) << 8; + status |= readb(cec->reg + S5P_CEC_STATUS_2) << 16; + status |= readb(cec->reg + S5P_CEC_STATUS_3) << 24; + + dev_dbg(cec->dev, "status = 0x%x!\n", status); + + return status; +} + +void s5p_clr_pending_tx(struct s5p_cec_dev *cec) +{ + writeb(S5P_CEC_IRQ_TX_DONE | S5P_CEC_IRQ_TX_ERROR, + cec->reg + S5P_CEC_IRQ_CLEAR); +} + +void s5p_clr_pending_rx(struct s5p_cec_dev *cec) +{ + writeb(S5P_CEC_IRQ_RX_DONE | S5P_CEC_IRQ_RX_ERROR, + cec->reg + S5P_CEC_IRQ_CLEAR); +} + +void s5p_cec_get_rx_buf(struct s5p_cec_dev *cec, u32 size, u8 *buffer) +{ + u32 i = 0; + char debug[40]; + + while (i < size) { + buffer[i] = readb(cec->reg + S5P_CEC_RX_BUFF0 + (i * 4)); + sprintf(debug + i * 2, "%02x ", buffer[i]); + i++; + } + dev_dbg(cec->dev, "cec-rx: cec size(%d): %s", size, debug); +} diff --git a/drivers/media/platform/s5p-cec/regs-cec.h b/drivers/media/platform/s5p-cec/regs-cec.h new file mode 100644 index 0000000..b2e7e12 --- /dev/null +++ b/drivers/media/platform/s5p-cec/regs-cec.h @@ -0,0 +1,96 @@ +/* drivers/media/platform/s5p-cec/regs-cec.h + * + * Copyright (c) 2010 Samsung Electronics + * http://www.samsung.com/ + * + * register header file for Samsung TVOUT driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __EXYNOS_REGS__H +#define __EXYNOS_REGS__H + +/* + * Register part + */ +#define S5P_CEC_STATUS_0 (0x0000) +#define S5P_CEC_STATUS_1 (0x0004) +#define S5P_CEC_STATUS_2 (0x0008) +#define S5P_CEC_STATUS_3 (0x000C) +#define S5P_CEC_IRQ_MASK (0x0010) +#define S5P_CEC_IRQ_CLEAR (0x0014) +#define S5P_CEC_LOGIC_ADDR (0x0020) +#define S5P_CEC_DIVISOR_0 (0x0030) +#define S5P_CEC_DIVISOR_1 (0x0034) +#define S5P_CEC_DIVISOR_2 (0x0038) +#define S5P_CEC_DIVISOR_3 (0x003C) + +#define S5P_CEC_TX_CTRL (0x0040) +#define S5P_CEC_TX_BYTES (0x0044) +#define S5P_CEC_TX_STAT0 (0x0060) +#define S5P_CEC_TX_STAT1 (0x0064) +#define S5P_CEC_TX_BUFF0 (0x0080) +#define S5P_CEC_TX_BUFF1 (0x0084) +#define S5P_CEC_TX_BUFF2 (0x0088) +#define S5P_CEC_TX_BUFF3 (0x008C) +#define S5P_CEC_TX_BUFF4 (0x0090) +#define S5P_CEC_TX_BUFF5 (0x0094) +#define S5P_CEC_TX_BUFF6 (0x0098) +#define S5P_CEC_TX_BUFF7 (0x009C) +#define S5P_CEC_TX_BUFF8 (0x00A0) +#define S5P_CEC_TX_BUFF9 (0x00A4) +#define S5P_CEC_TX_BUFF10 (0x00A8) +#define S5P_CEC_TX_BUFF11 (0x00AC) +#define S5P_CEC_TX_BUFF12 (0x00B0) +#define S5P_CEC_TX_BUFF13 (0x00B4) +#define S5P_CEC_TX_BUFF14 (0x00B8) +#define S5P_CEC_TX_BUFF15 (0x00BC) + +#define S5P_CEC_RX_CTRL (0x00C0) +#define S5P_CEC_RX_STAT0 (0x00E0) +#define S5P_CEC_RX_STAT1 (0x00E4) +#define S5P_CEC_RX_BUFF0 (0x0100) +#define S5P_CEC_RX_BUFF1 (0x0104) +#define S5P_CEC_RX_BUFF2 (0x0108) +#define S5P_CEC_RX_BUFF3 (0x010C) +#define S5P_CEC_RX_BUFF4 (0x0110) +#define S5P_CEC_RX_BUFF5 (0x0114) +#define S5P_CEC_RX_BUFF6 (0x0118) +#define S5P_CEC_RX_BUFF7 (0x011C) +#define S5P_CEC_RX_BUFF8 (0x0120) +#define S5P_CEC_RX_BUFF9 (0x0124) +#define S5P_CEC_RX_BUFF10 (0x0128) +#define S5P_CEC_RX_BUFF11 (0x012C) +#define S5P_CEC_RX_BUFF12 (0x0130) +#define S5P_CEC_RX_BUFF13 (0x0134) +#define S5P_CEC_RX_BUFF14 (0x0138) +#define S5P_CEC_RX_BUFF15 (0x013C) + +#define S5P_CEC_RX_FILTER_CTRL (0x0180) +#define S5P_CEC_RX_FILTER_TH (0x0184) + +/* + * Bit definition part + */ +#define S5P_CEC_IRQ_TX_DONE (1<<0) +#define S5P_CEC_IRQ_TX_ERROR (1<<1) +#define S5P_CEC_IRQ_RX_DONE (1<<4) +#define S5P_CEC_IRQ_RX_ERROR (1<<5) + +#define S5P_CEC_TX_CTRL_START (1<<0) +#define S5P_CEC_TX_CTRL_BCAST (1<<1) +#define S5P_CEC_TX_CTRL_RETRY (0x04<<4) +#define S5P_CEC_TX_CTRL_RESET (1<<7) + +#define S5P_CEC_RX_CTRL_ENABLE (1<<0) +#define S5P_CEC_RX_CTRL_RESET (1<<7) + +#define S5P_CEC_LOGIC_ADDR_MASK (0xF) + +/* PMU Registers for PHY */ +#define EXYNOS_HDMI_PHY_CONTROL 0x700 + +#endif /* __EXYNOS_REGS__H */ diff --git a/drivers/media/platform/s5p-cec/s5p_cec.c b/drivers/media/platform/s5p-cec/s5p_cec.c new file mode 100644 index 0000000..0bf84a7 --- /dev/null +++ b/drivers/media/platform/s5p-cec/s5p_cec.c @@ -0,0 +1,277 @@ +/* drivers/media/platform/s5p-cec/s5p_cec.c + * + * Samsung S5P CEC driver + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is based on the "cec interface driver for exynos soc" by + * SangPil Moon. + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/timer.h> +#include <linux/version.h> +#include <linux/workqueue.h> +#include <media/cec.h> + +#include "exynos_hdmi_cec.h" +#include "regs-cec.h" +#include "s5p_cec.h" + +#define CEC_NAME "s5p-cec" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +static int s5p_cec_enable(struct cec_adapter *adap, bool enable) +{ + struct s5p_cec_dev *cec = container_of(adap, struct s5p_cec_dev, adap); + int ret; + + if (enable) { + ret = pm_runtime_get_sync(cec->dev); + + s5p_cec_reset(cec); + + s5p_cec_set_divider(cec); + s5p_cec_threshold(cec); + + s5p_cec_unmask_tx_interrupts(cec); + s5p_cec_unmask_rx_interrupts(cec); + s5p_cec_enable_rx(cec); + } else { + s5p_cec_mask_tx_interrupts(cec); + s5p_cec_mask_rx_interrupts(cec); + pm_runtime_disable(cec->dev); + } + + return 0; +} + +static int s5p_cec_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct s5p_cec_dev *cec = container_of(adap, struct s5p_cec_dev, adap); + + s5p_cec_set_addr(cec, addr); + return 0; +} + +static int s5p_cec_transmit(struct cec_adapter *adap, u32 timeout_ms, struct cec_msg *msg) +{ + struct s5p_cec_dev *cec = container_of(adap, struct s5p_cec_dev, adap); + + s5p_cec_copy_packet(cec, msg->msg, msg->len); + return 0; +} + +static irqreturn_t s5p_cec_irq_handler(int irq, void *priv) +{ + struct s5p_cec_dev *cec = priv; + u32 status = 0; + + status = s5p_cec_get_status(cec); + + dev_dbg(cec->dev, "irq received\n"); + + if (status & CEC_STATUS_TX_DONE) { + if (status & CEC_STATUS_TX_ERROR) { + dev_dbg(cec->dev, "CEC_STATUS_TX_ERROR set\n"); + cec->tx = STATE_ERROR; + } else { + dev_dbg(cec->dev, "CEC_STATUS_TX_DONE\n"); + cec->tx = STATE_DONE; + } + s5p_clr_pending_tx(cec); + } + + if (status & CEC_STATUS_RX_DONE) { + if (status & CEC_STATUS_RX_ERROR) { + dev_dbg(cec->dev, "CEC_STATUS_RX_ERROR set\n"); + s5p_cec_rx_reset(cec); + s5p_cec_enable_rx(cec); + } else { + dev_dbg(cec->dev, "CEC_STATUS_RX_DONE set\n"); + if (cec->rx != STATE_IDLE) + dev_dbg(cec->dev, "Buffer overrun (worker did not process previous message)\n"); + cec->rx = STATE_BUSY; + cec->msg.len = status >> 24; + cec->msg.status = CEC_RX_STATUS_READY; + s5p_cec_get_rx_buf(cec, cec->msg.len, + cec->msg.msg); + cec->rx = STATE_DONE; + s5p_cec_enable_rx(cec); + } + /* Clear interrupt pending bit */ + s5p_clr_pending_rx(cec); + } + return IRQ_WAKE_THREAD; +} + +static irqreturn_t s5p_cec_irq_handler_thread(int irq, void *priv) +{ + struct s5p_cec_dev *cec = priv; + + dev_dbg(cec->dev, "irq processing thread\n"); + switch (cec->tx) { + case STATE_DONE: + cec_transmit_done(&cec->adap, CEC_TX_STATUS_OK); + cec->tx = STATE_IDLE; + break; + case STATE_ERROR: + cec_transmit_done(&cec->adap, CEC_TX_STATUS_RETRY_TIMEOUT); + cec->tx = STATE_IDLE; + break; + case STATE_BUSY: + dev_err(cec->dev, "state set to busy, this should not occur here\n"); + break; + default: + break; + } + + switch (cec->rx) { + case STATE_DONE: + cec_received_msg(&cec->adap, &cec->msg); + cec->rx = STATE_IDLE; + default: + break; + }; + + return IRQ_HANDLED; +} + +static int s5p_cec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct s5p_cec_dev *cec; + int ret; + + cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + cec->dev = dev; + + cec->irq = platform_get_irq(pdev, 0); + if (IS_ERR_VALUE(cec->irq)) + return cec->irq; + + ret = devm_request_threaded_irq(dev, cec->irq, s5p_cec_irq_handler, + s5p_cec_irq_handler_thread, 0, pdev->name, cec); + if (IS_ERR_VALUE(ret)) + return ret; + + cec->clk = devm_clk_get(dev, "hdmicec"); + if (IS_ERR(cec->clk)) + return PTR_ERR(cec->clk); + + cec->pmu = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,syscon-phandle"); + if (IS_ERR(cec->pmu)) + return -EPROBE_DEFER; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cec->reg = devm_ioremap_resource(dev, res); + if (IS_ERR(cec->reg)) + return PTR_ERR(cec->reg); + + cec->adap.available_log_addrs = 1; + cec->adap.adap_enable = s5p_cec_enable; + cec->adap.adap_log_addr = s5p_cec_log_addr; + cec->adap.adap_transmit = s5p_cec_transmit; + cec_create_adapter(&cec->adap, CEC_NAME, CEC_CAP_STATE | + CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS | CEC_CAP_IO | + CEC_CAP_IS_SOURCE, + 0, THIS_MODULE, &pdev->dev); + + platform_set_drvdata(pdev, cec); + pm_runtime_enable(dev); + + dev_dbg(dev, "successfuly probed\n"); + return 0; +} + +static int s5p_cec_remove(struct platform_device *pdev) +{ + struct s5p_cec_dev *cec = platform_get_drvdata(pdev); + + cec_delete_adapter(&cec->adap); + pm_runtime_disable(&pdev->dev); + return 0; +} + +static int s5p_cec_runtime_suspend(struct device *dev) +{ + struct s5p_cec_dev *cec = dev_get_drvdata(dev); + + clk_disable_unprepare(cec->clk); + return 0; +} + +static int s5p_cec_runtime_resume(struct device *dev) +{ + struct s5p_cec_dev *cec = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(cec->clk); + if (ret < 0) + return ret; + return 0; +} + +static int s5p_cec_suspend(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + return s5p_cec_runtime_suspend(dev); +} + +static int s5p_cec_resume(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + return s5p_cec_runtime_resume(dev); +} + +static const struct dev_pm_ops s5p_cec_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(s5p_cec_suspend, s5p_cec_resume) + SET_RUNTIME_PM_OPS(s5p_cec_runtime_suspend, s5p_cec_runtime_resume, + NULL) +}; + +static const struct of_device_id s5p_cec_match[] = { + { + .compatible = "samsung,s5p-cec", + }, + {}, +}; + +static struct platform_driver s5p_cec_pdrv = { + .probe = s5p_cec_probe, + .remove = s5p_cec_remove, + .driver = { + .name = CEC_NAME, + .owner = THIS_MODULE, + .of_match_table = s5p_cec_match, + .pm = &s5p_cec_pm_ops, + }, +}; + +module_platform_driver(s5p_cec_pdrv); + +MODULE_AUTHOR("Kamil Debski kamil@wypas.org"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung S5P CEC driver"); diff --git a/drivers/media/platform/s5p-cec/s5p_cec.h b/drivers/media/platform/s5p-cec/s5p_cec.h new file mode 100644 index 0000000..d6ffd92 --- /dev/null +++ b/drivers/media/platform/s5p-cec/s5p_cec.h @@ -0,0 +1,76 @@ +/* drivers/media/platform/s5p-cec/s5p_cec.h + * + * Samsung S5P HDMI CEC driver + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _S5P_CEC_H_ +#define _S5P_CEC_H_ __FILE__ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/timer.h> +#include <linux/version.h> +#include <linux/workqueue.h> +#include <media/cec.h> + +#include "exynos_hdmi_cec.h" +#include "regs-cec.h" +#include "s5p_cec.h" + +#define CEC_NAME "s5p-cec" + +#define CEC_STATUS_TX_RUNNING (1 << 0) +#define CEC_STATUS_TX_TRANSFERRING (1 << 1) +#define CEC_STATUS_TX_DONE (1 << 2) +#define CEC_STATUS_TX_ERROR (1 << 3) +#define CEC_STATUS_TX_BYTES (0xFF << 8) +#define CEC_STATUS_RX_RUNNING (1 << 16) +#define CEC_STATUS_RX_RECEIVING (1 << 17) +#define CEC_STATUS_RX_DONE (1 << 18) +#define CEC_STATUS_RX_ERROR (1 << 19) +#define CEC_STATUS_RX_BCAST (1 << 20) +#define CEC_STATUS_RX_BYTES (0xFF << 24) + +#define CEC_WORKER_TX_DONE (1 << 0) +#define CEC_WORKER_RX_MSG (1 << 1) + +/* CEC Rx buffer size */ +#define CEC_RX_BUFF_SIZE 16 +/* CEC Tx buffer size */ +#define CEC_TX_BUFF_SIZE 16 + +enum cec_state { + STATE_IDLE, + STATE_BUSY, + STATE_DONE, + STATE_ERROR +}; + +struct s5p_cec_dev { + struct cec_adapter adap; + struct clk *clk; + struct device *dev; + struct mutex lock; + struct regmap *pmu; + int irq; + void __iomem *reg; + + enum cec_state rx; + enum cec_state tx; + struct cec_msg msg; +}; + +#endif /* _S5P_CEC_H_ */
Add CEC support to the cobalt driver.
Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- drivers/media/pci/cobalt/cobalt-driver.c | 50 +++++++++++++-- drivers/media/pci/cobalt/cobalt-driver.h | 2 + drivers/media/pci/cobalt/cobalt-irq.c | 3 + drivers/media/pci/cobalt/cobalt-v4l2.c | 107 +++++++++++++++++++++++++++++-- 4 files changed, 151 insertions(+), 11 deletions(-)
diff --git a/drivers/media/pci/cobalt/cobalt-driver.c b/drivers/media/pci/cobalt/cobalt-driver.c index 8fed61e..920c9de 100644 --- a/drivers/media/pci/cobalt/cobalt-driver.c +++ b/drivers/media/pci/cobalt/cobalt-driver.c @@ -76,6 +76,7 @@ static u8 edid[256] = { 0x0A, 0x0A, 0x0A, 0x0A, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x68, + 0x02, 0x03, 0x1a, 0xc0, 0x48, 0xa2, 0x10, 0x04, 0x02, 0x01, 0x21, 0x14, 0x13, 0x23, 0x09, 0x07, 0x07, 0x65, 0x03, 0x0c, 0x00, 0x10, 0x00, 0xe2, @@ -149,17 +150,57 @@ static void cobalt_notify(struct v4l2_subdev *sd, struct cobalt *cobalt = to_cobalt(sd->v4l2_dev); unsigned sd_nr = cobalt_get_sd_nr(sd); struct cobalt_stream *s = &cobalt->streams[sd_nr]; - bool hotplug = arg ? *((int *)arg) : false;
- if (s->is_output) + switch (notification) { + case V4L2_SUBDEV_CEC_TX_DONE: + cec_transmit_done(&s->cec_adap, (unsigned long)arg); + return; + case V4L2_SUBDEV_CEC_RX_MSG: + cec_received_msg(&s->cec_adap, arg); + return; + case V4L2_SUBDEV_CEC_CONN_INPUTS: + cec_connected_inputs(&s->cec_adap, *(u16 *)arg); + return; + default: + break; + } + + if (s->is_output) { + switch (notification) { + case ADV7511_EDID_DETECT: { + struct adv7511_edid_detect *ed = arg; + + s->cec_adap.phys_addr = ed->phys_addr; + if (!ed->present) { + cec_enable(&s->cec_adap, false); + break; + } + cec_enable(&s->cec_adap, true); + break; + } + case ADV7511_MONITOR_DETECT: { + struct adv7511_monitor_detect *md = arg; + + if (!md->present) { + cec_enable(&s->cec_adap, false); + break; + } + break; + } + } return; + }
switch (notification) { - case ADV76XX_HOTPLUG: + case ADV76XX_HOTPLUG: { + bool hotplug = arg ? *((int *)arg) : false; + cobalt_s_bit_sysctrl(cobalt, COBALT_SYS_CTRL_HPD_TO_CONNECTOR_BIT(sd_nr), hotplug); + cec_connected_inputs(&s->cec_adap, hotplug); cobalt_dbg(1, "Set hotplug for adv %d to %d\n", sd_nr, hotplug); break; + } case V4L2_DEVICE_NOTIFY_EVENT: cobalt_dbg(1, "Format changed for adv %d\n", sd_nr); v4l2_event_queue(&s->vdev, arg); @@ -627,8 +668,9 @@ static int cobalt_subdevs_hsma_init(struct cobalt *cobalt) s->sd = v4l2_i2c_new_subdev_board(&cobalt->v4l2_dev, s->i2c_adap, &adv7842_info, NULL); if (s->sd) { - int err = v4l2_subdev_call(s->sd, pad, set_edid, &cobalt_edid); + int err;
+ err = v4l2_subdev_call(s->sd, pad, set_edid, &cobalt_edid); if (err) return err; err = v4l2_subdev_call(s->sd, pad, set_fmt, NULL, diff --git a/drivers/media/pci/cobalt/cobalt-driver.h b/drivers/media/pci/cobalt/cobalt-driver.h index c206df9..151c80f 100644 --- a/drivers/media/pci/cobalt/cobalt-driver.h +++ b/drivers/media/pci/cobalt/cobalt-driver.h @@ -31,6 +31,7 @@ #include <linux/workqueue.h> #include <linux/mutex.h>
+#include <media/cec.h> #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-device.h> @@ -221,6 +222,7 @@ struct cobalt_stream { struct list_head bufs; struct i2c_adapter *i2c_adap; struct v4l2_subdev *sd; + struct cec_adapter cec_adap; struct mutex lock; spinlock_t irqlock; struct v4l2_dv_timings timings; diff --git a/drivers/media/pci/cobalt/cobalt-irq.c b/drivers/media/pci/cobalt/cobalt-irq.c index dd4bff9..0963d96 100644 --- a/drivers/media/pci/cobalt/cobalt-irq.c +++ b/drivers/media/pci/cobalt/cobalt-irq.c @@ -233,6 +233,9 @@ void cobalt_irq_log_status(struct cobalt *cobalt) u32 mask; int i;
+ cobalt_info("irq: edge=%08x mask=%08x\n", + cobalt_read_bar1(cobalt, COBALT_SYS_STAT_EDGE), + cobalt_read_bar1(cobalt, COBALT_SYS_STAT_MASK)); cobalt_info("irq: adv1=%u adv2=%u advout=%u none=%u full=%u\n", cobalt->irq_adv1, cobalt->irq_adv2, cobalt->irq_advout, cobalt->irq_none, cobalt->irq_full_fifo); diff --git a/drivers/media/pci/cobalt/cobalt-v4l2.c b/drivers/media/pci/cobalt/cobalt-v4l2.c index 9756fd3..557202b 100644 --- a/drivers/media/pci/cobalt/cobalt-v4l2.c +++ b/drivers/media/pci/cobalt/cobalt-v4l2.c @@ -602,6 +602,7 @@ static int cobalt_log_status(struct file *file, void *priv_fh) cobalt_pcie_status_show(cobalt); cobalt_cpld_status(cobalt); cobalt_irq_log_status(cobalt); + cec_log_status(&s->cec_adap); v4l2_subdev_call(s->sd, core, log_status); if (!s->is_output) { cobalt_video_input_status_show(s); @@ -1161,6 +1162,64 @@ static const struct v4l2_file_operations cobalt_empty_fops = { .release = v4l2_fh_release, };
+static inline struct v4l2_subdev *adap_to_sd(struct cec_adapter *adap) +{ + struct cobalt_stream *s = container_of(adap, struct cobalt_stream, cec_adap); + + return s->sd; +} + +static int cobalt_cec_enable(struct cec_adapter *adap, bool enable) +{ + return v4l2_subdev_call(adap_to_sd(adap), video, cec_enable, enable); +} + +static int cobalt_cec_log_addr(struct cec_adapter *adap, u8 log_addr) +{ + return v4l2_subdev_call(adap_to_sd(adap), video, cec_log_addr, + log_addr); +} + +static int cobalt_cec_transmit(struct cec_adapter *adap, u32 timeout_ms, struct cec_msg *msg) +{ + return v4l2_subdev_call(adap_to_sd(adap), video, cec_transmit, timeout_ms, msg); +} + +static u8 cobalt_cec_cdc_hpd(struct cec_adapter *adap, u8 cdc_hpd_state) +{ + switch (cdc_hpd_state) { + case CEC_OP_HPD_STATE_EDID_DISABLE: + case CEC_OP_HPD_STATE_EDID_ENABLE: + case CEC_OP_HPD_STATE_EDID_DISABLE_ENABLE: + return CEC_OP_HPD_ERROR_NONE; + case CEC_OP_HPD_STATE_CP_EDID_DISABLE: + case CEC_OP_HPD_STATE_CP_EDID_ENABLE: + case CEC_OP_HPD_STATE_CP_EDID_DISABLE_ENABLE: + default: + return CEC_OP_HPD_ERROR_INITIATOR_WRONG_STATE; + } +} + +static int cobalt_sink_initiate_arc(struct cec_adapter *adap) +{ + return 0; +} + +static int cobalt_sink_terminate_arc(struct cec_adapter *adap) +{ + return 0; +} + +static int cobalt_source_arc_initiated(struct cec_adapter *adap) +{ + return 0; +} + +static int cobalt_source_arc_terminated(struct cec_adapter *adap) +{ + return 0; +} + static int cobalt_node_register(struct cobalt *cobalt, int node) { static const struct v4l2_dv_timings dv1080p60 = @@ -1168,7 +1227,7 @@ static int cobalt_node_register(struct cobalt *cobalt, int node) struct cobalt_stream *s = cobalt->streams + node; struct video_device *vdev = &s->vdev; struct vb2_queue *q = &s->q; - int ret; + int ret = 0;
mutex_init(&s->lock); spin_lock_init(&s->irqlock); @@ -1195,6 +1254,30 @@ static int cobalt_node_register(struct cobalt *cobalt, int node) if (!s->is_audio) { if (s->is_dummy) cobalt_warn("Setting up dummy video node %d\n", node); + if (s->sd) { + u32 caps = CEC_CAP_IO | CEC_CAP_LOG_ADDRS | + CEC_CAP_PASSTHROUGH | CEC_CAP_RC | + CEC_CAP_ARC | CEC_CAP_VENDOR_ID; + u8 nsinks = 0; + + s->cec_adap.adap_enable = cobalt_cec_enable; + s->cec_adap.adap_log_addr = cobalt_cec_log_addr; + s->cec_adap.adap_transmit = cobalt_cec_transmit; + s->cec_adap.source_cdc_hpd = cobalt_cec_cdc_hpd; + s->cec_adap.sink_initiate_arc = cobalt_sink_initiate_arc; + s->cec_adap.sink_terminate_arc = cobalt_sink_terminate_arc; + s->cec_adap.source_arc_initiated = cobalt_source_arc_initiated; + s->cec_adap.source_arc_terminated = cobalt_source_arc_terminated; + if (s->is_output) + caps |= CEC_CAP_IS_SOURCE | CEC_CAP_CDC_HPD; + else + nsinks = 1; + ret = cec_create_adapter(&s->cec_adap, vdev->name, + caps, nsinks, THIS_MODULE, + &cobalt->pci_dev->dev); + s->cec_adap.available_log_addrs = + v4l2_subdev_call(s->sd, video, cec_available_log_addrs); + } vdev->v4l2_dev = &cobalt->v4l2_dev; if (s->is_dummy) vdev->fops = &cobalt_empty_fops; @@ -1208,8 +1291,11 @@ static int cobalt_node_register(struct cobalt *cobalt, int node) vdev->ctrl_handler = s->sd->ctrl_handler; s->timings = dv1080p60; v4l2_subdev_call(s->sd, video, s_dv_timings, &s->timings); - if (!s->is_output && s->sd) + if (!s->is_output && s->sd) { cobalt_enable_input(s); + s->cec_adap.phys_addr = 0; + ret = cec_enable(&s->cec_adap, true); + } vdev->ioctl_ops = s->is_dummy ? &cobalt_ioctl_empty_ops : &cobalt_ioctl_ops; } @@ -1229,16 +1315,20 @@ static int cobalt_node_register(struct cobalt *cobalt, int node) vdev->queue = q;
video_set_drvdata(vdev, s); - ret = vb2_queue_init(q); + if (ret == 0) + ret = vb2_queue_init(q); if (!s->is_audio && ret == 0) ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); - else if (!s->is_dummy) + else if (!s->is_dummy && ret == 0) ret = cobalt_alsa_init(s);
if (ret < 0) { - if (!s->is_audio) + if (!s->is_audio) { + if (s->sd) + cec_delete_adapter(&s->cec_adap); cobalt_err("couldn't register v4l2 device node %d\n", node); + } return ret; } cobalt_info("registered node %d\n", node); @@ -1269,9 +1359,12 @@ void cobalt_nodes_unregister(struct cobalt *cobalt) struct cobalt_stream *s = cobalt->streams + node; struct video_device *vdev = &s->vdev;
- if (!s->is_audio) + if (!s->is_audio) { video_unregister_device(vdev); - else if (!s->is_dummy) + if (s->sd) + cec_delete_adapter(&s->cec_adap); + } else if (!s->is_dummy) { cobalt_alsa_exit(s); + } } }
dri-devel@lists.freedesktop.org