2019-05-27 08:55:06 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2014-06-08 15:08:10 -03:00
/*
* Driver for Allwinner sunXi IR controller
*
* Copyright ( C ) 2014 Alexsey Shestacov < wingrime @ linux - sunxi . org >
* Copyright ( C ) 2014 Alexander Bersenev < bay @ hackerdom . ru >
*
* Based on sun5i - ir . c :
* Copyright ( C ) 2007 - 2012 Daniel Wang
* Allwinner Technology Co . , Ltd . < www . allwinnertech . com >
*/
# include <linux/clk.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/of_platform.h>
2014-11-20 11:59:04 -03:00
# include <linux/reset.h>
2014-06-08 15:08:10 -03:00
# include <media/rc-core.h>
# define SUNXI_IR_DEV "sunxi-ir"
/* Registers */
/* IR Control */
# define SUNXI_IR_CTL_REG 0x00
/* Global Enable */
# define REG_CTL_GEN BIT(0)
/* RX block enable */
# define REG_CTL_RXEN BIT(1)
/* CIR mode */
# define REG_CTL_MD (BIT(4) | BIT(5))
/* Rx Config */
# define SUNXI_IR_RXCTL_REG 0x10
/* Pulse Polarity Invert flag */
# define REG_RXCTL_RPPI BIT(2)
/* Rx Data */
# define SUNXI_IR_RXFIFO_REG 0x20
/* Rx Interrupt Enable */
# define SUNXI_IR_RXINT_REG 0x2C
/* Rx FIFO Overflow */
# define REG_RXINT_ROI_EN BIT(0)
/* Rx Packet End */
# define REG_RXINT_RPEI_EN BIT(1)
/* Rx FIFO Data Available */
# define REG_RXINT_RAI_EN BIT(4)
/* Rx FIFO available byte level */
2014-11-20 12:10:47 -03:00
# define REG_RXINT_RAL(val) ((val) << 8)
2014-06-08 15:08:10 -03:00
/* Rx Interrupt Status */
# define SUNXI_IR_RXSTA_REG 0x30
/* RX FIFO Get Available Counter */
2014-11-20 12:10:47 -03:00
# define REG_RXSTA_GET_AC(val) (((val) >> 8) & (ir->fifo_size * 2 - 1))
2014-06-08 15:08:10 -03:00
/* Clear all interrupt status value */
# define REG_RXSTA_CLEARALL 0xff
/* IR Sample Config */
# define SUNXI_IR_CIR_REG 0x34
/* CIR_REG register noise threshold */
# define REG_CIR_NTHR(val) (((val) << 2) & (GENMASK(7, 2)))
/* CIR_REG register idle threshold */
# define REG_CIR_ITHR(val) (((val) << 8) & (GENMASK(15, 8)))
2018-02-13 07:29:47 -05:00
/* Required frequency for IR0 or IR1 clock in CIR mode (default) */
2014-06-08 15:08:10 -03:00
# define SUNXI_IR_BASE_CLK 8000000
/* Noise threshold in samples */
# define SUNXI_IR_RXNOISE 1
/* Idle Threshold in samples */
# define SUNXI_IR_RXIDLE 20
/* Time after which device stops sending data in ms */
# define SUNXI_IR_TIMEOUT 120
2019-06-07 20:10:49 -03:00
/**
* struct sunxi_ir_quirks - Differences between SoC variants .
*
* @ has_reset : SoC needs reset deasserted .
* @ fifo_size : size of the fifo .
*/
struct sunxi_ir_quirks {
bool has_reset ;
int fifo_size ;
} ;
2014-06-08 15:08:10 -03:00
struct sunxi_ir {
spinlock_t ir_lock ;
struct rc_dev * rc ;
void __iomem * base ;
int irq ;
2014-11-20 12:10:47 -03:00
int fifo_size ;
2014-06-08 15:08:10 -03:00
struct clk * clk ;
struct clk * apb_clk ;
2014-11-20 11:59:04 -03:00
struct reset_control * rst ;
2014-06-08 15:08:10 -03:00
const char * map_name ;
} ;
static irqreturn_t sunxi_ir_irq ( int irqno , void * dev_id )
{
unsigned long status ;
unsigned char dt ;
unsigned int cnt , rc ;
struct sunxi_ir * ir = dev_id ;
2018-08-21 15:57:52 -04:00
struct ir_raw_event rawir = { } ;
2014-06-08 15:08:10 -03:00
spin_lock ( & ir - > ir_lock ) ;
status = readl ( ir - > base + SUNXI_IR_RXSTA_REG ) ;
/* clean all pending statuses */
writel ( status | REG_RXSTA_CLEARALL , ir - > base + SUNXI_IR_RXSTA_REG ) ;
2014-11-20 12:10:47 -03:00
if ( status & ( REG_RXINT_RAI_EN | REG_RXINT_RPEI_EN ) ) {
2014-06-08 15:08:10 -03:00
/* How many messages in fifo */
rc = REG_RXSTA_GET_AC ( status ) ;
/* Sanity check */
2014-11-20 12:10:47 -03:00
rc = rc > ir - > fifo_size ? ir - > fifo_size : rc ;
2014-06-08 15:08:10 -03:00
/* If we have data */
for ( cnt = 0 ; cnt < rc ; cnt + + ) {
/* for each bit in fifo */
dt = readb ( ir - > base + SUNXI_IR_RXFIFO_REG ) ;
rawir . pulse = ( dt & 0x80 ) ! = 0 ;
2018-02-13 07:29:47 -05:00
rawir . duration = ( ( dt & 0x7f ) + 1 ) *
ir - > rc - > rx_resolution ;
2014-06-08 15:08:10 -03:00
ir_raw_event_store_with_filter ( ir - > rc , & rawir ) ;
}
}
if ( status & REG_RXINT_ROI_EN ) {
ir_raw_event_reset ( ir - > rc ) ;
} else if ( status & REG_RXINT_RPEI_EN ) {
ir_raw_event_set_idle ( ir - > rc , true ) ;
ir_raw_event_handle ( ir - > rc ) ;
}
spin_unlock ( & ir - > ir_lock ) ;
return IRQ_HANDLED ;
}
static int sunxi_ir_probe ( struct platform_device * pdev )
{
int ret = 0 ;
unsigned long tmp = 0 ;
struct device * dev = & pdev - > dev ;
struct device_node * dn = dev - > of_node ;
2019-06-07 20:10:49 -03:00
const struct sunxi_ir_quirks * quirks ;
2014-06-08 15:08:10 -03:00
struct resource * res ;
struct sunxi_ir * ir ;
2018-02-13 07:29:47 -05:00
u32 b_clk_freq = SUNXI_IR_BASE_CLK ;
2014-06-08 15:08:10 -03:00
ir = devm_kzalloc ( dev , sizeof ( struct sunxi_ir ) , GFP_KERNEL ) ;
if ( ! ir )
return - ENOMEM ;
2019-06-07 20:10:49 -03:00
quirks = of_device_get_match_data ( & pdev - > dev ) ;
if ( ! quirks ) {
dev_err ( & pdev - > dev , " Failed to determine the quirks to use \n " ) ;
return - ENODEV ;
}
2015-12-22 02:27:35 -02:00
spin_lock_init ( & ir - > ir_lock ) ;
2019-06-07 20:10:49 -03:00
ir - > fifo_size = quirks - > fifo_size ;
2014-11-20 12:10:47 -03:00
2014-06-08 15:08:10 -03:00
/* Clock */
ir - > apb_clk = devm_clk_get ( dev , " apb " ) ;
if ( IS_ERR ( ir - > apb_clk ) ) {
dev_err ( dev , " failed to get a apb clock. \n " ) ;
return PTR_ERR ( ir - > apb_clk ) ;
}
ir - > clk = devm_clk_get ( dev , " ir " ) ;
if ( IS_ERR ( ir - > clk ) ) {
dev_err ( dev , " failed to get a ir clock. \n " ) ;
return PTR_ERR ( ir - > clk ) ;
}
2018-02-13 07:29:47 -05:00
/* Base clock frequency (optional) */
of_property_read_u32 ( dn , " clock-frequency " , & b_clk_freq ) ;
2019-06-07 20:10:49 -03:00
/* Reset */
if ( quirks - > has_reset ) {
ir - > rst = devm_reset_control_get_exclusive ( dev , NULL ) ;
if ( IS_ERR ( ir - > rst ) )
return PTR_ERR ( ir - > rst ) ;
ret = reset_control_deassert ( ir - > rst ) ;
if ( ret )
return ret ;
}
2014-11-20 11:59:04 -03:00
2018-02-13 07:29:47 -05:00
ret = clk_set_rate ( ir - > clk , b_clk_freq ) ;
2014-06-08 15:08:10 -03:00
if ( ret ) {
dev_err ( dev , " set ir base clock failed! \n " ) ;
2014-11-20 11:59:04 -03:00
goto exit_reset_assert ;
2014-06-08 15:08:10 -03:00
}
2018-02-13 07:29:47 -05:00
dev_dbg ( dev , " set base clock frequency to %d Hz. \n " , b_clk_freq ) ;
2014-06-08 15:08:10 -03:00
if ( clk_prepare_enable ( ir - > apb_clk ) ) {
dev_err ( dev , " try to enable apb_ir_clk failed \n " ) ;
2014-11-20 11:59:04 -03:00
ret = - EINVAL ;
goto exit_reset_assert ;
2014-06-08 15:08:10 -03:00
}
if ( clk_prepare_enable ( ir - > clk ) ) {
dev_err ( dev , " try to enable ir_clk failed \n " ) ;
ret = - EINVAL ;
goto exit_clkdisable_apb_clk ;
}
/* IO */
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
ir - > base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( ir - > base ) ) {
ret = PTR_ERR ( ir - > base ) ;
goto exit_clkdisable_clk ;
}
2016-12-16 06:50:58 -02:00
ir - > rc = rc_allocate_device ( RC_DRIVER_IR_RAW ) ;
2014-06-08 15:08:10 -03:00
if ( ! ir - > rc ) {
dev_err ( dev , " failed to allocate device \n " ) ;
ret = - ENOMEM ;
goto exit_clkdisable_clk ;
}
ir - > rc - > priv = ir ;
2017-07-01 12:13:19 -04:00
ir - > rc - > device_name = SUNXI_IR_DEV ;
2014-06-08 15:08:10 -03:00
ir - > rc - > input_phys = " sunxi-ir/input0 " ;
ir - > rc - > input_id . bustype = BUS_HOST ;
ir - > rc - > input_id . vendor = 0x0001 ;
ir - > rc - > input_id . product = 0x0001 ;
ir - > rc - > input_id . version = 0x0100 ;
ir - > map_name = of_get_property ( dn , " linux,rc-map-name " , NULL ) ;
ir - > rc - > map_name = ir - > map_name ? : RC_MAP_EMPTY ;
ir - > rc - > dev . parent = dev ;
2017-08-07 16:20:58 -04:00
ir - > rc - > allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER ;
2018-02-13 07:29:47 -05:00
/* Frequency after IR internal divider with sample period in ns */
ir - > rc - > rx_resolution = ( 1000000000ul / ( b_clk_freq / 64 ) ) ;
2014-06-08 15:08:10 -03:00
ir - > rc - > timeout = MS_TO_NS ( SUNXI_IR_TIMEOUT ) ;
ir - > rc - > driver_name = SUNXI_IR_DEV ;
ret = rc_register_device ( ir - > rc ) ;
if ( ret ) {
dev_err ( dev , " failed to register rc device \n " ) ;
goto exit_free_dev ;
}
platform_set_drvdata ( pdev , ir ) ;
/* IRQ */
ir - > irq = platform_get_irq ( pdev , 0 ) ;
if ( ir - > irq < 0 ) {
dev_err ( dev , " no irq resource \n " ) ;
ret = ir - > irq ;
goto exit_free_dev ;
}
ret = devm_request_irq ( dev , ir - > irq , sunxi_ir_irq , 0 , SUNXI_IR_DEV , ir ) ;
if ( ret ) {
dev_err ( dev , " failed request irq \n " ) ;
goto exit_free_dev ;
}
/* Enable CIR Mode */
writel ( REG_CTL_MD , ir - > base + SUNXI_IR_CTL_REG ) ;
/* Set noise threshold and idle threshold */
writel ( REG_CIR_NTHR ( SUNXI_IR_RXNOISE ) | REG_CIR_ITHR ( SUNXI_IR_RXIDLE ) ,
ir - > base + SUNXI_IR_CIR_REG ) ;
/* Invert Input Signal */
writel ( REG_RXCTL_RPPI , ir - > base + SUNXI_IR_RXCTL_REG ) ;
/* Clear All Rx Interrupt Status */
writel ( REG_RXSTA_CLEARALL , ir - > base + SUNXI_IR_RXSTA_REG ) ;
/*
* Enable IRQ on overflow , packet end , FIFO available with trigger
* level
*/
writel ( REG_RXINT_ROI_EN | REG_RXINT_RPEI_EN |
2014-11-20 12:10:47 -03:00
REG_RXINT_RAI_EN | REG_RXINT_RAL ( ir - > fifo_size / 2 - 1 ) ,
2014-06-08 15:08:10 -03:00
ir - > base + SUNXI_IR_RXINT_REG ) ;
/* Enable IR Module */
tmp = readl ( ir - > base + SUNXI_IR_CTL_REG ) ;
writel ( tmp | REG_CTL_GEN | REG_CTL_RXEN , ir - > base + SUNXI_IR_CTL_REG ) ;
dev_info ( dev , " initialized sunXi IR driver \n " ) ;
return 0 ;
exit_free_dev :
rc_free_device ( ir - > rc ) ;
exit_clkdisable_clk :
clk_disable_unprepare ( ir - > clk ) ;
exit_clkdisable_apb_clk :
clk_disable_unprepare ( ir - > apb_clk ) ;
2014-11-20 11:59:04 -03:00
exit_reset_assert :
2017-03-15 08:31:38 -03:00
reset_control_assert ( ir - > rst ) ;
2014-06-08 15:08:10 -03:00
return ret ;
}
static int sunxi_ir_remove ( struct platform_device * pdev )
{
unsigned long flags ;
struct sunxi_ir * ir = platform_get_drvdata ( pdev ) ;
clk_disable_unprepare ( ir - > clk ) ;
clk_disable_unprepare ( ir - > apb_clk ) ;
2017-03-15 08:31:38 -03:00
reset_control_assert ( ir - > rst ) ;
2014-06-08 15:08:10 -03:00
spin_lock_irqsave ( & ir - > ir_lock , flags ) ;
/* disable IR IRQ */
writel ( 0 , ir - > base + SUNXI_IR_RXINT_REG ) ;
/* clear All Rx Interrupt Status */
writel ( REG_RXSTA_CLEARALL , ir - > base + SUNXI_IR_RXSTA_REG ) ;
/* disable IR */
writel ( 0 , ir - > base + SUNXI_IR_CTL_REG ) ;
spin_unlock_irqrestore ( & ir - > ir_lock , flags ) ;
rc_unregister_device ( ir - > rc ) ;
return 0 ;
}
2019-06-07 20:10:49 -03:00
static const struct sunxi_ir_quirks sun4i_a10_ir_quirks = {
. has_reset = false ,
. fifo_size = 16 ,
} ;
static const struct sunxi_ir_quirks sun5i_a13_ir_quirks = {
. has_reset = false ,
. fifo_size = 64 ,
} ;
2019-06-07 20:10:50 -03:00
static const struct sunxi_ir_quirks sun6i_a31_ir_quirks = {
. has_reset = true ,
. fifo_size = 64 ,
} ;
2014-06-08 15:08:10 -03:00
static const struct of_device_id sunxi_ir_match [ ] = {
2019-06-07 20:10:49 -03:00
{
. compatible = " allwinner,sun4i-a10-ir " ,
. data = & sun4i_a10_ir_quirks ,
} ,
{
. compatible = " allwinner,sun5i-a13-ir " ,
. data = & sun5i_a13_ir_quirks ,
} ,
2019-06-07 20:10:50 -03:00
{
. compatible = " allwinner,sun6i-a31-ir " ,
. data = & sun6i_a31_ir_quirks ,
} ,
2019-06-07 20:10:49 -03:00
{ }
2014-06-08 15:08:10 -03:00
} ;
2016-02-21 22:26:34 -03:00
MODULE_DEVICE_TABLE ( of , sunxi_ir_match ) ;
2014-06-08 15:08:10 -03:00
static struct platform_driver sunxi_ir_driver = {
. probe = sunxi_ir_probe ,
. remove = sunxi_ir_remove ,
. driver = {
. name = SUNXI_IR_DEV ,
. of_match_table = sunxi_ir_match ,
} ,
} ;
module_platform_driver ( sunxi_ir_driver ) ;
MODULE_DESCRIPTION ( " Allwinner sunXi IR controller driver " ) ;
MODULE_AUTHOR ( " Alexsey Shestacov <wingrime@linux-sunxi.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;