2015-09-07 14:15:27 +03:00
/* drivers/media/platform/s5p-cec/s5p_cec.c
*
* Samsung S5P CEC driver
*
* Copyright ( c ) 2014 Samsung Electronics Co . , Ltd .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This driver is based on the " cec interface driver for exynos soc " by
* SangPil Moon .
*/
# include <linux/clk.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/mfd/syscon.h>
# include <linux/module.h>
# include <linux/of.h>
2016-12-13 17:37:16 +03:00
# include <linux/of_platform.h>
2015-09-07 14:15:27 +03:00
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
# include <linux/timer.h>
# include <linux/workqueue.h>
# include <media/cec.h>
2016-12-13 17:37:16 +03:00
# include <media/cec-notifier.h>
2015-09-07 14:15:27 +03:00
# include "exynos_hdmi_cec.h"
# include "regs-cec.h"
# include "s5p_cec.h"
# define CEC_NAME "s5p-cec"
static int debug ;
module_param ( debug , int , 0644 ) ;
MODULE_PARM_DESC ( debug , " debug level (0-2) " ) ;
static int s5p_cec_adap_enable ( struct cec_adapter * adap , bool enable )
{
2017-03-24 19:47:54 +03:00
struct s5p_cec_dev * cec = cec_get_drvdata ( adap ) ;
2015-09-07 14:15:27 +03:00
if ( enable ) {
2016-06-28 17:44:06 +03:00
pm_runtime_get_sync ( cec - > dev ) ;
2015-09-07 14:15:27 +03:00
s5p_cec_reset ( cec ) ;
s5p_cec_set_divider ( cec ) ;
s5p_cec_threshold ( cec ) ;
s5p_cec_unmask_tx_interrupts ( cec ) ;
s5p_cec_unmask_rx_interrupts ( cec ) ;
s5p_cec_enable_rx ( cec ) ;
} else {
s5p_cec_mask_tx_interrupts ( cec ) ;
s5p_cec_mask_rx_interrupts ( cec ) ;
pm_runtime_disable ( cec - > dev ) ;
}
return 0 ;
}
static int s5p_cec_adap_log_addr ( struct cec_adapter * adap , u8 addr )
{
2017-03-24 19:47:54 +03:00
struct s5p_cec_dev * cec = cec_get_drvdata ( adap ) ;
2015-09-07 14:15:27 +03:00
s5p_cec_set_addr ( cec , addr ) ;
return 0 ;
}
static int s5p_cec_adap_transmit ( struct cec_adapter * adap , u8 attempts ,
u32 signal_free_time , struct cec_msg * msg )
{
2017-03-24 19:47:54 +03:00
struct s5p_cec_dev * cec = cec_get_drvdata ( adap ) ;
2015-09-07 14:15:27 +03:00
/*
* Unclear if 0 retries are allowed by the hardware , so have 1 as
* the minimum .
*/
s5p_cec_copy_packet ( cec , msg - > msg , msg - > len , max ( 1 , attempts - 1 ) ) ;
return 0 ;
}
static irqreturn_t s5p_cec_irq_handler ( int irq , void * priv )
{
struct s5p_cec_dev * cec = priv ;
u32 status = 0 ;
status = s5p_cec_get_status ( cec ) ;
dev_dbg ( cec - > dev , " irq received \n " ) ;
if ( status & CEC_STATUS_TX_DONE ) {
2017-08-31 19:56:10 +03:00
if ( status & CEC_STATUS_TX_NACK ) {
dev_dbg ( cec - > dev , " CEC_STATUS_TX_NACK set \n " ) ;
cec - > tx = STATE_NACK ;
} else if ( status & CEC_STATUS_TX_ERROR ) {
2015-09-07 14:15:27 +03:00
dev_dbg ( cec - > dev , " CEC_STATUS_TX_ERROR set \n " ) ;
cec - > tx = STATE_ERROR ;
} else {
dev_dbg ( cec - > dev , " CEC_STATUS_TX_DONE \n " ) ;
cec - > tx = STATE_DONE ;
}
s5p_clr_pending_tx ( cec ) ;
}
if ( status & CEC_STATUS_RX_DONE ) {
if ( status & CEC_STATUS_RX_ERROR ) {
dev_dbg ( cec - > dev , " CEC_STATUS_RX_ERROR set \n " ) ;
s5p_cec_rx_reset ( cec ) ;
s5p_cec_enable_rx ( cec ) ;
} else {
dev_dbg ( cec - > dev , " CEC_STATUS_RX_DONE set \n " ) ;
if ( cec - > rx ! = STATE_IDLE )
dev_dbg ( cec - > dev , " Buffer overrun (worker did not process previous message) \n " ) ;
cec - > rx = STATE_BUSY ;
cec - > msg . len = status > > 24 ;
cec - > msg . rx_status = CEC_RX_STATUS_OK ;
s5p_cec_get_rx_buf ( cec , cec - > msg . len ,
cec - > msg . msg ) ;
cec - > rx = STATE_DONE ;
s5p_cec_enable_rx ( cec ) ;
}
/* Clear interrupt pending bit */
s5p_clr_pending_rx ( cec ) ;
}
return IRQ_WAKE_THREAD ;
}
static irqreturn_t s5p_cec_irq_handler_thread ( int irq , void * priv )
{
struct s5p_cec_dev * cec = priv ;
dev_dbg ( cec - > dev , " irq processing thread \n " ) ;
switch ( cec - > tx ) {
case STATE_DONE :
cec_transmit_done ( cec - > adap , CEC_TX_STATUS_OK , 0 , 0 , 0 , 0 ) ;
cec - > tx = STATE_IDLE ;
break ;
2017-08-31 19:56:10 +03:00
case STATE_NACK :
cec_transmit_done ( cec - > adap ,
CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_NACK ,
0 , 1 , 0 , 0 ) ;
cec - > tx = STATE_IDLE ;
break ;
2015-09-07 14:15:27 +03:00
case STATE_ERROR :
cec_transmit_done ( cec - > adap ,
CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_ERROR ,
0 , 0 , 0 , 1 ) ;
cec - > tx = STATE_IDLE ;
break ;
case STATE_BUSY :
dev_err ( cec - > dev , " state set to busy, this should not occur here \n " ) ;
break ;
default :
break ;
}
switch ( cec - > rx ) {
case STATE_DONE :
cec_received_msg ( cec - > adap , & cec - > msg ) ;
cec - > rx = STATE_IDLE ;
break ;
default :
break ;
}
return IRQ_HANDLED ;
}
static const struct cec_adap_ops s5p_cec_adap_ops = {
. adap_enable = s5p_cec_adap_enable ,
. adap_log_addr = s5p_cec_adap_log_addr ,
. adap_transmit = s5p_cec_adap_transmit ,
} ;
static int s5p_cec_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
2016-12-13 17:37:16 +03:00
struct device_node * np ;
struct platform_device * hdmi_dev ;
2015-09-07 14:15:27 +03:00
struct resource * res ;
struct s5p_cec_dev * cec ;
2017-06-07 17:46:15 +03:00
bool needs_hpd = of_property_read_bool ( pdev - > dev . of_node , " needs-hpd " ) ;
2015-09-07 14:15:27 +03:00
int ret ;
2016-12-13 17:37:16 +03:00
np = of_parse_phandle ( pdev - > dev . of_node , " hdmi-phandle " , 0 ) ;
if ( ! np ) {
dev_err ( & pdev - > dev , " Failed to find hdmi node in device tree \n " ) ;
return - ENODEV ;
}
hdmi_dev = of_find_device_by_node ( np ) ;
if ( hdmi_dev = = NULL )
return - EPROBE_DEFER ;
2015-09-07 14:15:27 +03:00
cec = devm_kzalloc ( & pdev - > dev , sizeof ( * cec ) , GFP_KERNEL ) ;
2016-08-31 16:45:04 +03:00
if ( ! cec )
2015-09-07 14:15:27 +03:00
return - ENOMEM ;
cec - > dev = dev ;
cec - > irq = platform_get_irq ( pdev , 0 ) ;
if ( cec - > irq < 0 )
return cec - > irq ;
ret = devm_request_threaded_irq ( dev , cec - > irq , s5p_cec_irq_handler ,
s5p_cec_irq_handler_thread , 0 , pdev - > name , cec ) ;
if ( ret )
return ret ;
cec - > clk = devm_clk_get ( dev , " hdmicec " ) ;
if ( IS_ERR ( cec - > clk ) )
return PTR_ERR ( cec - > clk ) ;
cec - > pmu = syscon_regmap_lookup_by_phandle ( dev - > of_node ,
" samsung,syscon-phandle " ) ;
if ( IS_ERR ( cec - > pmu ) )
return - EPROBE_DEFER ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
cec - > reg = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( cec - > reg ) )
return PTR_ERR ( cec - > reg ) ;
2016-12-13 17:37:16 +03:00
cec - > notifier = cec_notifier_get ( & hdmi_dev - > dev ) ;
if ( cec - > notifier = = NULL )
return - ENOMEM ;
2017-08-04 13:41:53 +03:00
cec - > adap = cec_allocate_adapter ( & s5p_cec_adap_ops , cec , CEC_NAME ,
CEC_CAP_DEFAULTS | ( needs_hpd ? CEC_CAP_NEEDS_HPD : 0 ) , 1 ) ;
2015-09-07 14:15:27 +03:00
ret = PTR_ERR_OR_ZERO ( cec - > adap ) ;
if ( ret )
return ret ;
2016-12-13 17:37:16 +03:00
2016-11-25 11:23:34 +03:00
ret = cec_register_adapter ( cec - > adap , & pdev - > dev ) ;
2016-12-13 17:37:16 +03:00
if ( ret )
goto err_delete_adapter ;
cec_register_cec_notifier ( cec - > adap , cec - > notifier ) ;
2015-09-07 14:15:27 +03:00
platform_set_drvdata ( pdev , cec ) ;
pm_runtime_enable ( dev ) ;
2017-05-05 17:09:58 +03:00
dev_dbg ( dev , " successfully probed \n " ) ;
2015-09-07 14:15:27 +03:00
return 0 ;
2016-12-13 17:37:16 +03:00
err_delete_adapter :
cec_delete_adapter ( cec - > adap ) ;
return ret ;
2015-09-07 14:15:27 +03:00
}
static int s5p_cec_remove ( struct platform_device * pdev )
{
struct s5p_cec_dev * cec = platform_get_drvdata ( pdev ) ;
cec_unregister_adapter ( cec - > adap ) ;
2016-12-13 17:37:16 +03:00
cec_notifier_put ( cec - > notifier ) ;
2015-09-07 14:15:27 +03:00
pm_runtime_disable ( & pdev - > dev ) ;
return 0 ;
}
2016-10-13 17:39:04 +03:00
static int __maybe_unused s5p_cec_runtime_suspend ( struct device * dev )
2015-09-07 14:15:27 +03:00
{
struct s5p_cec_dev * cec = dev_get_drvdata ( dev ) ;
clk_disable_unprepare ( cec - > clk ) ;
return 0 ;
}
2016-10-13 17:39:04 +03:00
static int __maybe_unused s5p_cec_runtime_resume ( struct device * dev )
2015-09-07 14:15:27 +03:00
{
struct s5p_cec_dev * cec = dev_get_drvdata ( dev ) ;
int ret ;
ret = clk_prepare_enable ( cec - > clk ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
static const struct dev_pm_ops s5p_cec_pm_ops = {
2016-08-31 15:55:58 +03:00
SET_SYSTEM_SLEEP_PM_OPS ( pm_runtime_force_suspend ,
pm_runtime_force_resume )
2015-09-07 14:15:27 +03:00
SET_RUNTIME_PM_OPS ( s5p_cec_runtime_suspend , s5p_cec_runtime_resume ,
NULL )
} ;
static const struct of_device_id s5p_cec_match [ ] = {
{
. compatible = " samsung,s5p-cec " ,
} ,
{ } ,
} ;
2016-10-17 18:44:11 +03:00
MODULE_DEVICE_TABLE ( of , s5p_cec_match ) ;
2015-09-07 14:15:27 +03:00
static struct platform_driver s5p_cec_pdrv = {
. probe = s5p_cec_probe ,
. remove = s5p_cec_remove ,
. driver = {
. name = CEC_NAME ,
. of_match_table = s5p_cec_match ,
. pm = & s5p_cec_pm_ops ,
} ,
} ;
module_platform_driver ( s5p_cec_pdrv ) ;
MODULE_AUTHOR ( " Kamil Debski <kamil@wypas.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " Samsung S5P CEC driver " ) ;