Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8238addc authored by Sascha Hauer's avatar Sascha Hauer Committed by Samuel Ortiz
Browse files

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: default avatarSascha Hauer <s.hauer@pengutronix.de>
Signed-off-by: default avatarSamuel Ortiz <sameo@linux.intel.com>
parent 0ad651c9
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
 * 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");
+396 −0

File added.

Preview size limit exceeded, changes collapsed.

+84 −0
Original line number Diff line number Diff line
/*
 * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
 *
 * 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.
 */

#ifndef __INCLUDE_LINUX_MFD_MC13783_H
#define __INCLUDE_LINUX_MFD_MC13783_H

struct mc13783;
struct regulator_init_data;

struct mc13783_regulator_init_data {
	int id;
	struct regulator_init_data *init_data;
};

struct mc13783_platform_data {
	struct mc13783_regulator_init_data *regulators;
	int num_regulators;
	unsigned int flags;
};

/* mc13783_platform_data flags */
#define MC13783_USE_TOUCHSCREEN (1 << 0)
#define MC13783_USE_CODEC	(1 << 1)
#define MC13783_USE_ADC		(1 << 2)
#define MC13783_USE_RTC		(1 << 3)
#define MC13783_USE_REGULATOR	(1 << 4)

int mc13783_adc_do_conversion(struct mc13783 *mc13783, unsigned int mode,
		unsigned int channel, unsigned int *sample);

void mc13783_adc_set_ts_status(struct mc13783 *mc13783, unsigned int status);

#define	MC13783_SW_SW1A		0
#define	MC13783_SW_SW1B		1
#define	MC13783_SW_SW2A		2
#define	MC13783_SW_SW2B		3
#define	MC13783_SW_SW3		4
#define	MC13783_SW_PLL		5
#define	MC13783_REGU_VAUDIO	6
#define	MC13783_REGU_VIOHI	7
#define	MC13783_REGU_VIOLO	8
#define	MC13783_REGU_VDIG	9
#define	MC13783_REGU_VGEN	10
#define	MC13783_REGU_VRFDIG	11
#define	MC13783_REGU_VRFREF	12
#define	MC13783_REGU_VRFCP	13
#define	MC13783_REGU_VSIM	14
#define	MC13783_REGU_VESIM	15
#define	MC13783_REGU_VCAM	16
#define	MC13783_REGU_VRFBG	17
#define	MC13783_REGU_VVIB	18
#define	MC13783_REGU_VRF1	19
#define	MC13783_REGU_VRF2	20
#define	MC13783_REGU_VMMC1	21
#define	MC13783_REGU_VMMC2	22
#define	MC13783_REGU_GPO1	23
#define	MC13783_REGU_GPO2	24
#define	MC13783_REGU_GPO3	25
#define	MC13783_REGU_GPO4	26
#define	MC13783_REGU_V1		27
#define	MC13783_REGU_V2		28
#define	MC13783_REGU_V3		29
#define	MC13783_REGU_V4		30

#endif /* __INCLUDE_LINUX_MFD_MC13783_H */