diff options
Diffstat (limited to 'drivers/gpu/drm/bridge/sii902x.c')
| -rw-r--r-- | drivers/gpu/drm/bridge/sii902x.c | 389 |
1 files changed, 283 insertions, 106 deletions
diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c index 6dad025f8da7..1f0aba28ad1e 100644 --- a/drivers/gpu/drm/bridge/sii902x.c +++ b/drivers/gpu/drm/bridge/sii902x.c @@ -15,8 +15,10 @@ #include <linux/gpio/consumer.h> #include <linux/i2c-mux.h> #include <linux/i2c.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/regmap.h> +#include <linux/regulator/consumer.h> #include <linux/clk.h> #include <drm/drm_atomic_helper.h> @@ -161,13 +163,24 @@ #define SII902X_AUDIO_PORT_INDEX 3 +/* + * The maximum resolution supported by the HDMI bridge is 1080p@60Hz + * and 1920x1200 requiring a pixel clock of 165MHz and the minimum + * resolution supported is 480p@60Hz requiring a pixel clock of 25MHz + */ +#define SII902X_MIN_PIXEL_CLOCK_KHZ 25000 +#define SII902X_MAX_PIXEL_CLOCK_KHZ 165000 + struct sii902x { struct i2c_client *i2c; struct regmap *regmap; struct drm_bridge bridge; + struct drm_bridge *next_bridge; struct drm_connector connector; struct gpio_desc *reset_gpio; struct i2c_mux_core *i2cmux; + u32 bus_width; + /* * Mutex protects audio and video functions from interfering * each other, by keeping their i2c command sequences atomic. @@ -235,18 +248,16 @@ static void sii902x_reset(struct sii902x *sii902x) if (!sii902x->reset_gpio) return; - gpiod_set_value(sii902x->reset_gpio, 1); + gpiod_set_value_cansleep(sii902x->reset_gpio, 1); /* The datasheet says treset-min = 100us. Make it 150us to be sure. */ usleep_range(150, 200); - gpiod_set_value(sii902x->reset_gpio, 0); + gpiod_set_value_cansleep(sii902x->reset_gpio, 0); } -static enum drm_connector_status -sii902x_connector_detect(struct drm_connector *connector, bool force) +static enum drm_connector_status sii902x_detect(struct sii902x *sii902x) { - struct sii902x *sii902x = connector_to_sii902x(connector); unsigned int status; mutex_lock(&sii902x->mutex); @@ -259,6 +270,14 @@ sii902x_connector_detect(struct drm_connector *connector, bool force) connector_status_connected : connector_status_disconnected; } +static enum drm_connector_status +sii902x_connector_detect(struct drm_connector *connector, bool force) +{ + struct sii902x *sii902x = connector_to_sii902x(connector); + + return sii902x_detect(sii902x); +} + static const struct drm_connector_funcs sii902x_connector_funcs = { .detect = sii902x_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, @@ -268,58 +287,42 @@ static const struct drm_connector_funcs sii902x_connector_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; -static int sii902x_get_modes(struct drm_connector *connector) +static const struct drm_edid *sii902x_edid_read(struct sii902x *sii902x, + struct drm_connector *connector) { - struct sii902x *sii902x = connector_to_sii902x(connector); - u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; - u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI; - struct edid *edid; - int num = 0, ret; + const struct drm_edid *drm_edid; mutex_lock(&sii902x->mutex); - edid = drm_get_edid(connector, sii902x->i2cmux->adapter[0]); - drm_connector_update_edid_property(connector, edid); - if (edid) { - if (drm_detect_hdmi_monitor(edid)) - output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI; - - num = drm_add_edid_modes(connector, edid); - kfree(edid); - } - - ret = drm_display_info_set_bus_formats(&connector->display_info, - &bus_format, 1); - if (ret) - goto error_out; - - ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, - SII902X_SYS_CTRL_OUTPUT_MODE, output_mode); - if (ret) - goto error_out; - - ret = num; + drm_edid = drm_edid_read_ddc(connector, sii902x->i2cmux->adapter[0]); -error_out: mutex_unlock(&sii902x->mutex); - return ret; + return drm_edid; } -static enum drm_mode_status sii902x_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) +static int sii902x_get_modes(struct drm_connector *connector) { - /* TODO: check mode */ + struct sii902x *sii902x = connector_to_sii902x(connector); + const struct drm_edid *drm_edid; + int num = 0; + + drm_edid = sii902x_edid_read(sii902x, connector); + drm_edid_connector_update(connector, drm_edid); + if (drm_edid) { + num = drm_edid_connector_add_modes(connector); + drm_edid_free(drm_edid); + } - return MODE_OK; + return num; } static const struct drm_connector_helper_funcs sii902x_connector_helper_funcs = { .get_modes = sii902x_get_modes, - .mode_valid = sii902x_mode_valid, }; -static void sii902x_bridge_disable(struct drm_bridge *bridge) +static void sii902x_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_state *state) { struct sii902x *sii902x = bridge_to_sii902x(bridge); @@ -332,12 +335,21 @@ static void sii902x_bridge_disable(struct drm_bridge *bridge) mutex_unlock(&sii902x->mutex); } -static void sii902x_bridge_enable(struct drm_bridge *bridge) +static void sii902x_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_state *state) { struct sii902x *sii902x = bridge_to_sii902x(bridge); + struct drm_connector *connector; + u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI; + + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); + if (connector && connector->display_info.is_hdmi) + output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI; mutex_lock(&sii902x->mutex); + regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_OUTPUT_MODE, output_mode); regmap_update_bits(sii902x->regmap, SII902X_PWR_STATE_CTRL, SII902X_AVI_POWER_STATE_MSK, SII902X_AVI_POWER_STATE_D(0)); @@ -360,7 +372,7 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge, buf[0] = pixel_clock_10kHz & 0xff; buf[1] = pixel_clock_10kHz >> 8; - buf[2] = adj->vrefresh; + buf[2] = drm_mode_vrefresh(adj); buf[3] = 0x00; buf[4] = adj->hdisplay; buf[5] = adj->hdisplay >> 8; @@ -400,16 +412,17 @@ out: } static int sii902x_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, enum drm_bridge_attach_flags flags) { struct sii902x *sii902x = bridge_to_sii902x(bridge); + u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; struct drm_device *drm = bridge->dev; int ret; - if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { - DRM_ERROR("Fix bridge driver to make connector optional!"); - return -EINVAL; - } + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return drm_bridge_attach(encoder, sii902x->next_bridge, + bridge, flags); drm_connector_helper_add(&sii902x->connector, &sii902x_connector_helper_funcs); @@ -431,16 +444,113 @@ static int sii902x_bridge_attach(struct drm_bridge *bridge, else sii902x->connector.polled = DRM_CONNECTOR_POLL_CONNECT; - drm_connector_attach_encoder(&sii902x->connector, bridge->encoder); + ret = drm_display_info_set_bus_formats(&sii902x->connector.display_info, + &bus_format, 1); + if (ret) + return ret; + + drm_connector_attach_encoder(&sii902x->connector, encoder); + + return 0; +} + +static enum drm_connector_status +sii902x_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) +{ + struct sii902x *sii902x = bridge_to_sii902x(bridge); + + return sii902x_detect(sii902x); +} + +static const struct drm_edid *sii902x_bridge_edid_read(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct sii902x *sii902x = bridge_to_sii902x(bridge); + + return sii902x_edid_read(sii902x, connector); +} + +static u32 *sii902x_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + + struct sii902x *sii902x = bridge_to_sii902x(bridge); + u32 *input_fmts; + + *num_input_fmts = 0; + + input_fmts = kcalloc(1, sizeof(*input_fmts), GFP_KERNEL); + if (!input_fmts) + return NULL; + + switch (sii902x->bus_width) { + case 16: + input_fmts[0] = MEDIA_BUS_FMT_RGB565_1X16; + break; + case 18: + input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X18; + break; + case 24: + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + break; + default: + return NULL; + } + + *num_input_fmts = 1; + + return input_fmts; +} + +static int sii902x_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + if (crtc_state->mode.clock < SII902X_MIN_PIXEL_CLOCK_KHZ || + crtc_state->mode.clock > SII902X_MAX_PIXEL_CLOCK_KHZ) + return -EINVAL; + + /* + * There might be flags negotiation supported in future but + * set the bus flags in atomic_check statically for now. + */ + bridge_state->input_bus_cfg.flags = bridge->timings->input_bus_flags; return 0; } +static enum drm_mode_status +sii902x_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->clock < SII902X_MIN_PIXEL_CLOCK_KHZ) + return MODE_CLOCK_LOW; + + if (mode->clock > SII902X_MAX_PIXEL_CLOCK_KHZ) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + static const struct drm_bridge_funcs sii902x_bridge_funcs = { .attach = sii902x_bridge_attach, .mode_set = sii902x_bridge_mode_set, - .disable = sii902x_bridge_disable, - .enable = sii902x_bridge_enable, + .atomic_disable = sii902x_bridge_atomic_disable, + .atomic_enable = sii902x_bridge_atomic_enable, + .detect = sii902x_bridge_detect, + .edid_read = sii902x_bridge_edid_read, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_get_input_bus_fmts = sii902x_bridge_atomic_get_input_bus_fmts, + .atomic_check = sii902x_bridge_atomic_check, + .mode_valid = sii902x_bridge_mode_valid, }; static int sii902x_mute(struct sii902x *sii902x, bool mute) @@ -510,8 +620,9 @@ static int sii902x_audio_hw_params(struct device *dev, void *data, unsigned long mclk_rate; int i, ret; - if (daifmt->bit_clk_master || daifmt->frame_clk_master) { - dev_dbg(dev, "%s: I2S master mode not supported\n", __func__); + if (daifmt->bit_clk_provider || daifmt->frame_clk_provider) { + dev_dbg(dev, "%s: I2S clock provider mode not supported\n", + __func__); return -EINVAL; } @@ -672,8 +783,8 @@ static void sii902x_audio_shutdown(struct device *dev, void *data) clk_disable_unprepare(sii902x->audio.mclk); } -static int sii902x_audio_digital_mute(struct device *dev, - void *data, bool enable) +static int sii902x_audio_mute(struct device *dev, void *data, + bool enable, int direction) { struct sii902x *sii902x = dev_get_drvdata(dev); @@ -702,7 +813,8 @@ static int sii902x_audio_get_eld(struct device *dev, void *data, } static int sii902x_audio_get_dai_id(struct snd_soc_component *component, - struct device_node *endpoint) + struct device_node *endpoint, + void *data) { struct of_endpoint of_ep; int ret; @@ -724,7 +836,7 @@ static int sii902x_audio_get_dai_id(struct snd_soc_component *component, static const struct hdmi_codec_ops sii902x_audio_codec_ops = { .hw_params = sii902x_audio_hw_params, .audio_shutdown = sii902x_audio_shutdown, - .digital_mute = sii902x_audio_digital_mute, + .mute_stream = sii902x_audio_mute, .get_eld = sii902x_audio_get_eld, .get_dai_id = sii902x_audio_get_dai_id, }; @@ -749,11 +861,12 @@ static int sii902x_audio_codec_init(struct sii902x *sii902x, .i2s = 1, /* Only i2s support for now. */ .spdif = 0, .max_i2s_channels = 0, + .no_capture_mute = 1, }; u8 lanes[4]; int num_lanes, i; - if (!of_property_read_bool(dev->of_node, "#sound-dai-cells")) { + if (!of_property_present(dev->of_node, "#sound-dai-cells")) { dev_dbg(dev, "%s: No \"#sound-dai-cells\", no audio\n", __func__); return 0; @@ -772,7 +885,7 @@ static int sii902x_audio_codec_init(struct sii902x *sii902x, lanes[0] = 0; } else if (num_lanes < 0) { dev_err(dev, - "%s: Error gettin \"sil,i2s-data-lanes\": %d\n", + "%s: Error getting \"sil,i2s-data-lanes\": %d\n", __func__, num_lanes); return num_lanes; } @@ -826,8 +939,12 @@ static irqreturn_t sii902x_interrupt(int irq, void *data) mutex_unlock(&sii902x->mutex); - if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev) + if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev) { drm_helper_hpd_irq_event(sii902x->bridge.dev); + drm_bridge_hpd_notify(&sii902x->bridge, (status & SII902X_PLUGGED_STATUS) + ? connector_status_connected + : connector_status_disconnected); + } return IRQ_HANDLED; } @@ -953,41 +1070,13 @@ static const struct drm_bridge_timings default_sii902x_timings = { | DRM_BUS_FLAG_DE_HIGH, }; -static int sii902x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int sii902x_init(struct sii902x *sii902x) { - struct device *dev = &client->dev; + struct device *dev = &sii902x->i2c->dev; unsigned int status = 0; - struct sii902x *sii902x; u8 chipid[4]; int ret; - ret = i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_BYTE_DATA); - if (!ret) { - dev_err(dev, "I2C adapter not suitable\n"); - return -EIO; - } - - sii902x = devm_kzalloc(dev, sizeof(*sii902x), GFP_KERNEL); - if (!sii902x) - return -ENOMEM; - - sii902x->i2c = client; - sii902x->regmap = devm_regmap_init_i2c(client, &sii902x_regmap_config); - if (IS_ERR(sii902x->regmap)) - return PTR_ERR(sii902x->regmap); - - sii902x->reset_gpio = devm_gpiod_get_optional(dev, "reset", - GPIOD_OUT_LOW); - if (IS_ERR(sii902x->reset_gpio)) { - dev_err(dev, "Failed to retrieve/request reset gpio: %ld\n", - PTR_ERR(sii902x->reset_gpio)); - return PTR_ERR(sii902x->reset_gpio); - } - - mutex_init(&sii902x->mutex); - sii902x_reset(sii902x); ret = regmap_write(sii902x->regmap, SII902X_REG_TPI_RQB, 0x0); @@ -1011,11 +1100,11 @@ static int sii902x_probe(struct i2c_client *client, regmap_read(sii902x->regmap, SII902X_INT_STATUS, &status); regmap_write(sii902x->regmap, SII902X_INT_STATUS, status); - if (client->irq > 0) { + if (sii902x->i2c->irq > 0) { regmap_write(sii902x->regmap, SII902X_INT_ENABLE, SII902X_HOTPLUG_EVENT); - ret = devm_request_threaded_irq(dev, client->irq, NULL, + ret = devm_request_threaded_irq(dev, sii902x->i2c->irq, NULL, sii902x_interrupt, IRQF_ONESHOT, dev_name(dev), sii902x); @@ -1023,35 +1112,123 @@ static int sii902x_probe(struct i2c_client *client, return ret; } - sii902x->bridge.funcs = &sii902x_bridge_funcs; - sii902x->bridge.of_node = dev->of_node; - sii902x->bridge.timings = &default_sii902x_timings; - drm_bridge_add(&sii902x->bridge); - - sii902x_audio_codec_init(sii902x, dev); + ret = sii902x_audio_codec_init(sii902x, dev); + if (ret) + return ret; - i2c_set_clientdata(client, sii902x); + i2c_set_clientdata(sii902x->i2c, sii902x); - sii902x->i2cmux = i2c_mux_alloc(client->adapter, dev, + sii902x->i2cmux = i2c_mux_alloc(sii902x->i2c->adapter, dev, 1, 0, I2C_MUX_GATE, sii902x_i2c_bypass_select, sii902x_i2c_bypass_deselect); - if (!sii902x->i2cmux) - return -ENOMEM; + if (!sii902x->i2cmux) { + ret = -ENOMEM; + goto err_unreg_audio; + } sii902x->i2cmux->priv = sii902x; - return i2c_mux_add_adapter(sii902x->i2cmux, 0, 0, 0); + ret = i2c_mux_add_adapter(sii902x->i2cmux, 0, 0); + if (ret) + goto err_unreg_audio; + + sii902x->bridge.of_node = dev->of_node; + sii902x->bridge.timings = &default_sii902x_timings; + sii902x->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID; + sii902x->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + if (sii902x->i2c->irq > 0) + sii902x->bridge.ops |= DRM_BRIDGE_OP_HPD; + + drm_bridge_add(&sii902x->bridge); + + return 0; + +err_unreg_audio: + if (!PTR_ERR_OR_ZERO(sii902x->audio.pdev)) + platform_device_unregister(sii902x->audio.pdev); + + return ret; } -static int sii902x_remove(struct i2c_client *client) +static int sii902x_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device_node *endpoint; + struct sii902x *sii902x; + static const char * const supplies[] = {"iovcc", "cvcc12"}; + int ret; + + ret = i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA); + if (!ret) { + dev_err(dev, "I2C adapter not suitable\n"); + return -EIO; + } + sii902x = devm_drm_bridge_alloc(dev, struct sii902x, bridge, &sii902x_bridge_funcs); + if (IS_ERR(sii902x)) + return PTR_ERR(sii902x); + + sii902x->i2c = client; + sii902x->regmap = devm_regmap_init_i2c(client, &sii902x_regmap_config); + if (IS_ERR(sii902x->regmap)) + return PTR_ERR(sii902x->regmap); + + sii902x->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(sii902x->reset_gpio)) { + dev_err(dev, "Failed to retrieve/request reset gpio: %ld\n", + PTR_ERR(sii902x->reset_gpio)); + return PTR_ERR(sii902x->reset_gpio); + } + + sii902x->bus_width = 24; + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); + if (endpoint) + of_property_read_u32(endpoint, "bus-width", &sii902x->bus_width); + + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1); + if (endpoint) { + struct device_node *remote = of_graph_get_remote_port_parent(endpoint); + + of_node_put(endpoint); + if (!remote) { + dev_err(dev, "Endpoint in port@1 unconnected\n"); + return -ENODEV; + } + + if (!of_device_is_available(remote)) { + dev_err(dev, "port@1 remote device is disabled\n"); + of_node_put(remote); + return -ENODEV; + } + + sii902x->next_bridge = of_drm_find_bridge(remote); + of_node_put(remote); + if (!sii902x->next_bridge) + return dev_err_probe(dev, -EPROBE_DEFER, + "Failed to find remote bridge\n"); + } + + mutex_init(&sii902x->mutex); + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supplies), supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable supplies"); + + return sii902x_init(sii902x); +} + +static void sii902x_remove(struct i2c_client *client) { struct sii902x *sii902x = i2c_get_clientdata(client); - i2c_mux_del_adapters(sii902x->i2cmux); drm_bridge_remove(&sii902x->bridge); + i2c_mux_del_adapters(sii902x->i2cmux); - return 0; + if (!PTR_ERR_OR_ZERO(sii902x->audio.pdev)) + platform_device_unregister(sii902x->audio.pdev); } static const struct of_device_id sii902x_dt_ids[] = { @@ -1061,8 +1238,8 @@ static const struct of_device_id sii902x_dt_ids[] = { MODULE_DEVICE_TABLE(of, sii902x_dt_ids); static const struct i2c_device_id sii902x_i2c_ids[] = { - { "sii9022", 0 }, - { }, + { "sii9022" }, + { } }; MODULE_DEVICE_TABLE(i2c, sii902x_i2c_ids); |
