brintos

brintos / linux-shallow public Read only

0
0
Text · 7.1 KiB · 51ab4a8 Raw
310 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/* drivers/media/platform/s5p-cec/s5p_cec.c3 *4 * Samsung S5P CEC driver5 *6 * Copyright (c) 2014 Samsung Electronics Co., Ltd.7 *8 * This driver is based on the "cec interface driver for exynos soc" by9 * SangPil Moon.10 */11 12#include <linux/clk.h>13#include <linux/interrupt.h>14#include <linux/kernel.h>15#include <linux/mfd/syscon.h>16#include <linux/module.h>17#include <linux/of.h>18#include <linux/of_platform.h>19#include <linux/platform_device.h>20#include <linux/pm_runtime.h>21#include <linux/timer.h>22#include <linux/workqueue.h>23#include <media/cec.h>24#include <media/cec-notifier.h>25 26#include "exynos_hdmi_cec.h"27#include "regs-cec.h"28#include "s5p_cec.h"29 30#define CEC_NAME	"s5p-cec"31 32static int debug;33module_param(debug, int, 0644);34MODULE_PARM_DESC(debug, "debug level (0-2)");35 36static int s5p_cec_adap_enable(struct cec_adapter *adap, bool enable)37{38	int ret;39	struct s5p_cec_dev *cec = cec_get_drvdata(adap);40 41	if (enable) {42		ret = pm_runtime_resume_and_get(cec->dev);43		if (ret < 0)44			return ret;45 46		s5p_cec_reset(cec);47 48		s5p_cec_set_divider(cec);49		s5p_cec_threshold(cec);50 51		s5p_cec_unmask_tx_interrupts(cec);52		s5p_cec_unmask_rx_interrupts(cec);53		s5p_cec_enable_rx(cec);54	} else {55		s5p_cec_mask_tx_interrupts(cec);56		s5p_cec_mask_rx_interrupts(cec);57		pm_runtime_put(cec->dev);58	}59 60	return 0;61}62 63static int s5p_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)64{65	struct s5p_cec_dev *cec = cec_get_drvdata(adap);66 67	s5p_cec_set_addr(cec, addr);68	return 0;69}70 71static int s5p_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,72				 u32 signal_free_time, struct cec_msg *msg)73{74	struct s5p_cec_dev *cec = cec_get_drvdata(adap);75 76	/*77	 * Unclear if 0 retries are allowed by the hardware, so have 1 as78	 * the minimum.79	 */80	s5p_cec_copy_packet(cec, msg->msg, msg->len, max(1, attempts - 1));81	return 0;82}83 84static irqreturn_t s5p_cec_irq_handler(int irq, void *priv)85{86	struct s5p_cec_dev *cec = priv;87	u32 status = 0;88 89	status = s5p_cec_get_status(cec);90 91	dev_dbg(cec->dev, "irq received\n");92 93	if (status & CEC_STATUS_TX_DONE) {94		if (status & CEC_STATUS_TX_NACK) {95			dev_dbg(cec->dev, "CEC_STATUS_TX_NACK set\n");96			cec->tx = STATE_NACK;97		} else if (status & CEC_STATUS_TX_ERROR) {98			dev_dbg(cec->dev, "CEC_STATUS_TX_ERROR set\n");99			cec->tx = STATE_ERROR;100		} else {101			dev_dbg(cec->dev, "CEC_STATUS_TX_DONE\n");102			cec->tx = STATE_DONE;103		}104		s5p_clr_pending_tx(cec);105	}106 107	if (status & CEC_STATUS_RX_DONE) {108		if (status & CEC_STATUS_RX_ERROR) {109			dev_dbg(cec->dev, "CEC_STATUS_RX_ERROR set\n");110			s5p_cec_rx_reset(cec);111			s5p_cec_enable_rx(cec);112		} else {113			dev_dbg(cec->dev, "CEC_STATUS_RX_DONE set\n");114			if (cec->rx != STATE_IDLE)115				dev_dbg(cec->dev, "Buffer overrun (worker did not process previous message)\n");116			cec->rx = STATE_BUSY;117			cec->msg.len = status >> 24;118			if (cec->msg.len > CEC_MAX_MSG_SIZE)119				cec->msg.len = CEC_MAX_MSG_SIZE;120			cec->msg.rx_status = CEC_RX_STATUS_OK;121			s5p_cec_get_rx_buf(cec, cec->msg.len,122					cec->msg.msg);123			cec->rx = STATE_DONE;124			s5p_cec_enable_rx(cec);125		}126		/* Clear interrupt pending bit */127		s5p_clr_pending_rx(cec);128	}129	return IRQ_WAKE_THREAD;130}131 132static irqreturn_t s5p_cec_irq_handler_thread(int irq, void *priv)133{134	struct s5p_cec_dev *cec = priv;135 136	dev_dbg(cec->dev, "irq processing thread\n");137	switch (cec->tx) {138	case STATE_DONE:139		cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);140		cec->tx = STATE_IDLE;141		break;142	case STATE_NACK:143		cec_transmit_done(cec->adap,144			CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_NACK,145			0, 1, 0, 0);146		cec->tx = STATE_IDLE;147		break;148	case STATE_ERROR:149		cec_transmit_done(cec->adap,150			CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_ERROR,151			0, 0, 0, 1);152		cec->tx = STATE_IDLE;153		break;154	case STATE_BUSY:155		dev_err(cec->dev, "state set to busy, this should not occur here\n");156		break;157	default:158		break;159	}160 161	switch (cec->rx) {162	case STATE_DONE:163		cec_received_msg(cec->adap, &cec->msg);164		cec->rx = STATE_IDLE;165		break;166	default:167		break;168	}169 170	return IRQ_HANDLED;171}172 173static const struct cec_adap_ops s5p_cec_adap_ops = {174	.adap_enable = s5p_cec_adap_enable,175	.adap_log_addr = s5p_cec_adap_log_addr,176	.adap_transmit = s5p_cec_adap_transmit,177};178 179static int s5p_cec_probe(struct platform_device *pdev)180{181	struct device *dev = &pdev->dev;182	struct device *hdmi_dev;183	struct s5p_cec_dev *cec;184	bool needs_hpd = of_property_read_bool(pdev->dev.of_node, "needs-hpd");185	int ret;186 187	hdmi_dev = cec_notifier_parse_hdmi_phandle(dev);188 189	if (IS_ERR(hdmi_dev))190		return PTR_ERR(hdmi_dev);191 192	cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL);193	if (!cec)194		return -ENOMEM;195 196	cec->dev = dev;197 198	cec->irq = platform_get_irq(pdev, 0);199	if (cec->irq < 0)200		return cec->irq;201 202	ret = devm_request_threaded_irq(dev, cec->irq, s5p_cec_irq_handler,203		s5p_cec_irq_handler_thread, 0, pdev->name, cec);204	if (ret)205		return ret;206 207	cec->clk = devm_clk_get(dev, "hdmicec");208	if (IS_ERR(cec->clk))209		return PTR_ERR(cec->clk);210 211	cec->pmu = syscon_regmap_lookup_by_phandle(dev->of_node,212						 "samsung,syscon-phandle");213	if (IS_ERR(cec->pmu))214		return -EPROBE_DEFER;215 216	cec->reg = devm_platform_ioremap_resource(pdev, 0);217	if (IS_ERR(cec->reg))218		return PTR_ERR(cec->reg);219 220	cec->adap = cec_allocate_adapter(&s5p_cec_adap_ops, cec, CEC_NAME,221		CEC_CAP_DEFAULTS | (needs_hpd ? CEC_CAP_NEEDS_HPD : 0) |222		CEC_CAP_CONNECTOR_INFO, 1);223	ret = PTR_ERR_OR_ZERO(cec->adap);224	if (ret)225		return ret;226 227	cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL,228						       cec->adap);229	if (!cec->notifier) {230		ret = -ENOMEM;231		goto err_delete_adapter;232	}233 234	ret = cec_register_adapter(cec->adap, &pdev->dev);235	if (ret)236		goto err_notifier;237 238	platform_set_drvdata(pdev, cec);239	pm_runtime_enable(dev);240 241	dev_dbg(dev, "successfully probed\n");242	return 0;243 244err_notifier:245	cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);246 247err_delete_adapter:248	cec_delete_adapter(cec->adap);249	return ret;250}251 252static void s5p_cec_remove(struct platform_device *pdev)253{254	struct s5p_cec_dev *cec = platform_get_drvdata(pdev);255 256	cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);257	cec_unregister_adapter(cec->adap);258	pm_runtime_disable(&pdev->dev);259}260 261static int __maybe_unused s5p_cec_runtime_suspend(struct device *dev)262{263	struct s5p_cec_dev *cec = dev_get_drvdata(dev);264 265	clk_disable_unprepare(cec->clk);266	return 0;267}268 269static int __maybe_unused s5p_cec_runtime_resume(struct device *dev)270{271	struct s5p_cec_dev *cec = dev_get_drvdata(dev);272	int ret;273 274	ret = clk_prepare_enable(cec->clk);275	if (ret < 0)276		return ret;277	return 0;278}279 280static const struct dev_pm_ops s5p_cec_pm_ops = {281	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,282				pm_runtime_force_resume)283	SET_RUNTIME_PM_OPS(s5p_cec_runtime_suspend, s5p_cec_runtime_resume,284			   NULL)285};286 287static const struct of_device_id s5p_cec_match[] = {288	{289		.compatible	= "samsung,s5p-cec",290	},291	{},292};293MODULE_DEVICE_TABLE(of, s5p_cec_match);294 295static struct platform_driver s5p_cec_pdrv = {296	.probe	= s5p_cec_probe,297	.remove_new = s5p_cec_remove,298	.driver	= {299		.name		= CEC_NAME,300		.of_match_table	= s5p_cec_match,301		.pm		= &s5p_cec_pm_ops,302	},303};304 305module_platform_driver(s5p_cec_pdrv);306 307MODULE_AUTHOR("Kamil Debski <kamil@wypas.org>");308MODULE_LICENSE("GPL");309MODULE_DESCRIPTION("Samsung S5P CEC driver");310