2019-06-01 10:08:37 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2014-08-30 23:39:10 -03:00
/*
* Copyright ( c ) 2014 Linaro Ltd .
2021-03-30 08:50:00 +02:00
* Copyright ( c ) 2014 HiSilicon Limited .
2014-08-30 23:39:10 -03:00
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
# include <linux/mfd/syscon.h>
# include <linux/module.h>
# include <linux/of_device.h>
# include <linux/regmap.h>
# include <media/rc-core.h>
# define IR_ENABLE 0x00
# define IR_CONFIG 0x04
# define CNT_LEADS 0x08
# define CNT_LEADE 0x0c
# define CNT_SLEADE 0x10
# define CNT0_B 0x14
# define CNT1_B 0x18
# define IR_BUSY 0x1c
# define IR_DATAH 0x20
# define IR_DATAL 0x24
# define IR_INTM 0x28
# define IR_INTS 0x2c
# define IR_INTC 0x30
# define IR_START 0x34
/* interrupt mask */
# define INTMS_SYMBRCV (BIT(24) | BIT(8))
# define INTMS_TIMEOUT (BIT(25) | BIT(9))
# define INTMS_OVERFLOW (BIT(26) | BIT(10))
# define INT_CLR_OVERFLOW BIT(18)
# define INT_CLR_TIMEOUT BIT(17)
# define INT_CLR_RCV BIT(16)
# define INT_CLR_RCVTIMEOUT (BIT(16) | BIT(17))
# define IR_CLK_ENABLE BIT(4)
# define IR_CLK_RESET BIT(5)
2019-12-26 13:02:22 +01:00
/* IR_ENABLE register bits */
# define IR_ENABLE_EN BIT(0)
# define IR_ENABLE_EN_EXTRA BIT(8)
2014-08-30 23:39:10 -03:00
# define IR_CFG_WIDTH_MASK 0xffff
# define IR_CFG_WIDTH_SHIFT 16
# define IR_CFG_FORMAT_MASK 0x3
# define IR_CFG_FORMAT_SHIFT 14
# define IR_CFG_INT_LEVEL_MASK 0x3f
# define IR_CFG_INT_LEVEL_SHIFT 8
/* only support raw mode */
# define IR_CFG_MODE_RAW BIT(7)
# define IR_CFG_FREQ_MASK 0x7f
# define IR_CFG_FREQ_SHIFT 0
# define IR_CFG_INT_THRESHOLD 1
/* symbol start from low to high, symbol stream end at high*/
# define IR_CFG_SYMBOL_FMT 0
# define IR_CFG_SYMBOL_MAXWIDTH 0x3e80
# define IR_HIX5HD2_NAME "hix5hd2-ir"
2019-12-26 13:02:22 +01:00
/* Need to set extra bit for enabling IR */
# define HIX5HD2_FLAG_EXTRA_ENABLE BIT(0)
struct hix5hd2_soc_data {
u32 clk_reg ;
u32 flags ;
} ;
static const struct hix5hd2_soc_data hix5hd2_data = {
. clk_reg = 0x48 ,
} ;
static const struct hix5hd2_soc_data hi3796cv300_data = {
. clk_reg = 0x60 ,
. flags = HIX5HD2_FLAG_EXTRA_ENABLE ,
} ;
2014-08-30 23:39:10 -03:00
struct hix5hd2_ir_priv {
int irq ;
2015-04-14 15:21:39 -03:00
void __iomem * base ;
2014-08-30 23:39:10 -03:00
struct device * dev ;
struct rc_dev * rdev ;
struct regmap * regmap ;
struct clk * clock ;
unsigned long rate ;
2019-12-26 13:02:22 +01:00
const struct hix5hd2_soc_data * socdata ;
2014-08-30 23:39:10 -03:00
} ;
2019-12-26 13:02:22 +01:00
static int hix5hd2_ir_clk_enable ( struct hix5hd2_ir_priv * dev , bool on )
2014-08-30 23:39:10 -03:00
{
2019-12-26 13:02:22 +01:00
u32 clk_reg = dev - > socdata - > clk_reg ;
2014-08-30 23:39:10 -03:00
u32 val ;
2018-01-26 17:10:17 -05:00
int ret = 0 ;
2014-08-30 23:39:10 -03:00
2016-11-15 05:31:32 -02:00
if ( dev - > regmap ) {
2019-12-26 13:02:22 +01:00
regmap_read ( dev - > regmap , clk_reg , & val ) ;
2016-11-15 05:31:32 -02:00
if ( on ) {
val & = ~ IR_CLK_RESET ;
val | = IR_CLK_ENABLE ;
} else {
val & = ~ IR_CLK_ENABLE ;
val | = IR_CLK_RESET ;
}
2019-12-26 13:02:22 +01:00
regmap_write ( dev - > regmap , clk_reg , val ) ;
2014-08-30 23:39:10 -03:00
} else {
2016-11-15 05:31:32 -02:00
if ( on )
2018-01-26 17:10:17 -05:00
ret = clk_prepare_enable ( dev - > clock ) ;
2016-11-15 05:31:32 -02:00
else
clk_disable_unprepare ( dev - > clock ) ;
2014-08-30 23:39:10 -03:00
}
2018-01-26 17:10:17 -05:00
return ret ;
2014-08-30 23:39:10 -03:00
}
2019-12-26 13:02:22 +01:00
static inline void hix5hd2_ir_enable ( struct hix5hd2_ir_priv * priv )
{
u32 val = IR_ENABLE_EN ;
if ( priv - > socdata - > flags & HIX5HD2_FLAG_EXTRA_ENABLE )
val | = IR_ENABLE_EN_EXTRA ;
writel_relaxed ( val , priv - > base + IR_ENABLE ) ;
}
2014-08-30 23:39:10 -03:00
static int hix5hd2_ir_config ( struct hix5hd2_ir_priv * priv )
{
int timeout = 10000 ;
u32 val , rate ;
2019-12-26 13:02:22 +01:00
hix5hd2_ir_enable ( priv ) ;
2014-08-30 23:39:10 -03:00
while ( readl_relaxed ( priv - > base + IR_BUSY ) ) {
if ( timeout - - ) {
udelay ( 1 ) ;
} else {
dev_err ( priv - > dev , " IR_BUSY timeout \n " ) ;
return - ETIMEDOUT ;
}
}
/* Now only support raw mode, with symbol start from low to high */
rate = DIV_ROUND_CLOSEST ( priv - > rate , 1000000 ) ;
val = IR_CFG_SYMBOL_MAXWIDTH & IR_CFG_WIDTH_MASK < < IR_CFG_WIDTH_SHIFT ;
val | = IR_CFG_SYMBOL_FMT & IR_CFG_FORMAT_MASK < < IR_CFG_FORMAT_SHIFT ;
val | = ( IR_CFG_INT_THRESHOLD - 1 ) & IR_CFG_INT_LEVEL_MASK
< < IR_CFG_INT_LEVEL_SHIFT ;
val | = IR_CFG_MODE_RAW ;
val | = ( rate - 1 ) & IR_CFG_FREQ_MASK < < IR_CFG_FREQ_SHIFT ;
writel_relaxed ( val , priv - > base + IR_CONFIG ) ;
writel_relaxed ( 0x00 , priv - > base + IR_INTM ) ;
/* write arbitrary value to start */
writel_relaxed ( 0x01 , priv - > base + IR_START ) ;
return 0 ;
}
static int hix5hd2_ir_open ( struct rc_dev * rdev )
{
struct hix5hd2_ir_priv * priv = rdev - > priv ;
2018-01-26 17:10:17 -05:00
int ret ;
2019-12-26 13:02:22 +01:00
ret = hix5hd2_ir_clk_enable ( priv , true ) ;
2018-01-26 17:10:17 -05:00
if ( ret )
return ret ;
2014-08-30 23:39:10 -03:00
2018-01-26 17:10:17 -05:00
ret = hix5hd2_ir_config ( priv ) ;
if ( ret ) {
2019-12-26 13:02:22 +01:00
hix5hd2_ir_clk_enable ( priv , false ) ;
2018-01-26 17:10:17 -05:00
return ret ;
}
return 0 ;
2014-08-30 23:39:10 -03:00
}
static void hix5hd2_ir_close ( struct rc_dev * rdev )
{
struct hix5hd2_ir_priv * priv = rdev - > priv ;
2019-12-26 13:02:22 +01:00
hix5hd2_ir_clk_enable ( priv , false ) ;
2014-08-30 23:39:10 -03:00
}
static irqreturn_t hix5hd2_ir_rx_interrupt ( int irq , void * data )
{
u32 symb_num , symb_val , symb_time ;
u32 data_l , data_h ;
u32 irq_sr , i ;
struct hix5hd2_ir_priv * priv = data ;
irq_sr = readl_relaxed ( priv - > base + IR_INTS ) ;
if ( irq_sr & INTMS_OVERFLOW ) {
/*
* we must read IR_DATAL first , then we can clean up
* IR_INTS availably since logic would not clear
* fifo when overflow , drv do the job
*/
ir_raw_event_reset ( priv - > rdev ) ;
symb_num = readl_relaxed ( priv - > base + IR_DATAH ) ;
for ( i = 0 ; i < symb_num ; i + + )
readl_relaxed ( priv - > base + IR_DATAL ) ;
writel_relaxed ( INT_CLR_OVERFLOW , priv - > base + IR_INTC ) ;
dev_info ( priv - > dev , " overflow, level=%d \n " ,
IR_CFG_INT_THRESHOLD ) ;
}
if ( ( irq_sr & INTMS_SYMBRCV ) | | ( irq_sr & INTMS_TIMEOUT ) ) {
2018-08-21 15:57:52 -04:00
struct ir_raw_event ev = { } ;
2014-08-30 23:39:10 -03:00
symb_num = readl_relaxed ( priv - > base + IR_DATAH ) ;
for ( i = 0 ; i < symb_num ; i + + ) {
symb_val = readl_relaxed ( priv - > base + IR_DATAL ) ;
data_l = ( ( symb_val & 0xffff ) * 10 ) ;
data_h = ( ( symb_val > > 16 ) & 0xffff ) * 10 ;
symb_time = ( data_l + data_h ) / 10 ;
2020-08-23 19:23:05 +02:00
ev . duration = data_l ;
2014-08-30 23:39:10 -03:00
ev . pulse = true ;
ir_raw_event_store ( priv - > rdev , & ev ) ;
if ( symb_time < IR_CFG_SYMBOL_MAXWIDTH ) {
2020-08-23 19:23:05 +02:00
ev . duration = data_h ;
2014-08-30 23:39:10 -03:00
ev . pulse = false ;
ir_raw_event_store ( priv - > rdev , & ev ) ;
} else {
ir_raw_event_set_idle ( priv - > rdev , true ) ;
}
}
if ( irq_sr & INTMS_SYMBRCV )
writel_relaxed ( INT_CLR_RCV , priv - > base + IR_INTC ) ;
if ( irq_sr & INTMS_TIMEOUT )
writel_relaxed ( INT_CLR_TIMEOUT , priv - > base + IR_INTC ) ;
}
/* Empty software fifo */
ir_raw_event_handle ( priv - > rdev ) ;
return IRQ_HANDLED ;
}
2019-12-26 13:02:22 +01:00
static const struct of_device_id hix5hd2_ir_table [ ] = {
{ . compatible = " hisilicon,hix5hd2-ir " , & hix5hd2_data , } ,
{ . compatible = " hisilicon,hi3796cv300-ir " , & hi3796cv300_data , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , hix5hd2_ir_table ) ;
2014-08-30 23:39:10 -03:00
static int hix5hd2_ir_probe ( struct platform_device * pdev )
{
struct rc_dev * rdev ;
struct device * dev = & pdev - > dev ;
struct hix5hd2_ir_priv * priv ;
struct device_node * node = pdev - > dev . of_node ;
2019-12-26 13:02:22 +01:00
const struct of_device_id * of_id ;
2014-08-30 23:39:10 -03:00
const char * map_name ;
int ret ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2019-12-26 13:02:22 +01:00
of_id = of_match_device ( hix5hd2_ir_table , dev ) ;
if ( ! of_id ) {
dev_err ( dev , " Unable to initialize IR data \n " ) ;
return - ENODEV ;
}
priv - > socdata = of_id - > data ;
2014-08-30 23:39:10 -03:00
priv - > regmap = syscon_regmap_lookup_by_phandle ( node ,
" hisilicon,power-syscon " ) ;
if ( IS_ERR ( priv - > regmap ) ) {
2016-11-15 05:31:32 -02:00
dev_info ( dev , " no power-reg \n " ) ;
priv - > regmap = NULL ;
2014-08-30 23:39:10 -03:00
}
2021-09-01 07:54:27 +02:00
priv - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2015-04-14 15:21:39 -03:00
if ( IS_ERR ( priv - > base ) )
return PTR_ERR ( priv - > base ) ;
2014-08-30 23:39:10 -03:00
priv - > irq = platform_get_irq ( pdev , 0 ) ;
2019-07-30 15:15:25 -03:00
if ( priv - > irq < 0 )
2014-08-30 23:39:10 -03:00
return priv - > irq ;
2016-12-16 06:50:58 -02:00
rdev = rc_allocate_device ( RC_DRIVER_IR_RAW ) ;
2014-08-30 23:39:10 -03:00
if ( ! rdev )
return - ENOMEM ;
priv - > clock = devm_clk_get ( dev , NULL ) ;
if ( IS_ERR ( priv - > clock ) ) {
dev_err ( dev , " clock not found \n " ) ;
ret = PTR_ERR ( priv - > clock ) ;
goto err ;
}
2018-01-26 17:10:17 -05:00
ret = clk_prepare_enable ( priv - > clock ) ;
if ( ret )
goto err ;
2014-08-30 23:39:10 -03:00
priv - > rate = clk_get_rate ( priv - > clock ) ;
2017-08-07 16:20:58 -04:00
rdev - > allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER ;
2014-08-30 23:39:10 -03:00
rdev - > priv = priv ;
rdev - > open = hix5hd2_ir_open ;
rdev - > close = hix5hd2_ir_close ;
rdev - > driver_name = IR_HIX5HD2_NAME ;
map_name = of_get_property ( node , " linux,rc-map-name " , NULL ) ;
rdev - > map_name = map_name ? : RC_MAP_EMPTY ;
2017-07-01 12:13:19 -04:00
rdev - > device_name = IR_HIX5HD2_NAME ;
2014-08-30 23:39:10 -03:00
rdev - > input_phys = IR_HIX5HD2_NAME " /input0 " ;
rdev - > input_id . bustype = BUS_HOST ;
rdev - > input_id . vendor = 0x0001 ;
rdev - > input_id . product = 0x0001 ;
rdev - > input_id . version = 0x0100 ;
2020-08-23 19:23:05 +02:00
rdev - > rx_resolution = 10 ;
rdev - > timeout = IR_CFG_SYMBOL_MAXWIDTH * 10 ;
2014-08-30 23:39:10 -03:00
ret = rc_register_device ( rdev ) ;
if ( ret < 0 )
goto clkerr ;
if ( devm_request_irq ( dev , priv - > irq , hix5hd2_ir_rx_interrupt ,
2015-09-21 12:47:11 -03:00
0 , pdev - > name , priv ) < 0 ) {
2014-08-30 23:39:10 -03:00
dev_err ( dev , " IRQ %d register failed \n " , priv - > irq ) ;
ret = - EINVAL ;
goto regerr ;
}
priv - > rdev = rdev ;
priv - > dev = dev ;
platform_set_drvdata ( pdev , priv ) ;
return ret ;
regerr :
rc_unregister_device ( rdev ) ;
rdev = NULL ;
clkerr :
clk_disable_unprepare ( priv - > clock ) ;
err :
rc_free_device ( rdev ) ;
dev_err ( dev , " Unable to register device (%d) \n " , ret ) ;
return ret ;
}
static int hix5hd2_ir_remove ( struct platform_device * pdev )
{
struct hix5hd2_ir_priv * priv = platform_get_drvdata ( pdev ) ;
clk_disable_unprepare ( priv - > clock ) ;
rc_unregister_device ( priv - > rdev ) ;
return 0 ;
}
2014-10-10 03:53:47 -03:00
# ifdef CONFIG_PM_SLEEP
2014-08-30 23:39:10 -03:00
static int hix5hd2_ir_suspend ( struct device * dev )
{
struct hix5hd2_ir_priv * priv = dev_get_drvdata ( dev ) ;
clk_disable_unprepare ( priv - > clock ) ;
2019-12-26 13:02:22 +01:00
hix5hd2_ir_clk_enable ( priv , false ) ;
2014-08-30 23:39:10 -03:00
return 0 ;
}
static int hix5hd2_ir_resume ( struct device * dev )
{
struct hix5hd2_ir_priv * priv = dev_get_drvdata ( dev ) ;
2018-01-26 17:10:17 -05:00
int ret ;
2014-08-30 23:39:10 -03:00
2019-12-26 13:02:22 +01:00
ret = hix5hd2_ir_clk_enable ( priv , true ) ;
2018-01-26 17:10:17 -05:00
if ( ret )
return ret ;
ret = clk_prepare_enable ( priv - > clock ) ;
if ( ret ) {
2019-12-26 13:02:22 +01:00
hix5hd2_ir_clk_enable ( priv , false ) ;
2018-01-26 17:10:17 -05:00
return ret ;
}
2014-08-30 23:39:10 -03:00
2019-12-26 13:02:22 +01:00
hix5hd2_ir_enable ( priv ) ;
2014-08-30 23:39:10 -03:00
writel_relaxed ( 0x00 , priv - > base + IR_INTM ) ;
writel_relaxed ( 0xff , priv - > base + IR_INTC ) ;
writel_relaxed ( 0x01 , priv - > base + IR_START ) ;
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( hix5hd2_ir_pm_ops , hix5hd2_ir_suspend ,
hix5hd2_ir_resume ) ;
static struct platform_driver hix5hd2_ir_driver = {
. driver = {
. name = IR_HIX5HD2_NAME ,
. of_match_table = hix5hd2_ir_table ,
. pm = & hix5hd2_ir_pm_ops ,
} ,
. probe = hix5hd2_ir_probe ,
. remove = hix5hd2_ir_remove ,
} ;
module_platform_driver ( hix5hd2_ir_driver ) ;
MODULE_DESCRIPTION ( " IR controller driver for hix5hd2 platforms " ) ;
MODULE_AUTHOR ( " Guoxiong Yan <yanguoxiong@huawei.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:hix5hd2-ir " ) ;