| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Chrontel CH7033 Video Encoder Driver | |
| * | |
| * Copyright (C) 2019,2020 Lubomir Rintel | |
| */ | |
| #include <linux/gpio/consumer.h> | |
| #include <linux/i2c.h> | |
| #include <linux/module.h> | |
| #include <linux/regmap.h> | |
| #include <drm/drm_atomic_helper.h> | |
| #include <drm/drm_bridge.h> | |
| #include <drm/drm_edid.h> | |
| #include <drm/drm_of.h> | |
| #include <drm/drm_print.h> | |
| #include <drm/drm_probe_helper.h> | |
| /* Page 0, Register 0x07 */ | |
| enum { | |
| DRI_PD = BIT(3), | |
| IO_PD = BIT(5), | |
| }; | |
| /* Page 0, Register 0x08 */ | |
| enum { | |
| DRI_PDDRI = GENMASK(7, 4), | |
| PDDAC = GENMASK(3, 1), | |
| PANEN = BIT(0), | |
| }; | |
| /* Page 0, Register 0x09 */ | |
| enum { | |
| DPD = BIT(7), | |
| GCKOFF = BIT(6), | |
| TV_BP = BIT(5), | |
| SCLPD = BIT(4), | |
| SDPD = BIT(3), | |
| VGA_PD = BIT(2), | |
| HDBKPD = BIT(1), | |
| HDMI_PD = BIT(0), | |
| }; | |
| /* Page 0, Register 0x0a */ | |
| enum { | |
| MEMINIT = BIT(7), | |
| MEMIDLE = BIT(6), | |
| MEMPD = BIT(5), | |
| STOP = BIT(4), | |
| LVDS_PD = BIT(3), | |
| HD_DVIB = BIT(2), | |
| HDCP_PD = BIT(1), | |
| MCU_PD = BIT(0), | |
| }; | |
| /* Page 0, Register 0x18 */ | |
| enum { | |
| IDF = GENMASK(7, 4), | |
| INTEN = BIT(3), | |
| SWAP = GENMASK(2, 0), | |
| }; | |
| enum { | |
| BYTE_SWAP_RGB = 0, | |
| BYTE_SWAP_RBG = 1, | |
| BYTE_SWAP_GRB = 2, | |
| BYTE_SWAP_GBR = 3, | |
| BYTE_SWAP_BRG = 4, | |
| BYTE_SWAP_BGR = 5, | |
| }; | |
| /* Page 0, Register 0x19 */ | |
| enum { | |
| HPO_I = BIT(5), | |
| VPO_I = BIT(4), | |
| DEPO_I = BIT(3), | |
| CRYS_EN = BIT(2), | |
| GCLKFREQ = GENMASK(2, 0), | |
| }; | |
| /* Page 0, Register 0x2e */ | |
| enum { | |
| HFLIP = BIT(7), | |
| VFLIP = BIT(6), | |
| DEPO_O = BIT(5), | |
| HPO_O = BIT(4), | |
| VPO_O = BIT(3), | |
| TE = GENMASK(2, 0), | |
| }; | |
| /* Page 0, Register 0x2b */ | |
| enum { | |
| SWAPS = GENMASK(7, 4), | |
| VFMT = GENMASK(3, 0), | |
| }; | |
| /* Page 0, Register 0x54 */ | |
| enum { | |
| COMP_BP = BIT(7), | |
| DAC_EN_T = BIT(6), | |
| HWO_HDMI_HI = GENMASK(5, 3), | |
| HOO_HDMI_HI = GENMASK(2, 0), | |
| }; | |
| /* Page 0, Register 0x57 */ | |
| enum { | |
| FLDSEN = BIT(7), | |
| VWO_HDMI_HI = GENMASK(5, 3), | |
| VOO_HDMI_HI = GENMASK(2, 0), | |
| }; | |
| /* Page 0, Register 0x7e */ | |
| enum { | |
| HDMI_LVDS_SEL = BIT(7), | |
| DE_GEN = BIT(6), | |
| PWM_INDEX_HI = BIT(5), | |
| USE_DE = BIT(4), | |
| R_INT = GENMASK(3, 0), | |
| }; | |
| /* Page 1, Register 0x07 */ | |
| enum { | |
| BPCKSEL = BIT(7), | |
| DRI_CMFB_EN = BIT(6), | |
| CEC_PUEN = BIT(5), | |
| CEC_T = BIT(3), | |
| CKINV = BIT(2), | |
| CK_TVINV = BIT(1), | |
| DRI_CKS2 = BIT(0), | |
| }; | |
| /* Page 1, Register 0x08 */ | |
| enum { | |
| DACG = BIT(6), | |
| DACKTST = BIT(5), | |
| DEDGEB = BIT(4), | |
| SYO = BIT(3), | |
| DRI_IT_LVDS = GENMASK(2, 1), | |
| DISPON = BIT(0), | |
| }; | |
| /* Page 1, Register 0x0c */ | |
| enum { | |
| DRI_PLL_CP = GENMASK(7, 6), | |
| DRI_PLL_DIVSEL = BIT(5), | |
| DRI_PLL_N1_1 = BIT(4), | |
| DRI_PLL_N1_0 = BIT(3), | |
| DRI_PLL_N3_1 = BIT(2), | |
| DRI_PLL_N3_0 = BIT(1), | |
| DRI_PLL_CKTSTEN = BIT(0), | |
| }; | |
| /* Page 1, Register 0x6b */ | |
| enum { | |
| VCO3CS = GENMASK(7, 6), | |
| ICPGBK2_0 = GENMASK(5, 3), | |
| DRI_VCO357SC = BIT(2), | |
| PDPLL2 = BIT(1), | |
| DRI_PD_SER = BIT(0), | |
| }; | |
| /* Page 1, Register 0x6c */ | |
| enum { | |
| PLL2N11 = GENMASK(7, 4), | |
| PLL2N5_4 = BIT(3), | |
| PLL2N5_TOP = BIT(2), | |
| DRI_PLL_PD = BIT(1), | |
| PD_I2CM = BIT(0), | |
| }; | |
| /* Page 3, Register 0x28 */ | |
| enum { | |
| DIFF_EN = GENMASK(7, 6), | |
| CORREC_EN = GENMASK(5, 4), | |
| VGACLK_BP = BIT(3), | |
| HM_LV_SEL = BIT(2), | |
| HD_VGA_SEL = BIT(1), | |
| }; | |
| /* Page 3, Register 0x2a */ | |
| enum { | |
| LVDSCLK_BP = BIT(7), | |
| HDTVCLK_BP = BIT(6), | |
| HDMICLK_BP = BIT(5), | |
| HDTV_BP = BIT(4), | |
| HDMI_BP = BIT(3), | |
| THRWL = GENMASK(2, 0), | |
| }; | |
| /* Page 4, Register 0x52 */ | |
| enum { | |
| PGM_ARSTB = BIT(7), | |
| MCU_ARSTB = BIT(6), | |
| MCU_RETB = BIT(2), | |
| RESETIB = BIT(1), | |
| RESETDB = BIT(0), | |
| }; | |
| struct ch7033_priv { | |
| struct regmap *regmap; | |
| struct drm_bridge bridge; | |
| struct drm_connector connector; | |
| }; | |
| #define conn_to_ch7033_priv(x) \ | |
| container_of(x, struct ch7033_priv, connector) | |
| #define bridge_to_ch7033_priv(x) \ | |
| container_of(x, struct ch7033_priv, bridge) | |
| static enum drm_connector_status ch7033_connector_detect( | |
| struct drm_connector *connector, bool force) | |
| { | |
| struct ch7033_priv *priv = conn_to_ch7033_priv(connector); | |
| return drm_bridge_detect(priv->bridge.next_bridge, connector); | |
| } | |
| static const struct drm_connector_funcs ch7033_connector_funcs = { | |
| .reset = drm_atomic_helper_connector_reset, | |
| .fill_modes = drm_helper_probe_single_connector_modes, | |
| .detect = ch7033_connector_detect, | |
| .destroy = drm_connector_cleanup, | |
| .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | |
| .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | |
| }; | |
| static int ch7033_connector_get_modes(struct drm_connector *connector) | |
| { | |
| struct ch7033_priv *priv = conn_to_ch7033_priv(connector); | |
| const struct drm_edid *drm_edid; | |
| int ret; | |
| drm_edid = drm_bridge_edid_read(priv->bridge.next_bridge, connector); | |
| drm_edid_connector_update(connector, drm_edid); | |
| if (drm_edid) { | |
| ret = drm_edid_connector_add_modes(connector); | |
| drm_edid_free(drm_edid); | |
| } else { | |
| ret = drm_add_modes_noedid(connector, 1920, 1080); | |
| drm_set_preferred_mode(connector, 1024, 768); | |
| } | |
| return ret; | |
| } | |
| static struct drm_encoder *ch7033_connector_best_encoder( | |
| struct drm_connector *connector) | |
| { | |
| struct ch7033_priv *priv = conn_to_ch7033_priv(connector); | |
| return priv->bridge.encoder; | |
| } | |
| static const struct drm_connector_helper_funcs ch7033_connector_helper_funcs = { | |
| .get_modes = ch7033_connector_get_modes, | |
| .best_encoder = ch7033_connector_best_encoder, | |
| }; | |
| static void ch7033_hpd_event(void *arg, enum drm_connector_status status) | |
| { | |
| struct ch7033_priv *priv = arg; | |
| if (priv->bridge.dev) | |
| drm_helper_hpd_irq_event(priv->connector.dev); | |
| } | |
| static int ch7033_bridge_attach(struct drm_bridge *bridge, | |
| struct drm_encoder *encoder, | |
| enum drm_bridge_attach_flags flags) | |
| { | |
| struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); | |
| struct drm_connector *connector = &priv->connector; | |
| int ret; | |
| ret = drm_bridge_attach(encoder, priv->bridge.next_bridge, bridge, | |
| DRM_BRIDGE_ATTACH_NO_CONNECTOR); | |
| if (ret) | |
| return ret; | |
| if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) | |
| return 0; | |
| if (priv->bridge.next_bridge->ops & DRM_BRIDGE_OP_DETECT) { | |
| connector->polled = DRM_CONNECTOR_POLL_HPD; | |
| } else { | |
| connector->polled = DRM_CONNECTOR_POLL_CONNECT | | |
| DRM_CONNECTOR_POLL_DISCONNECT; | |
| } | |
| if (priv->bridge.next_bridge->ops & DRM_BRIDGE_OP_HPD) { | |
| drm_bridge_hpd_enable(priv->bridge.next_bridge, ch7033_hpd_event, | |
| priv); | |
| } | |
| drm_connector_helper_add(connector, | |
| &ch7033_connector_helper_funcs); | |
| ret = drm_connector_init_with_ddc(bridge->dev, &priv->connector, | |
| &ch7033_connector_funcs, | |
| priv->bridge.next_bridge->type, | |
| priv->bridge.next_bridge->ddc); | |
| if (ret) { | |
| DRM_ERROR("Failed to initialize connector\n"); | |
| return ret; | |
| } | |
| return drm_connector_attach_encoder(&priv->connector, encoder); | |
| } | |
| static void ch7033_bridge_detach(struct drm_bridge *bridge) | |
| { | |
| struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); | |
| if (priv->bridge.next_bridge->ops & DRM_BRIDGE_OP_HPD) | |
| drm_bridge_hpd_disable(priv->bridge.next_bridge); | |
| drm_connector_cleanup(&priv->connector); | |
| } | |
| static enum drm_mode_status ch7033_bridge_mode_valid(struct drm_bridge *bridge, | |
| const struct drm_display_info *info, | |
| const struct drm_display_mode *mode) | |
| { | |
| if (mode->clock > 165000) | |
| return MODE_CLOCK_HIGH; | |
| if (mode->hdisplay >= 1920) | |
| return MODE_BAD_HVALUE; | |
| if (mode->vdisplay >= 1080) | |
| return MODE_BAD_VVALUE; | |
| return MODE_OK; | |
| } | |
| static void ch7033_bridge_disable(struct drm_bridge *bridge, | |
| struct drm_atomic_commit *commit) | |
| { | |
| struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); | |
| regmap_write(priv->regmap, 0x03, 0x04); | |
| regmap_update_bits(priv->regmap, 0x52, RESETDB, 0x00); | |
| } | |
| static void ch7033_bridge_enable(struct drm_bridge *bridge, | |
| struct drm_atomic_commit *commit) | |
| { | |
| struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); | |
| regmap_write(priv->regmap, 0x03, 0x04); | |
| regmap_update_bits(priv->regmap, 0x52, RESETDB, RESETDB); | |
| } | |
| static void ch7033_bridge_mode_set(struct drm_bridge *bridge, | |
| const struct drm_display_mode *mode, | |
| const struct drm_display_mode *adjusted_mode) | |
| { | |
| struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); | |
| int hbporch = mode->hsync_start - mode->hdisplay; | |
| int hsynclen = mode->hsync_end - mode->hsync_start; | |
| int vbporch = mode->vsync_start - mode->vdisplay; | |
| int vsynclen = mode->vsync_end - mode->vsync_start; | |
| /* | |
| * Page 4 | |
| */ | |
| regmap_write(priv->regmap, 0x03, 0x04); | |
| /* Turn everything off to set all the registers to their defaults. */ | |
| regmap_write(priv->regmap, 0x52, 0x00); | |
| /* Bring I/O block up. */ | |
| regmap_write(priv->regmap, 0x52, RESETIB); | |
| /* | |
| * Page 0 | |
| */ | |
| regmap_write(priv->regmap, 0x03, 0x00); | |
| /* Bring up parts we need from the power down. */ | |
| regmap_update_bits(priv->regmap, 0x07, DRI_PD | IO_PD, 0); | |
| regmap_update_bits(priv->regmap, 0x08, DRI_PDDRI | PDDAC | PANEN, 0); | |
| regmap_update_bits(priv->regmap, 0x09, DPD | GCKOFF | | |
| HDMI_PD | VGA_PD, 0); | |
| regmap_update_bits(priv->regmap, 0x0a, HD_DVIB, 0); | |
| /* Horizontal input timing. */ | |
| regmap_write(priv->regmap, 0x0b, (mode->htotal >> 8) << 3 | | |
| (mode->hdisplay >> 8)); | |
| regmap_write(priv->regmap, 0x0c, mode->hdisplay); | |
| regmap_write(priv->regmap, 0x0d, mode->htotal); | |
| regmap_write(priv->regmap, 0x0e, (hsynclen >> 8) << 3 | | |
| (hbporch >> 8)); | |
| regmap_write(priv->regmap, 0x0f, hbporch); | |
| regmap_write(priv->regmap, 0x10, hsynclen); | |
| /* Vertical input timing. */ | |
| regmap_write(priv->regmap, 0x11, (mode->vtotal >> 8) << 3 | | |
| (mode->vdisplay >> 8)); | |
| regmap_write(priv->regmap, 0x12, mode->vdisplay); | |
| regmap_write(priv->regmap, 0x13, mode->vtotal); | |
| regmap_write(priv->regmap, 0x14, ((vsynclen >> 8) << 3) | | |
| (vbporch >> 8)); | |
| regmap_write(priv->regmap, 0x15, vbporch); | |
| regmap_write(priv->regmap, 0x16, vsynclen); | |
| /* Input color swap. */ | |
| regmap_update_bits(priv->regmap, 0x18, SWAP, BYTE_SWAP_BGR); | |
| /* Input clock and sync polarity. */ | |
| regmap_update_bits(priv->regmap, 0x19, 0x1, mode->clock >> 16); | |
| regmap_update_bits(priv->regmap, 0x19, HPO_I | VPO_I | GCLKFREQ, | |
| (mode->flags & DRM_MODE_FLAG_PHSYNC) ? HPO_I : 0 | | |
| (mode->flags & DRM_MODE_FLAG_PVSYNC) ? VPO_I : 0 | | |
| mode->clock >> 16); | |
| regmap_write(priv->regmap, 0x1a, mode->clock >> 8); | |
| regmap_write(priv->regmap, 0x1b, mode->clock); | |
| /* Horizontal output timing. */ | |
| regmap_write(priv->regmap, 0x1f, (mode->htotal >> 8) << 3 | | |
| (mode->hdisplay >> 8)); | |
| regmap_write(priv->regmap, 0x20, mode->hdisplay); | |
| regmap_write(priv->regmap, 0x21, mode->htotal); | |
| /* Vertical output timing. */ | |
| regmap_write(priv->regmap, 0x25, (mode->vtotal >> 8) << 3 | | |
| (mode->vdisplay >> 8)); | |
| regmap_write(priv->regmap, 0x26, mode->vdisplay); | |
| regmap_write(priv->regmap, 0x27, mode->vtotal); | |
| /* VGA channel bypass */ | |
| regmap_update_bits(priv->regmap, 0x2b, VFMT, 9); | |
| /* Output sync polarity. */ | |
| regmap_update_bits(priv->regmap, 0x2e, HPO_O | VPO_O, | |
| (mode->flags & DRM_MODE_FLAG_PHSYNC) ? HPO_O : 0 | | |
| (mode->flags & DRM_MODE_FLAG_PVSYNC) ? VPO_O : 0); | |
| /* HDMI horizontal output timing. */ | |
| regmap_update_bits(priv->regmap, 0x54, HWO_HDMI_HI | HOO_HDMI_HI, | |
| (hsynclen >> 8) << 3 | | |
| (hbporch >> 8)); | |
| regmap_write(priv->regmap, 0x55, hbporch); | |
| regmap_write(priv->regmap, 0x56, hsynclen); | |
| /* HDMI vertical output timing. */ | |
| regmap_update_bits(priv->regmap, 0x57, VWO_HDMI_HI | VOO_HDMI_HI, | |
| (vsynclen >> 8) << 3 | | |
| (vbporch >> 8)); | |
| regmap_write(priv->regmap, 0x58, vbporch); | |
| regmap_write(priv->regmap, 0x59, vsynclen); | |
| /* Pick HDMI, not LVDS. */ | |
| regmap_update_bits(priv->regmap, 0x7e, HDMI_LVDS_SEL, HDMI_LVDS_SEL); | |
| /* | |
| * Page 1 | |
| */ | |
| regmap_write(priv->regmap, 0x03, 0x01); | |
| /* No idea what these do, but VGA is wobbly and blinky without them. */ | |
| regmap_update_bits(priv->regmap, 0x07, CKINV, CKINV); | |
| regmap_update_bits(priv->regmap, 0x08, DISPON, DISPON); | |
| /* DRI PLL */ | |
| regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_DIVSEL, DRI_PLL_DIVSEL); | |
| if (mode->clock <= 40000) { | |
| regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | | |
| DRI_PLL_N1_0 | | |
| DRI_PLL_N3_1 | | |
| DRI_PLL_N3_0, | |
| 0); | |
| } else if (mode->clock < 80000) { | |
| regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | | |
| DRI_PLL_N1_0 | | |
| DRI_PLL_N3_1 | | |
| DRI_PLL_N3_0, | |
| DRI_PLL_N3_0 | | |
| DRI_PLL_N1_0); | |
| } else { | |
| regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | | |
| DRI_PLL_N1_0 | | |
| DRI_PLL_N3_1 | | |
| DRI_PLL_N3_0, | |
| DRI_PLL_N3_1 | | |
| DRI_PLL_N1_1); | |
| } | |
| /* This seems to be color calibration for VGA. */ | |
| regmap_write(priv->regmap, 0x64, 0x29); /* LSB Blue */ | |
| regmap_write(priv->regmap, 0x65, 0x29); /* LSB Green */ | |
| regmap_write(priv->regmap, 0x66, 0x29); /* LSB Red */ | |
| regmap_write(priv->regmap, 0x67, 0x00); /* MSB Blue */ | |
| regmap_write(priv->regmap, 0x68, 0x00); /* MSB Green */ | |
| regmap_write(priv->regmap, 0x69, 0x00); /* MSB Red */ | |
| regmap_update_bits(priv->regmap, 0x6b, DRI_PD_SER, 0x00); | |
| regmap_update_bits(priv->regmap, 0x6c, DRI_PLL_PD, 0x00); | |
| /* | |
| * Page 3 | |
| */ | |
| regmap_write(priv->regmap, 0x03, 0x03); | |
| /* More bypasses and apparently another HDMI/LVDS selector. */ | |
| regmap_update_bits(priv->regmap, 0x28, VGACLK_BP | HM_LV_SEL, | |
| VGACLK_BP | HM_LV_SEL); | |
| regmap_update_bits(priv->regmap, 0x2a, HDMICLK_BP | HDMI_BP, | |
| HDMICLK_BP | HDMI_BP); | |
| /* | |
| * Page 4 | |
| */ | |
| regmap_write(priv->regmap, 0x03, 0x04); | |
| /* Output clock. */ | |
| regmap_write(priv->regmap, 0x10, mode->clock >> 16); | |
| regmap_write(priv->regmap, 0x11, mode->clock >> 8); | |
| regmap_write(priv->regmap, 0x12, mode->clock); | |
| } | |
| static const struct drm_bridge_funcs ch7033_bridge_funcs = { | |
| .atomic_create_state = drm_atomic_helper_bridge_create_state, | |
| .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, | |
| .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, | |
| .attach = ch7033_bridge_attach, | |
| .detach = ch7033_bridge_detach, | |
| .mode_valid = ch7033_bridge_mode_valid, | |
| .atomic_disable = ch7033_bridge_disable, | |
| .atomic_enable = ch7033_bridge_enable, | |
| .mode_set = ch7033_bridge_mode_set, | |
| }; | |
| static const struct regmap_config ch7033_regmap_config = { | |
| .reg_bits = 8, | |
| .val_bits = 8, | |
| .max_register = 0x7f, | |
| }; | |
| static int ch7033_probe(struct i2c_client *client) | |
| { | |
| struct device *dev = &client->dev; | |
| struct ch7033_priv *priv; | |
| unsigned int val; | |
| int ret; | |
| priv = devm_drm_bridge_alloc(dev, struct ch7033_priv, bridge, | |
| &ch7033_bridge_funcs); | |
| if (IS_ERR(priv)) | |
| return PTR_ERR(priv); | |
| dev_set_drvdata(dev, priv); | |
| priv->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, -1); | |
| if (IS_ERR(priv->bridge.next_bridge)) | |
| return PTR_ERR(priv->bridge.next_bridge); | |
| priv->regmap = devm_regmap_init_i2c(client, &ch7033_regmap_config); | |
| if (IS_ERR(priv->regmap)) { | |
| dev_err(&client->dev, "regmap init failed\n"); | |
| return PTR_ERR(priv->regmap); | |
| } | |
| ret = regmap_read(priv->regmap, 0x00, &val); | |
| if (ret < 0) { | |
| dev_err(&client->dev, "error reading the model id: %d\n", ret); | |
| return ret; | |
| } | |
| if ((val & 0xf7) != 0x56) { | |
| dev_err(&client->dev, "the device is not a ch7033\n"); | |
| return -ENODEV; | |
| } | |
| regmap_write(priv->regmap, 0x03, 0x04); | |
| ret = regmap_read(priv->regmap, 0x51, &val); | |
| if (ret < 0) { | |
| dev_err(&client->dev, "error reading the model id: %d\n", ret); | |
| return ret; | |
| } | |
| if ((val & 0x0f) != 3) { | |
| dev_err(&client->dev, "unknown revision %u\n", val); | |
| return -ENODEV; | |
| } | |
| INIT_LIST_HEAD(&priv->bridge.list); | |
| priv->bridge.of_node = dev->of_node; | |
| drm_bridge_add(&priv->bridge); | |
| dev_info(dev, "Chrontel CH7033 Video Encoder\n"); | |
| return 0; | |
| } | |
| static void ch7033_remove(struct i2c_client *client) | |
| { | |
| struct device *dev = &client->dev; | |
| struct ch7033_priv *priv = dev_get_drvdata(dev); | |
| drm_bridge_remove(&priv->bridge); | |
| } | |
| static const struct of_device_id ch7033_dt_ids[] = { | |
| { .compatible = "chrontel,ch7033", }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(of, ch7033_dt_ids); | |
| static const struct i2c_device_id ch7033_ids[] = { | |
| { .name = "ch7033" }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(i2c, ch7033_ids); | |
| static struct i2c_driver ch7033_driver = { | |
| .probe = ch7033_probe, | |
| .remove = ch7033_remove, | |
| .driver = { | |
| .name = "ch7033", | |
| .of_match_table = ch7033_dt_ids, | |
| }, | |
| .id_table = ch7033_ids, | |
| }; | |
| module_i2c_driver(ch7033_driver); | |
| MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); | |
| MODULE_DESCRIPTION("Chrontel CH7033 Video Encoder Driver"); | |
| MODULE_LICENSE("GPL v2"); |