linux/drivers/media/platform/s5p-cec/exynos_hdmi_cecctrl.c
Hans Verkuil a93d429b51 [media] s5p-cec: add cec-notifier support, move out of staging
By using the CEC notifier framework there is no longer any reason
to manually set the physical address. This was the one blocking
issue that prevented this driver from going out of staging, so do
this move as well.

Update the bindings documenting the new hdmi phandle and
update exynos4.dtsi accordingly.

Tested with my Odroid U3.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Acked-by: Krzysztof Kozlowski <krzk@kernel.org>
CC: linux-samsung-soc@vger.kernel.org
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-04-10 13:14:03 -03:00

209 lines
4.8 KiB
C

/* drivers/media/platform/s5p-cec/exynos_hdmi_cecctrl.c
*
* Copyright (c) 2009, 2014 Samsung Electronics
* http://www.samsung.com/
*
* cec ftn file for Samsung TVOUT driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/io.h>
#include <linux/device.h>
#include "exynos_hdmi_cec.h"
#include "regs-cec.h"
#define S5P_HDMI_FIN 24000000
#define CEC_DIV_RATIO 320000
#define CEC_MESSAGE_BROADCAST_MASK 0x0F
#define CEC_MESSAGE_BROADCAST 0x0F
#define CEC_FILTER_THRESHOLD 0x15
void s5p_cec_set_divider(struct s5p_cec_dev *cec)
{
u32 div_ratio, div_val;
unsigned int reg;
div_ratio = S5P_HDMI_FIN / CEC_DIV_RATIO - 1;
if (regmap_read(cec->pmu, EXYNOS_HDMI_PHY_CONTROL, &reg)) {
dev_err(cec->dev, "failed to read phy control\n");
return;
}
reg = (reg & ~(0x3FF << 16)) | (div_ratio << 16);
if (regmap_write(cec->pmu, EXYNOS_HDMI_PHY_CONTROL, reg)) {
dev_err(cec->dev, "failed to write phy control\n");
return;
}
div_val = CEC_DIV_RATIO * 0.00005 - 1;
writeb(0x0, cec->reg + S5P_CEC_DIVISOR_3);
writeb(0x0, cec->reg + S5P_CEC_DIVISOR_2);
writeb(0x0, cec->reg + S5P_CEC_DIVISOR_1);
writeb(div_val, cec->reg + S5P_CEC_DIVISOR_0);
}
void s5p_cec_enable_rx(struct s5p_cec_dev *cec)
{
u8 reg;
reg = readb(cec->reg + S5P_CEC_RX_CTRL);
reg |= S5P_CEC_RX_CTRL_ENABLE;
writeb(reg, cec->reg + S5P_CEC_RX_CTRL);
}
void s5p_cec_mask_rx_interrupts(struct s5p_cec_dev *cec)
{
u8 reg;
reg = readb(cec->reg + S5P_CEC_IRQ_MASK);
reg |= S5P_CEC_IRQ_RX_DONE;
reg |= S5P_CEC_IRQ_RX_ERROR;
writeb(reg, cec->reg + S5P_CEC_IRQ_MASK);
}
void s5p_cec_unmask_rx_interrupts(struct s5p_cec_dev *cec)
{
u8 reg;
reg = readb(cec->reg + S5P_CEC_IRQ_MASK);
reg &= ~S5P_CEC_IRQ_RX_DONE;
reg &= ~S5P_CEC_IRQ_RX_ERROR;
writeb(reg, cec->reg + S5P_CEC_IRQ_MASK);
}
void s5p_cec_mask_tx_interrupts(struct s5p_cec_dev *cec)
{
u8 reg;
reg = readb(cec->reg + S5P_CEC_IRQ_MASK);
reg |= S5P_CEC_IRQ_TX_DONE;
reg |= S5P_CEC_IRQ_TX_ERROR;
writeb(reg, cec->reg + S5P_CEC_IRQ_MASK);
}
void s5p_cec_unmask_tx_interrupts(struct s5p_cec_dev *cec)
{
u8 reg;
reg = readb(cec->reg + S5P_CEC_IRQ_MASK);
reg &= ~S5P_CEC_IRQ_TX_DONE;
reg &= ~S5P_CEC_IRQ_TX_ERROR;
writeb(reg, cec->reg + S5P_CEC_IRQ_MASK);
}
void s5p_cec_reset(struct s5p_cec_dev *cec)
{
u8 reg;
writeb(S5P_CEC_RX_CTRL_RESET, cec->reg + S5P_CEC_RX_CTRL);
writeb(S5P_CEC_TX_CTRL_RESET, cec->reg + S5P_CEC_TX_CTRL);
reg = readb(cec->reg + 0xc4);
reg &= ~0x1;
writeb(reg, cec->reg + 0xc4);
}
void s5p_cec_tx_reset(struct s5p_cec_dev *cec)
{
writeb(S5P_CEC_TX_CTRL_RESET, cec->reg + S5P_CEC_TX_CTRL);
}
void s5p_cec_rx_reset(struct s5p_cec_dev *cec)
{
u8 reg;
writeb(S5P_CEC_RX_CTRL_RESET, cec->reg + S5P_CEC_RX_CTRL);
reg = readb(cec->reg + 0xc4);
reg &= ~0x1;
writeb(reg, cec->reg + 0xc4);
}
void s5p_cec_threshold(struct s5p_cec_dev *cec)
{
writeb(CEC_FILTER_THRESHOLD, cec->reg + S5P_CEC_RX_FILTER_TH);
writeb(0, cec->reg + S5P_CEC_RX_FILTER_CTRL);
}
void s5p_cec_copy_packet(struct s5p_cec_dev *cec, char *data,
size_t count, u8 retries)
{
int i = 0;
u8 reg;
while (i < count) {
writeb(data[i], cec->reg + (S5P_CEC_TX_BUFF0 + (i * 4)));
i++;
}
writeb(count, cec->reg + S5P_CEC_TX_BYTES);
reg = readb(cec->reg + S5P_CEC_TX_CTRL);
reg |= S5P_CEC_TX_CTRL_START;
reg &= ~0x70;
reg |= retries << 4;
if ((data[0] & CEC_MESSAGE_BROADCAST_MASK) == CEC_MESSAGE_BROADCAST) {
dev_dbg(cec->dev, "Broadcast");
reg |= S5P_CEC_TX_CTRL_BCAST;
} else {
dev_dbg(cec->dev, "No Broadcast");
reg &= ~S5P_CEC_TX_CTRL_BCAST;
}
writeb(reg, cec->reg + S5P_CEC_TX_CTRL);
dev_dbg(cec->dev, "cec-tx: cec count (%zu): %*ph", count,
(int)count, data);
}
void s5p_cec_set_addr(struct s5p_cec_dev *cec, u32 addr)
{
writeb(addr & 0x0F, cec->reg + S5P_CEC_LOGIC_ADDR);
}
u32 s5p_cec_get_status(struct s5p_cec_dev *cec)
{
u32 status = 0;
status = readb(cec->reg + S5P_CEC_STATUS_0);
status |= readb(cec->reg + S5P_CEC_STATUS_1) << 8;
status |= readb(cec->reg + S5P_CEC_STATUS_2) << 16;
status |= readb(cec->reg + S5P_CEC_STATUS_3) << 24;
dev_dbg(cec->dev, "status = 0x%x!\n", status);
return status;
}
void s5p_clr_pending_tx(struct s5p_cec_dev *cec)
{
writeb(S5P_CEC_IRQ_TX_DONE | S5P_CEC_IRQ_TX_ERROR,
cec->reg + S5P_CEC_IRQ_CLEAR);
}
void s5p_clr_pending_rx(struct s5p_cec_dev *cec)
{
writeb(S5P_CEC_IRQ_RX_DONE | S5P_CEC_IRQ_RX_ERROR,
cec->reg + S5P_CEC_IRQ_CLEAR);
}
void s5p_cec_get_rx_buf(struct s5p_cec_dev *cec, u32 size, u8 *buffer)
{
u32 i = 0;
char debug[40];
while (i < size) {
buffer[i] = readb(cec->reg + S5P_CEC_RX_BUFF0 + (i * 4));
sprintf(debug + i * 2, "%02x ", buffer[i]);
i++;
}
dev_dbg(cec->dev, "cec-rx: cec size(%d): %s", size, debug);
}