/* * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ #include #include #include #include #include #include #include "dp_bridge_hpd.h" struct dp_bridge_hpd_private { struct device *dev; struct dp_hpd base; struct dp_aux_bridge *bridge; struct delayed_work work; struct dp_hpd_cb *cb; bool hpd; bool hpd_irq; struct mutex hpd_lock; }; static int dp_bridge_hpd_connect(struct dp_bridge_hpd_private *bridge_hpd, bool hpd) { int rc = 0; if (!bridge_hpd) { pr_err("invalid input\n"); rc = -EINVAL; goto error; } bridge_hpd->base.hpd_high = hpd; bridge_hpd->base.alt_mode_cfg_done = hpd; bridge_hpd->base.hpd_irq = false; if (!bridge_hpd->cb || !bridge_hpd->cb->configure || !bridge_hpd->cb->disconnect) { pr_err("invalid cb\n"); rc = -EINVAL; goto error; } if (hpd) rc = bridge_hpd->cb->configure(bridge_hpd->dev); else rc = bridge_hpd->cb->disconnect(bridge_hpd->dev); error: return rc; } static int dp_bridge_hpd_attention(struct dp_bridge_hpd_private *bridge_hpd) { int rc = 0; if (!bridge_hpd) { pr_err("invalid input\n"); rc = -EINVAL; goto error; } bridge_hpd->base.hpd_irq = true; if (bridge_hpd->cb && bridge_hpd->cb->attention) rc = bridge_hpd->cb->attention(bridge_hpd->dev); error: return rc; } static void dp_bridge_hpd_work(struct work_struct *work) { struct delayed_work *dw = to_delayed_work(work); struct dp_bridge_hpd_private *bridge_hpd = container_of(dw, struct dp_bridge_hpd_private, work); mutex_lock(&bridge_hpd->hpd_lock); if (bridge_hpd->hpd_irq) dp_bridge_hpd_attention(bridge_hpd); else dp_bridge_hpd_connect(bridge_hpd, bridge_hpd->hpd); mutex_unlock(&bridge_hpd->hpd_lock); } static int dp_bridge_hpd_simulate_connect(struct dp_hpd *dp_hpd, bool hpd) { int rc = 0; struct dp_bridge_hpd_private *bridge_hpd; if (!dp_hpd) { pr_err("invalid input\n"); rc = -EINVAL; goto error; } bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base); dp_bridge_hpd_connect(bridge_hpd, hpd); error: return rc; } static int dp_bridge_hpd_simulate_attention(struct dp_hpd *dp_hpd, int vdo) { int rc = 0; struct dp_bridge_hpd_private *bridge_hpd; if (!dp_hpd) { pr_err("invalid input\n"); rc = -EINVAL; goto error; } bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base); dp_bridge_hpd_attention(bridge_hpd); error: return rc; } static int dp_bridge_hpd_cb(void *dp_hpd, bool hpd, bool hpd_irq) { struct dp_bridge_hpd_private *bridge_hpd = dp_hpd; mutex_lock(&bridge_hpd->hpd_lock); bridge_hpd->hpd = hpd; bridge_hpd->hpd_irq = hpd_irq; queue_delayed_work(system_wq, &bridge_hpd->work, 0); mutex_unlock(&bridge_hpd->hpd_lock); return 0; } static int dp_bridge_hpd_register(struct dp_hpd *dp_hpd) { struct dp_bridge_hpd_private *bridge_hpd; if (!dp_hpd) return -EINVAL; bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base); return bridge_hpd->bridge->register_hpd(bridge_hpd->bridge, dp_bridge_hpd_cb, bridge_hpd); } struct dp_hpd *dp_bridge_hpd_get(struct device *dev, struct dp_hpd_cb *cb, struct dp_aux_bridge *aux_bridge) { int rc = 0; struct dp_bridge_hpd_private *bridge_hpd; if (!dev || !cb) { pr_err("invalid device\n"); rc = -EINVAL; goto error; } bridge_hpd = devm_kzalloc(dev, sizeof(*bridge_hpd), GFP_KERNEL); if (!bridge_hpd) { rc = -ENOMEM; goto error; } bridge_hpd->dev = dev; bridge_hpd->cb = cb; bridge_hpd->bridge = aux_bridge; mutex_init(&bridge_hpd->hpd_lock); INIT_DELAYED_WORK(&bridge_hpd->work, dp_bridge_hpd_work); bridge_hpd->base.simulate_connect = dp_bridge_hpd_simulate_connect; bridge_hpd->base.simulate_attention = dp_bridge_hpd_simulate_attention; bridge_hpd->base.register_hpd = dp_bridge_hpd_register; return &bridge_hpd->base; error: return ERR_PTR(rc); } void dp_bridge_hpd_put(struct dp_hpd *dp_hpd) { struct dp_bridge_hpd_private *bridge_hpd; if (!dp_hpd) return; bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base); devm_kfree(bridge_hpd->dev, bridge_hpd); }