mfd: Add Freescale MC13783 driver

This driver provides the core Freescale MC13783 support. It
registers the client platform_devices and provides access
to the A/D converter.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
Sascha Hauer
2009-08-19 01:40:28 +02:00
committed by Samuel Ortiz
parent 0ad651c94c
commit 8238addcc5
5 changed files with 919 additions and 0 deletions

View File

@ -238,6 +238,16 @@ config MFD_PCF50633
facilities, and registers devices for the various functions
so that function-specific drivers can bind to them.
config MFD_MC13783
tristate "Support Freescale MC13783"
depends on SPI_MASTER
select MFD_CORE
help
Support for the Freescale (Atlas) MC13783 PMIC and audio CODEC.
This driver provides common support for accessing the device,
additional drivers must be enabled in order to use the
functionality of the device.
config PCF50633_ADC
tristate "Support for NXP PCF50633 ADC"
depends on MFD_PCF50633

View File

@ -26,6 +26,8 @@ obj-$(CONFIG_MENELAUS) += menelaus.o
obj-$(CONFIG_TWL4030_CORE) += twl4030-core.o twl4030-irq.o
obj-$(CONFIG_MFD_MC13783) += mc13783-core.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
obj-$(CONFIG_EZX_PCAP) += ezx-pcap.o

427
drivers/mfd/mc13783-core.c Normal file
View File

