Path: blob/master/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
26494 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Driver for MegaChips STDP4028 with GE B850v3 firmware (LVDS-DP)3* Driver for MegaChips STDP2690 with GE B850v3 firmware (DP-DP++)45* Copyright (c) 2017, Collabora Ltd.6* Copyright (c) 2017, General Electric Company789* This driver creates a drm_bridge and a drm_connector for the LVDS to DP++10* display bridge of the GE B850v3. There are two physical bridges on the video11* signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The12* physical bridges are automatically configured by the input video signal, and13* the driver has no access to the video processing pipeline. The driver is14* only needed to read EDID from the STDP2690 and to handle HPD events from the15* STDP4028. The driver communicates with both bridges over i2c. The video16* signal pipeline is as follows:17*18* Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output19*/2021#include <linux/i2c.h>22#include <linux/module.h>23#include <linux/of.h>2425#include <drm/drm_atomic.h>26#include <drm/drm_atomic_helper.h>27#include <drm/drm_bridge.h>28#include <drm/drm_edid.h>29#include <drm/drm_print.h>30#include <drm/drm_probe_helper.h>3132#define EDID_EXT_BLOCK_CNT 0x7E3334#define STDP4028_IRQ_OUT_CONF_REG 0x0235#define STDP4028_DPTX_IRQ_EN_REG 0x3C36#define STDP4028_DPTX_IRQ_STS_REG 0x3D37#define STDP4028_DPTX_STS_REG 0x3E3839#define STDP4028_DPTX_DP_IRQ_EN 0x10004041#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x040042#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x200043#define STDP4028_DPTX_IRQ_CONFIG \44(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)4546#define STDP4028_DPTX_HOTPLUG_STS 0x020047#define STDP4028_DPTX_LINK_STS 0x100048#define STDP4028_CON_STATE_CONNECTED \49(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)5051#define STDP4028_DPTX_HOTPLUG_CH_STS 0x040052#define STDP4028_DPTX_LINK_CH_STS 0x200053#define STDP4028_DPTX_IRQ_CLEAR \54(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)5556static DEFINE_MUTEX(ge_b850v3_lvds_dev_mutex);5758struct ge_b850v3_lvds {59struct drm_connector connector;60struct drm_bridge bridge;61struct i2c_client *stdp4028_i2c;62struct i2c_client *stdp2690_i2c;63};6465static struct ge_b850v3_lvds *ge_b850v3_lvds_ptr;6667static int stdp2690_read_block(void *context, u8 *buf, unsigned int block, size_t len)68{69struct i2c_client *client = context;70struct i2c_adapter *adapter = client->adapter;71unsigned char start = block * EDID_LENGTH;7273struct i2c_msg msgs[] = {74{75.addr = client->addr,76.flags = 0,77.len = 1,78.buf = &start,79}, {80.addr = client->addr,81.flags = I2C_M_RD,82.len = len,83.buf = buf,84}85};8687if (i2c_transfer(adapter, msgs, 2) != 2)88return -1;8990return 0;91}9293static const struct drm_edid *ge_b850v3_lvds_edid_read(struct drm_bridge *bridge,94struct drm_connector *connector)95{96struct i2c_client *client;9798client = ge_b850v3_lvds_ptr->stdp2690_i2c;99100return drm_edid_read_custom(connector, stdp2690_read_block, client);101}102103static int ge_b850v3_lvds_get_modes(struct drm_connector *connector)104{105const struct drm_edid *drm_edid;106int num_modes;107108drm_edid = ge_b850v3_lvds_edid_read(&ge_b850v3_lvds_ptr->bridge, connector);109110drm_edid_connector_update(connector, drm_edid);111num_modes = drm_edid_connector_add_modes(connector);112drm_edid_free(drm_edid);113114return num_modes;115}116117static const struct118drm_connector_helper_funcs ge_b850v3_lvds_connector_helper_funcs = {119.get_modes = ge_b850v3_lvds_get_modes,120};121122static enum drm_connector_status123ge_b850v3_lvds_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)124{125struct i2c_client *stdp4028_i2c =126ge_b850v3_lvds_ptr->stdp4028_i2c;127s32 link_state;128129link_state = i2c_smbus_read_word_data(stdp4028_i2c,130STDP4028_DPTX_STS_REG);131132if (link_state == STDP4028_CON_STATE_CONNECTED)133return connector_status_connected;134135if (link_state == 0)136return connector_status_disconnected;137138return connector_status_unknown;139}140141static enum drm_connector_status ge_b850v3_lvds_detect(struct drm_connector *connector,142bool force)143{144return ge_b850v3_lvds_bridge_detect(&ge_b850v3_lvds_ptr->bridge, connector);145}146147static const struct drm_connector_funcs ge_b850v3_lvds_connector_funcs = {148.fill_modes = drm_helper_probe_single_connector_modes,149.detect = ge_b850v3_lvds_detect,150.destroy = drm_connector_cleanup,151.reset = drm_atomic_helper_connector_reset,152.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,153.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,154};155156static int ge_b850v3_lvds_create_connector(struct drm_bridge *bridge)157{158struct drm_connector *connector = &ge_b850v3_lvds_ptr->connector;159int ret;160161connector->polled = DRM_CONNECTOR_POLL_HPD;162163drm_connector_helper_add(connector,164&ge_b850v3_lvds_connector_helper_funcs);165166ret = drm_connector_init(bridge->dev, connector,167&ge_b850v3_lvds_connector_funcs,168DRM_MODE_CONNECTOR_DisplayPort);169if (ret) {170DRM_ERROR("Failed to initialize connector with drm\n");171return ret;172}173174return drm_connector_attach_encoder(connector, bridge->encoder);175}176177static irqreturn_t ge_b850v3_lvds_irq_handler(int irq, void *dev_id)178{179struct i2c_client *stdp4028_i2c180= ge_b850v3_lvds_ptr->stdp4028_i2c;181182i2c_smbus_write_word_data(stdp4028_i2c,183STDP4028_DPTX_IRQ_STS_REG,184STDP4028_DPTX_IRQ_CLEAR);185186if (ge_b850v3_lvds_ptr->bridge.dev)187drm_kms_helper_hotplug_event(ge_b850v3_lvds_ptr->bridge.dev);188189return IRQ_HANDLED;190}191192static int ge_b850v3_lvds_attach(struct drm_bridge *bridge,193struct drm_encoder *encoder,194enum drm_bridge_attach_flags flags)195{196struct i2c_client *stdp4028_i2c197= ge_b850v3_lvds_ptr->stdp4028_i2c;198199/* Configures the bridge to re-enable interrupts after each ack. */200i2c_smbus_write_word_data(stdp4028_i2c,201STDP4028_IRQ_OUT_CONF_REG,202STDP4028_DPTX_DP_IRQ_EN);203204/* Enable interrupts */205i2c_smbus_write_word_data(stdp4028_i2c,206STDP4028_DPTX_IRQ_EN_REG,207STDP4028_DPTX_IRQ_CONFIG);208209if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)210return 0;211212return ge_b850v3_lvds_create_connector(bridge);213}214215static const struct drm_bridge_funcs ge_b850v3_lvds_funcs = {216.attach = ge_b850v3_lvds_attach,217.detect = ge_b850v3_lvds_bridge_detect,218.edid_read = ge_b850v3_lvds_edid_read,219};220221static int ge_b850v3_lvds_init(struct device *dev)222{223mutex_lock(&ge_b850v3_lvds_dev_mutex);224225if (ge_b850v3_lvds_ptr)226goto success;227228ge_b850v3_lvds_ptr = devm_drm_bridge_alloc(dev, struct ge_b850v3_lvds, bridge,229&ge_b850v3_lvds_funcs);230if (IS_ERR(ge_b850v3_lvds_ptr)) {231mutex_unlock(&ge_b850v3_lvds_dev_mutex);232return PTR_ERR(ge_b850v3_lvds_ptr);233}234235success:236mutex_unlock(&ge_b850v3_lvds_dev_mutex);237return 0;238}239240static void ge_b850v3_lvds_remove(void)241{242mutex_lock(&ge_b850v3_lvds_dev_mutex);243/*244* This check is to avoid both the drivers245* removing the bridge in their remove() function246*/247if (!ge_b850v3_lvds_ptr ||248!ge_b850v3_lvds_ptr->stdp2690_i2c ||249!ge_b850v3_lvds_ptr->stdp4028_i2c)250goto out;251252drm_bridge_remove(&ge_b850v3_lvds_ptr->bridge);253254ge_b850v3_lvds_ptr = NULL;255out:256mutex_unlock(&ge_b850v3_lvds_dev_mutex);257}258259static int ge_b850v3_register(void)260{261struct i2c_client *stdp4028_i2c = ge_b850v3_lvds_ptr->stdp4028_i2c;262struct device *dev = &stdp4028_i2c->dev;263264/* drm bridge initialization */265ge_b850v3_lvds_ptr->bridge.ops = DRM_BRIDGE_OP_DETECT |266DRM_BRIDGE_OP_EDID;267ge_b850v3_lvds_ptr->bridge.type = DRM_MODE_CONNECTOR_DisplayPort;268ge_b850v3_lvds_ptr->bridge.of_node = dev->of_node;269drm_bridge_add(&ge_b850v3_lvds_ptr->bridge);270271/* Clear pending interrupts since power up. */272i2c_smbus_write_word_data(stdp4028_i2c,273STDP4028_DPTX_IRQ_STS_REG,274STDP4028_DPTX_IRQ_CLEAR);275276if (!stdp4028_i2c->irq)277return 0;278279return devm_request_threaded_irq(&stdp4028_i2c->dev,280stdp4028_i2c->irq, NULL,281ge_b850v3_lvds_irq_handler,282IRQF_TRIGGER_HIGH | IRQF_ONESHOT,283"ge-b850v3-lvds-dp", ge_b850v3_lvds_ptr);284}285286static int stdp4028_ge_b850v3_fw_probe(struct i2c_client *stdp4028_i2c)287{288struct device *dev = &stdp4028_i2c->dev;289int ret;290291ret = ge_b850v3_lvds_init(dev);292293if (ret)294return ret;295296ge_b850v3_lvds_ptr->stdp4028_i2c = stdp4028_i2c;297i2c_set_clientdata(stdp4028_i2c, ge_b850v3_lvds_ptr);298299/* Only register after both bridges are probed */300if (!ge_b850v3_lvds_ptr->stdp2690_i2c)301return 0;302303return ge_b850v3_register();304}305306static void stdp4028_ge_b850v3_fw_remove(struct i2c_client *stdp4028_i2c)307{308ge_b850v3_lvds_remove();309}310311static const struct i2c_device_id stdp4028_ge_b850v3_fw_i2c_table[] = {312{ "stdp4028_ge_fw" },313{}314};315MODULE_DEVICE_TABLE(i2c, stdp4028_ge_b850v3_fw_i2c_table);316317static const struct of_device_id stdp4028_ge_b850v3_fw_match[] = {318{ .compatible = "megachips,stdp4028-ge-b850v3-fw" },319{},320};321MODULE_DEVICE_TABLE(of, stdp4028_ge_b850v3_fw_match);322323static struct i2c_driver stdp4028_ge_b850v3_fw_driver = {324.id_table = stdp4028_ge_b850v3_fw_i2c_table,325.probe = stdp4028_ge_b850v3_fw_probe,326.remove = stdp4028_ge_b850v3_fw_remove,327.driver = {328.name = "stdp4028-ge-b850v3-fw",329.of_match_table = stdp4028_ge_b850v3_fw_match,330},331};332333static int stdp2690_ge_b850v3_fw_probe(struct i2c_client *stdp2690_i2c)334{335struct device *dev = &stdp2690_i2c->dev;336int ret;337338ret = ge_b850v3_lvds_init(dev);339340if (ret)341return ret;342343ge_b850v3_lvds_ptr->stdp2690_i2c = stdp2690_i2c;344i2c_set_clientdata(stdp2690_i2c, ge_b850v3_lvds_ptr);345346/* Only register after both bridges are probed */347if (!ge_b850v3_lvds_ptr->stdp4028_i2c)348return 0;349350return ge_b850v3_register();351}352353static void stdp2690_ge_b850v3_fw_remove(struct i2c_client *stdp2690_i2c)354{355ge_b850v3_lvds_remove();356}357358static const struct i2c_device_id stdp2690_ge_b850v3_fw_i2c_table[] = {359{ "stdp2690_ge_fw" },360{}361};362MODULE_DEVICE_TABLE(i2c, stdp2690_ge_b850v3_fw_i2c_table);363364static const struct of_device_id stdp2690_ge_b850v3_fw_match[] = {365{ .compatible = "megachips,stdp2690-ge-b850v3-fw" },366{},367};368MODULE_DEVICE_TABLE(of, stdp2690_ge_b850v3_fw_match);369370static struct i2c_driver stdp2690_ge_b850v3_fw_driver = {371.id_table = stdp2690_ge_b850v3_fw_i2c_table,372.probe = stdp2690_ge_b850v3_fw_probe,373.remove = stdp2690_ge_b850v3_fw_remove,374.driver = {375.name = "stdp2690-ge-b850v3-fw",376.of_match_table = stdp2690_ge_b850v3_fw_match,377},378};379380static int __init stdpxxxx_ge_b850v3_init(void)381{382int ret;383384ret = i2c_add_driver(&stdp4028_ge_b850v3_fw_driver);385if (ret)386return ret;387388ret = i2c_add_driver(&stdp2690_ge_b850v3_fw_driver);389if (ret)390i2c_del_driver(&stdp4028_ge_b850v3_fw_driver);391392return ret;393}394module_init(stdpxxxx_ge_b850v3_init);395396static void __exit stdpxxxx_ge_b850v3_exit(void)397{398i2c_del_driver(&stdp2690_ge_b850v3_fw_driver);399i2c_del_driver(&stdp4028_ge_b850v3_fw_driver);400}401module_exit(stdpxxxx_ge_b850v3_exit);402403MODULE_AUTHOR("Peter Senna Tschudin <[email protected]>");404MODULE_AUTHOR("Martyn Welch <[email protected]>");405MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)");406MODULE_LICENSE("GPL v2");407408409