2015-12-14 16:53:50 +03:00
/*
* Copyright ( C ) 2014 - 2015 Pengutronix , Markus Pargmann < mpa @ pengutronix . de >
*
* 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 .
*/
# include <linux/clk.h>
# include <linux/interrupt.h>
# include <linux/irqchip/chained_irq.h>
# include <linux/irqdesc.h>
# include <linux/irqdomain.h>
# include <linux/irq.h>
# include <linux/mfd/imx25-tsadc.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_platform.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
static struct regmap_config mx25_tsadc_regmap_config = {
. fast_io = true ,
. max_register = 8 ,
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = 4 ,
} ;
static void mx25_tsadc_irq_handler ( struct irq_desc * desc )
{
struct mx25_tsadc * tsadc = irq_desc_get_handler_data ( desc ) ;
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
u32 status ;
chained_irq_enter ( chip , desc ) ;
regmap_read ( tsadc - > regs , MX25_TSC_TGSR , & status ) ;
if ( status & MX25_TGSR_GCQ_INT )
generic_handle_irq ( irq_find_mapping ( tsadc - > domain , 1 ) ) ;
if ( status & MX25_TGSR_TCQ_INT )
generic_handle_irq ( irq_find_mapping ( tsadc - > domain , 0 ) ) ;
chained_irq_exit ( chip , desc ) ;
}
static int mx25_tsadc_domain_map ( struct irq_domain * d , unsigned int irq ,
irq_hw_number_t hwirq )
{
struct mx25_tsadc * tsadc = d - > host_data ;
irq_set_chip_data ( irq , tsadc ) ;
irq_set_chip_and_handler ( irq , & dummy_irq_chip ,
handle_level_irq ) ;
irq_modify_status ( irq , IRQ_NOREQUEST , IRQ_NOPROBE ) ;
return 0 ;
}
static struct irq_domain_ops mx25_tsadc_domain_ops = {
. map = mx25_tsadc_domain_map ,
. xlate = irq_domain_xlate_onecell ,
} ;
static int mx25_tsadc_setup_irq ( struct platform_device * pdev ,
struct mx25_tsadc * tsadc )
{
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
int irq ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < = 0 ) {
dev_err ( dev , " Failed to get irq \n " ) ;
return irq ;
}
tsadc - > domain = irq_domain_add_simple ( np , 2 , 0 , & mx25_tsadc_domain_ops ,
tsadc ) ;
if ( ! tsadc - > domain ) {
dev_err ( dev , " Failed to add irq domain \n " ) ;
return - ENOMEM ;
}
irq_set_chained_handler ( irq , mx25_tsadc_irq_handler ) ;
irq_set_handler_data ( irq , tsadc ) ;
return 0 ;
}
static void mx25_tsadc_setup_clk ( struct platform_device * pdev ,
struct mx25_tsadc * tsadc )
{
unsigned clk_div ;
/*
* According to the datasheet the ADC clock should never
* exceed 1 , 75 MHz . Base clock is the IPG and the ADC unit uses
* a funny clock divider . To keep the ADC conversion time constant
* adapt the ADC internal clock divider to the IPG clock rate .
*/
dev_dbg ( & pdev - > dev , " Found master clock at %lu Hz \n " ,
clk_get_rate ( tsadc - > clk ) ) ;
clk_div = DIV_ROUND_UP ( clk_get_rate ( tsadc - > clk ) , 1750000 ) ;
dev_dbg ( & pdev - > dev , " Setting up ADC clock divider to %u \n " , clk_div ) ;
/* adc clock = IPG clock / (2 * div + 2) */
clk_div - = 2 ;
clk_div / = 2 ;
/*
* the ADC clock divider changes its behaviour when values below 4
* are used : it is fixed to " / 10 " in this case
*/
clk_div = max_t ( unsigned , 4 , clk_div ) ;
dev_dbg ( & pdev - > dev , " Resulting ADC conversion clock at %lu Hz \n " ,
clk_get_rate ( tsadc - > clk ) / ( 2 * clk_div + 2 ) ) ;
regmap_update_bits ( tsadc - > regs , MX25_TSC_TGCR ,
MX25_TGCR_ADCCLKCFG ( 0x1f ) ,
MX25_TGCR_ADCCLKCFG ( clk_div ) ) ;
}
static int mx25_tsadc_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
struct mx25_tsadc * tsadc ;
struct resource * res ;
int ret ;
void __iomem * iomem ;
tsadc = devm_kzalloc ( dev , sizeof ( * tsadc ) , GFP_KERNEL ) ;
if ( ! tsadc )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
iomem = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( iomem ) )
return PTR_ERR ( iomem ) ;
tsadc - > regs = devm_regmap_init_mmio ( dev , iomem ,
& mx25_tsadc_regmap_config ) ;
if ( IS_ERR ( tsadc - > regs ) ) {
dev_err ( dev , " Failed to initialize regmap \n " ) ;
return PTR_ERR ( tsadc - > regs ) ;
}
tsadc - > clk = devm_clk_get ( dev , " ipg " ) ;
if ( IS_ERR ( tsadc - > clk ) ) {
dev_err ( dev , " Failed to get ipg clock \n " ) ;
return PTR_ERR ( tsadc - > clk ) ;
}
/* setup clock according to the datasheet */
mx25_tsadc_setup_clk ( pdev , tsadc ) ;
/* Enable clock and reset the component */
regmap_update_bits ( tsadc - > regs , MX25_TSC_TGCR , MX25_TGCR_CLK_EN ,
MX25_TGCR_CLK_EN ) ;
regmap_update_bits ( tsadc - > regs , MX25_TSC_TGCR , MX25_TGCR_TSC_RST ,
MX25_TGCR_TSC_RST ) ;
/* Setup powersaving mode, but enable internal reference voltage */
regmap_update_bits ( tsadc - > regs , MX25_TSC_TGCR , MX25_TGCR_POWERMODE_MASK ,
MX25_TGCR_POWERMODE_SAVE ) ;
regmap_update_bits ( tsadc - > regs , MX25_TSC_TGCR , MX25_TGCR_INTREFEN ,
MX25_TGCR_INTREFEN ) ;
ret = mx25_tsadc_setup_irq ( pdev , tsadc ) ;
if ( ret )
return ret ;
platform_set_drvdata ( pdev , tsadc ) ;
of_platform_populate ( np , NULL , NULL , dev ) ;
return 0 ;
}
static const struct of_device_id mx25_tsadc_ids [ ] = {
{ . compatible = " fsl,imx25-tsadc " } ,
{ /* Sentinel */ }
} ;
2016-10-14 18:40:50 +03:00
MODULE_DEVICE_TABLE ( of , mx25_tsadc_ids ) ;
2015-12-14 16:53:50 +03:00
static struct platform_driver mx25_tsadc_driver = {
. driver = {
. name = " mx25-tsadc " ,
. of_match_table = of_match_ptr ( mx25_tsadc_ids ) ,
} ,
. probe = mx25_tsadc_probe ,
} ;
module_platform_driver ( mx25_tsadc_driver ) ;
MODULE_DESCRIPTION ( " MFD for ADC/TSC for Freescale mx25 " ) ;
MODULE_AUTHOR ( " Markus Pargmann <mpa@pengutronix.de> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:mx25-tsadc " ) ;