@ -0,0 +1,427 @@
/*
* Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
*
* This code is in parts based on wm8350-core.c and pcf50633-core.c
*
* Initial development of this code was funded by
* Phytec Messtechnik GmbH, http://www.phytec.de
*
* 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 program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/mfd/mc13783-private.h>
#include <linux/platform_device.h>
#include <linux/mfd/mc13783.h>
#include <linux/completion.h>
#include <linux/interrupt.h>
#include <linux/mfd/core.h>
#include <linux/spi/spi.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/irq.h>
#define MC13783_MAX_REG_NUM 0x3f
#define MC13783_FRAME_MASK 0x00ffffff
#define MC13783_MAX_REG_NUM 0x3f
#define MC13783_REG_NUM_SHIFT 0x19
#define MC13783_WRITE_BIT_SHIFT 31
static inline int spi_rw(struct spi_device *spi, u8 * buf, size_t len)
{
struct spi_transfer t = {
.tx_buf = (const void *)buf,
.rx_buf = buf,
.len = len,
.cs_change = 0,
.delay_usecs = 0,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
if (spi_sync(spi, &m) != 0 || m.status != 0)
return -EINVAL;
return len - m.actual_length;
}
static int mc13783_read(struct mc13783 *mc13783, int reg_num, u32 *reg_val)
{
unsigned int frame = 0;
int ret = 0;
if (reg_num > MC13783_MAX_REG_NUM)
return -EINVAL;
frame |= reg_num << MC13783_REG_NUM_SHIFT;
ret = spi_rw(mc13783->spi_device, (u8 *)&frame, 4);
*reg_val = frame & MC13783_FRAME_MASK;
return ret;
}
static int mc13783_write(struct mc13783 *mc13783, int reg_num, u32 reg_val)
{
unsigned int frame = 0;
if (reg_num > MC13783_MAX_REG_NUM)
return -EINVAL;
frame |= (1 << MC13783_WRITE_BIT_SHIFT);
frame |= reg_num << MC13783_REG_NUM_SHIFT;
frame |= reg_val & MC13783_FRAME_MASK;
return spi_rw(mc13783->spi_device, (u8 *)&frame, 4);
}
int mc13783_reg_read(struct mc13783 *mc13783, int reg_num, u32 *reg_val)
{
int ret;
mutex_lock(&mc13783->io_lock);
ret = mc13783_read(mc13783, reg_num, reg_val);
mutex_unlock(&mc13783->io_lock);
return ret;
}
EXPORT_SYMBOL_GPL(mc13783_reg_read);
int mc13783_reg_write(struct mc13783 *mc13783, int reg_num, u32 reg_val)
{
int ret;
mutex_lock(&mc13783->io_lock);
ret = mc13783_write(mc13783, reg_num, reg_val);
mutex_unlock(&mc13783->io_lock);
return ret;
}
EXPORT_SYMBOL_GPL(mc13783_reg_write);
/**
* mc13783_set_bits - Bitmask write
*
* @mc13783: Pointer to mc13783 control structure
* @reg: Register to access
* @mask: Mask of bits to change
* @val: Value to set for masked bits
*/
int mc13783_set_bits(struct mc13783 *mc13783, int reg, u32 mask, u32 val)
{
u32 tmp;
int ret;
mutex_lock(&mc13783->io_lock);
ret = mc13783_read(mc13783, reg, &tmp);
tmp = (tmp & ~mask) | val;
if (ret == 0)
ret = mc13783_write(mc13783, reg, tmp);
mutex_unlock(&mc13783->io_lock);
return ret;
}
EXPORT_SYMBOL_GPL(mc13783_set_bits);
int mc13783_register_irq(struct mc13783 *mc13783, int irq,
void (*handler) (int, void *), void *data)
{
if (irq < 0 || irq > MC13783_NUM_IRQ || !handler)
return -EINVAL;
if (WARN_ON(mc13783->irq_handler[irq].handler))
return -EBUSY;
mutex_lock(&mc13783->io_lock);
mc13783->irq_handler[irq].handler = handler;
mc13783->irq_handler[irq].data = data;
mutex_unlock(&mc13783->io_lock);
return 0;
}
EXPORT_SYMBOL_GPL(mc13783_register_irq);
int mc13783_free_irq(struct mc13783 *mc13783, int irq)
{
if (irq < 0 || irq > MC13783_NUM_IRQ)
return -EINVAL;
mutex_lock(&mc13783->io_lock);
mc13783->irq_handler[irq].handler = NULL;
mutex_unlock(&mc13783->io_lock);
return 0;
}
EXPORT_SYMBOL_GPL(mc13783_free_irq);
static void mc13783_irq_work(struct work_struct *work)
{
struct mc13783 *mc13783 = container_of(work, struct mc13783, work);
int i;
unsigned int adc_sts;
/* check if the adc has finished any completion */
mc13783_reg_read(mc13783, MC13783_REG_INTERRUPT_STATUS_0, &adc_sts);
mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_STATUS_0,
adc_sts & MC13783_INT_STAT_ADCDONEI);
if (adc_sts & MC13783_INT_STAT_ADCDONEI)
complete_all(&mc13783->adc_done);
for (i = 0; i < MC13783_NUM_IRQ; i++)
if (mc13783->irq_handler[i].handler)
mc13783->irq_handler[i].handler(i,
mc13783->irq_handler[i].data);
enable_irq(mc13783->irq);
}
static irqreturn_t mc13783_interrupt(int irq, void *dev_id)
{
struct mc13783 *mc13783 = dev_id;
disable_irq_nosync(irq);
schedule_work(&mc13783->work);
return IRQ_HANDLED;
}
/* set adc to ts interrupt mode, which generates touchscreen wakeup interrupt */
static inline void mc13783_adc_set_ts_irq_mode(struct mc13783 *mc13783)
{
unsigned int reg_adc0, reg_adc1;
reg_adc0 = MC13783_ADC0_ADREFEN | MC13783_ADC0_ADREFMODE
| MC13783_ADC0_TSMOD0;
reg_adc1 = MC13783_ADC1_ADEN | MC13783_ADC1_ADTRIGIGN;
mc13783_reg_write(mc13783, MC13783_REG_ADC_0, reg_adc0);
mc13783_reg_write(mc13783, MC13783_REG_ADC_1, reg_adc1);
}
int mc13783_adc_do_conversion(struct mc13783 *mc13783, unsigned int mode,
unsigned int channel, unsigned int *sample)
{
unsigned int reg_adc0, reg_adc1;
int i;
mutex_lock(&mc13783->adc_conv_lock);
/* set up auto incrementing anyway to make quick read */
reg_adc0 = MC13783_ADC0_ADINC1 | MC13783_ADC0_ADINC2;
/* enable the adc, ignore external triggering and set ASC to trigger
* conversion */
reg_adc1 = MC13783_ADC1_ADEN | MC13783_ADC1_ADTRIGIGN
| MC13783_ADC1_ASC;
/* setup channel number */
if (channel > 7)
reg_adc1 |= MC13783_ADC1_ADSEL;
switch (mode) {
case MC13783_ADC_MODE_TS:
/* enables touch screen reference mode and set touchscreen mode
* to position mode */
reg_adc0 |= MC13783_ADC0_ADREFEN | MC13783_ADC0_ADREFMODE
| MC13783_ADC0_TSMOD0 | MC13783_ADC0_TSMOD1;
reg_adc1 |= 4 << MC13783_ADC1_CHAN1_SHIFT;
break;
case MC13783_ADC_MODE_SINGLE_CHAN:
reg_adc1 |= (channel & 0x7) << MC13783_ADC1_CHAN0_SHIFT;
reg_adc1 |= MC13783_ADC1_RAND;
break;
case MC13783_ADC_MODE_MULT_CHAN:
reg_adc1 |= 4 << MC13783_ADC1_CHAN1_SHIFT;
break;
default:
return -EINVAL;
}
mc13783_reg_write(mc13783, MC13783_REG_ADC_0, reg_adc0);
mc13783_reg_write(mc13783, MC13783_REG_ADC_1, reg_adc1);
wait_for_completion_interruptible(&mc13783->adc_done);
for (i = 0; i < 4; i++)
mc13783_reg_read(mc13783, MC13783_REG_ADC_2, &sample[i]);
if (mc13783->ts_active)
mc13783_adc_set_ts_irq_mode(mc13783);
mutex_unlock(&mc13783->adc_conv_lock);
return 0;
}
EXPORT_SYMBOL_GPL(mc13783_adc_do_conversion);
void mc13783_adc_set_ts_status(struct mc13783 *mc13783, unsigned int status)
{
mc13783->ts_active = status;
}
EXPORT_SYMBOL_GPL(mc13783_adc_set_ts_status);
static int mc13783_check_revision(struct mc13783 *mc13783)
{
u32 rev_id, rev1, rev2, finid, icid;
mc13783_read(mc13783, MC13783_REG_REVISION, &rev_id);
rev1 = (rev_id & 0x018) >> 3;
rev2 = (rev_id & 0x007);
icid = (rev_id & 0x01C0) >> 6;
finid = (rev_id & 0x01E00) >> 9;
/* Ver 0.2 is actually 3.2a. Report as 3.2 */
if ((rev1 == 0) && (rev2 == 2))
rev1 = 3;
if (rev1 == 0 || icid != 2) {
dev_err(mc13783->dev, "No MC13783 detected.\n");
return -ENODEV;
}
mc13783->revision = ((rev1 * 10) + rev2);
dev_info(mc13783->dev, "MC13783 Rev %d.%d FinVer %x detected\n", rev1,
rev2, finid);
return 0;
}
/*
* Register a client device. This is non-fatal since there is no need to
* fail the entire device init due to a single platform device failing.
*/
static void mc13783_client_dev_register(struct mc13783 *mc13783,
const char *name)
{
struct mfd_cell cell = {};
cell.name = name;
mfd_add_devices(mc13783->dev, -1, &cell, 1, NULL, 0);
}
static int __devinit mc13783_probe(struct spi_device *spi)
{
struct mc13783 *mc13783;
struct mc13783_platform_data *pdata = spi->dev.platform_data;
int ret;
mc13783 = kzalloc(sizeof(struct mc13783), GFP_KERNEL);
if (!mc13783)
return -ENOMEM;
dev_set_drvdata(&spi->dev, mc13783);
spi->mode = SPI_MODE_0 | SPI_CS_HIGH;
spi->bits_per_word = 32;
spi_setup(spi);
mc13783->spi_device = spi;
mc13783->dev = &spi->dev;
mc13783->irq = spi->irq;
INIT_WORK(&mc13783->work, mc13783_irq_work);
mutex_init(&mc13783->io_lock);
mutex_init(&mc13783->adc_conv_lock);
init_completion(&mc13783->adc_done);
if (pdata) {
mc13783->flags = pdata->flags;
mc13783->regulators = pdata->regulators;
mc13783->num_regulators = pdata->num_regulators;
}
if (mc13783_check_revision(mc13783)) {
ret = -ENODEV;
goto err_out;
}
/* clear and mask all interrupts */
mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_STATUS_0, 0x00ffffff);
mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_MASK_0, 0x00ffffff);
mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_STATUS_1, 0x00ffffff);
mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_MASK_1, 0x00ffffff);
/* unmask adcdone interrupts */
mc13783_set_bits(mc13783, MC13783_REG_INTERRUPT_MASK_0,
MC13783_INT_MASK_ADCDONEM, 0);
ret = request_irq(mc13783->irq, mc13783_interrupt,
IRQF_DISABLED | IRQF_TRIGGER_HIGH, "mc13783",
mc13783);
if (ret)
goto err_out;
if (mc13783->flags & MC13783_USE_CODEC)
mc13783_client_dev_register(mc13783, "mc13783-codec");
if (mc13783->flags & MC13783_USE_ADC)
mc13783_client_dev_register(mc13783, "mc13783-adc");
if (mc13783->flags & MC13783_USE_RTC)
mc13783_client_dev_register(mc13783, "mc13783-rtc");
if (mc13783->flags & MC13783_USE_REGULATOR)
mc13783_client_dev_register(mc13783, "mc13783-regulator");
if (mc13783->flags & MC13783_USE_TOUCHSCREEN)
mc13783_client_dev_register(mc13783, "mc13783-ts");
return 0;
err_out:
kfree(mc13783);
return ret;
}
static int __devexit mc13783_remove(struct spi_device *spi)
{
struct mc13783 *mc13783;
mc13783 = dev_get_drvdata(&spi->dev);
free_irq(mc13783->irq, mc13783);
mfd_remove_devices(&spi->dev);
return 0;
}
static struct spi_driver pmic_driver = {
.driver = {
.name = "mc13783",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
},
.probe = mc13783_probe,
.remove = __devexit_p(mc13783_remove),
};
static int __init pmic_init(void)
{
return spi_register_driver(&pmic_driver);
}
subsys_initcall(pmic_init);
static void __exit pmic_exit(void)
{
spi_unregister_driver(&pmic_driver);
}
module_exit(pmic_exit);
MODULE_DESCRIPTION("Core/Protocol driver for Freescale MC13783 PMIC");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_LICENSE("GPL");