diff options
author | Chanwoo Choi <cw00.choi@samsung.com> | 2018-03-21 13:58:23 +0900 |
---|---|---|
committer | Chanwoo Choi <cw00.choi@samsung.com> | 2018-03-21 13:58:23 +0900 |
commit | eb7768e774d12eac1189984a62925bc3bb386e13 (patch) | |
tree | 5d27211a79cf77c509bca923ab7e49709a50ca95 /drivers | |
parent | d368e7de5e8bac32a9121608a7d4c96a229d5af2 (diff) | |
parent | 688838442147d9dd94c2ef7c2c31a35cf150c5fa (diff) |
Merge branch 'ib-extcon-drm-dt-v4.17' into extcon-next
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/extcon/extcon.c | 44 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/sil-sii8620.c | 97 |
2 files changed, 128 insertions, 13 deletions
diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index cb38c2747684..8bff5fd18185 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -1336,6 +1336,28 @@ void extcon_dev_unregister(struct extcon_dev *edev) EXPORT_SYMBOL_GPL(extcon_dev_unregister); #ifdef CONFIG_OF + +/* + * extcon_find_edev_by_node - Find the extcon device from devicetree. + * @node : OF node identifying edev + * + * Return the pointer of extcon device if success or ERR_PTR(err) if fail. + */ +struct extcon_dev *extcon_find_edev_by_node(struct device_node *node) +{ + struct extcon_dev *edev; + + mutex_lock(&extcon_dev_list_lock); + list_for_each_entry(edev, &extcon_dev_list, entry) + if (edev->dev.parent && edev->dev.parent->of_node == node) + goto out; + edev = ERR_PTR(-EPROBE_DEFER); +out: + mutex_unlock(&extcon_dev_list_lock); + + return edev; +} + /* * extcon_get_edev_by_phandle - Get the extcon device from devicetree. * @dev : the instance to the given device @@ -1363,25 +1385,27 @@ struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) return ERR_PTR(-ENODEV); } - mutex_lock(&extcon_dev_list_lock); - list_for_each_entry(edev, &extcon_dev_list, entry) { - if (edev->dev.parent && edev->dev.parent->of_node == node) { - mutex_unlock(&extcon_dev_list_lock); - of_node_put(node); - return edev; - } - } - mutex_unlock(&extcon_dev_list_lock); + edev = extcon_find_edev_by_node(node); of_node_put(node); - return ERR_PTR(-EPROBE_DEFER); + return edev; } + #else + +struct extcon_dev *extcon_find_edev_by_node(struct device_node *node) +{ + return ERR_PTR(-ENOSYS); +} + struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) { return ERR_PTR(-ENOSYS); } + #endif /* CONFIG_OF */ + +EXPORT_SYMBOL_GPL(extcon_find_edev_by_node); EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle); /** diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c index 86789f8918a4..7ab36042a822 100644 --- a/drivers/gpu/drm/bridge/sil-sii8620.c +++ b/drivers/gpu/drm/bridge/sil-sii8620.c @@ -17,6 +17,7 @@ #include <linux/clk.h> #include <linux/delay.h> +#include <linux/extcon.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/interrupt.h> @@ -25,6 +26,7 @@ #include <linux/list.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/of_graph.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> @@ -81,6 +83,10 @@ struct sii8620 { struct edid *edid; unsigned int gen2_write_burst:1; enum sii8620_mt_state mt_state; + struct extcon_dev *extcon; + struct notifier_block extcon_nb; + struct work_struct extcon_wq; + int cable_state; struct list_head mt_queue; struct { int r_size; @@ -2170,6 +2176,77 @@ static void sii8620_init_rcp_input_dev(struct sii8620 *ctx) ctx->rc_dev = rc_dev; } +static void sii8620_cable_out(struct sii8620 *ctx) +{ + disable_irq(to_i2c_client(ctx->dev)->irq); + sii8620_hw_off(ctx); +} + +static void sii8620_extcon_work(struct work_struct *work) +{ + struct sii8620 *ctx = + container_of(work, struct sii8620, extcon_wq); + int state = extcon_get_state(ctx->extcon, EXTCON_DISP_MHL); + + if (state == ctx->cable_state) + return; + + ctx->cable_state = state; + + if (state > 0) + sii8620_cable_in(ctx); + else + sii8620_cable_out(ctx); +} + +static int sii8620_extcon_notifier(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct sii8620 *ctx = + container_of(self, struct sii8620, extcon_nb); + + schedule_work(&ctx->extcon_wq); + + return NOTIFY_DONE; +} + +static int sii8620_extcon_init(struct sii8620 *ctx) +{ + struct extcon_dev *edev; + struct device_node *musb, *muic; + int ret; + + /* get micro-USB connector node */ + musb = of_graph_get_remote_node(ctx->dev->of_node, 1, -1); + /* next get micro-USB Interface Controller node */ + muic = of_get_next_parent(musb); + + if (!muic) { + dev_info(ctx->dev, "no extcon found, switching to 'always on' mode\n"); + return 0; + } + + edev = extcon_find_edev_by_node(muic); + of_node_put(muic); + if (IS_ERR(edev)) { + if (PTR_ERR(edev) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_err(ctx->dev, "Invalid or missing extcon\n"); + return PTR_ERR(edev); + } + + ctx->extcon = edev; + ctx->extcon_nb.notifier_call = sii8620_extcon_notifier; + INIT_WORK(&ctx->extcon_wq, sii8620_extcon_work); + ret = extcon_register_notifier(edev, EXTCON_DISP_MHL, &ctx->extcon_nb); + if (ret) { + dev_err(ctx->dev, "failed to register notifier for MHL\n"); + return ret; + } + + return 0; +} + static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge) { return container_of(bridge, struct sii8620, bridge); @@ -2302,13 +2379,20 @@ static int sii8620_probe(struct i2c_client *client, if (ret) return ret; + ret = sii8620_extcon_init(ctx); + if (ret < 0) { + dev_err(ctx->dev, "failed to initialize EXTCON\n"); + return ret; + } + i2c_set_clientdata(client, ctx); ctx->bridge.funcs = &sii8620_bridge_funcs; ctx->bridge.of_node = dev->of_node; drm_bridge_add(&ctx->bridge); - sii8620_cable_in(ctx); + if (!ctx->extcon) + sii8620_cable_in(ctx); return 0; } @@ -2317,8 +2401,15 @@ static int sii8620_remove(struct i2c_client *client) { struct sii8620 *ctx = i2c_get_clientdata(client); - disable_irq(to_i2c_client(ctx->dev)->irq); - sii8620_hw_off(ctx); + if (ctx->extcon) { + extcon_unregister_notifier(ctx->extcon, EXTCON_DISP_MHL, + &ctx->extcon_nb); + flush_work(&ctx->extcon_wq); + if (ctx->cable_state > 0) + sii8620_cable_out(ctx); + } else { + sii8620_cable_out(ctx); + } drm_bridge_remove(&ctx->bridge); return 0; |