Hi Maciej, Unfortunately I do not have the MHL spec, but I was wondering what the relationship between RCP and CEC is. CEC has remote control support as well, so is RCP that subset of the CEC specification or is it completely separate? I'm CC-ing Sean Young and the linux-media mailinglist as well since Sean maintains the rc subsystem. Which you probably should use, but I'm not the expert on that. Regards, Hans On 08/03/17 09:44, Maciej Purski wrote: > MHL specification defines Remote Control Protocol(RCP) to > send input events between MHL devices. > The driver now recognizes RCP messages and reacts to them > by reporting key events to input subsystem, allowing > a user to control a device using TV remote control. > > Signed-off-by: Maciej Purski <m.purski@xxxxxxxxxxx> > --- > drivers/gpu/drm/bridge/sil-sii8620.c | 188 ++++++++++++++++++++++++++++++++++- > include/drm/bridge/mhl.h | 4 + > 2 files changed, 187 insertions(+), 5 deletions(-) > > diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c > index 2d51a22..7e75f2f 100644 > --- a/drivers/gpu/drm/bridge/sil-sii8620.c > +++ b/drivers/gpu/drm/bridge/sil-sii8620.c > @@ -19,6 +19,7 @@ > #include <linux/delay.h> > #include <linux/gpio/consumer.h> > #include <linux/i2c.h> > +#include <linux/input.h> > #include <linux/interrupt.h> > #include <linux/irq.h> > #include <linux/kernel.h> > @@ -58,6 +59,7 @@ enum sii8620_mt_state { > struct sii8620 { > struct drm_bridge bridge; > struct device *dev; > + struct input_dev *rcp_input_dev; > struct clk *clk_xtal; > struct gpio_desc *gpio_reset; > struct gpio_desc *gpio_int; > @@ -106,6 +108,82 @@ struct sii8620_mt_msg { > sii8620_cb continuation; > }; > > +static struct { > + u16 key; > + u16 extra_key; > + bool autorepeat; > +} rcp_keymap[] = { > + [0x00] = { KEY_SELECT }, > + [0x01] = { KEY_UP, 0, true }, > + [0x02] = { KEY_DOWN, 0, true }, > + [0x03] = { KEY_LEFT, 0, true }, > + [0x04] = { KEY_RIGHT, 0, true }, > + > + [0x05] = { KEY_RIGHT, KEY_UP, true }, > + [0x06] = { KEY_RIGHT, KEY_DOWN, true }, > + [0x07] = { KEY_LEFT, KEY_UP, true }, > + [0x08] = { KEY_LEFT, KEY_DOWN, true }, > + > + [0x09] = { KEY_MENU }, > + [0x0A] = { KEY_UNKNOWN }, > + [0x0B] = { KEY_UNKNOWN }, > + [0x0C] = { KEY_BOOKMARKS }, > + [0x0D] = { KEY_EXIT }, > + > + [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 }, > + > + [0x30] = { KEY_CHANNELUP, 0, true }, > + [0x31] = { KEY_CHANNELDOWN, 0, true }, > + > + [0x33] = { KEY_SOUND }, > + [0x35] = { KEY_PROGRAM }, /* Show Information */ > + > + [0x37] = { KEY_PAGEUP, 0, true }, > + [0x38] = { KEY_PAGEDOWN, 0, true }, > + > + [0x41] = { KEY_VOLUMEUP, 0, true }, > + [0x42] = { KEY_VOLUMEDOWN, 0, true }, > + [0x43] = { KEY_MUTE }, > + [0x44] = { KEY_PLAY }, > + [0x45] = { KEY_STOP }, > + [0x46] = { KEY_PLAYPAUSE }, /* Pause */ > + [0x47] = { KEY_RECORD }, > + [0x48] = { KEY_REWIND, 0, true }, > + [0x49] = { KEY_FASTFORWARD, 0, true }, > + [0x4A] = { KEY_EJECTCD }, > + [0x4B] = { KEY_NEXTSONG, 0, true }, /* Forward */ > + [0x4C] = { KEY_PREVIOUSSONG, 0, true }, /* Backward */ > + > + [0x60] = { KEY_PLAYPAUSE }, /* Play */ > + [0x61] = { KEY_PLAYPAUSE }, /* Pause the Play */ > + [0x62] = { KEY_RECORD }, > + [0x63] = { KEY_PAUSE }, > + [0x64] = { KEY_STOP }, > + [0x65] = { KEY_MUTE }, > + [0x66] = { KEY_MUTE }, /* Restore Mute */ > + > + [0x71] = { KEY_F1 }, > + [0x72] = { KEY_F2 }, > + [0x73] = { KEY_F3 }, > + [0x74] = { KEY_F4 }, > + [0x75] = { KEY_F5 }, > + > + [0x7E] = { KEY_VENDOR }, > +}; > + > static const u8 sii8620_i2c_page[] = { > 0x39, /* Main System */ > 0x3d, /* TDM and HSIC */ > @@ -431,6 +509,16 @@ static void sii8620_mt_rap(struct sii8620 *ctx, u8 code) > sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RAP, code); > } > > +static void sii8620_mt_rcpk(struct sii8620 *ctx, u8 code) > +{ > + sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RCPK, code); > +} > + > +static void sii8620_mt_rcpe(struct sii8620 *ctx, u8 code) > +{ > + sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RCPE, code); > +} > + > static void sii8620_mt_read_devcap_send(struct sii8620 *ctx, > struct sii8620_mt_msg *msg) > { > @@ -1753,6 +1841,43 @@ static void sii8620_send_features(struct sii8620 *ctx) > sii8620_write_buf(ctx, REG_MDT_XMIT_WRITE_PORT, buf, ARRAY_SIZE(buf)); > } > > +static void sii8620_rcp_report_key(struct sii8620 *ctx, u8 keycode, bool pressed) > +{ > + input_report_key(ctx->rcp_input_dev, > + rcp_keymap[keycode].key, pressed); > + > + if (rcp_keymap[keycode].extra_key) > + input_report_key(ctx->rcp_input_dev, > + rcp_keymap[keycode].extra_key, pressed); > +} > + > +static bool sii8620_rcp_consume(struct sii8620 *ctx, u8 keycode) > +{ > + bool pressed = !(keycode & MHL_RCP_KEY_RELEASED_MASK); > + > + if (!ctx->rcp_input_dev) { > + dev_dbg(ctx->dev, "RCP input device not initialized\n"); > + return false; > + } > + > + keycode &= MHL_RCP_KEY_ID_MASK; > + if (keycode >= ARRAY_SIZE(rcp_keymap) || !rcp_keymap[keycode].key) { > + dev_dbg(ctx->dev, "Unsupported RCP key code: %d\n", keycode); > + return false; > + } > + > + if (rcp_keymap[keycode].autorepeat && pressed) { > + sii8620_rcp_report_key(ctx, keycode, true); > + sii8620_rcp_report_key(ctx, keycode, false); > + } else if (!rcp_keymap[keycode].autorepeat) { > + sii8620_rcp_report_key(ctx, keycode, pressed); > + } > + > + input_sync(ctx->rcp_input_dev); > + > + return true; > +} > + > static void sii8620_msc_mr_set_int(struct sii8620 *ctx) > { > u8 ints[MHL_INT_SIZE]; > @@ -1804,19 +1929,25 @@ static void sii8620_msc_mt_done(struct sii8620 *ctx) > > static void sii8620_msc_mr_msc_msg(struct sii8620 *ctx) > { > - struct sii8620_mt_msg *msg = sii8620_msc_msg_first(ctx); > + struct sii8620_mt_msg *msg; > u8 buf[2]; > > - if (!msg) > - return; > - > sii8620_read_buf(ctx, REG_MSC_MR_MSC_MSG_RCVD_1ST_DATA, buf, 2); > > switch (buf[0]) { > case MHL_MSC_MSG_RAPK: > + msg = sii8620_msc_msg_first(ctx); > + if (!msg) > + return; > msg->ret = buf[1]; > ctx->mt_state = MT_STATE_DONE; > break; > + case MHL_MSC_MSG_RCP: > + if (!sii8620_rcp_consume(ctx, buf[1])) > + sii8620_mt_rcpe(ctx, > + MHL_RCPE_STATUS_INEFFECTIVE_KEY_CODE); > + sii8620_mt_rcpk(ctx, buf[1]); > + break; > default: > dev_err(ctx->dev, "%s message type %d,%d not supported", > __func__, buf[0], buf[1]); > @@ -2102,6 +2233,51 @@ static void sii8620_cable_in(struct sii8620 *ctx) > enable_irq(to_i2c_client(ctx->dev)->irq); > } > > +static void sii8620_init_rcp_input_dev(struct sii8620 *ctx) > +{ > + struct input_dev *i_dev = input_allocate_device(); > + int ret, i; > + u16 keycode; > + > + if (!i_dev) { > + dev_err(ctx->dev, "Failed to allocate RCP input device\n"); > + ctx->error = -ENOMEM; > + } > + > + set_bit(EV_KEY, i_dev->evbit); > + i_dev->name = "MHL Remote Control"; > + i_dev->keycode = rcp_keymap; > + i_dev->keycodesize = sizeof(u16); > + i_dev->keycodemax = ARRAY_SIZE(rcp_keymap); > + > + for (i = 0; i < ARRAY_SIZE(rcp_keymap); i++) { > + keycode = rcp_keymap[i].key; > + if (keycode) > + __set_bit(keycode, i_dev->keybit); > + } > + > + i_dev->id.bustype = BUS_VIRTUAL; > + ret = input_register_device(i_dev); > + > + if (ret) { > + dev_err(ctx->dev, "Failed to register rcp input device\n"); > + input_free_device(i_dev); > + ctx->error = ret; > + } > + > + ctx->rcp_input_dev = i_dev; > +} > + > +static void sii8620_remove_rcp_input_dev(struct sii8620 *ctx) > +{ > + if (!ctx->rcp_input_dev) > + return; > + > + input_unregister_device(ctx->rcp_input_dev); > + input_free_device(ctx->rcp_input_dev); > + ctx->rcp_input_dev = NULL; > +} > + > static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge) > { > return container_of(bridge, struct sii8620, bridge); > @@ -2207,6 +2383,7 @@ static int sii8620_probe(struct i2c_client *client, > ctx->bridge.of_node = dev->of_node; > drm_bridge_add(&ctx->bridge); > > + sii8620_init_rcp_input_dev(ctx); > sii8620_cable_in(ctx); > > return 0; > @@ -2217,8 +2394,9 @@ static int sii8620_remove(struct i2c_client *client) > struct sii8620 *ctx = i2c_get_clientdata(client); > > disable_irq(to_i2c_client(ctx->dev)->irq); > - drm_bridge_remove(&ctx->bridge); > sii8620_hw_off(ctx); > + sii8620_remove_rcp_input_dev(ctx); > + drm_bridge_remove(&ctx->bridge); > > return 0; > } > diff --git a/include/drm/bridge/mhl.h b/include/drm/bridge/mhl.h > index fbdfc8d..96a5e0f 100644 > --- a/include/drm/bridge/mhl.h > +++ b/include/drm/bridge/mhl.h > @@ -262,6 +262,10 @@ enum { > #define MHL_RAPK_UNSUPPORTED 0x02 /* Rcvd RAP action code not supported */ > #define MHL_RAPK_BUSY 0x03 /* Responder too busy to respond */ > > +/* Bit masks for RCP messages */ > +#define MHL_RCP_KEY_RELEASED_MASK 0x80 > +#define MHL_RCP_KEY_ID_MASK 0x7F > + > /* > * Error status codes for RCPE messages > */ >