// SPDX-License-Identifier: GPL-2.0-only /* * TDA9950 Consumer Electronics Control driver * * The NXP TDA9950 implements the HDMI Consumer Electronics Control * interface. The host interface is similar to a mailbox: the data * registers starting at REG_CDR0 are written to send a command to the * internal CPU, and replies are read from these registers. * * As the data registers represent a mailbox, they must be accessed * as a single I2C transaction. See the TDA9950 data sheet for details.
*/ #include <linux/delay.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/module.h> #include <linux/platform_data/tda9950.h> #include <linux/slab.h> #include <drm/drm_edid.h> #include <media/cec.h> #include <media/cec-notifier.h>
/* * This should never happen: the data sheet says that there will * always be a valid message if the interrupt line is asserted.
*/ if (buf[0] == 0) {
dev_warn(&priv->client->dev, "interrupt pending, but no message?\n"); return IRQ_NONE;
}
switch (buf[1]) { case CDR1_CNF: /* transmit result */
arb_lost_cnt = nack_cnt = err_cnt = 0; switch (buf[2]) { case CDR2_CNF_SUCCESS:
tx_status = CEC_TX_STATUS_OK; break;
case CDR2_CNF_ARB_ERROR:
tx_status = CEC_TX_STATUS_ARB_LOST;
arb_lost_cnt = cconr; break;
case CDR2_CNF_NACK_ADDR:
tx_status = CEC_TX_STATUS_NACK;
nack_cnt = cconr; break;
default: /* some other error, refer to TDA9950 docs */
dev_err(&priv->client->dev, "CNF reply error 0x%02x\n",
buf[2]);
tx_status = CEC_TX_STATUS_ERROR;
err_cnt = cconr; break;
} /* TDA9950 executes all retries for us */ if (tx_status != CEC_TX_STATUS_OK)
tx_status |= CEC_TX_STATUS_MAX_RETRIES;
cec_transmit_done(priv->adap, tx_status, arb_lost_cnt,
nack_cnt, 0, err_cnt); break;
case CDR1_IND:
priv->rx_msg.len = buf[0] - 2; if (priv->rx_msg.len > CEC_MAX_MSG_SIZE)
priv->rx_msg.len = CEC_MAX_MSG_SIZE;
/* * When operating as part of the TDA998x, we need additional handling * to initialise and shut down the TDA9950 part of the device. These * two hooks are provided to allow the TDA998x code to perform those * activities.
*/ staticint tda9950_glue_open(struct tda9950_priv *priv)
{ int ret = 0;
if (priv->glue && priv->glue->open)
ret = priv->glue->open(priv->glue->data);
/* Stop the command processor */
tda9950_write(client, REG_CCR, 0);
/* Wait up to .5s for it to signal non-busy */ do {
csr = tda9950_read(client, REG_CSR); if (!(csr & CSR_BUSY) || !--timeout) break;
msleep(10);
} while (1);
/* Warn the user that their IRQ may die if it's shared. */ if (csr & CSR_BUSY)
dev_warn(&client->dev, "command processor failed to stop, irq%d may die (csr=0x%02x)\n",
client->irq, csr);
/* * When operating as part of the TDA998x, we need to claim additional * resources. These two hooks permit the management of those resources.
*/ staticvoid tda9950_devm_glue_exit(void *data)
{ struct tda9950_glue *glue = data;
if (glue && glue->exit)
glue->exit(glue->data);
}
staticint tda9950_devm_glue_init(struct device *dev, struct tda9950_glue *glue)
{ int ret;
if (glue && glue->init) {
ret = glue->init(glue->data); if (ret) return ret;
}
ret = devm_add_action(dev, tda9950_devm_glue_exit, glue); if (ret)
tda9950_devm_glue_exit(glue);
/* * We must have I2C functionality: our multi-byte accesses * must be performed as a single contiguous transaction.
*/ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "adapter does not support I2C functionality\n"); return -ENXIO;
}
/* We must have an interrupt to be functional. */ if (client->irq <= 0) {
dev_err(&client->dev, "driver requires an interrupt\n"); return -ENXIO;
}
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM;
priv->client = client;
priv->glue = glue;
i2c_set_clientdata(client, priv);
/* * If we're part of a TDA998x, we want the class devices to be * associated with the HDMI Tx so we have a tight relationship * between the HDMI interface and the CEC interface.
*/
priv->hdmi = dev; if (glue && glue->parent)
priv->hdmi = glue->parent;
irqflags = IRQF_TRIGGER_FALLING; if (glue)
irqflags = glue->irq_flags;
ret = devm_request_threaded_irq(dev, client->irq, NULL, tda9950_irq,
irqflags | IRQF_SHARED | IRQF_ONESHOT,
dev_name(&client->dev), priv); if (ret < 0) return ret;
priv->notify = cec_notifier_cec_adap_register(priv->hdmi, NULL,
priv->adap); if (!priv->notify) return -ENOMEM;
ret = cec_register_adapter(priv->adap, priv->hdmi); if (ret < 0) {
cec_notifier_cec_adap_unregister(priv->notify, priv->adap); return ret;
}
/* * CEC documentation says we must not call cec_delete_adapter * after a successful call to cec_register_adapter().
*/
devm_remove_action(dev, tda9950_cec_del, priv);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.