2019-05-27 09:55:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2016-01-04 20:36:38 +03:00
/*
* Copyright ( c ) 2014 MediaTek Inc .
* Author : Jie Qiu < jie . qiu @ mediatek . com >
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/i2c.h>
# include <linux/time.h>
# include <linux/delay.h>
# include <linux/errno.h>
# include <linux/err.h>
# include <linux/platform_device.h>
# include <linux/clk.h>
# include <linux/slab.h>
# include <linux/io.h>
# include <linux/iopoll.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/of_platform.h>
# define SIF1_CLOK (288)
# define DDC_DDCMCTL0 (0x0)
# define DDCM_ODRAIN BIT(31)
# define DDCM_CLK_DIV_OFFSET (16)
# define DDCM_CLK_DIV_MASK (0xfff << 16)
# define DDCM_CS_STATUS BIT(4)
# define DDCM_SCL_STATE BIT(3)
# define DDCM_SDA_STATE BIT(2)
# define DDCM_SM0EN BIT(1)
# define DDCM_SCL_STRECH BIT(0)
# define DDC_DDCMCTL1 (0x4)
# define DDCM_ACK_OFFSET (16)
# define DDCM_ACK_MASK (0xff << 16)
# define DDCM_PGLEN_OFFSET (8)
# define DDCM_PGLEN_MASK (0x7 << 8)
# define DDCM_SIF_MODE_OFFSET (4)
# define DDCM_SIF_MODE_MASK (0x7 << 4)
# define DDCM_START (0x1)
# define DDCM_WRITE_DATA (0x2)
# define DDCM_STOP (0x3)
# define DDCM_READ_DATA_NO_ACK (0x4)
# define DDCM_READ_DATA_ACK (0x5)
# define DDCM_TRI BIT(0)
# define DDC_DDCMD0 (0x8)
# define DDCM_DATA3 (0xff << 24)
# define DDCM_DATA2 (0xff << 16)
# define DDCM_DATA1 (0xff << 8)
# define DDCM_DATA0 (0xff << 0)
# define DDC_DDCMD1 (0xc)
# define DDCM_DATA7 (0xff << 24)
# define DDCM_DATA6 (0xff << 16)
# define DDCM_DATA5 (0xff << 8)
# define DDCM_DATA4 (0xff << 0)
struct mtk_hdmi_ddc {
struct i2c_adapter adap ;
struct clk * clk ;
void __iomem * regs ;
} ;
static inline void sif_set_bit ( struct mtk_hdmi_ddc * ddc , unsigned int offset ,
unsigned int val )
{
writel ( readl ( ddc - > regs + offset ) | val , ddc - > regs + offset ) ;
}
static inline void sif_clr_bit ( struct mtk_hdmi_ddc * ddc , unsigned int offset ,
unsigned int val )
{
writel ( readl ( ddc - > regs + offset ) & ~ val , ddc - > regs + offset ) ;
}
static inline bool sif_bit_is_set ( struct mtk_hdmi_ddc * ddc , unsigned int offset ,
unsigned int val )
{
return ( readl ( ddc - > regs + offset ) & val ) = = val ;
}
static inline void sif_write_mask ( struct mtk_hdmi_ddc * ddc , unsigned int offset ,
unsigned int mask , unsigned int shift ,
unsigned int val )
{
unsigned int tmp ;
tmp = readl ( ddc - > regs + offset ) ;
tmp & = ~ mask ;
tmp | = ( val < < shift ) & mask ;
writel ( tmp , ddc - > regs + offset ) ;
}
static inline unsigned int sif_read_mask ( struct mtk_hdmi_ddc * ddc ,
unsigned int offset , unsigned int mask ,
unsigned int shift )
{
return ( readl ( ddc - > regs + offset ) & mask ) > > shift ;
}
static void ddcm_trigger_mode ( struct mtk_hdmi_ddc * ddc , int mode )
{
u32 val ;
sif_write_mask ( ddc , DDC_DDCMCTL1 , DDCM_SIF_MODE_MASK ,
DDCM_SIF_MODE_OFFSET , mode ) ;
sif_set_bit ( ddc , DDC_DDCMCTL1 , DDCM_TRI ) ;
readl_poll_timeout ( ddc - > regs + DDC_DDCMCTL1 , val ,
( val & DDCM_TRI ) ! = DDCM_TRI , 4 , 20000 ) ;
}
static int mtk_hdmi_ddc_read_msg ( struct mtk_hdmi_ddc * ddc , struct i2c_msg * msg )
{
struct device * dev = ddc - > adap . dev . parent ;
u32 remain_count , ack_count , ack_final , read_count , temp_count ;
u32 index = 0 ;
u32 ack ;
int i ;
ddcm_trigger_mode ( ddc , DDCM_START ) ;
sif_write_mask ( ddc , DDC_DDCMD0 , 0xff , 0 , ( msg - > addr < < 1 ) | 0x01 ) ;
sif_write_mask ( ddc , DDC_DDCMCTL1 , DDCM_PGLEN_MASK , DDCM_PGLEN_OFFSET ,
0x00 ) ;
ddcm_trigger_mode ( ddc , DDCM_WRITE_DATA ) ;
ack = sif_read_mask ( ddc , DDC_DDCMCTL1 , DDCM_ACK_MASK , DDCM_ACK_OFFSET ) ;
dev_dbg ( dev , " ack = 0x%x \n " , ack ) ;
if ( ack ! = 0x01 ) {
dev_err ( dev , " i2c ack err! \n " ) ;
return - ENXIO ;
}
remain_count = msg - > len ;
ack_count = ( msg - > len - 1 ) / 8 ;
ack_final = 0 ;
while ( remain_count > 0 ) {
if ( ack_count > 0 ) {
read_count = 8 ;
ack_final = 0 ;
ack_count - - ;
} else {
read_count = remain_count ;
ack_final = 1 ;
}
sif_write_mask ( ddc , DDC_DDCMCTL1 , DDCM_PGLEN_MASK ,
DDCM_PGLEN_OFFSET , read_count - 1 ) ;
ddcm_trigger_mode ( ddc , ( ack_final = = 1 ) ?
DDCM_READ_DATA_NO_ACK :
DDCM_READ_DATA_ACK ) ;
ack = sif_read_mask ( ddc , DDC_DDCMCTL1 , DDCM_ACK_MASK ,
DDCM_ACK_OFFSET ) ;
temp_count = 0 ;
while ( ( ( ack & ( 1 < < temp_count ) ) ! = 0 ) & & ( temp_count < 8 ) )
temp_count + + ;
if ( ( ( ack_final = = 1 ) & & ( temp_count ! = ( read_count - 1 ) ) ) | |
( ( ack_final = = 0 ) & & ( temp_count ! = read_count ) ) ) {
dev_err ( dev , " Address NACK! ACK(0x%x) \n " , ack ) ;
break ;
}
for ( i = read_count ; i > = 1 ; i - - ) {
int shift ;
int offset ;
if ( i > 4 ) {
offset = DDC_DDCMD1 ;
shift = ( i - 5 ) * 8 ;
} else {
offset = DDC_DDCMD0 ;
shift = ( i - 1 ) * 8 ;
}
msg - > buf [ index + i - 1 ] = sif_read_mask ( ddc , offset ,
0xff < < shift ,
shift ) ;
}
remain_count - = read_count ;
index + = read_count ;
}
return 0 ;
}
static int mtk_hdmi_ddc_write_msg ( struct mtk_hdmi_ddc * ddc , struct i2c_msg * msg )
{
struct device * dev = ddc - > adap . dev . parent ;
u32 ack ;
ddcm_trigger_mode ( ddc , DDCM_START ) ;
sif_write_mask ( ddc , DDC_DDCMD0 , DDCM_DATA0 , 0 , msg - > addr < < 1 ) ;
sif_write_mask ( ddc , DDC_DDCMD0 , DDCM_DATA1 , 8 , msg - > buf [ 0 ] ) ;
sif_write_mask ( ddc , DDC_DDCMCTL1 , DDCM_PGLEN_MASK , DDCM_PGLEN_OFFSET ,
0x1 ) ;
ddcm_trigger_mode ( ddc , DDCM_WRITE_DATA ) ;
ack = sif_read_mask ( ddc , DDC_DDCMCTL1 , DDCM_ACK_MASK , DDCM_ACK_OFFSET ) ;
dev_dbg ( dev , " ack = %d \n " , ack ) ;
if ( ack ! = 0x03 ) {
dev_err ( dev , " i2c ack err! \n " ) ;
return - EIO ;
}
return 0 ;
}
static int mtk_hdmi_ddc_xfer ( struct i2c_adapter * adapter ,
struct i2c_msg * msgs , int num )
{
struct mtk_hdmi_ddc * ddc = adapter - > algo_data ;
struct device * dev = adapter - > dev . parent ;
int ret ;
int i ;
if ( ! ddc ) {
dev_err ( dev , " invalid arguments \n " ) ;
return - EINVAL ;
}
sif_set_bit ( ddc , DDC_DDCMCTL0 , DDCM_SCL_STRECH ) ;
sif_set_bit ( ddc , DDC_DDCMCTL0 , DDCM_SM0EN ) ;
sif_clr_bit ( ddc , DDC_DDCMCTL0 , DDCM_ODRAIN ) ;
if ( sif_bit_is_set ( ddc , DDC_DDCMCTL1 , DDCM_TRI ) ) {
dev_err ( dev , " ddc line is busy! \n " ) ;
return - EBUSY ;
}
sif_write_mask ( ddc , DDC_DDCMCTL0 , DDCM_CLK_DIV_MASK ,
DDCM_CLK_DIV_OFFSET , SIF1_CLOK ) ;
for ( i = 0 ; i < num ; i + + ) {
struct i2c_msg * msg = & msgs [ i ] ;
dev_dbg ( dev , " i2c msg, adr:0x%x, flags:%d, len :0x%x \n " ,
msg - > addr , msg - > flags , msg - > len ) ;
if ( msg - > flags & I2C_M_RD )
ret = mtk_hdmi_ddc_read_msg ( ddc , msg ) ;
else
ret = mtk_hdmi_ddc_write_msg ( ddc , msg ) ;
if ( ret < 0 )
goto xfer_end ;
}
ddcm_trigger_mode ( ddc , DDCM_STOP ) ;
return i ;
xfer_end :
ddcm_trigger_mode ( ddc , DDCM_STOP ) ;
dev_err ( dev , " ddc failed! \n " ) ;
return ret ;
}
static u32 mtk_hdmi_ddc_func ( struct i2c_adapter * adapter )
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL ;
}
static const struct i2c_algorithm mtk_hdmi_ddc_algorithm = {
. master_xfer = mtk_hdmi_ddc_xfer ,
. functionality = mtk_hdmi_ddc_func ,
} ;
static int mtk_hdmi_ddc_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct mtk_hdmi_ddc * ddc ;
struct resource * mem ;
int ret ;
ddc = devm_kzalloc ( dev , sizeof ( struct mtk_hdmi_ddc ) , GFP_KERNEL ) ;
if ( ! ddc )
return - ENOMEM ;
ddc - > clk = devm_clk_get ( dev , " ddc-i2c " ) ;
if ( IS_ERR ( ddc - > clk ) ) {
dev_err ( dev , " get ddc_clk failed: %p , \n " , ddc - > clk ) ;
return PTR_ERR ( ddc - > clk ) ;
}
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
ddc - > regs = devm_ioremap_resource ( & pdev - > dev , mem ) ;
if ( IS_ERR ( ddc - > regs ) )
return PTR_ERR ( ddc - > regs ) ;
ret = clk_prepare_enable ( ddc - > clk ) ;
if ( ret ) {
dev_err ( dev , " enable ddc clk failed! \n " ) ;
return ret ;
}
strlcpy ( ddc - > adap . name , " mediatek-hdmi-ddc " , sizeof ( ddc - > adap . name ) ) ;
ddc - > adap . owner = THIS_MODULE ;
ddc - > adap . class = I2C_CLASS_DDC ;
ddc - > adap . algo = & mtk_hdmi_ddc_algorithm ;
ddc - > adap . retries = 3 ;
ddc - > adap . dev . of_node = dev - > of_node ;
ddc - > adap . algo_data = ddc ;
ddc - > adap . dev . parent = & pdev - > dev ;
ret = i2c_add_adapter ( & ddc - > adap ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed to add bus to i2c core \n " ) ;
goto err_clk_disable ;
}
platform_set_drvdata ( pdev , ddc ) ;
dev_dbg ( dev , " ddc->adap: %p \n " , & ddc - > adap ) ;
dev_dbg ( dev , " ddc->clk: %p \n " , ddc - > clk ) ;
dev_dbg ( dev , " physical adr: %pa, end: %pa \n " , & mem - > start ,
& mem - > end ) ;
return 0 ;
err_clk_disable :
clk_disable_unprepare ( ddc - > clk ) ;
return ret ;
}
static int mtk_hdmi_ddc_remove ( struct platform_device * pdev )
{
struct mtk_hdmi_ddc * ddc = platform_get_drvdata ( pdev ) ;
i2c_del_adapter ( & ddc - > adap ) ;
clk_disable_unprepare ( ddc - > clk ) ;
return 0 ;
}
static const struct of_device_id mtk_hdmi_ddc_match [ ] = {
{ . compatible = " mediatek,mt8173-hdmi-ddc " , } ,
{ } ,
} ;
struct platform_driver mtk_hdmi_ddc_driver = {
. probe = mtk_hdmi_ddc_probe ,
. remove = mtk_hdmi_ddc_remove ,
. driver = {
. name = " mediatek-hdmi-ddc " ,
. of_match_table = mtk_hdmi_ddc_match ,
} ,
} ;
MODULE_AUTHOR ( " Jie Qiu <jie.qiu@mediatek.com> " ) ;
MODULE_DESCRIPTION ( " MediaTek HDMI DDC Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;