2016-01-04 18:36:38 +01:00
/*
* Copyright ( c ) 2014 MediaTek Inc .
* Author : Jie Qiu < jie . qiu @ mediatek . com >
*
* 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 .
*
* 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 .
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/io.h>
# include <linux/interrupt.h>
2018-06-19 22:47:28 -07:00
# include <linux/mod_devicetable.h>
2016-01-04 18:36:38 +01:00
# include <linux/platform_device.h>
# include "mtk_cec.h"
# define TR_CONFIG 0x00
# define CLEAR_CEC_IRQ BIT(15)
# define CEC_CKGEN 0x04
# define CEC_32K_PDN BIT(19)
# define PDN BIT(16)
# define RX_EVENT 0x54
# define HDMI_PORD BIT(25)
# define HDMI_HTPLG BIT(24)
# define HDMI_PORD_INT_EN BIT(9)
# define HDMI_HTPLG_INT_EN BIT(8)
# define RX_GEN_WD 0x58
# define HDMI_PORD_INT_32K_STATUS BIT(26)
# define RX_RISC_INT_32K_STATUS BIT(25)
# define HDMI_HTPLG_INT_32K_STATUS BIT(24)
# define HDMI_PORD_INT_32K_CLR BIT(18)
# define RX_INT_32K_CLR BIT(17)
# define HDMI_HTPLG_INT_32K_CLR BIT(16)
# define HDMI_PORD_INT_32K_STA_MASK BIT(10)
# define RX_RISC_INT_32K_STA_MASK BIT(9)
# define HDMI_HTPLG_INT_32K_STA_MASK BIT(8)
# define HDMI_PORD_INT_32K_EN BIT(2)
# define RX_INT_32K_EN BIT(1)
# define HDMI_HTPLG_INT_32K_EN BIT(0)
# define NORMAL_INT_CTRL 0x5C
# define HDMI_HTPLG_INT_STA BIT(0)
# define HDMI_PORD_INT_STA BIT(1)
# define HDMI_HTPLG_INT_CLR BIT(16)
# define HDMI_PORD_INT_CLR BIT(17)
# define HDMI_FULL_INT_CLR BIT(20)
struct mtk_cec {
void __iomem * regs ;
struct clk * clk ;
int irq ;
bool hpd ;
void ( * hpd_event ) ( bool hpd , struct device * dev ) ;
struct device * hdmi_dev ;
spinlock_t lock ;
} ;
static void mtk_cec_clear_bits ( struct mtk_cec * cec , unsigned int offset ,
unsigned int bits )
{
void __iomem * reg = cec - > regs + offset ;
u32 tmp ;
tmp = readl ( reg ) ;
tmp & = ~ bits ;
writel ( tmp , reg ) ;
}
static void mtk_cec_set_bits ( struct mtk_cec * cec , unsigned int offset ,
unsigned int bits )
{
void __iomem * reg = cec - > regs + offset ;
u32 tmp ;
tmp = readl ( reg ) ;
tmp | = bits ;
writel ( tmp , reg ) ;
}
static void mtk_cec_mask ( struct mtk_cec * cec , unsigned int offset ,
unsigned int val , unsigned int mask )
{
u32 tmp = readl ( cec - > regs + offset ) & ~ mask ;
tmp | = val & mask ;
writel ( val , cec - > regs + offset ) ;
}
void mtk_cec_set_hpd_event ( struct device * dev ,
void ( * hpd_event ) ( bool hpd , struct device * dev ) ,
struct device * hdmi_dev )
{
struct mtk_cec * cec = dev_get_drvdata ( dev ) ;
unsigned long flags ;
spin_lock_irqsave ( & cec - > lock , flags ) ;
cec - > hdmi_dev = hdmi_dev ;
cec - > hpd_event = hpd_event ;
spin_unlock_irqrestore ( & cec - > lock , flags ) ;
}
bool mtk_cec_hpd_high ( struct device * dev )
{
struct mtk_cec * cec = dev_get_drvdata ( dev ) ;
unsigned int status ;
status = readl ( cec - > regs + RX_EVENT ) ;
return ( status & ( HDMI_PORD | HDMI_HTPLG ) ) = = ( HDMI_PORD | HDMI_HTPLG ) ;
}
static void mtk_cec_htplg_irq_init ( struct mtk_cec * cec )
{
mtk_cec_mask ( cec , CEC_CKGEN , 0 | CEC_32K_PDN , PDN | CEC_32K_PDN ) ;
mtk_cec_set_bits ( cec , RX_GEN_WD , HDMI_PORD_INT_32K_CLR |
RX_INT_32K_CLR | HDMI_HTPLG_INT_32K_CLR ) ;
mtk_cec_mask ( cec , RX_GEN_WD , 0 , HDMI_PORD_INT_32K_CLR | RX_INT_32K_CLR |
HDMI_HTPLG_INT_32K_CLR | HDMI_PORD_INT_32K_EN |
RX_INT_32K_EN | HDMI_HTPLG_INT_32K_EN ) ;
}
static void mtk_cec_htplg_irq_enable ( struct mtk_cec * cec )
{
mtk_cec_set_bits ( cec , RX_EVENT , HDMI_PORD_INT_EN | HDMI_HTPLG_INT_EN ) ;
}
static void mtk_cec_htplg_irq_disable ( struct mtk_cec * cec )
{
mtk_cec_clear_bits ( cec , RX_EVENT , HDMI_PORD_INT_EN | HDMI_HTPLG_INT_EN ) ;
}
static void mtk_cec_clear_htplg_irq ( struct mtk_cec * cec )
{
mtk_cec_set_bits ( cec , TR_CONFIG , CLEAR_CEC_IRQ ) ;
mtk_cec_set_bits ( cec , NORMAL_INT_CTRL , HDMI_HTPLG_INT_CLR |
HDMI_PORD_INT_CLR | HDMI_FULL_INT_CLR ) ;
mtk_cec_set_bits ( cec , RX_GEN_WD , HDMI_PORD_INT_32K_CLR |
RX_INT_32K_CLR | HDMI_HTPLG_INT_32K_CLR ) ;
usleep_range ( 5 , 10 ) ;
mtk_cec_clear_bits ( cec , NORMAL_INT_CTRL , HDMI_HTPLG_INT_CLR |
HDMI_PORD_INT_CLR | HDMI_FULL_INT_CLR ) ;
mtk_cec_clear_bits ( cec , TR_CONFIG , CLEAR_CEC_IRQ ) ;
mtk_cec_clear_bits ( cec , RX_GEN_WD , HDMI_PORD_INT_32K_CLR |
RX_INT_32K_CLR | HDMI_HTPLG_INT_32K_CLR ) ;
}
static void mtk_cec_hpd_event ( struct mtk_cec * cec , bool hpd )
{
void ( * hpd_event ) ( bool hpd , struct device * dev ) ;
struct device * hdmi_dev ;
unsigned long flags ;
spin_lock_irqsave ( & cec - > lock , flags ) ;
hpd_event = cec - > hpd_event ;
hdmi_dev = cec - > hdmi_dev ;
spin_unlock_irqrestore ( & cec - > lock , flags ) ;
if ( hpd_event )
hpd_event ( hpd , hdmi_dev ) ;
}
static irqreturn_t mtk_cec_htplg_isr_thread ( int irq , void * arg )
{
struct device * dev = arg ;
struct mtk_cec * cec = dev_get_drvdata ( dev ) ;
bool hpd ;
mtk_cec_clear_htplg_irq ( cec ) ;
hpd = mtk_cec_hpd_high ( dev ) ;
if ( cec - > hpd ! = hpd ) {
dev_dbg ( dev , " hotplug event! cur hpd = %d, hpd = %d \n " ,
cec - > hpd , hpd ) ;
cec - > hpd = hpd ;
mtk_cec_hpd_event ( cec , hpd ) ;
}
return IRQ_HANDLED ;
}
static int mtk_cec_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct mtk_cec * cec ;
struct resource * res ;
int ret ;
cec = devm_kzalloc ( dev , sizeof ( * cec ) , GFP_KERNEL ) ;
if ( ! cec )
return - ENOMEM ;
platform_set_drvdata ( pdev , cec ) ;
spin_lock_init ( & cec - > lock ) ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
cec - > regs = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( cec - > regs ) ) {
ret = PTR_ERR ( cec - > regs ) ;
dev_err ( dev , " Failed to ioremap cec: %d \n " , ret ) ;
return ret ;
}
cec - > clk = devm_clk_get ( dev , NULL ) ;
if ( IS_ERR ( cec - > clk ) ) {
ret = PTR_ERR ( cec - > clk ) ;
dev_err ( dev , " Failed to get cec clock: %d \n " , ret ) ;
return ret ;
}
cec - > irq = platform_get_irq ( pdev , 0 ) ;
if ( cec - > irq < 0 ) {
dev_err ( dev , " Failed to get cec irq: %d \n " , cec - > irq ) ;
return cec - > irq ;
}
ret = devm_request_threaded_irq ( dev , cec - > irq , NULL ,
mtk_cec_htplg_isr_thread ,
IRQF_SHARED | IRQF_TRIGGER_LOW |
IRQF_ONESHOT , " hdmi hpd " , dev ) ;
if ( ret ) {
dev_err ( dev , " Failed to register cec irq: %d \n " , ret ) ;
return ret ;
}
ret = clk_prepare_enable ( cec - > clk ) ;
if ( ret ) {
dev_err ( dev , " Failed to enable cec clock: %d \n " , ret ) ;
return ret ;
}
mtk_cec_htplg_irq_init ( cec ) ;
mtk_cec_htplg_irq_enable ( cec ) ;
return 0 ;
}
static int mtk_cec_remove ( struct platform_device * pdev )
{
struct mtk_cec * cec = platform_get_drvdata ( pdev ) ;
mtk_cec_htplg_irq_disable ( cec ) ;
clk_disable_unprepare ( cec - > clk ) ;
return 0 ;
}
static const struct of_device_id mtk_cec_of_ids [ ] = {
{ . compatible = " mediatek,mt8173-cec " , } ,
{ }
} ;
struct platform_driver mtk_cec_driver = {
. probe = mtk_cec_probe ,
. remove = mtk_cec_remove ,
. driver = {
. name = " mediatek-cec " ,
. of_match_table = mtk_cec_of_ids ,
} ,
} ;