diff options
Diffstat (limited to 'drivers/mfd')
27 files changed, 8029 insertions, 195 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 491ac0f800d..f5a60b49dfb 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -108,6 +108,19 @@ config TWL4030_CORE high speed USB OTG transceiver, an audio codec (on most versions) and many other features. +config TWL4030_POWER + bool "Support power resources on TWL4030 family chips" + depends on TWL4030_CORE && ARM + help + Say yes here if you want to use the power resources on the + TWL4030 family chips. Most of these resources are regulators, + which have a separate driver; some are control signals, such + as clock request handshaking. + + This driver uses board-specific data to initialize the resources + and load scripts controling which resources are switched off/on + or reset when a sleep, wakeup or warm reset event occurs. + config MFD_TMIO bool default n @@ -157,6 +170,16 @@ config MFD_WM8400 the device, additional drivers must be enabled in order to use the functionality of the device. +config MFD_WM831X + tristate "Support Wolfson Microelectronics WM831x PMICs" + select MFD_CORE + depends on I2C + help + Support for the Wolfson Microelecronics WM831x PMICs. This + driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + config MFD_WM8350 tristate @@ -228,6 +251,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 @@ -256,6 +289,15 @@ config AB3100_CORE LEDs, vibrator, system power and temperature, power management and ALSA sound. +config AB3100_OTP + tristate "ST-Ericsson AB3100 OTP functions" + depends on AB3100_CORE + default y if AB3100_CORE + help + Select this to enable the AB3100 Mixed Signal IC OTP (one-time + programmable memory) support. This exposes a sysfs file to read + out OTP values. + config EZX_PCAP bool "PCAP Support" depends on GENERIC_HARDIRQS && SPI_MASTER @@ -263,6 +305,8 @@ config EZX_PCAP This enables the PCAP ASIC present on EZX Phones. This is needed for MMC, TouchScreen, Sound, USB, etc.. +source "drivers/mfd/glamo/Kconfig" + endmenu menu "Multimedia Capabilities Port drivers" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 6f8a9a1af20..5272b0451f0 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o +obj-$(CONFIG_MFD_GLAMO) += glamo/ obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o @@ -15,6 +16,8 @@ obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o obj-$(CONFIG_MFD_WM8400) += wm8400-core.o +wm831x-objs := wm831x-core.o wm831x-irq.o wm831x-otp.o +obj-$(CONFIG_MFD_WM831X) += wm831x.o wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o obj-$(CONFIG_MFD_WM8350) += wm8350.o obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o @@ -23,6 +26,9 @@ obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_TWL4030_CORE) += twl4030-core.o twl4030-irq.o +obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o + +obj-$(CONFIG_MFD_MC13783) += mc13783-core.o obj-$(CONFIG_MFD_CORE) += mfd-core.o @@ -44,3 +50,4 @@ obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o obj-$(CONFIG_AB3100_CORE) += ab3100-core.o +obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index 13e7d7bfe85..61348102827 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -14,7 +14,6 @@ #include <linux/platform_device.h> #include <linux/device.h> #include <linux/interrupt.h> -#include <linux/workqueue.h> #include <linux/debugfs.h> #include <linux/seq_file.h> #include <linux/uaccess.h> @@ -58,8 +57,6 @@ * The AB3100 is usually assigned address 0x48 (7-bit) * The chip is defined in the platform i2c_board_data section. */ -static unsigned short normal_i2c[] = { 0x48, I2C_CLIENT_END }; -I2C_CLIENT_INSMOD_1(ab3100); u8 ab3100_get_chip_type(struct ab3100 *ab3100) { @@ -77,7 +74,7 @@ u8 ab3100_get_chip_type(struct ab3100 *ab3100) } EXPORT_SYMBOL(ab3100_get_chip_type); -int ab3100_set_register(struct ab3100 *ab3100, u8 reg, u8 regval) +int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval) { u8 regandval[2] = {reg, regval}; int err; @@ -107,9 +104,10 @@ int ab3100_set_register(struct ab3100 *ab3100, u8 reg, u8 regval) err = 0; } mutex_unlock(&ab3100->access_mutex); - return 0; + return err; } -EXPORT_SYMBOL(ab3100_set_register); +EXPORT_SYMBOL(ab3100_set_register_interruptible); + /* * The test registers exist at an I2C bus address up one @@ -118,7 +116,7 @@ EXPORT_SYMBOL(ab3100_set_register); * anyway. It's currently only used from this file so declare * it static and do not export. */ -static int ab3100_set_test_register(struct ab3100 *ab3100, +static int ab3100_set_test_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval) { u8 regandval[2] = {reg, regval}; @@ -148,7 +146,8 @@ static int ab3100_set_test_register(struct ab3100 *ab3100, return err; } -int ab3100_get_register(struct ab3100 *ab3100, u8 reg, u8 *regval) + +int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval) { int err; @@ -202,9 +201,10 @@ int ab3100_get_register(struct ab3100 *ab3100, u8 reg, u8 *regval) mutex_unlock(&ab3100->access_mutex); return err; } -EXPORT_SYMBOL(ab3100_get_register); +EXPORT_SYMBOL(ab3100_get_register_interruptible); -int ab3100_get_register_page(struct ab3100 *ab3100, + +int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, u8 first_reg, u8 *regvals, u8 numregs) { int err; @@ -258,9 +258,10 @@ int ab3100_get_register_page(struct ab3100 *ab3100, mutex_unlock(&ab3100->access_mutex); return err; } -EXPORT_SYMBOL(ab3100_get_register_page); +EXPORT_SYMBOL(ab3100_get_register_page_interruptible); + -int ab3100_mask_and_set_register(struct ab3100 *ab3100, +int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 andmask, u8 ormask) { u8 regandval[2] = {reg, 0}; @@ -328,7 +329,8 @@ int ab3100_mask_and_set_register(struct ab3100 *ab3100, mutex_unlock(&ab3100->access_mutex); return err; } -EXPORT_SYMBOL(ab3100_mask_and_set_register); +EXPORT_SYMBOL(ab3100_mask_and_set_register_interruptible); + /* * Register a simple callback for handling any AB3100 events. @@ -371,7 +373,7 @@ static void ab3100_work(struct work_struct *work) u32 fatevent; int err; - err = ab3100_get_register_page(ab3100, AB3100_EVENTA1, + err = ab3100_get_register_page_interruptible(ab3100, AB3100_EVENTA1, event_regs, 3); if (err) goto err_event_wq; @@ -417,7 +419,7 @@ static irqreturn_t ab3100_irq_handler(int irq, void *data) * stuff and we will re-enable the interrupts once th * worker has finished. */ - disable_irq(ab3100->i2c_client->irq); + disable_irq_nosync(irq); schedule_work(&ab3100->work); return IRQ_HANDLED; } @@ -435,7 +437,7 @@ static int ab3100_registers_print(struct seq_file *s, void *p) seq_printf(s, "AB3100 registers:\n"); for (reg = 0; reg < 0xff; reg++) { - ab3100_get_register(ab3100, reg, &value); + ab3100_get_register_interruptible(ab3100, reg, &value); seq_printf(s, "[0x%x]: 0x%x\n", reg, value); } return 0; @@ -465,14 +467,14 @@ static int ab3100_get_set_reg_open_file(struct inode *inode, struct file *file) return 0; } -static int ab3100_get_set_reg(struct file *file, - const char __user *user_buf, - size_t count, loff_t *ppos) +static ssize_t ab3100_get_set_reg(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) { struct ab3100_get_set_reg_priv *priv = file->private_data; struct ab3100 *ab3100 = priv->ab3100; char buf[32]; - int buf_size; + ssize_t buf_size; int regp; unsigned long user_reg; int err; @@ -515,7 +517,7 @@ static int ab3100_get_set_reg(struct file *file, u8 reg = (u8) user_reg; u8 regvalue; - ab3100_get_register(ab3100, reg, ®value); + ab3100_get_register_interruptible(ab3100, reg, ®value); dev_info(ab3100->dev, "debug read AB3100 reg[0x%02x]: 0x%02x\n", @@ -547,8 +549,8 @@ static int ab3100_get_set_reg(struct file *file, return -EINVAL; value = (u8) user_value; - ab3100_set_register(ab3100, reg, value); - ab3100_get_register(ab3100, reg, ®value); + ab3100_set_register_interruptible(ab3100, reg, value); + ab3100_get_register_interruptible(ab3100, reg, ®value); dev_info(ab3100->dev, "debug write reg[0x%02x] with 0x%02x, " @@ -643,7 +645,7 @@ struct ab3100_init_setting { u8 setting; }; -static const struct ab3100_init_setting __initdata +static const struct ab3100_init_setting __initconst ab3100_init_settings[] = { { .abreg = AB3100_MCA, @@ -662,7 +664,7 @@ ab3100_init_settings[] = { .setting = 0x01 }, { .abreg = AB3100_IMRB1, - .setting = 0xFF + .setting = 0xBF }, { .abreg = AB3100_IMRB2, .setting = 0xFF @@ -696,7 +698,7 @@ static int __init ab3100_setup(struct ab3100 *ab3100) int i; for (i = 0; i < ARRAY_SIZE(ab3100_init_settings); i++) { - err = ab3100_set_register(ab3100, + err = ab3100_set_register_interruptible(ab3100, ab3100_init_settings[i].abreg, ab3100_init_settings[i].setting); if (err) @@ -705,14 +707,14 @@ static int __init ab3100_setup(struct ab3100 *ab3100) /* * Special trick to make the AB3100 use the 32kHz clock (RTC) - * bit 3 in test registe 0x02 is a special, undocumented test + * bit 3 in test register 0x02 is a special, undocumented test * register bit that only exist in AB3100 P1E */ if (ab3100->chip_id == 0xc4) { dev_warn(ab3100->dev, "AB3100 P1E variant detected, " "forcing chip to 32KHz\n"); - err = ab3100_set_test_register(ab3100, 0x02, 0x08); + err = ab3100_set_test_register_interruptible(ab3100, 0x02, 0x08); } exit_no_setup: @@ -833,6 +835,8 @@ static int __init ab3100_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ab3100 *ab3100; + struct ab3100_platform_data *ab3100_plf_data = + client->dev.platform_data; int err; int i; @@ -852,8 +856,8 @@ static int __init ab3100_probe(struct i2c_client *client, i2c_set_clientdata(client, ab3100); /* Read chip ID register */ - err = ab3100_get_register(ab3100, AB3100_CID, - &ab3100->chip_id); + err = ab3100_get_register_interruptible(ab3100, AB3100_CID, + &ab3100->chip_id); if (err) { dev_err(&client->dev, "could not communicate with the AB3100 analog " @@ -916,6 +920,8 @@ static int __init ab3100_probe(struct i2c_client *client, for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++) { ab3100_platform_devs[i]->dev.parent = &client->dev; + ab3100_platform_devs[i]->dev.platform_data = + ab3100_plf_data; platform_set_drvdata(ab3100_platform_devs[i], ab3100); } @@ -958,7 +964,7 @@ static int __exit ab3100_remove(struct i2c_client *client) } static const struct i2c_device_id ab3100_id[] = { - { "ab3100", ab3100 }, + { "ab3100", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ab3100_id); diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c new file mode 100644 index 00000000000..0499b2031a2 --- /dev/null +++ b/drivers/mfd/ab3100-otp.c @@ -0,0 +1,268 @@ +/* + * drivers/mfd/ab3100_otp.c + * + * Copyright (C) 2007-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Driver to read out OTP from the AB3100 Mixed-signal circuit + * Author: Linus Walleij <linus.walleij@stericsson.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mfd/ab3100.h> +#include <linux/debugfs.h> + +/* The OTP registers */ +#define AB3100_OTP0 0xb0 +#define AB3100_OTP1 0xb1 +#define AB3100_OTP2 0xb2 +#define AB3100_OTP3 0xb3 +#define AB3100_OTP4 0xb4 +#define AB3100_OTP5 0xb5 +#define AB3100_OTP6 0xb6 +#define AB3100_OTP7 0xb7 +#define AB3100_OTPP 0xbf + +/** + * struct ab3100_otp + * @dev containing device + * @ab3100 a pointer to the parent ab3100 device struct + * @locked whether the OTP is locked, after locking, no more bits + * can be changed but before locking it is still possible + * to change bits from 1->0. + * @freq clocking frequency for the OTP, this frequency is either + * 32768Hz or 1MHz/30 + * @paf product activation flag, indicates whether this is a real + * product (paf true) or a lab board etc (paf false) + * @imeich if this is set it is possible to override the + * IMEI number found in the tac, fac and svn fields with + * (secured) software + * @cid customer ID + * @tac type allocation code of the IMEI + * @fac final assembly code of the IMEI + * @svn software version number of the IMEI + * @debugfs a debugfs file used when dumping to file + */ +struct ab3100_otp { + struct device *dev; + struct ab3100 *ab3100; + bool locked; + u32 freq; + bool paf; + bool imeich; + u16 cid:14; + u32 tac:20; + u8 fac; + u32 svn:20; + struct dentry *debugfs; +}; + +static int __init ab3100_otp_read(struct ab3100_otp *otp) +{ + struct ab3100 *ab = otp->ab3100; + u8 otpval[8]; + u8 otpp; + int err; + + err = ab3100_get_register_interruptible(ab, AB3100_OTPP, &otpp); + if (err) { + dev_err(otp->dev, "unable to read OTPP register\n"); + return err; + } + + err = ab3100_get_register_page_interruptible(ab, AB3100_OTP0, + otpval, 8); + if (err) { + dev_err(otp->dev, "unable to read OTP register page\n"); + return err; + } + + /* Cache OTP properties, they never change by nature */ + otp->locked = (otpp & 0x80); + otp->freq = (otpp & 0x40) ? 32768 : 34100; + otp->paf = (otpval[1] & 0x80); + otp->imeich = (otpval[1] & 0x40); + otp->cid = ((otpval[1] << 8) | otpval[0]) & 0x3fff; + otp->tac = ((otpval[4] & 0x0f) << 16) | (otpval[3] << 8) | otpval[2]; + otp->fac = ((otpval[5] & 0x0f) << 4) | (otpval[4] >> 4); + otp->svn = (otpval[7] << 12) | (otpval[6] << 4) | (otpval[5] >> 4); + return 0; +} + +/* + * This is a simple debugfs human-readable file that dumps out + * the contents of the OTP. + */ +#ifdef CONFIG_DEBUGFS +static int show_otp(struct seq_file *s, void *v) +{ + struct ab3100_otp *otp = s->private; + int err; + + seq_printf(s, "OTP is %s\n", otp->locked ? "LOCKED" : "UNLOCKED"); + seq_printf(s, "OTP clock switch startup is %uHz\n", otp->freq); + seq_printf(s, "PAF is %s\n", otp->paf ? "SET" : "NOT SET"); + seq_printf(s, "IMEI is %s\n", otp->imeich ? + "CHANGEABLE" : "NOT CHANGEABLE"); + seq_printf(s, "CID: 0x%04x (decimal: %d)\n", otp->cid, otp->cid); + seq_printf(s, "IMEI: %u-%u-%u\n", otp->tac, otp->fac, otp->svn); + return 0; +} + +static int ab3100_otp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab3100_otp_show, inode->i_private); +} + +static const struct file_operations ab3100_otp_operations = { + .open = ab3100_otp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init ab3100_otp_init_debugfs(struct device *dev, + struct ab3100_otp *otp) +{ + otp->debugfs = debugfs_create_file("ab3100_otp", S_IFREG | S_IRUGO, + NULL, otp, + &ab3100_otp_operations); + if (!otp->debugfs) { + dev_err(dev, "AB3100 debugfs OTP file registration failed!\n"); + return err; + } +} + +static void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) +{ + debugfs_remove_file(otp->debugfs); +} +#else +/* Compile this out if debugfs not selected */ +static inline int __init ab3100_otp_init_debugfs(struct device *dev, + struct ab3100_otp *otp) +{ + return 0; +} + +static inline void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) +{ +} +#endif + +#define SHOW_AB3100_ATTR(name) \ +static ssize_t ab3100_otp_##name##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{\ + struct ab3100_otp *otp = dev_get_drvdata(dev); \ + return sprintf(buf, "%u\n", otp->name); \ +} + +SHOW_AB3100_ATTR(locked) +SHOW_AB3100_ATTR(freq) +SHOW_AB3100_ATTR(paf) +SHOW_AB3100_ATTR(imeich) +SHOW_AB3100_ATTR(cid) +SHOW_AB3100_ATTR(fac) +SHOW_AB3100_ATTR(tac) +SHOW_AB3100_ATTR(svn) + +static struct device_attribute ab3100_otp_attrs[] = { + __ATTR(locked, S_IRUGO, ab3100_otp_locked_show, NULL), + __ATTR(freq, S_IRUGO, ab3100_otp_freq_show, NULL), + __ATTR(paf, S_IRUGO, ab3100_otp_paf_show, NULL), + __ATTR(imeich, S_IRUGO, ab3100_otp_imeich_show, NULL), + __ATTR(cid, S_IRUGO, ab3100_otp_cid_show, NULL), + __ATTR(fac, S_IRUGO, ab3100_otp_fac_show, NULL), + __ATTR(tac, S_IRUGO, ab3100_otp_tac_show, NULL), + __ATTR(svn, S_IRUGO, ab3100_otp_svn_show, NULL), +}; + +static int __init ab3100_otp_probe(struct platform_device *pdev) +{ + struct ab3100_otp *otp; + int err = 0; + int i; + + otp = kzalloc(sizeof(struct ab3100_otp), GFP_KERNEL); + if (!otp) { + dev_err(&pdev->dev, "could not allocate AB3100 OTP device\n"); + return -ENOMEM; + } + otp->dev = &pdev->dev; + + /* Replace platform data coming in with a local struct */ + otp->ab3100 = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, otp); + + err = ab3100_otp_read(otp); + if (err) + return err; + + dev_info(&pdev->dev, "AB3100 OTP readout registered\n"); + + /* sysfs entries */ + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) { + err = device_create_file(&pdev->dev, + &ab3100_otp_attrs[i]); + if (err) + goto out_no_sysfs; + } + + /* debugfs entries */ + err = ab3100_otp_init_debugfs(&pdev->dev, otp); + if (err) + goto out_no_debugfs; + + return 0; + +out_no_sysfs: + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) + device_remove_file(&pdev->dev, + &ab3100_otp_attrs[i]); +out_no_debugfs: + kfree(otp); + return err; +} + +static int __exit ab3100_otp_remove(struct platform_device *pdev) +{ + struct ab3100_otp *otp = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) + device_remove_file(&pdev->dev, + &ab3100_otp_attrs[i]); + ab3100_otp_exit_debugfs(otp); + kfree(otp); + return 0; +} + +static struct platform_driver ab3100_otp_driver = { + .driver = { + .name = "ab3100-otp", + .owner = THIS_MODULE, + }, + .remove = __exit_p(ab3100_otp_remove), +}; + +static int __init ab3100_otp_init(void) +{ + return platform_driver_probe(&ab3100_otp_driver, + ab3100_otp_probe); +} + +static void __exit ab3100_otp_exit(void) +{ + platform_driver_unregister(&ab3100_otp_driver); +} + +module_init(ab3100_otp_init); +module_exit(ab3100_otp_exit); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); +MODULE_DESCRIPTION("AB3100 OTP Readout Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/dm355evm_msp.c b/drivers/mfd/dm355evm_msp.c index 5b6e58a3ba4..3d4a861976c 100644 --- a/drivers/mfd/dm355evm_msp.c +++ b/drivers/mfd/dm355evm_msp.c @@ -107,8 +107,16 @@ static const u8 msp_gpios[] = { MSP_GPIO(2, SWITCH1), MSP_GPIO(3, SWITCH1), MSP_GPIO(4, SWITCH1), /* switches on MMC/SD sockets */ - MSP_GPIO(1, SDMMC), MSP_GPIO(2, SDMMC), /* mmc0 WP, nCD */ - MSP_GPIO(3, SDMMC), MSP_GPIO(4, SDMMC), /* mmc1 WP, nCD */ + /* + * Note: EVMDM355_ECP_VA4.pdf suggests that Bit 2 and 4 should be + * checked for card detection. However on the EVM bit 1 and 3 gives + * this status, for 0 and 1 instance respectively. The pdf also + * suggests that Bit 1 and 3 should be checked for write protection. + * However on the EVM bit 2 and 4 gives this status,for 0 and 1 + * instance respectively. + */ + MSP_GPIO(2, SDMMC), MSP_GPIO(1, SDMMC), /* mmc0 WP, nCD */ + MSP_GPIO(4, SDMMC), MSP_GPIO(3, SDMMC), /* mmc1 WP, nCD */ }; #define MSP_GPIO_REG(offset) (msp_gpios[(offset)] >> 3) diff --git a/drivers/mfd/ezx-pcap.c b/drivers/mfd/ezx-pcap.c index c1de4afa89a..87628891797 100644 --- a/drivers/mfd/ezx-pcap.c +++ b/drivers/mfd/ezx-pcap.c @@ -17,6 +17,7 @@ #include <linux/irq.h> #include <linux/mfd/ezx-pcap.h> #include <linux/spi/spi.h> +#include <linux/gpio.h> #define PCAP_ADC_MAXQ 8 struct pcap_adc_request { @@ -106,11 +107,35 @@ int ezx_pcap_read(struct pcap_chip *pcap, u8 reg_num, u32 *value) } EXPORT_SYMBOL_GPL(ezx_pcap_read); +int ezx_pcap_set_bits(struct pcap_chip *pcap, u8 reg_num, u32 mask, u32 val) +{ + int ret; + u32 tmp = PCAP_REGISTER_READ_OP_BIT | + (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + + mutex_lock(&pcap->io_mutex); + ret = ezx_pcap_putget(pcap, &tmp); + if (ret) + goto out_unlock; + + tmp &= (PCAP_REGISTER_VALUE_MASK & ~mask); + tmp |= (val & mask) | PCAP_REGISTER_WRITE_OP_BIT | + (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + + ret = ezx_pcap_putget(pcap, &tmp); +out_unlock: + mutex_unlock(&pcap->io_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(ezx_pcap_set_bits); + /* IRQ */ -static inline unsigned int irq2pcap(struct pcap_chip *pcap, int irq) +int irq_to_pcap(struct pcap_chip *pcap, int irq) { - return 1 << (irq - pcap->irq_base); + return irq - pcap->irq_base; } +EXPORT_SYMBOL_GPL(irq_to_pcap); int pcap_to_irq(struct pcap_chip *pcap, int irq) { @@ -122,7 +147,7 @@ static void pcap_mask_irq(unsigned int irq) { struct pcap_chip *pcap = get_irq_chip_data(irq); - pcap->msr |= irq2pcap(pcap, irq); + pcap->msr |= 1 << irq_to_pcap(pcap, irq); queue_work(pcap->workqueue, &pcap->msr_work); } @@ -130,7 +155,7 @@ static void pcap_unmask_irq(unsigned int irq) { struct pcap_chip *pcap = get_irq_chip_data(irq); - pcap->msr &= ~irq2pcap(pcap, irq); + pcap->msr &= ~(1 << irq_to_pcap(pcap, irq)); queue_work(pcap->workqueue, &pcap->msr_work); } @@ -154,34 +179,38 @@ static void pcap_isr_work(struct work_struct *work) u32 msr, isr, int_sel, service; int irq; - ezx_pcap_read(pcap, PCAP_REG_MSR, &msr); - ezx_pcap_read(pcap, PCAP_REG_ISR, &isr); + do { + ezx_pcap_read(pcap, PCAP_REG_MSR, &msr); + ezx_pcap_read(pcap, PCAP_REG_ISR, &isr); - /* We cant service/ack irqs that are assigned to port 2 */ - if (!(pdata->config & PCAP_SECOND_PORT)) { - ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel); - isr &= ~int_sel; - } - ezx_pcap_write(pcap, PCAP_REG_ISR, isr); + /* We cant service/ack irqs that are assigned to port 2 */ + if (!(pdata->config & PCAP_SECOND_PORT)) { + ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel); + isr &= ~int_sel; + } - local_irq_disable(); - service = isr & ~msr; + ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr); + ezx_pcap_write(pcap, PCAP_REG_ISR, isr); - for (irq = pcap->irq_base; service; service >>= 1, irq++) { - if (service & 1) { - struct irq_desc *desc = irq_to_desc(irq); + local_irq_disable(); + service = isr & ~msr; + for (irq = pcap->irq_base; service; service >>= 1, irq++) { + if (service & 1) { + struct irq_desc *desc = irq_to_desc(irq); - if (WARN(!desc, KERN_WARNING - "Invalid PCAP IRQ %d\n", irq)) - break; + if (WARN(!desc, KERN_WARNING + "Invalid PCAP IRQ %d\n", irq)) + break; - if (desc->status & IRQ_DISABLED) - note_interrupt(irq, desc, IRQ_NONE); - else - desc->handle_irq(irq, desc); + if (desc->status & IRQ_DISABLED) + note_interrupt(irq, desc, IRQ_NONE); + else + desc->handle_irq(irq, desc); + } } - } - local_irq_enable(); + local_irq_enable(); + ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr); + } while (gpio_get_value(irq_to_gpio(pcap->spi->irq))); } static void pcap_irq_handler(unsigned int irq, struct irq_desc *desc) @@ -194,6 +223,19 @@ static void pcap_irq_handler(unsigned int irq, struct irq_desc *desc) } /* ADC */ +void pcap_set_ts_bits(struct pcap_chip *pcap, u32 bits) +{ + u32 tmp; + + mutex_lock(&pcap->adc_mutex); + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= ~(PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + tmp |= bits & (PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); + mutex_unlock(&pcap->adc_mutex); +} +EXPORT_SYMBOL_GPL(pcap_set_ts_bits); + static void pcap_disable_adc(struct pcap_chip *pcap) { u32 tmp; @@ -216,15 +258,16 @@ static void pcap_adc_trigger(struct pcap_chip *pcap) mutex_unlock(&pcap->adc_mutex); return; } - mutex_unlock(&pcap->adc_mutex); - - /* start conversion on requested bank */ - tmp = pcap->adc_queue[head]->flags | PCAP_ADC_ADEN; + /* start conversion on requested bank, save TS_M bits */ + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= (PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + tmp |= pcap->adc_queue[head]->flags | PCAP_ADC_ADEN; if (pcap->adc_queue[head]->bank == PCAP_ADC_BANK_1) tmp |= PCAP_ADC_AD_SEL1; ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); + mutex_unlock(&pcap->adc_mutex); ezx_pcap_write(pcap, PCAP_REG_ADR, PCAP_ADR_ASC); } @@ -499,9 +542,10 @@ static void __exit ezx_pcap_exit(void) spi_unregister_driver(&ezxpcap_driver); } -module_init(ezx_pcap_init); +subsys_initcall(ezx_pcap_init); module_exit(ezx_pcap_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Daniel Ribeiro / Harald Welte"); MODULE_DESCRIPTION("Motorola PCAP2 ASIC Driver"); +MODULE_ALIAS("spi:ezx-pcap"); diff --git a/drivers/mfd/glamo/Kconfig b/drivers/mfd/glamo/Kconfig new file mode 100644 index 00000000000..3aa4831a4ff --- /dev/null +++ b/drivers/mfd/glamo/Kconfig @@ -0,0 +1,42 @@ +config MFD_GLAMO + bool "Smedia Glamo 336x/337x support" + select MFD_CORE + help + This enables the core driver for the Smedia Glamo 336x/337x + multi-function device. It includes irq_chip demultiplex as + well as clock / power management and GPIO support. + +config MFD_GLAMO_FB + tristate "Smedia Glamo 336x/337x framebuffer support" + depends on FB && MFD_GLAMO + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Frame buffer driver for the LCD controller in the Smedia Glamo + 336x/337x. + + This driver is also available as a module ( = code which can be + inserted and removed from the running kernel whenever you want). The + module will be called glamofb. If you want to compile it as a module, + say M here and read <file:Documentation/modules.txt>. + + If unsure, say N. + +config MFD_GLAMO_GPIO + tristate "Glamo GPIO support" + depends on MFD_GLAMO + + help + Enable a bitbanging SPI adapter driver for the Smedia Glamo. + +config MFD_GLAMO_MCI + tristate "Glamo S3C SD/MMC Card Interface support" + depends on MFD_GLAMO && MMC && REGULATOR + select CRC7 + help + This selects a driver for the MCI interface found in + the S-Media GLAMO chip, as used in Openmoko + neo1973 GTA-02. + + If unsure, say N. diff --git a/drivers/mfd/glamo/Makefile b/drivers/mfd/glamo/Makefile new file mode 100644 index 00000000000..ebf26f7e473 --- /dev/null +++ b/drivers/mfd/glamo/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for the Smedia Glamo framebuffer driver +# + +obj-$(CONFIG_MFD_GLAMO) += glamo-core.o +obj-$(CONFIG_MFD_GLAMO_GPIO) += glamo-gpio.o +obj-$(CONFIG_MFD_GLAMO_SPI) += glamo-spi.o + +obj-$(CONFIG_MFD_GLAMO_FB) += glamo-fb.o +obj-$(CONFIG_MFD_GLAMO_MCI) += glamo-mci.o + diff --git a/drivers/mfd/glamo/glamo-core.c b/drivers/mfd/glamo/glamo-core.c new file mode 100644 index 00000000000..e0e39409595 --- /dev/null +++ b/drivers/mfd/glamo/glamo-core.c @@ -0,0 +1,1284 @@ +/* Smedia Glamo 3362 driver + * + * (C) 2007 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * (C) 2009, Lars-Peter Clausen <lars@metafoo.de> + * All rights reserved. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/kernel_stat.h> +#include <linux/spinlock.h> +#include <linux/mfd/core.h> +#include <linux/mfd/glamo.h> +#include <linux/io.h> + +#include <linux/pm.h> + +#include "glamo-regs.h" +#include "glamo-core.h" + +#define GLAMO_MEM_REFRESH_COUNT 0x100 + +#define GLAMO_NR_IRQS 9 + +#define GLAMO_IRQ_HOSTBUS 0 +#define GLAMO_IRQ_JPEG 1 +#define GLAMO_IRQ_MPEG 2 +#define GLAMO_IRQ_MPROC1 3 +#define GLAMO_IRQ_MPROC0 4 +#define GLAMO_IRQ_CMDQUEUE 5 +#define GLAMO_IRQ_2D 6 +#define GLAMO_IRQ_MMC 7 +#define GLAMO_IRQ_RISC 8 + +/* + * Glamo internal settings + * + * We run the memory interface from the faster PLLB on 2.6.28 kernels and + * above. Couple of GTA02 users report trouble with memory bus when they + * upgraded from 2.6.24. So this parameter allows reversion to 2.6.24 + * scheme if their Glamo chip needs it. + * + * you can override the faster default on kernel commandline using + * + * glamo3362.slow_memory=1 + * + * for example + */ + +static int slow_memory; +module_param(slow_memory, int, 0644); + +struct reg_range { + int start; + int count; + char *name; + unsigned dump:1; +}; + +static const struct reg_range reg_range[] = { + { 0x0000, 0x76, "General", 1 }, + { 0x0200, 0x18, "Host Bus", 1 }, + { 0x0300, 0x38, "Memory", 1 }, +/* { 0x0400, 0x100, "Sensor", 0 }, */ +/* { 0x0500, 0x300, "ISP", 0 }, */ +/* { 0x0800, 0x400, "JPEG", 0 }, */ +/* { 0x0c00, 0xcc, "MPEG", 0 }, */ + { 0x1100, 0xb2, "LCD 1", 1 }, + { 0x1200, 0x64, "LCD 2", 1 }, + { 0x1400, 0x42, "MMC", 1 }, +/* { 0x1500, 0x080, "MPU 0", 0 }, + { 0x1580, 0x080, "MPU 1", 0 }, + { 0x1600, 0x080, "Cmd Queue", 0 }, + { 0x1680, 0x080, "RISC CPU", 0 },*/ + { 0x1700, 0x400, "2D Unit", 1 }, +/* { 0x1b00, 0x900, "3D Unit", 0 }, */ +}; + +static inline void __reg_write(struct glamo_core *glamo, + uint16_t reg, uint16_t val) +{ + writew(val, glamo->base + reg); +} + +void glamo_reg_write(struct glamo_core *glamo, + uint16_t reg, uint16_t val) +{ + spin_lock(&glamo->lock); + __reg_write(glamo, reg, val); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL_GPL(glamo_reg_write); + + +static inline uint16_t __reg_read(struct glamo_core *glamo, + uint16_t reg) +{ + return readw(glamo->base + reg); +} + +uint16_t glamo_reg_read(struct glamo_core *glamo, uint16_t reg) +{ + uint16_t val; + spin_lock(&glamo->lock); + val = __reg_read(glamo, reg); + spin_unlock(&glamo->lock); + + return val; +} +EXPORT_SYMBOL_GPL(glamo_reg_read); + +static void __reg_set_bit_mask(struct glamo_core *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + uint16_t tmp; + + val &= mask; + + tmp = __reg_read(glamo, reg); + tmp &= ~mask; + tmp |= val; + __reg_write(glamo, reg, tmp); +} + +static void reg_set_bit_mask(struct glamo_core *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + spin_lock(&glamo->lock); + __reg_set_bit_mask(glamo, reg, mask, val); + spin_unlock(&glamo->lock); +} + +static inline void __reg_set_bit(struct glamo_core *glamo, + uint16_t reg, uint16_t bit) +{ + uint16_t tmp; + tmp = __reg_read(glamo, reg); + tmp |= bit; + __reg_write(glamo, reg, tmp); +} + +static inline void __reg_clear_bit(struct glamo_core *glamo, + uint16_t reg, uint16_t bit) +{ + uint16_t tmp; + tmp = __reg_read(glamo, reg); + tmp &= ~bit; + __reg_write(glamo, reg, tmp); +} + +static void __reg_write_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + uint16_t end; + for (end = reg + count * 2; reg < end; reg += 2, ++values) + __reg_write(glamo, reg, *values); +} + +static void __reg_read_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + uint16_t end; + for (end = reg + count * 2; reg < end; reg += 2, ++values) + *values = __reg_read(glamo, reg); +} + +void glamo_reg_write_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + spin_lock(&glamo->lock); + __reg_write_batch(glamo, reg, count, values); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL(glamo_reg_write_batch); + +void glamo_reg_read_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + spin_lock(&glamo->lock); + __reg_read_batch(glamo, reg, count, values); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL(glamo_reg_read_batch); + +/*********************************************************************** + * resources of sibling devices + ***********************************************************************/ + +static struct resource glamo_fb_resources[] = { + { + .name = "glamo-fb-regs", + .start = GLAMO_REGOFS_LCD, + .end = GLAMO_REGOFS_MMC - 1, + .flags = IORESOURCE_MEM, + }, { + .name = "glamo-fb-mem", + .start = GLAMO_OFFSET_FB, + .end = GLAMO_OFFSET_FB + GLAMO_FB_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource glamo_mmc_resources[] = { + { + .name = "glamo-mmc-regs", + .start = GLAMO_REGOFS_MMC, + .end = GLAMO_REGOFS_MPROC0 - 1, + .flags = IORESOURCE_MEM + }, { + .name = "glamo-mmc-mem", + .start = GLAMO_OFFSET_FB + GLAMO_FB_SIZE, + .end = GLAMO_OFFSET_FB + GLAMO_FB_SIZE + + GLAMO_MMC_BUFFER_SIZE - 1, + .flags = IORESOURCE_MEM + }, { + .start = GLAMO_IRQ_MMC, + .end = GLAMO_IRQ_MMC, + .flags = IORESOURCE_IRQ, + }, +}; + +enum glamo_cells { + GLAMO_CELL_FB, + GLAMO_CELL_MMC, + GLAMO_CELL_GPIO, +}; + +static const struct mfd_cell glamo_cells[] = { + [GLAMO_CELL_FB] = { + .name = "glamo-fb", + .num_resources = ARRAY_SIZE(glamo_fb_resources), + .resources = glamo_fb_resources, + }, + [GLAMO_CELL_MMC] = { + .name = "glamo-mci", + .num_resources = ARRAY_SIZE(glamo_mmc_resources), + .resources = glamo_mmc_resources, + }, + [GLAMO_CELL_GPIO] = { + .name = "glamo-gpio", + }, +}; + +/*********************************************************************** + * IRQ demultiplexer + ***********************************************************************/ +#define glamo_irq_bit(glamo, x) BIT(x - glamo->irq_base) + +static inline struct glamo_core *irq_to_glamo(unsigned int irq) +{ + return (struct glamo_core *)get_irq_chip_data(irq); +} + +static void glamo_ack_irq(unsigned int irq) +{ + struct glamo_core *glamo = irq_to_glamo(irq); + /* clear interrupt source */ + __reg_write(glamo, GLAMO_REG_IRQ_CLEAR, glamo_irq_bit(glamo, irq)); +} + +static void glamo_mask_irq(unsigned int irq) +{ + struct glamo_core *glamo = irq_to_glamo(irq); + + /* clear bit in enable register */ + __reg_clear_bit(glamo, GLAMO_REG_IRQ_ENABLE, glamo_irq_bit(glamo, irq)); +} + +static void glamo_unmask_irq(unsigned int irq) +{ + struct glamo_core *glamo = irq_to_glamo(irq); + + /* set bit in enable register */ + __reg_set_bit(glamo, GLAMO_REG_IRQ_ENABLE, glamo_irq_bit(glamo, irq)); +} + +static struct irq_chip glamo_irq_chip = { + .name = "glamo", + .ack = glamo_ack_irq, + .mask = glamo_mask_irq, + .unmask = glamo_unmask_irq, +}; + +static void glamo_irq_demux_handler(unsigned int irq, struct irq_desc *desc) +{ + struct glamo_core *glamo = get_irq_desc_data(desc); + desc->status &= ~(IRQ_REPLAY | IRQ_WAITING); + + if (unlikely(desc->status & IRQ_INPROGRESS)) { + desc->status |= (IRQ_PENDING | IRQ_MASKED); + desc->chip->mask(irq); + desc->chip->ack(irq); + return; + } + kstat_incr_irqs_this_cpu(irq, desc); + + desc->chip->ack(irq); + desc->status |= IRQ_INPROGRESS; + + do { + uint16_t irqstatus; + int i; + + if (unlikely((desc->status & + (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) == + (IRQ_PENDING | IRQ_MASKED))) { + /* dealing with pending IRQ, unmasking */ + desc->chip->unmask(irq); + desc->status &= ~IRQ_MASKED; + } + + desc->status &= ~IRQ_PENDING; + + /* read IRQ status register */ + irqstatus = __reg_read(glamo, GLAMO_REG_IRQ_STATUS); + for (i = 0; i < 9; ++i) { + if (irqstatus & BIT(i)) + generic_handle_irq(glamo->irq_base + i); + } + + } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING); + + desc->status &= ~IRQ_INPROGRESS; +} + +/* +sysfs +*/ + +static ssize_t regs_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + unsigned int reg; + unsigned int val; + + sscanf(buf, "%u %u", ®, &val); + printk(KERN_INFO"reg 0x%02x <-- 0x%04x\n", + reg, val); + + glamo_reg_write(glamo, reg, val); + + return count; +} + +static ssize_t regs_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + int i, n; + char *end = buf; + const struct reg_range *rr = reg_range; + + spin_lock(&glamo->lock); + + for (i = 0; i < ARRAY_SIZE(reg_range); ++i, ++rr) { + if (!rr->dump) + continue; + end += sprintf(end, "\n%s\n", rr->name); + for (n = rr->start; n < rr->start + rr->count; n += 2) { + if ((n & 15) == 0) + end += sprintf(end, "\n%04X: ", n); + end += sprintf(end, "%04x ", __reg_read(glamo, n)); + } + end += sprintf(end, "\n"); + } + spin_unlock(&glamo->lock); + + return end - buf; +} + +static DEVICE_ATTR(regs, 0644, regs_read, regs_write); + +struct glamo_engine_reg_set { + uint16_t reg; + uint16_t mask_suspended; + uint16_t mask_enabled; +}; + +struct glamo_engine_desc { + const char *name; + uint16_t hostbus; + const struct glamo_engine_reg_set *regs; + int num_regs; +}; + +static const struct glamo_engine_reg_set glamo_lcd_regs[] = { + { GLAMO_REG_CLOCK_LCD, + GLAMO_CLOCK_LCD_EN_M5CLK | + GLAMO_CLOCK_LCD_DG_M5CLK | + GLAMO_CLOCK_LCD_EN_DMCLK, + + GLAMO_CLOCK_LCD_EN_DHCLK | + GLAMO_CLOCK_LCD_EN_DCLK + }, + { GLAMO_REG_CLOCK_GEN5_1, + GLAMO_CLOCK_GEN51_EN_DIV_DMCLK, + + GLAMO_CLOCK_GEN51_EN_DIV_DHCLK | + GLAMO_CLOCK_GEN51_EN_DIV_DCLK + } +}; + +static const struct glamo_engine_reg_set glamo_mmc_regs[] = { + { GLAMO_REG_CLOCK_MMC, + GLAMO_CLOCK_MMC_EN_M9CLK | + GLAMO_CLOCK_MMC_DG_M9CLK, + + GLAMO_CLOCK_MMC_EN_TCLK | + GLAMO_CLOCK_MMC_DG_TCLK + }, + { GLAMO_REG_CLOCK_GEN5_1, + 0, + GLAMO_CLOCK_GEN51_EN_DIV_TCLK + } +}; + +static const struct glamo_engine_reg_set glamo_2d_regs[] = { + { GLAMO_REG_CLOCK_2D, + GLAMO_CLOCK_2D_EN_M7CLK | + GLAMO_CLOCK_2D_DG_M7CLK, + + GLAMO_CLOCK_2D_EN_GCLK | + GLAMO_CLOCK_2D_DG_GCLK + }, + { GLAMO_REG_CLOCK_GEN5_1, + 0, + GLAMO_CLOCK_GEN51_EN_DIV_GCLK, + } +}; + +static const struct glamo_engine_reg_set glamo_cmdq_regs[] = { + { GLAMO_REG_CLOCK_2D, + GLAMO_CLOCK_2D_EN_M6CLK, + 0 + }, +}; + +#define GLAMO_ENGINE(xname, xhostbus, xregs) { \ + .name = xname, \ + .hostbus = xhostbus, \ + .num_regs = ARRAY_SIZE(xregs), \ + .regs = xregs, \ +} + +static const struct glamo_engine_desc glamo_engines[] = { + [GLAMO_ENGINE_LCD] = GLAMO_ENGINE("LCD", GLAMO_HOSTBUS2_MMIO_EN_LCD, + glamo_lcd_regs), + [GLAMO_ENGINE_MMC] = GLAMO_ENGINE("MMC", GLAMO_HOSTBUS2_MMIO_EN_MMC, + glamo_mmc_regs), + [GLAMO_ENGINE_2D] = GLAMO_ENGINE("2D", GLAMO_HOSTBUS2_MMIO_EN_2D, + glamo_2d_regs), + [GLAMO_ENGINE_CMDQ] = GLAMO_ENGINE("CMDQ", GLAMO_HOSTBUS2_MMIO_EN_CQ, + glamo_cmdq_regs), +}; + +static inline const char *glamo_engine_name(enum glamo_engine engine) +{ + return glamo_engines[engine].name; +} + +/*********************************************************************** + * 'engine' support + ***********************************************************************/ + +int __glamo_engine_enable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int i; + const struct glamo_engine_desc *engine_desc = &glamo_engines[engine]; + const struct glamo_engine_reg_set *reg; + + switch (engine) { + case GLAMO_ENGINE_LCD: + case GLAMO_ENGINE_MMC: + case GLAMO_ENGINE_2D: + case GLAMO_ENGINE_CMDQ: + break; + default: + return -EINVAL; + } + + reg = engine_desc->regs; + + __reg_set_bit(glamo, GLAMO_REG_HOSTBUS(2), + engine_desc->hostbus); + for (i = engine_desc->num_regs; i; --i, ++reg) + __reg_set_bit(glamo, reg->reg, + reg->mask_suspended | reg->mask_enabled); + + return 0; +} + +int glamo_engine_enable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int ret = 0; + + spin_lock(&glamo->lock); + + if (glamo->engine_state[engine] != GLAMO_ENGINE_ENABLED) { + ret = __glamo_engine_enable(glamo, engine); + if (!ret) + glamo->engine_state[engine] = GLAMO_ENGINE_ENABLED; + } + + spin_unlock(&glamo->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(glamo_engine_enable); + +int __glamo_engine_disable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int i; + const struct glamo_engine_desc *engine_desc = &glamo_engines[engine]; + const struct glamo_engine_reg_set *reg; + + switch (engine) { + case GLAMO_ENGINE_LCD: + case GLAMO_ENGINE_MMC: + case GLAMO_ENGINE_2D: + case GLAMO_ENGINE_CMDQ: + break; + default: + return -EINVAL; + } + + reg = engine_desc->regs; + + __reg_clear_bit(glamo, GLAMO_REG_HOSTBUS(2), + engine_desc->hostbus); + for (i = engine_desc->num_regs; i; --i, ++reg) + __reg_clear_bit(glamo, reg->reg, + reg->mask_suspended | reg->mask_enabled); + + return 0; +} +int glamo_engine_disable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int ret = 0; + + spin_lock(&glamo->lock); + + if (glamo->engine_state[engine] != GLAMO_ENGINE_DISABLED) { + ret = __glamo_engine_disable(glamo, engine); + if (!ret) + glamo->engine_state[engine] = GLAMO_ENGINE_DISABLED; + } + + spin_unlock(&glamo->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(glamo_engine_disable); + +int __glamo_engine_suspend(struct glamo_core *glamo, enum glamo_engine engine) +{ + int i; + const struct glamo_engine_desc *engine_desc = &glamo_engines[engine]; + const struct glamo_engine_reg_set *reg; + + switch (engine) { + case GLAMO_ENGINE_LCD: + case GLAMO_ENGINE_MMC: + case GLAMO_ENGINE_2D: + case GLAMO_ENGINE_CMDQ: + break; + default: + return -EINVAL; + } + + reg = engine_desc->regs; + + __reg_set_bit(glamo, GLAMO_REG_HOSTBUS(2), + engine_desc->hostbus); + for (i = engine_desc->num_regs; i; --i, ++reg) { + __reg_set_bit(glamo, reg->reg, reg->mask_suspended); + __reg_clear_bit(glamo, reg->reg, reg->mask_enabled); + } + + return 0; +} + +int glamo_engine_suspend(struct glamo_core *glamo, enum glamo_engine engine) +{ + int ret = 0; + + spin_lock(&glamo->lock); + + if (glamo->engine_state[engine] != GLAMO_ENGINE_SUSPENDED) { + ret = __glamo_engine_suspend(glamo, engine); + if (!ret) + glamo->engine_state[engine] = GLAMO_ENGINE_SUSPENDED; + } + + spin_unlock(&glamo->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(glamo_engine_suspend); + +static const struct glamo_script reset_regs[] = { + [GLAMO_ENGINE_LCD] = { + GLAMO_REG_CLOCK_LCD, GLAMO_CLOCK_LCD_RESET + }, + [GLAMO_ENGINE_MMC] = { + GLAMO_REG_CLOCK_MMC, GLAMO_CLOCK_MMC_RESET + }, + [GLAMO_ENGINE_CMDQ] = { + GLAMO_REG_CLOCK_2D, GLAMO_CLOCK_2D_CQ_RESET + }, + [GLAMO_ENGINE_2D] = { + GLAMO_REG_CLOCK_2D, GLAMO_CLOCK_2D_RESET + }, + [GLAMO_ENGINE_JPEG] = { + GLAMO_REG_CLOCK_JPEG, GLAMO_CLOCK_JPEG_RESET + }, +}; + +void glamo_engine_reset(struct glamo_core *glamo, enum glamo_engine engine) +{ + uint16_t reg = reset_regs[engine].reg; + uint16_t val = reset_regs[engine].val; + + if (engine >= ARRAY_SIZE(reset_regs)) { + dev_warn(&glamo->pdev->dev, "unknown engine %u ", engine); + return; + } + + spin_lock(&glamo->lock); + __reg_set_bit(glamo, reg, val); + __reg_clear_bit(glamo, reg, val); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL_GPL(glamo_engine_reset); + +int glamo_pll_rate(struct glamo_core *glamo, + enum glamo_pll pll) +{ + uint16_t reg; + unsigned int osci = glamo->pdata->osci_clock_rate; + + switch (pll) { + case GLAMO_PLL1: + reg = __reg_read(glamo, GLAMO_REG_PLL_GEN1); + break; + case GLAMO_PLL2: + reg = __reg_read(glamo, GLAMO_REG_PLL_GEN3); + break; + default: + return -EINVAL; + } + + return (int)osci * (int)reg; +} +EXPORT_SYMBOL_GPL(glamo_pll_rate); + +int glamo_engine_reclock(struct glamo_core *glamo, + enum glamo_engine engine, + int hz) +{ + int pll; + uint16_t reg, mask, div; + + if (!hz) + return -EINVAL; + + switch (engine) { + case GLAMO_ENGINE_LCD: + pll = GLAMO_PLL1; + reg = GLAMO_REG_CLOCK_GEN7; + mask = 0xff; + break; + case GLAMO_ENGINE_MMC: + pll = GLAMO_PLL1; + reg = GLAMO_REG_CLOCK_GEN8; + mask = 0xff; + break; + default: + dev_warn(&glamo->pdev->dev, + "reclock of engine 0x%x not supported\n", engine); + return -EINVAL; + break; + } + + pll = glamo_pll_rate(glamo, pll); + + div = pll / hz; + + if (div != 0 && pll / div <= hz) + --div; + + if (div > mask) + div = mask; + + dev_dbg(&glamo->pdev->dev, + "PLL %d, kHZ %d, div %d\n", pll, hz / 1000, div); + + reg_set_bit_mask(glamo, reg, mask, div); + mdelay(5); /* wait some time to stabilize */ + + return pll / (div + 1); +} +EXPORT_SYMBOL_GPL(glamo_engine_reclock); + +/*********************************************************************** + * script support + ***********************************************************************/ + +#define GLAMO_SCRIPT_END 0xffff +#define GLAMO_SCRIPT_WAIT 0xfffe +#define GLAMO_SCRIPT_LOCK_PLL 0xfffd + +/* + * couple of people reported artefacts with 2.6.28 changes, this + * allows reversion to 2.6.24 settings +*/ +static const uint16_t reg_0x200[] = { +0xe03, /* 0 waits on Async BB R & W, Use PLL 2 for mem bus */ +0xef0, /* 3 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xea0, /* 2 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xe50, /* 1 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xe00, /* 0 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xef3, /* 3 waits on Async BB R & W, Use PLL 2 for mem bus */ +0xea3, /* 2 waits on Async BB R & W, Use PLL 2 for mem bus */ +0xe53, /* 1 waits on Async BB R & W, Use PLL 2 for mem bus */ +}; + +static int glamo_run_script(struct glamo_core *glamo, + const struct glamo_script *script, int len, + int may_sleep) +{ + int i; + uint16_t status; + const struct glamo_script *line = script; + + for (i = 0; i < len; ++i, ++line) { + switch (line->reg) { + case GLAMO_SCRIPT_END: + return 0; + case GLAMO_SCRIPT_WAIT: + if (may_sleep) + msleep(line->val); + else + mdelay(line->val * 4); + break; + case GLAMO_SCRIPT_LOCK_PLL: + /* spin until PLLs lock */ + do { + status = __reg_read(glamo, GLAMO_REG_PLL_GEN5); + } while ((status & 3) != 3); + break; + case 0x200: + __reg_write(glamo, line->reg, + reg_0x200[slow_memory & 0x8]); + break; + default: + __reg_write(glamo, line->reg, line->val); + break; + } + } + + return 0; +} + +static const struct glamo_script glamo_init_script[] = { + { GLAMO_REG_CLOCK_HOST, 0x1000 }, + { GLAMO_SCRIPT_WAIT, 2 }, + { GLAMO_REG_CLOCK_MEMORY, 0x1000 }, + { GLAMO_REG_CLOCK_MEMORY, 0x2000 }, + { GLAMO_REG_CLOCK_LCD, 0x1000 }, + { GLAMO_REG_CLOCK_MMC, 0x1000 }, + { GLAMO_REG_CLOCK_ISP, 0x1000 }, + { GLAMO_REG_CLOCK_ISP, 0x3000 }, + { GLAMO_REG_CLOCK_JPEG, 0x1000 }, + { GLAMO_REG_CLOCK_3D, 0x1000 }, + { GLAMO_REG_CLOCK_3D, 0x3000 }, + { GLAMO_REG_CLOCK_2D, 0x1000 }, + { GLAMO_REG_CLOCK_2D, 0x3000 }, + { GLAMO_REG_CLOCK_RISC1, 0x1000 }, + { GLAMO_REG_CLOCK_MPEG, 0x1000 }, + { GLAMO_REG_CLOCK_MPEG, 0x3000 }, + { GLAMO_REG_CLOCK_MPROC, 0x1000 /*0x100f*/ }, + { GLAMO_SCRIPT_WAIT, 2 }, + { GLAMO_REG_CLOCK_HOST, 0x0000 }, + { GLAMO_REG_CLOCK_MEMORY, 0x0000 }, + { GLAMO_REG_CLOCK_LCD, 0x0000 }, + { GLAMO_REG_CLOCK_MMC, 0x0000 }, + { GLAMO_REG_PLL_GEN1, 0x05db }, /* 48MHz */ + { GLAMO_REG_PLL_GEN3, 0x0aba }, /* 90MHz */ + { GLAMO_SCRIPT_LOCK_PLL, 0 }, + /* + * b9 of this register MUST be zero to get any interrupts on INT# + * the other set bits enable all the engine interrupt sources + */ + { GLAMO_REG_IRQ_ENABLE, 0x0100 }, + { GLAMO_REG_CLOCK_GEN6, 0x2000 }, + { GLAMO_REG_CLOCK_GEN7, 0x0101 }, + { GLAMO_REG_CLOCK_GEN8, 0x0100 }, + { GLAMO_REG_CLOCK_HOST, 0x000d }, + /* + * b7..b4 = 0 = no wait states on read or write + * b0 = 1 select PLL2 for Host interface, b1 = enable it + */ + { GLAMO_REG_HOSTBUS(0), 0x0e03 /* this is replaced by script parser */ }, + { GLAMO_REG_HOSTBUS(1), 0x07ff }, /* TODO: Disable all */ + { GLAMO_REG_HOSTBUS(10), 0x0000 }, + { GLAMO_REG_HOSTBUS(11), 0x4000 }, + { GLAMO_REG_HOSTBUS(12), 0xf00e }, + + /* S-Media recommended "set tiling mode to 512 mode for memory access + * more efficiency when 640x480" */ + { GLAMO_REG_MEM_TYPE, 0x0c74 }, /* 8MB, 16 word pg wr+rd */ + { GLAMO_REG_MEM_GEN, 0xafaf }, /* 63 grants min + max */ + + { GLAMO_REG_MEM_TIMING1, 0x0108 }, + { GLAMO_REG_MEM_TIMING2, 0x0010 }, /* Taa = 3 MCLK */ + { GLAMO_REG_MEM_TIMING3, 0x0000 }, + { GLAMO_REG_MEM_TIMING4, 0x0000 }, /* CE1# delay fall/rise */ + { GLAMO_REG_MEM_TIMING5, 0x0000 }, /* UB# LB# */ + { GLAMO_REG_MEM_TIMING6, 0x0000 }, /* OE# */ + { GLAMO_REG_MEM_TIMING7, 0x0000 }, /* WE# */ + { GLAMO_REG_MEM_TIMING8, 0x1002 }, /* MCLK delay, was 0x1000 */ + { GLAMO_REG_MEM_TIMING9, 0x6006 }, + { GLAMO_REG_MEM_TIMING10, 0x00ff }, + { GLAMO_REG_MEM_TIMING11, 0x0001 }, + { GLAMO_REG_MEM_POWER1, 0x0020 }, + { GLAMO_REG_MEM_POWER2, 0x0000 }, + { GLAMO_REG_MEM_DRAM1, 0x0000 }, + { GLAMO_SCRIPT_WAIT, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xc100 }, + { GLAMO_SCRIPT_WAIT, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xe100 }, + { GLAMO_REG_MEM_DRAM2, 0x01d6 }, + { GLAMO_REG_CLOCK_MEMORY, 0x000b }, +}; + +/* Find out if we can support this version of the Glamo chip */ +static int __devinit glamo_supported(struct glamo_core *glamo) +{ + uint16_t dev_id, rev_id; + + dev_id = __reg_read(glamo, GLAMO_REG_DEVICE_ID); + rev_id = __reg_read(glamo, GLAMO_REG_REVISION_ID); + + switch (dev_id) { + case 0x3650: + switch (rev_id) { + case GLAMO_CORE_REV_A2: + break; + case GLAMO_CORE_REV_A0: + case GLAMO_CORE_REV_A1: + case GLAMO_CORE_REV_A3: + dev_warn(&glamo->pdev->dev, "untested core revision " + "%04x, your mileage may vary\n", rev_id); + break; + default: + dev_warn(&glamo->pdev->dev, "unknown glamo revision " + "%04x, your mileage may vary\n", rev_id); + } + break; + default: + dev_err(&glamo->pdev->dev, "unsupported Glamo device %04x\n", + dev_id); + return 0; + } + + dev_dbg(&glamo->pdev->dev, "Detected Glamo core %04x Revision %04x " + "(%uHz CPU / %uHz Memory)\n", dev_id, rev_id, + glamo_pll_rate(glamo, GLAMO_PLL1), + glamo_pll_rate(glamo, GLAMO_PLL2)); + + return 1; +} + +static int __devinit glamo_probe(struct platform_device *pdev) +{ + int ret = 0, irq, irq_base; + struct glamo_core *glamo; + struct resource *mem; + + glamo = kmalloc(GFP_KERNEL, sizeof(*glamo)); + if (!glamo) + return -ENOMEM; + + spin_lock_init(&glamo->lock); + + glamo->pdev = pdev; + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + glamo->irq = platform_get_irq(pdev, 0); + glamo->irq_base = irq_base = platform_get_irq(pdev, 1); + glamo->pdata = pdev->dev.platform_data; + + if (glamo->irq < 0) { + ret = glamo->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free; + } + + if (irq_base < 0) { + ret = glamo->irq; + dev_err(&pdev->dev, "Failed to get glamo irq base: %d\n", ret); + goto err_free; + } + + if (!mem) { + dev_err(&pdev->dev, "Failed to get platform memory\n"); + ret = -ENOENT; + goto err_free; + } + + if (!glamo->pdata) { + dev_err(&pdev->dev, "Missing platform data\n"); + ret = -ENOENT; + goto err_free; + } + + /* only request the generic, hostbus and memory controller registers */ + glamo->mem = request_mem_region(mem->start, GLAMO_REGOFS_VIDCAP, + pdev->name); + + if (!glamo->mem) { + dev_err(&pdev->dev, "Failed to request io memory region\n"); + ret = -ENOENT; + goto err_free; + } + + glamo->base = ioremap(glamo->mem->start, resource_size(glamo->mem)); + if (!glamo->base) { + dev_err(&pdev->dev, "Failed to ioremap() memory region\n"); + goto err_release_mem_region; + } + + /* confirm it isn't insane version */ + if (!glamo_supported(glamo)) { + dev_err(&pdev->dev, + "This version of the Glamo is not supported\n"); + goto err_iounmap; + } + + platform_set_drvdata(pdev, glamo); + + /* sysfs */ + ret = device_create_file(&pdev->dev, &dev_attr_regs); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to create sysfs file\n"); + goto err_iounmap; + } + + /* init the chip with canned register set */ + glamo_run_script(glamo, glamo_init_script, + ARRAY_SIZE(glamo_init_script), 1); + + /* + * finally set the mfd interrupts up + */ + for (irq = irq_base; irq < irq_base + GLAMO_NR_IRQS; ++irq) { +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + set_irq_chip_data(irq, glamo); + set_irq_chip_and_handler(irq, &glamo_irq_chip, + handle_level_irq); + } + + set_irq_type(glamo->irq, IRQ_TYPE_EDGE_FALLING); + set_irq_data(glamo->irq, glamo); + set_irq_chained_handler(glamo->irq, glamo_irq_demux_handler); + glamo->irq_works = 1; + + ret = mfd_add_devices(&pdev->dev, pdev->id, glamo_cells, + ARRAY_SIZE(glamo_cells), mem, glamo->irq_base); + + if (ret) { + dev_err(&pdev->dev, "Failed to add child devices: %d\n", ret); + goto err_free_irqs; + } + + dev_info(&glamo->pdev->dev, "Glamo core PLL1: %uHz, PLL2: %uHz\n", + glamo_pll_rate(glamo, GLAMO_PLL1), + glamo_pll_rate(glamo, GLAMO_PLL2)); + + return 0; + +err_free_irqs: + disable_irq(glamo->irq); + set_irq_chained_handler(glamo->irq, NULL); + set_irq_chip_data(glamo->irq, NULL); + + for (irq = irq_base; irq < irq_base + GLAMO_NR_IRQS; ++irq) { + set_irq_chip(irq, NULL); +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#else + set_irq_probe(irq); +#endif + set_irq_chip_data(irq, NULL); + } +err_iounmap: + iounmap(glamo->base); +err_release_mem_region: + release_mem_region(glamo->mem->start, resource_size(glamo->mem)); +err_free: + platform_set_drvdata(pdev, NULL); + kfree(glamo); + + return ret; +} + +static int __devexit glamo_remove(struct platform_device *pdev) +{ + struct glamo_core *glamo = platform_get_drvdata(pdev); + int irq; + int irq_base = glamo->irq_base; + + mfd_remove_devices(&pdev->dev); + + disable_irq(glamo->irq); + set_irq_chained_handler(glamo->irq, NULL); + set_irq_chip_data(glamo->irq, NULL); + + for (irq = irq_base; irq < irq_base + GLAMO_NR_IRQS; ++irq) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#else + set_irq_noprobe(); +#endif + set_irq_chip(irq, NULL); + set_irq_chip_data(irq, NULL); + } + + platform_set_drvdata(pdev, NULL); + iounmap(glamo->base); + release_mem_region(glamo->mem->start, resource_size(glamo->mem)); + kfree(glamo); + + return 0; +} + +#ifdef CONFIG_PM +#if 0 +static struct glamo_script glamo_resume_script[] = { + + { GLAMO_REG_PLL_GEN1, 0x05db }, /* 48MHz */ + { GLAMO_REG_PLL_GEN3, 0x0aba }, /* 90MHz */ + { GLAMO_REG_DFT_GEN6, 1 }, + { 0xfffe, 100 }, + { 0xfffd, 0 }, + { 0x200, 0x0e03 }, + + /* + * b9 of this register MUST be zero to get any interrupts on INT# + * the other set bits enable all the engine interrupt sources + */ + { GLAMO_REG_IRQ_ENABLE, 0x01ff }, + { GLAMO_REG_CLOCK_HOST, 0x0018 }, + { GLAMO_REG_CLOCK_GEN5_1, 0x18b1 }, + + { GLAMO_REG_MEM_DRAM1, 0x0000 }, + { 0xfffe, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xc100 }, + { 0xfffe, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xe100 }, + { GLAMO_REG_MEM_DRAM2, 0x01d6 }, + { GLAMO_REG_CLOCK_MEMORY, 0x000b }, +}; +#endif + +#if 0 +static void glamo_power(struct glamo_core *glamo) +{ + unsigned long flags; + + spin_lock_irqsave(&glamo->lock, flags); + + /* +Power management +static const REG_VALUE_MASK_TYPE reg_powerOn[] = +{ + { REG_GEN_DFT6, REG_BIT_ALL, REG_DATA(1u << 0) }, + { REG_GEN_PLL3, 0u, REG_DATA(1u << 13) }, + { REG_GEN_MEM_CLK, REG_BIT_ALL, REG_BIT_EN_MOCACLK }, + { REG_MEM_DRAM2, 0u, REG_BIT_EN_DEEP_POWER_DOWN }, + { REG_MEM_DRAM1, 0u, REG_BIT_SELF_REFRESH } +}; + +static const REG_VALUE_MASK_TYPE reg_powerStandby[] = +{ + { REG_MEM_DRAM1, REG_BIT_ALL, REG_BIT_SELF_REFRESH }, + { REG_GEN_MEM_CLK, 0u, REG_BIT_EN_MOCACLK }, + { REG_GEN_PLL3, REG_BIT_ALL, REG_DATA(1u << 13) }, + { REG_GEN_DFT5, REG_BIT_ALL, REG_DATA(1u << 0) } +}; + +static const REG_VALUE_MASK_TYPE reg_powerSuspend[] = +{ + { REG_MEM_DRAM2, REG_BIT_ALL, REG_BIT_EN_DEEP_POWER_DOWN }, + { REG_GEN_MEM_CLK, 0u, REG_BIT_EN_MOCACLK }, + { REG_GEN_PLL3, REG_BIT_ALL, REG_DATA(1u << 13) }, + { REG_GEN_DFT5, REG_BIT_ALL, REG_DATA(1u << 0) } +}; +*/ + switch (new_state) { + case GLAMO_POWER_ON: + + /* + * glamo state on resume is nondeterministic in some + * fundamental way, it has also been observed that the + * Glamo reset pin can get asserted by, eg, touching it with + * a scope probe. So the only answer is to roll with it and + * force an external reset on the Glamo during resume. + */ + + + break; + + case GLAMO_POWER_SUSPEND: + + break; + } + spin_unlock_irqrestore(&glamo->lock, flags); +} +#endif + +static int glamo_suspend(struct device *dev) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + int n; + + spin_lock(&glamo->lock); + + /* nuke interrupts */ + __reg_write(glamo, GLAMO_REG_IRQ_ENABLE, 0x200); + + /* take down each engine before we kill mem and pll */ + for (n = 0; n < __NUM_GLAMO_ENGINES; n++) { + if (glamo->engine_state != GLAMO_ENGINE_DISABLED) + __glamo_engine_disable(glamo, n); + } + + /* enable self-refresh */ + + __reg_write(glamo, GLAMO_REG_MEM_DRAM1, + GLAMO_MEM_DRAM1_EN_DRAM_REFRESH | + GLAMO_MEM_DRAM1_EN_GATE_CKE | + GLAMO_MEM_DRAM1_SELF_REFRESH | + GLAMO_MEM_REFRESH_COUNT); + __reg_write(glamo, GLAMO_REG_MEM_DRAM1, + GLAMO_MEM_DRAM1_EN_MODEREG_SET | + GLAMO_MEM_DRAM1_EN_DRAM_REFRESH | + GLAMO_MEM_DRAM1_EN_GATE_CKE | + GLAMO_MEM_DRAM1_SELF_REFRESH | + GLAMO_MEM_REFRESH_COUNT); + + /* force RAM into deep powerdown */ + __reg_write(glamo, GLAMO_REG_MEM_DRAM2, + GLAMO_MEM_DRAM2_DEEP_PWRDOWN | + (7 << 6) | /* tRC */ + (1 << 4) | /* tRP */ + (1 << 2) | /* tRCD */ + 2); /* CAS latency */ + + /* disable clocks to memory */ + __reg_write(glamo, GLAMO_REG_CLOCK_MEMORY, 0); + + /* all dividers from OSCI */ + __reg_set_bit_mask(glamo, GLAMO_REG_CLOCK_GEN5_1, 0x400, 0x400); + + /* PLL2 into bypass */ + __reg_set_bit_mask(glamo, GLAMO_REG_PLL_GEN3, 1 << 12, 1 << 12); + + __reg_write(glamo, GLAMO_BASIC_MMC_EN_TCLK_DLYA1, 0x0e00); + + /* kill PLLS 1 then 2 */ + __reg_write(glamo, GLAMO_REG_DFT_GEN5, 0x0001); + __reg_set_bit_mask(glamo, GLAMO_REG_PLL_GEN3, 1 << 13, 1 << 13); + + spin_unlock(&glamo->lock); + + return 0; +} + +static int glamo_resume(struct device *dev) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + int n; + + (glamo->pdata->glamo_external_reset)(0); + udelay(10); + (glamo->pdata->glamo_external_reset)(1); + mdelay(5); + + spin_lock(&glamo->lock); + + glamo_run_script(glamo, glamo_init_script, + ARRAY_SIZE(glamo_init_script), 0); + + + for (n = 0; n < __NUM_GLAMO_ENGINES; n++) { + switch (glamo->engine_state[n]) { + case GLAMO_ENGINE_SUSPENDED: + __glamo_engine_suspend(glamo, n); + break; + case GLAMO_ENGINE_ENABLED: + __glamo_engine_enable(glamo, n); + break; + default: + break; + } + } + + spin_unlock(&glamo->lock); + + return 0; +} + +static struct dev_pm_ops glamo_pm_ops = { + .suspend = glamo_suspend, + .resume = glamo_resume, + .poweroff = glamo_suspend, + .restore = glamo_resume, +}; + +#define GLAMO_PM_OPS (&glamo_pm_ops) + +#else +#define GLAMO_PM_OPS NULL +#endif + +static struct platform_driver glamo_driver = { + .probe = glamo_probe, + .remove = __devexit_p(glamo_remove), + .driver = { + .name = "glamo3362", + .owner = THIS_MODULE, + .pm = GLAMO_PM_OPS, + }, +}; + +static int __devinit glamo_init(void) +{ + return platform_driver_register(&glamo_driver); +} +module_init(glamo_init); + +static void __exit glamo_exit(void) +{ + platform_driver_unregister(&glamo_driver); +} +module_exit(glamo_exit); + +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Smedia Glamo 3362 core/resource driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:glamo3362"); diff --git a/drivers/mfd/glamo/glamo-core.h b/drivers/mfd/glamo/glamo-core.h new file mode 100644 index 00000000000..e5b1a35518a --- /dev/null +++ b/drivers/mfd/glamo/glamo-core.h @@ -0,0 +1,60 @@ +#ifndef __GLAMO_CORE_H +#define __GLAMO_CORE_H + +#include <linux/mfd/glamo.h> + +/* for the time being, we put the on-screen framebuffer into the lowest + * VRAM space. This should make the code easily compatible with the various + * 2MB/4MB/8MB variants of the Smedia chips */ +#define GLAMO_OFFSET_VRAM 0x800000 +#define GLAMO_OFFSET_FB (GLAMO_OFFSET_VRAM) + +/* we only allocate the minimum possible size for the framebuffer to make + * sure we have sufficient memory for other functions of the chip */ +/*#define GLAMO_FB_SIZE (640*480*4) *//* == 0x12c000 */ +#define GLAMO_INTERNAL_RAM_SIZE 0x800000 +#define GLAMO_MMC_BUFFER_SIZE (64 * 1024) +#define GLAMO_FB_SIZE (GLAMO_INTERNAL_RAM_SIZE - GLAMO_MMC_BUFFER_SIZE) + +enum glamo_pll { + GLAMO_PLL1, + GLAMO_PLL2, +}; + +enum glamo_engine_state { + GLAMO_ENGINE_DISABLED, + GLAMO_ENGINE_SUSPENDED, + GLAMO_ENGINE_ENABLED, +}; + +struct glamo_core { + int irq; + int irq_base; + int irq_works; /* 0 means PCB does not support Glamo IRQ */ + struct resource *mem; + void __iomem *base; + struct platform_device *pdev; + struct glamo_platform_data *pdata; + enum glamo_engine_state engine_state[__NUM_GLAMO_ENGINES]; + spinlock_t lock; +}; + +struct glamo_script { + uint16_t reg; + uint16_t val; +}; + +int glamo_pll_rate(struct glamo_core *glamo, enum glamo_pll pll); + +int glamo_engine_enable(struct glamo_core *glamo, enum glamo_engine engine); +int glamo_engine_suspend(struct glamo_core *glamo, enum glamo_engine engine); +int glamo_engine_disable(struct glamo_core *glamo, enum glamo_engine engine); +void glamo_engine_reset(struct glamo_core *glamo, enum glamo_engine engine); +int glamo_engine_reclock(struct glamo_core *glamo, + enum glamo_engine engine, int ps); + +void glamo_reg_read_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values); +void glamo_reg_write_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values); +#endif /* __GLAMO_CORE_H */ diff --git a/drivers/mfd/glamo/glamo-fb.c b/drivers/mfd/glamo/glamo-fb.c new file mode 100644 index 00000000000..61a457760f1 --- /dev/null +++ b/drivers/mfd/glamo/glamo-fb.c @@ -0,0 +1,981 @@ +/* Smedia Glamo 336x/337x driver + * + * (C) 2007-2008 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * All rights reserved. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/mfd/glamo.h> + +#include <asm/div64.h> + +#ifdef CONFIG_PM +#include <linux/pm.h> +#endif + +#include <linux/glamofb.h> + +#include "glamo-regs.h" +#include "glamo-core.h" + +static void glamofb_program_mode(struct glamofb_handle *glamo); + +struct glamofb_handle { + struct glamo_core *core; + struct fb_info *fb; + struct device *dev; + struct resource *reg; + struct resource *fb_res; + char __iomem *base; + struct glamo_fb_platform_data *mach_info; + char __iomem *cursor_addr; + int cursor_on; + u_int32_t pseudo_pal[16]; + spinlock_t lock_cmd; + int blank_mode; + int mode_set; /* 0 if the current display mode hasn't been set on the glamo */ + int output_enabled; /* 0 if the video output is disabled */ +}; + +static void glamo_output_enable(struct glamofb_handle *gfb) +{ + struct glamo_core *gcore = gfb->core; + + if (gfb->output_enabled) + return; + + /* enable the pixel clock if off */ + glamo_engine_enable(gcore, GLAMO_ENGINE_LCD); + + gfb->output_enabled = 1; + if (!gfb->mode_set) + glamofb_program_mode(gfb); +} + +static void glamo_output_disable(struct glamofb_handle *gfb) +{ + struct glamo_core *gcore = gfb->core; + + if (!gfb->output_enabled) + return; + + /* enable the pixel clock if off */ + glamo_engine_suspend(gcore, GLAMO_ENGINE_LCD); + + gfb->output_enabled = 0; +} + + +static int reg_read(struct glamofb_handle *glamo, + u_int16_t reg) +{ + int i = 0; + + for (i = 0; i != 2; i++) + nop(); + + return readw(glamo->base + reg); +} + +static void reg_write(struct glamofb_handle *glamo, + uint16_t reg, uint16_t val) +{ + int i = 0; + + for (i = 0; i != 2; i++) + nop(); + + writew(val, glamo->base + reg); +} + +static struct glamo_script glamo_regs[] = { + { GLAMO_REG_LCD_MODE1, 0x0020 }, + /* no display rotation, no hardware cursor, no dither, no gamma, + * no retrace flip, vsync low-active, hsync low active, + * no TVCLK, no partial display, hw dest color from fb, + * no partial display mode, LCD1, software flip, */ + { GLAMO_REG_LCD_MODE2, 0x9020 }, + /* video flip, no ptr, no ptr, dhclk off, + * normal mode, no cpuif, + * res, serial msb first, single fb, no fr ctrl, + * cpu if bits all zero, no crc + * 0000 0000 0010 0000 */ + { GLAMO_REG_LCD_MODE3, 0x0b40 }, + /* src data rgb565, res, 18bit rgb666 + * 000 01 011 0100 0000 */ + { GLAMO_REG_LCD_POLARITY, 0x440c }, + /* DE high active, no cpu/lcd if, cs0 force low, a0 low active, + * np cpu if, 9bit serial data, sclk rising edge latch data + * 01 00 0 100 0 000 01 0 0 */ + /* The following values assume 640*480@16bpp */ + { GLAMO_REG_LCD_A_BASE1, 0x0000 }, /* display A base address 15:0 */ + { GLAMO_REG_LCD_A_BASE2, 0x0000 }, /* display A base address 22:16 */ + { GLAMO_REG_LCD_CURSOR_BASE1, 0xC000 }, /* cursor base address 15:0 */ + { GLAMO_REG_LCD_CURSOR_BASE2, 0x0012 }, /* cursor base address 22:16 */ + { GLAMO_REG_LCD_COMMAND2, 0x0000 }, /* display page A */ +}; + +static int glamofb_run_script(struct glamofb_handle *glamo, + struct glamo_script *script, int len) +{ + int i; + + for (i = 0; i < len; i++) { + struct glamo_script *line = &script[i]; + + if (line->reg == 0xffff) + return 0; + else if (line->reg == 0xfffe) + msleep(line->val); + else + reg_write(glamo, script[i].reg, script[i].val); + } + + return 0; +} + +static int glamofb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct glamofb_handle *glamo = info->par; + + if (var->bits_per_pixel != 16) + var->bits_per_pixel = 16; + + var->height = glamo->mach_info->height; + var->width = glamo->mach_info->width; + + /* FIXME: set rgb positions */ + switch (var->bits_per_pixel) { + case 16: + switch (reg_read(glamo, GLAMO_REG_LCD_MODE3) & 0xc000) { + case GLAMO_LCD_SRC_RGB565: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + var->transp.length = 0; + break; + case GLAMO_LCD_SRC_ARGB1555: + var->transp.offset = 15; + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->transp.length = 1; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + break; + case GLAMO_LCD_SRC_ARGB4444: + var->transp.offset = 12; + var->red.offset = 8; + var->green.offset = 4; + var->blue.offset = 0; + var->transp.length = 4; + var->red.length = 4; + var->green.length = 4; + var->blue.length = 4; + break; + } + break; + case 24: + case 32: + default: + /* The Smedia Glamo doesn't support anything but 16bit color */ + printk(KERN_ERR + "Smedia driver does not [yet?] support 24/32bpp\n"); + return -EINVAL; + } + + return 0; +} + +static void reg_set_bit_mask(struct glamofb_handle *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + u_int16_t tmp; + + val &= mask; + + tmp = reg_read(glamo, reg); + tmp &= ~mask; + tmp |= val; + reg_write(glamo, reg, tmp); +} + +#define GLAMO_LCD_WIDTH_MASK 0x03FF +#define GLAMO_LCD_HEIGHT_MASK 0x03FF +#define GLAMO_LCD_PITCH_MASK 0x07FE +#define GLAMO_LCD_HV_TOTAL_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_START_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_END_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_DISP_START_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_DISP_END_MASK 0x03FF + +/* the caller has to enxure lock_cmd is held and we are in cmd mode */ +static void __rotate_lcd(struct glamofb_handle *glamo, __u32 rotation) +{ + int glamo_rot; + + switch (rotation) { + case FB_ROTATE_CW: + glamo_rot = GLAMO_LCD_ROT_MODE_90; + break; + case FB_ROTATE_UD: + glamo_rot = GLAMO_LCD_ROT_MODE_180; + break; + case FB_ROTATE_CCW: + glamo_rot = GLAMO_LCD_ROT_MODE_270; + break; + default: + glamo_rot = GLAMO_LCD_ROT_MODE_0; + break; + } + + reg_set_bit_mask(glamo, + GLAMO_REG_LCD_WIDTH, + GLAMO_LCD_ROT_MODE_MASK, + glamo_rot); + reg_set_bit_mask(glamo, + GLAMO_REG_LCD_MODE1, + GLAMO_LCD_MODE1_ROTATE_EN, + (glamo_rot != GLAMO_LCD_ROT_MODE_0) ? + GLAMO_LCD_MODE1_ROTATE_EN : 0); +} + +static void glamofb_program_mode(struct glamofb_handle *gfb) +{ + int sync, bp, disp, fp, total; + unsigned long flags; + struct glamo_core *gcore = gfb->core; + struct fb_var_screeninfo *var = &gfb->fb->var; + + dev_dbg(&gcore->pdev->dev, + "glamofb_program_mode spin_lock_irqsave\n"); + spin_lock_irqsave(&gfb->lock_cmd, flags); + + if (glamofb_cmd_mode(gfb, 1)) + goto out_unlock; + + if (var->pixclock) + glamo_engine_reclock(gcore, GLAMO_ENGINE_LCD, + (1000000000UL / gfb->fb->var.pixclock) * 1000); + + reg_set_bit_mask(gfb, + GLAMO_REG_LCD_WIDTH, + GLAMO_LCD_WIDTH_MASK, + var->xres); + reg_set_bit_mask(gfb, + GLAMO_REG_LCD_HEIGHT, + GLAMO_LCD_HEIGHT_MASK, + var->yres); + reg_set_bit_mask(gfb, + GLAMO_REG_LCD_PITCH, + GLAMO_LCD_PITCH_MASK, + gfb->fb->fix.line_length); + + /* honour the rotation request */ + __rotate_lcd(gfb, var->rotate); + + /* update scannout timings */ + sync = 0; + bp = sync + var->hsync_len; + disp = bp + var->left_margin; + fp = disp + var->xres; + total = fp + var->right_margin; + + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_TOTAL, + GLAMO_LCD_HV_TOTAL_MASK, total); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_RETR_START, + GLAMO_LCD_HV_RETR_START_MASK, sync); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_RETR_END, + GLAMO_LCD_HV_RETR_END_MASK, bp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_DISP_START, + GLAMO_LCD_HV_RETR_DISP_START_MASK, disp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_DISP_END, + GLAMO_LCD_HV_RETR_DISP_END_MASK, fp); + + sync = 0; + bp = sync + var->vsync_len; + disp = bp + var->upper_margin; + fp = disp + var->yres; + total = fp + var->lower_margin; + + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_TOTAL, + GLAMO_LCD_HV_TOTAL_MASK, total); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_RETR_START, + GLAMO_LCD_HV_RETR_START_MASK, sync); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_RETR_END, + GLAMO_LCD_HV_RETR_END_MASK, bp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_DISP_START, + GLAMO_LCD_HV_RETR_DISP_START_MASK, disp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_DISP_END, + GLAMO_LCD_HV_RETR_DISP_END_MASK, fp); + + glamofb_cmd_mode(gfb, 0); + + gfb->mode_set = 1; + +out_unlock: + dev_dbg(&gcore->pdev->dev, + "glamofb_program_mode spin_unlock_irqrestore\n"); + spin_unlock_irqrestore(&gfb->lock_cmd, flags); +} + + +static int glamofb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + return 0; +} + +static struct fb_videomode *glamofb_find_mode(struct fb_info *info, + struct fb_var_screeninfo *var) +{ + struct glamofb_handle *glamo = info->par; + struct glamo_fb_platform_data *pdata = glamo->mach_info; + struct fb_videomode *mode; + int i; + + for (i = pdata->num_modes, mode = pdata->modes; i; --i, ++mode) { + if (mode->xres == var->xres && + mode->yres == var->yres) + return mode; + } + + return NULL; +} + +static int glamofb_set_par(struct fb_info *info) +{ + struct glamofb_handle *glamo = info->par; + struct fb_var_screeninfo *var = &info->var; + struct fb_videomode *mode; + + mode = glamofb_find_mode(info, var); + if (!mode) + return -EINVAL; + + fb_videomode_to_var(var, mode); + + info->mode = mode; + + glamo->mode_set = 0; + + switch (var->rotate) { + case FB_ROTATE_CW: + case FB_ROTATE_CCW: + info->fix.line_length = (var->yres * var->bits_per_pixel) / 8; + /* FIXME: Limit pixelclock */ + var->pixclock *= 2; + break; + default: + info->fix.line_length = (var->xres * var->bits_per_pixel) / 8; + break; + } + + if (glamo->output_enabled) + glamofb_program_mode(glamo); + + return 0; +} + +static int glamofb_blank(int blank_mode, struct fb_info *info) +{ + struct glamofb_handle *gfb = info->par; + + dev_dbg(gfb->dev, "glamofb_blank(%u)\n", blank_mode); + + switch (blank_mode) { + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + /* FIXME: add pdata hook/flag to indicate whether + * we should already switch off pixel clock here */ + break; + case FB_BLANK_POWERDOWN: + /* disable the pixel clock */ + glamo_output_disable(gfb); + gfb->blank_mode = blank_mode; + break; + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + glamo_output_enable(gfb); + gfb->blank_mode = blank_mode; + break; + } + + /* FIXME: once we have proper clock management in glamo-core, + * we can determine if other units need MCLK1 or the PLL, and + * disable it if not used. */ + return 0; +} + +static inline unsigned int chan_to_field(unsigned int chan, + struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int glamofb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct glamofb_handle *glamo = info->par; + unsigned int val; + + switch (glamo->fb->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_DIRECTCOLOR: + /* true-colour, use pseuo-palette */ + + if (regno < 16) { + u32 *pal = glamo->fb->pseudo_palette; + + val = chan_to_field(red, &glamo->fb->var.red); + val |= chan_to_field(green, &glamo->fb->var.green); + val |= chan_to_field(blue, &glamo->fb->var.blue); + + pal[regno] = val; + }; + break; + default: + return 1; /* unknown type */ + } + + return 0; +} + +static int glamofb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct glamofb_handle *gfb = (struct glamofb_handle *)info->par; + struct glamo_core *gcore = gfb->core; + int retval = -ENOTTY; + + switch (cmd) { + case GLAMOFB_ENGINE_ENABLE: + retval = glamo_engine_enable(gcore, arg); + break; + case GLAMOFB_ENGINE_DISABLE: + retval = glamo_engine_disable(gcore, arg); + break; + case GLAMOFB_ENGINE_RESET: + glamo_engine_reset(gcore, arg); + retval = 0; + break; + default: + break; + } + + return retval; +} + + +#ifdef CONFIG_MFD_GLAMO_HWACCEL +static inline void glamofb_vsync_wait(struct glamofb_handle *glamo, + int line, int size, int range) +{ + int count[2]; + + do { + count[0] = reg_read(glamo, GLAMO_REG_LCD_STATUS2) & 0x3ff; + count[1] = reg_read(glamo, GLAMO_REG_LCD_STATUS2) & 0x3ff; + } while (count[0] != count[1] || + (line < count[0] + range && + size > count[0] - range) || + count[0] < range * 2); +} + +/* + * Enable/disable the hardware cursor mode altogether + * (for blinking and such, use glamofb_cursor()). + */ +static void glamofb_cursor_onoff(struct glamofb_handle *glamo, int on) +{ + int y, size; + + if (glamo->cursor_on) { + y = reg_read(glamo, GLAMO_REG_LCD_CURSOR_Y_POS); + size = reg_read(glamo, GLAMO_REG_LCD_CURSOR_Y_SIZE); + + glamofb_vsync_wait(glamo, y, size, 30); + } + + reg_set_bit_mask(glamo, GLAMO_REG_LCD_MODE1, + GLAMO_LCD_MODE1_CURSOR_EN, + on ? GLAMO_LCD_MODE1_CURSOR_EN : 0); + glamo->cursor_on = on; + + /* Hide the cursor by default */ + reg_write(glamo, GLAMO_REG_LCD_CURSOR_X_SIZE, 0); +} + +static int glamofb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct glamofb_handle *glamo = info->par; + unsigned long flags; + + spin_lock_irqsave(&glamo->lock_cmd, flags); + + reg_write(glamo, GLAMO_REG_LCD_CURSOR_X_SIZE, + cursor->enable ? cursor->image.width : 0); + + if (cursor->set & FB_CUR_SETPOS) { + reg_write(glamo, GLAMO_REG_LCD_CURSOR_X_POS, + cursor->image.dx); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_Y_POS, + cursor->image.dy); + } + + if (cursor->set & FB_CUR_SETCMAP) { + uint16_t fg = glamo->pseudo_pal[cursor->image.fg_color]; + uint16_t bg = glamo->pseudo_pal[cursor->image.bg_color]; + + reg_write(glamo, GLAMO_REG_LCD_CURSOR_FG_COLOR, fg); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_BG_COLOR, bg); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_DST_COLOR, fg); + } + + if (cursor->set & FB_CUR_SETHOT) + reg_write(glamo, GLAMO_REG_LCD_CURSOR_PRESET, + (cursor->hot.x << 8) | cursor->hot.y); + + if ((cursor->set & FB_CUR_SETSIZE) || + (cursor->set & (FB_CUR_SETIMAGE | FB_CUR_SETSHAPE))) { + int x, y, pitch, op; + const uint8_t *pcol = cursor->image.data; + const uint8_t *pmsk = cursor->mask; + uint8_t __iomem *dst = glamo->cursor_addr; + uint8_t dcol = 0; + uint8_t dmsk = 0; + uint8_t byte = 0; + + if (cursor->image.depth > 1) { + spin_unlock_irqrestore(&glamo->lock_cmd, flags); + return -EINVAL; + } + + pitch = ((cursor->image.width + 7) >> 2) & ~1; + reg_write(glamo, GLAMO_REG_LCD_CURSOR_PITCH, + pitch); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_Y_SIZE, + cursor->image.height); + + for (y = 0; y < cursor->image.height; y++) { + byte = 0; + for (x = 0; x < cursor->image.width; x++) { + if ((x % 8) == 0) { + dcol = *pcol++; + dmsk = *pmsk++; + } else { + dcol >>= 1; + dmsk >>= 1; + } + + if (cursor->rop == ROP_COPY) + op = (dmsk & 1) ? + (dcol & 1) ? 1 : 3 : 0; + else + op = ((dmsk & 1) << 1) | + ((dcol & 1) << 0); + byte |= op << ((x & 3) << 1); + + if (x % 4 == 3) { + writeb(byte, dst + x / 4); + byte = 0; + } + } + if (x % 4) { + writeb(byte, dst + x / 4); + byte = 0; + } + + dst += pitch; + } + } + + spin_unlock_irqrestore(&glamo->lock_cmd, flags); + + return 0; +} +#endif + +static inline int glamofb_cmdq_empty(struct glamofb_handle *gfb) +{ + /* DGCMdQempty -- 1 == command queue is empty */ + return reg_read(gfb, GLAMO_REG_LCD_STATUS1) & (1 << 15); +} + +/* call holding gfb->lock_cmd when locking, until you unlock */ +int glamofb_cmd_mode(struct glamofb_handle *gfb, int on) +{ + int timeout = 2000000; + + dev_dbg(gfb->dev, "glamofb_cmd_mode(gfb=%p, on=%d)\n", gfb, on); + if (on) { + dev_dbg(gfb->dev, "%s: waiting for cmdq empty: ", + __func__); + while (!glamofb_cmdq_empty(gfb) && (timeout--)) + cpu_relax(); + if (timeout < 0) { + printk(KERN_ERR "glamofb cmd_queue never got empty\n"); + return -EIO; + } + dev_dbg(gfb->dev, "empty!\n"); + + /* display the entire frame then switch to command */ + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, + GLAMO_LCD_CMD_TYPE_DISP | + GLAMO_LCD_CMD_DATA_FIRE_VSYNC); + + /* wait until lcd idle */ + dev_dbg(gfb->dev, "waiting for lcd idle: "); + timeout = 2000000; + while (!(reg_read(gfb, GLAMO_REG_LCD_STATUS2) & (1 << 12)) && + (timeout--)) + cpu_relax(); + if (timeout < 0) { + printk(KERN_ERR"*************" + "glamofb lcd never idle" + "*************\n"); + return -EIO; + } + + mdelay(100); + + dev_dbg(gfb->dev, "cmd mode entered\n"); + + } else { + /* RGB interface needs vsync/hsync */ + if (reg_read(gfb, GLAMO_REG_LCD_MODE3) & GLAMO_LCD_MODE3_RGB) + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, + GLAMO_LCD_CMD_TYPE_DISP | + GLAMO_LCD_CMD_DATA_DISP_SYNC); + + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, + GLAMO_LCD_CMD_TYPE_DISP | + GLAMO_LCD_CMD_DATA_DISP_FIRE); + } + + return 0; +} +EXPORT_SYMBOL_GPL(glamofb_cmd_mode); + + +int glamofb_cmd_write(struct glamofb_handle *gfb, u_int16_t val) +{ + int timeout = 200000; + + dev_dbg(gfb->dev, "%s: waiting for cmdq empty\n", __func__); + while ((!glamofb_cmdq_empty(gfb)) && (timeout--)) + yield(); + if (timeout < 0) { + printk(KERN_ERR"*************" + "glamofb cmd_queue never got empty" + "*************\n"); + return 1; + } + dev_dbg(gfb->dev, "idle, writing 0x%04x\n", val); + + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, val); + + return 0; +} +EXPORT_SYMBOL_GPL(glamofb_cmd_write); + +static struct fb_ops glamofb_ops = { + .owner = THIS_MODULE, + .fb_check_var = glamofb_check_var, + .fb_pan_display = glamofb_pan_display, + .fb_set_par = glamofb_set_par, + .fb_blank = glamofb_blank, + .fb_setcolreg = glamofb_setcolreg, + .fb_ioctl = glamofb_ioctl, +#ifdef CONFIG_MFD_GLAMO_HWACCEL + .fb_cursor = glamofb_cursor, +#endif + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int glamofb_init_regs(struct glamofb_handle *glamo) +{ + struct fb_info *info = glamo->fb; + + glamofb_check_var(&info->var, info); + glamofb_run_script(glamo, glamo_regs, ARRAY_SIZE(glamo_regs)); + glamofb_set_par(info); + + return 0; +} + +static int __init glamofb_probe(struct platform_device *pdev) +{ + int rc = -EIO; + struct fb_info *fbinfo; + struct glamofb_handle *glamofb; + struct glamo_core *core = dev_get_drvdata(pdev->dev.parent); + struct glamo_fb_platform_data *mach_info; + + printk(KERN_INFO "SMEDIA Glamo frame buffer driver (C) 2007 " + "Openmoko, Inc.\n"); + + if (!core->pdata || !core->pdata->fb_data) + return -ENOENT; + + + fbinfo = framebuffer_alloc(sizeof(struct glamofb_handle), &pdev->dev); + if (!fbinfo) + return -ENOMEM; + + + glamofb = fbinfo->par; + glamofb->fb = fbinfo; + glamofb->dev = &pdev->dev; + + glamofb->blank_mode = FB_BLANK_POWERDOWN; + + strcpy(fbinfo->fix.id, "SMedia Glamo"); + + glamofb->reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "glamo-fb-regs"); + if (!glamofb->reg) { + dev_err(&pdev->dev, "platform device with no registers?\n"); + rc = -ENOENT; + goto out_free; + } + + glamofb->fb_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "glamo-fb-mem"); + if (!glamofb->fb_res) { + dev_err(&pdev->dev, "platform device with no memory ?\n"); + rc = -ENOENT; + goto out_free; + } + + glamofb->reg = request_mem_region(glamofb->reg->start, + resource_size(glamofb->reg), + pdev->name); + if (!glamofb->reg) { + dev_err(&pdev->dev, "failed to request mmio region\n"); + goto out_free; + } + + glamofb->fb_res = request_mem_region(glamofb->fb_res->start, + resource_size(glamofb->fb_res), + pdev->name); + if (!glamofb->fb_res) { + dev_err(&pdev->dev, "failed to request vram region\n"); + goto out_release_reg; + } + + /* we want to remap only the registers required for this core + * driver. */ + glamofb->base = ioremap_nocache(glamofb->reg->start, + resource_size(glamofb->reg)); + if (!glamofb->base) { + dev_err(&pdev->dev, "failed to ioremap() mmio memory\n"); + goto out_release_fb; + } + + fbinfo->fix.smem_start = (unsigned long) glamofb->fb_res->start; + fbinfo->fix.smem_len = (__u32) resource_size(glamofb->fb_res); + + fbinfo->screen_base = ioremap(glamofb->fb_res->start, + resource_size(glamofb->fb_res)); + if (!fbinfo->screen_base) { + dev_err(&pdev->dev, "failed to ioremap() vram memory\n"); + goto out_release_fb; + } + glamofb->cursor_addr = fbinfo->screen_base + 0x12C000; + + platform_set_drvdata(pdev, glamofb); + + mach_info = core->pdata->fb_data; + glamofb->core = core; + glamofb->mach_info = mach_info; + + fbinfo->fix.visual = FB_VISUAL_TRUECOLOR; + fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; + fbinfo->fix.type_aux = 0; + fbinfo->fix.xpanstep = 0; + fbinfo->fix.ypanstep = 0; + fbinfo->fix.ywrapstep = 0; + fbinfo->fix.accel = FB_ACCEL_GLAMO; + + + fbinfo->fbops = &glamofb_ops; + fbinfo->flags = FBINFO_FLAG_DEFAULT; + fbinfo->pseudo_palette = &glamofb->pseudo_pal; + + fbinfo->mode = mach_info->modes; + fb_videomode_to_var(&fbinfo->var, fbinfo->mode); + fbinfo->var.bits_per_pixel = 16; + fbinfo->var.nonstd = 0; + fbinfo->var.activate = FB_ACTIVATE_NOW; + fbinfo->var.height = mach_info->height; + fbinfo->var.width = mach_info->width; + fbinfo->var.accel_flags = 0; + fbinfo->var.vmode = FB_VMODE_NONINTERLACED; + + glamo_engine_enable(core, GLAMO_ENGINE_LCD); + glamo_engine_reset(core, GLAMO_ENGINE_LCD); + glamofb->output_enabled = 1; + glamofb->mode_set = 1; + + dev_info(&pdev->dev, "spin_lock_init\n"); + spin_lock_init(&glamofb->lock_cmd); + glamofb_init_regs(glamofb); +#ifdef CONFIG_MFD_GLAMO_HWACCEL + glamofb_cursor_onoff(glamofb, 1); +#endif + + fb_videomode_to_modelist(mach_info->modes, mach_info->num_modes, + &fbinfo->modelist); + + rc = register_framebuffer(fbinfo); + if (rc < 0) { + dev_err(&pdev->dev, "failed to register framebuffer\n"); + goto out_unmap_fb; + } + + printk(KERN_INFO "fb%d: %s frame buffer device\n", + fbinfo->node, fbinfo->fix.id); + + return 0; + +out_unmap_fb: + iounmap(fbinfo->screen_base); + iounmap(glamofb->base); +out_release_fb: + release_mem_region(glamofb->fb_res->start, + resource_size(glamofb->fb_res)); +out_release_reg: + release_mem_region(glamofb->reg->start, + resource_size(glamofb->reg)); +out_free: + framebuffer_release(fbinfo); + return rc; +} + +static int glamofb_remove(struct platform_device *pdev) +{ + struct glamofb_handle *glamofb = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + iounmap(glamofb->base); + release_mem_region(glamofb->reg->start, resource_size(glamofb->reg)); + kfree(glamofb); + + return 0; +} + +#ifdef CONFIG_PM + +static int glamofb_suspend(struct device *dev) +{ + struct glamofb_handle *gfb = dev_get_drvdata(dev); + + acquire_console_sem(); + fb_set_suspend(gfb->fb, 1); + release_console_sem(); + + /* seriously -- nobody is allowed to touch glamo memory when we + * are suspended or we lock on nWAIT + */ + /* iounmap(gfb->fb->screen_base); */ + + return 0; +} + +static int glamofb_resume(struct device *dev) +{ + struct glamofb_handle *gfb = dev_get_drvdata(dev); + + /* OK let's allow framebuffer ops again */ + /* gfb->fb->screen_base = ioremap(gfb->fb_res->start, + resource_size(gfb->fb_res)); */ + glamo_engine_enable(gfb->core, GLAMO_ENGINE_LCD); + glamo_engine_reset(gfb->core, GLAMO_ENGINE_LCD); + + glamofb_init_regs(gfb); +#ifdef CONFIG_MFD_GLAMO_HWACCEL + glamofb_cursor_onoff(gfb, 1); +#endif + + acquire_console_sem(); + fb_set_suspend(gfb->fb, 0); + release_console_sem(); + + return 0; +} + +static struct dev_pm_ops glamofb_pm_ops = { + .suspend = glamofb_suspend, + .resume = glamofb_resume, +}; + +#define GLAMOFB_PM_OPS (&glamofb_pm_ops) + +#else +#define GLAMOFB_PM_OPS NULL +#endif + +static struct platform_driver glamofb_driver = { + .probe = glamofb_probe, + .remove = glamofb_remove, + .driver = { + .name = "glamo-fb", + .owner = THIS_MODULE, + .pm = GLAMOFB_PM_OPS + }, +}; + +static int __devinit glamofb_init(void) +{ + return platform_driver_register(&glamofb_driver); +} + +static void __exit glamofb_cleanup(void) +{ + platform_driver_unregister(&glamofb_driver); +} + +module_init(glamofb_init); +module_exit(glamofb_cleanup); + +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); +MODULE_DESCRIPTION("Smedia Glamo 336x/337x framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/glamo/glamo-gpio.c b/drivers/mfd/glamo/glamo-gpio.c new file mode 100644 index 00000000000..1bcec99de41 --- /dev/null +++ b/drivers/mfd/glamo/glamo-gpio.c @@ -0,0 +1,284 @@ +/* Smedia Glamo 336x/337x gpio driver + * + * (C) 2009 Lars-Peter Clausen + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/platform_device.h> + +#include <linux/gpio.h> +#include <linux/mfd/glamo.h> + +#include "glamo-core.h" +#include "glamo-regs.h" + +#define GLAMO_NR_GPIO 21 +#define GLAMO_NR_GPIO_REGS DIV_ROUND_UP(GLAMO_NR_GPIO, 4) + +#define GLAMO_REG_GPIO(x) (((x) * 2) + GLAMO_REG_GPIO_GEN1) + +struct glamo_gpio { + struct glamo_core *glamo; + struct gpio_chip chip; + uint16_t saved_regs[GLAMO_NR_GPIO_REGS]; +}; + +#define REG_OF_GPIO(gpio) (GLAMO_REG_GPIO(gpio >> 2)) +#define NUM_OF_GPIO(gpio) (gpio & 0x3) +#define DIRECTION_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 0)) +#define OUTPUT_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 4)) +#define INPUT_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 8)) +#define FUNC_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 12)) + + +static inline struct glamo_core *chip_to_glamo(struct gpio_chip *chip) +{ + return container_of(chip, struct glamo_gpio, chip)->glamo; +} + +static void glamo_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if (value) + tmp |= OUTPUT_BIT(offset); + else + tmp &= ~OUTPUT_BIT(offset); + writew(tmp, glamo->base + reg); + spin_unlock(&glamo->lock); +} + +static int glamo_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + return !!(readw(glamo->base + REG_OF_GPIO(offset)) & INPUT_BIT(offset)); +} + +static int glamo_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if ((tmp & FUNC_BIT(offset)) == 0) { + tmp |= FUNC_BIT(offset); + writew(tmp, glamo->base + reg); + } + spin_unlock(&glamo->lock); + + return 0; +} + +static void glamo_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if ((tmp & FUNC_BIT(offset)) == 1) { + tmp &= ~FUNC_BIT(offset); + writew(tmp, glamo->base + reg); + } + spin_unlock(&glamo->lock); +} + +static int glamo_gpio_direction_output(struct gpio_chip *chip, unsigned offset, + int value) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + tmp &= ~DIRECTION_BIT(offset); + + if (value) + tmp |= OUTPUT_BIT(offset); + else + tmp &= ~OUTPUT_BIT(offset); + + writew(tmp, glamo->base + reg); + spin_unlock(&glamo->lock); + + return 0; +} + +static int glamo_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if ((tmp & DIRECTION_BIT(offset)) == 0) { + tmp |= DIRECTION_BIT(offset); + writew(tmp, glamo->base + reg); + } + spin_unlock(&glamo->lock); + + return 0; +} + +static const struct __devinit gpio_chip glamo_gpio_chip = { + .label = "glamo", + .request = glamo_gpio_request, + .free = glamo_gpio_free, + .direction_input = glamo_gpio_direction_input, + .get = glamo_gpio_get, + .direction_output = glamo_gpio_direction_output, + .set = glamo_gpio_set, + .base = -1, + .ngpio = GLAMO_NR_GPIO, + .can_sleep = 0, + .owner = THIS_MODULE, +}; + +static int __devinit glamo_gpio_probe(struct platform_device *pdev) +{ + struct glamo_platform_data *pdata = pdev->dev.parent->platform_data; + struct glamo_gpio *glamo_gpio; + int ret; + + glamo_gpio = kzalloc(sizeof(struct glamo_gpio), GFP_KERNEL); + if (!glamo_gpio) + return -ENOMEM; + + glamo_gpio->glamo = dev_get_drvdata(pdev->dev.parent); + glamo_gpio->chip = glamo_gpio_chip; + glamo_gpio->chip.dev = &pdev->dev; + if (pdata && pdata->gpio_data) + glamo_gpio->chip.base = pdata->gpio_data->base; + + ret = gpiochip_add(&glamo_gpio->chip); + + if (ret) { + dev_err(&pdev->dev, "Could not register gpio chip: %d\n", ret); + goto err; + } + + platform_set_drvdata(pdev, glamo_gpio); + + if (pdata && pdata->gpio_data && pdata->gpio_data->registered) + pdata->gpio_data->registered(&pdev->dev); + + return 0; +err: + kfree(glamo_gpio); + return ret; +} + +static int __devexit glamo_gpio_remove(struct platform_device *pdev) +{ + struct glamo_gpio *glamo_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&glamo_gpio->chip); + if (!ret) + goto done; + + platform_set_drvdata(pdev, NULL); + kfree(glamo_gpio); + +done: + return ret; +} + +#ifdef CONFIG_PM + +static int glamo_gpio_suspend(struct device *dev) +{ + struct glamo_gpio *glamo_gpio = dev_get_drvdata(dev); + struct glamo_core *glamo = glamo_gpio->glamo; + uint16_t *saved_regs = glamo_gpio->saved_regs; + int i; + + spin_lock(&glamo->lock); + for (i = 0; i < GLAMO_NR_GPIO / 4; ++i) + saved_regs[i] = readw(glamo->base + GLAMO_REG_GPIO(i)); + spin_unlock(&glamo->lock); + + return 0; +} + +static int glamo_gpio_resume(struct device *dev) +{ + struct glamo_gpio *glamo_gpio = dev_get_drvdata(dev); + struct glamo_core *glamo = glamo_gpio->glamo; + uint16_t *saved_regs = glamo_gpio->saved_regs; + int i; + + spin_lock(&glamo->lock); + for (i = 0; i < GLAMO_NR_GPIO_REGS; ++i) + writew(saved_regs[i], glamo->base + GLAMO_REG_GPIO(i)); + spin_unlock(&glamo->lock); + return 0; +} + +static const struct dev_pm_ops glamo_pm_ops = { + .suspend = glamo_gpio_suspend, + .resume = glamo_gpio_resume, + .freeze = glamo_gpio_suspend, + .thaw = glamo_gpio_resume, +}; + +#define GLAMO_GPIO_PM_OPS (&glamo_pm_ops) + +#else +#define GLAMO_GPIO_PM_OPS NULL +#endif + +static struct platform_driver glamo_gpio_driver = { + .driver = { + .name = "glamo-gpio", + .owner = THIS_MODULE, + .pm = GLAMO_GPIO_PM_OPS, + }, + .probe = glamo_gpio_probe, + .remove = __devexit_p(glamo_gpio_remove), +}; + +static int __devinit glamo_gpio_init(void) +{ + return platform_driver_register(&glamo_gpio_driver); +} +module_init(glamo_gpio_init); + +static void __exit glamo_gpio_exit(void) +{ + platform_driver_unregister(&glamo_gpio_driver); +} +module_exit(glamo_gpio_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("GPIO interface for the Glamo multimedia device"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:glamo-gpio"); diff --git a/drivers/mfd/glamo/glamo-mci.c b/drivers/mfd/glamo/glamo-mci.c new file mode 100644 index 00000000000..faeb885c553 --- /dev/null +++ b/drivers/mfd/glamo/glamo-mci.c @@ -0,0 +1,1025 @@ +/* + * linux/drivers/mmc/host/glamo-mmc.c - Glamo MMC driver + * + * Copyright (C) 2007 Openmoko, Inc, Andy Green <andy@openmoko.com> + * Copyright (C) 2009, Lars-Peter Clausen <lars@metafoo.de> + * Based on S3C MMC driver that was: + * Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel <tk@maintech.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/module.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sd.h> +#include <linux/mmc/host.h> +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/crc7.h> +#include <linux/scatterlist.h> +#include <linux/io.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/mfd/glamo.h> + +#include "glamo-core.h" +#include "glamo-regs.h" + +struct glamo_mci_host { + struct glamo_mmc_platform_data *pdata; + struct platform_device *pdev; + struct glamo_core *core; + struct mmc_host *mmc; + struct resource *mmio_mem; + struct resource *data_mem; + void __iomem *mmio_base; + uint16_t __iomem *data_base; + + unsigned int irq; + + struct regulator *regulator; + struct mmc_request *mrq; + + unsigned int clk_rate; + + unsigned short vdd; + char power_mode; + + unsigned char request_counter; + + struct timer_list disable_timer; + + struct work_struct irq_work; + struct work_struct read_work; + + unsigned clk_enabled:1; +}; + +static void glamo_mci_send_request(struct mmc_host *mmc, + struct mmc_request *mrq); +static void glamo_mci_send_command(struct glamo_mci_host *host, + struct mmc_command *cmd); + +/* + * Max SD clock rate + * + * held at /(3 + 1) due to concerns of 100R recommended series resistor + * allows 16MHz @ 4-bit --> 8MBytes/sec raw + * + * you can override this on kernel commandline using + * + * glamo_mci.sd_max_clk=10000000 + * + * for example + */ + +static int sd_max_clk = 21000000; +module_param(sd_max_clk, int, 0644); + +/* + * Slow SD clock rate + * + * you can override this on kernel commandline using + * + * glamo_mci.sd_slow_ratio=8 + * + * for example + * + * platform callback is used to decide effective clock rate, if not + * defined then max is used, if defined and returns nonzero, rate is + * divided by this factor + */ + +static int sd_slow_ratio = 8; +module_param(sd_slow_ratio, int, 0644); + +/* + * Post-power SD clock rate + * + * you can override this on kernel commandline using + * + * glamo_mci.sd_post_power_clock=1000000 + * + * for example + * + * After changing power to card, clock is held at this rate until first bulk + * transfer completes + */ + +static int sd_post_power_clock = 1000000; +module_param(sd_post_power_clock, int, 0644); + + +static inline void glamo_reg_write(struct glamo_mci_host *glamo, + uint16_t reg, uint16_t val) +{ + writew(val, glamo->mmio_base + reg); +} + +static inline uint16_t glamo_reg_read(struct glamo_mci_host *glamo, + uint16_t reg) +{ + return readw(glamo->mmio_base + reg); +} + +static void glamo_reg_set_bit_mask(struct glamo_mci_host *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + uint16_t tmp; + + val &= mask; + + tmp = glamo_reg_read(glamo, reg); + tmp &= ~mask; + tmp |= val; + glamo_reg_write(glamo, reg, tmp); +} + +static void glamo_mci_reset(struct glamo_mci_host *host) +{ + glamo_engine_reset(host->core, GLAMO_ENGINE_MMC); + + glamo_reg_write(host, GLAMO_REG_MMC_WDATADS1, + (uint16_t)(host->data_mem->start)); + glamo_reg_write(host, GLAMO_REG_MMC_WDATADS2, + (uint16_t)(host->data_mem->start >> 16)); + + glamo_reg_write(host, GLAMO_REG_MMC_RDATADS1, + (uint16_t)(host->data_mem->start)); + glamo_reg_write(host, GLAMO_REG_MMC_RDATADS2, + (uint16_t)(host->data_mem->start >> 16)); + +} + +static void glamo_mci_clock_disable(struct glamo_mci_host *host) +{ + glamo_engine_suspend(host->core, GLAMO_ENGINE_MMC); +} + +static void glamo_mci_clock_enable(struct glamo_mci_host *host) +{ + del_timer_sync(&host->disable_timer); + + glamo_engine_enable(host->core, GLAMO_ENGINE_MMC); +} + +static void glamo_mci_disable_timer(unsigned long data) +{ + struct glamo_mci_host *host = (struct glamo_mci_host *)data; + glamo_mci_clock_disable(host); +} + + +static void do_pio_read(struct glamo_mci_host *host, struct mmc_data *data) +{ + struct sg_mapping_iter miter; + uint16_t __iomem *from_ptr = host->data_base; + + dev_dbg(&host->pdev->dev, "pio_read():\n"); + + sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_TO_SG); + + while (sg_miter_next(&miter)) { + memcpy(miter.addr, from_ptr, miter.length); + from_ptr += miter.length >> 1; + + data->bytes_xfered += miter.length; + } + + sg_miter_stop(&miter); + + dev_dbg(&host->pdev->dev, "pio_read(): " + "complete (no more data).\n"); +} + +static void do_pio_write(struct glamo_mci_host *host, struct mmc_data *data) +{ + struct sg_mapping_iter miter; + uint16_t __iomem *to_ptr = host->data_base; + + dev_dbg(&host->pdev->dev, "pio_write():\n"); + sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_FROM_SG); + + while (sg_miter_next(&miter)) { + memcpy(to_ptr, miter.addr, miter.length); + to_ptr += miter.length >> 1; + + data->bytes_xfered += miter.length; + } + + sg_miter_stop(&miter); + dev_dbg(&host->pdev->dev, "pio_write(): complete\n"); +} + +static int glamo_mci_set_card_clock(struct glamo_mci_host *host, int freq) +{ + int real_rate = 0; + + if (freq) { + glamo_mci_clock_enable(host); + real_rate = glamo_engine_reclock(host->core, GLAMO_ENGINE_MMC, + freq); + } else { + glamo_mci_clock_disable(host); + } + + return real_rate; +} + +static int glamo_mci_wait_idle(struct glamo_mci_host *host, + unsigned long timeout) +{ + uint16_t status; + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + } while (!(status & GLAMO_STAT1_MMC_IDLE) && + time_is_after_jiffies(timeout)); + + if (time_is_before_eq_jiffies(timeout)) { + glamo_mci_reset(host); + return -ETIMEDOUT; + } + + return 0; +} + +static void glamo_mci_request_done(struct glamo_mci_host *host, struct +mmc_request *mrq) +{ + mod_timer(&host->disable_timer, jiffies + HZ / 16); + mmc_request_done(host->mmc, mrq); +} + + +static void glamo_mci_irq_worker(struct work_struct *work) +{ + struct glamo_mci_host *host = container_of(work, struct glamo_mci_host, + irq_work); + struct mmc_request *mrq; + struct mmc_command *cmd; + uint16_t status; + + if (!host->mrq || !host->mrq->cmd) + return; + + mrq = host->mrq; + cmd = mrq->cmd; + +#if 0 + if (cmd->data->flags & MMC_DATA_READ) + return; +#endif + + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + dev_dbg(&host->pdev->dev, "status = 0x%04x\n", status); + + /* we ignore a data timeout report if we are also told the data came */ + if (status & GLAMO_STAT1_MMC_RB_DRDY) + status &= ~GLAMO_STAT1_MMC_DTOUT; + + if (status & (GLAMO_STAT1_MMC_RTOUT | GLAMO_STAT1_MMC_DTOUT)) + cmd->error = -ETIMEDOUT; + else if (status & (GLAMO_STAT1_MMC_BWERR | GLAMO_STAT1_MMC_BRERR)) + cmd->error = -EILSEQ; + + if (cmd->error) { + dev_info(&host->pdev->dev, "Error after cmd: 0x%x\n", status); + goto done; + } + + /* issue STOP if we have been given one to use */ + if (mrq->stop) + glamo_mci_send_command(host, mrq->stop); + + if (cmd->data->flags & MMC_DATA_READ) + do_pio_read(host, cmd->data); + + if (mrq->stop) + mrq->stop->error = glamo_mci_wait_idle(host, jiffies + HZ); + +done: + host->mrq = NULL; + glamo_mci_request_done(host, cmd->mrq); +} + +static void glamo_mci_read_worker(struct work_struct *work) +{ + struct glamo_mci_host *host = container_of(work, struct glamo_mci_host, + read_work); + struct mmc_command *cmd; + uint16_t status; + uint16_t blocks_ready; + size_t data_read = 0; + size_t data_ready; + struct scatterlist *sg; + uint16_t __iomem *from_ptr = host->data_base; + void *sg_pointer; + + + cmd = host->mrq->cmd; + sg = cmd->data->sg; + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + + if (status & (GLAMO_STAT1_MMC_RTOUT | GLAMO_STAT1_MMC_DTOUT)) + cmd->error = -ETIMEDOUT; + if (status & (GLAMO_STAT1_MMC_BWERR | GLAMO_STAT1_MMC_BRERR)) + cmd->error = -EILSEQ; + if (cmd->error) { + dev_info(&host->pdev->dev, "Error after cmd: 0x%x\n", + status); + goto done; + } + + blocks_ready = glamo_reg_read(host, GLAMO_REG_MMC_RB_BLKCNT); + data_ready = blocks_ready * cmd->data->blksz; + + if (data_ready == data_read) + yield(); + + while (sg && data_read + sg->length <= data_ready) { + sg_pointer = page_address(sg_page(sg)) + sg->offset; + memcpy(sg_pointer, from_ptr, sg->length); + from_ptr += sg->length >> 1; + + data_read += sg->length; + sg = sg_next(sg); + } + + } while (sg); + cmd->data->bytes_xfered = data_read; + + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + } while (!(status & GLAMO_STAT1_MMC_IDLE)); + + if (host->mrq->stop) + glamo_mci_send_command(host, host->mrq->stop); + + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + } while (!(status & GLAMO_STAT1_MMC_IDLE)); +done: + host->mrq = NULL; + glamo_mci_request_done(host, cmd->mrq); +} + +static irqreturn_t glamo_mci_irq(int irq, void *devid) +{ + struct glamo_mci_host *host = (struct glamo_mci_host *)devid; + schedule_work(&host->irq_work); + + return IRQ_HANDLED; +} + +static void glamo_mci_send_command(struct glamo_mci_host *host, + struct mmc_command *cmd) +{ + uint8_t u8a[6]; + uint16_t fire = 0; + unsigned int timeout = 1000000; + uint16_t *reg_resp = (uint16_t *)(host->mmio_base + GLAMO_REG_MMC_CMD_RSP1); + uint16_t status; + int triggers_int = 1; + + /* if we can't do it, reject as busy */ + if (!(glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1) & + GLAMO_STAT1_MMC_IDLE)) { + cmd->error = -EBUSY; + return; + } + + /* create an array in wire order for CRC computation */ + u8a[0] = 0x40 | (cmd->opcode & 0x3f); + u8a[1] = (uint8_t)(cmd->arg >> 24); + u8a[2] = (uint8_t)(cmd->arg >> 16); + u8a[3] = (uint8_t)(cmd->arg >> 8); + u8a[4] = (uint8_t)cmd->arg; + u8a[5] = (crc7(0, u8a, 5) << 1) | 0x01; + + /* issue the wire-order array including CRC in register order */ + glamo_reg_write(host, GLAMO_REG_MMC_CMD_REG1, ((u8a[4] << 8) | u8a[5])); + glamo_reg_write(host, GLAMO_REG_MMC_CMD_REG2, ((u8a[2] << 8) | u8a[3])); + glamo_reg_write(host, GLAMO_REG_MMC_CMD_REG3, ((u8a[0] << 8) | u8a[1])); + + /* command index toggle */ + fire |= (host->request_counter & 1) << 12; + + /* set type of command */ + switch (mmc_cmd_type(cmd)) { + case MMC_CMD_BC: + fire |= GLAMO_FIRE_MMC_CMDT_BNR; + break; + case MMC_CMD_BCR: + fire |= GLAMO_FIRE_MMC_CMDT_BR; + break; + case MMC_CMD_AC: + fire |= GLAMO_FIRE_MMC_CMDT_AND; + break; + case MMC_CMD_ADTC: + fire |= GLAMO_FIRE_MMC_CMDT_AD; + break; + } + /* + * if it expects a response, set the type expected + * + * R1, Length : 48bit, Normal response + * R1b, Length : 48bit, same R1, but added card busy status + * R2, Length : 136bit (really 128 bits with CRC snipped) + * R3, Length : 48bit (OCR register value) + * R4, Length : 48bit, SDIO_OP_CONDITION, Reverse SDIO Card + * R5, Length : 48bit, IO_RW_DIRECTION, Reverse SDIO Card + * R6, Length : 48bit (RCA register) + * R7, Length : 48bit (interface condition, VHS(voltage supplied), + * check pattern, CRC7) + */ + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1: /* same index as R6 and R7 */ + fire |= GLAMO_FIRE_MMC_RSPT_R1; + break; + case MMC_RSP_R1B: + fire |= GLAMO_FIRE_MMC_RSPT_R1b; + break; + case MMC_RSP_R2: + fire |= GLAMO_FIRE_MMC_RSPT_R2; + break; + case MMC_RSP_R3: + fire |= GLAMO_FIRE_MMC_RSPT_R3; + break; + /* R4 and R5 supported by chip not defined in linux/mmc/core.h (sdio) */ + } + /* + * From the command index, set up the command class in the host ctrllr + * + * missing guys present on chip but couldn't figure out how to use yet: + * 0x0 "stream read" + * 0x9 "cancel running command" + */ + switch (cmd->opcode) { + case MMC_READ_SINGLE_BLOCK: + fire |= GLAMO_FIRE_MMC_CC_SBR; /* single block read */ + break; + case MMC_SWITCH: /* 64 byte payload */ + case SD_APP_SEND_SCR: + case MMC_READ_MULTIPLE_BLOCK: + /* we will get an interrupt off this */ + if (!cmd->mrq->stop) { + /* multiblock no stop */ + fire |= GLAMO_FIRE_MMC_CC_MBRNS; + } else { + /* multiblock with stop */ + fire |= GLAMO_FIRE_MMC_CC_MBRS; + } + break; + case MMC_WRITE_BLOCK: + fire |= GLAMO_FIRE_MMC_CC_SBW; /* single block write */ + break; + case MMC_WRITE_MULTIPLE_BLOCK: + if (cmd->mrq->stop) { + /* multiblock with stop */ + fire |= GLAMO_FIRE_MMC_CC_MBWS; + } else { + /* multiblock NO stop-- 'RESERVED'? */ + fire |= GLAMO_FIRE_MMC_CC_MBWNS; + } + break; + case MMC_STOP_TRANSMISSION: + fire |= GLAMO_FIRE_MMC_CC_STOP; /* STOP */ + triggers_int = 0; + break; + default: + fire |= GLAMO_FIRE_MMC_CC_BASIC; /* "basic command" */ + triggers_int = 0; + break; + } + + if (cmd->data) + host->mrq = cmd->mrq; + + /* always largest timeout */ + glamo_reg_write(host, GLAMO_REG_MMC_TIMEOUT, 0xfff); + + /* Generate interrupt on txfer */ + glamo_reg_set_bit_mask(host, GLAMO_REG_MMC_BASIC, 0xff36, + 0x0800 | + GLAMO_BASIC_MMC_NO_CLK_RD_WAIT | + GLAMO_BASIC_MMC_EN_COMPL_INT | + GLAMO_BASIC_MMC_EN_DATA_PUPS | + GLAMO_BASIC_MMC_EN_CMD_PUP); + + /* send the command out on the wire */ + /* dev_info(&host->pdev->dev, "Using FIRE %04X\n", fire); */ + glamo_reg_write(host, GLAMO_REG_MMC_CMD_FIRE, fire); + + /* we are deselecting card? because it isn't going to ack then... */ + if ((cmd->opcode == 7) && (cmd->arg == 0)) + return; + + /* + * we must spin until response is ready or timed out + * -- we don't get interrupts unless there is a bulk rx + */ + do + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + while (((((status >> 15) & 1) != (host->request_counter & 1)) || + (!(status & (GLAMO_STAT1_MMC_RB_RRDY | + GLAMO_STAT1_MMC_RTOUT | + GLAMO_STAT1_MMC_DTOUT | + GLAMO_STAT1_MMC_BWERR | + GLAMO_STAT1_MMC_BRERR)))) && (timeout--)); + + if ((status & (GLAMO_STAT1_MMC_RTOUT | + GLAMO_STAT1_MMC_DTOUT)) || + (timeout == 0)) { + cmd->error = -ETIMEDOUT; + } else if (status & (GLAMO_STAT1_MMC_BWERR | GLAMO_STAT1_MMC_BRERR)) { + cmd->error = -EILSEQ; + } + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) { + cmd->resp[3] = readw(®_resp[0]) | + (readw(®_resp[1]) << 16); + cmd->resp[2] = readw(®_resp[2]) | + (readw(®_resp[3]) << 16); + cmd->resp[1] = readw(®_resp[4]) | + (readw(®_resp[5]) << 16); + cmd->resp[0] = readw(®_resp[6]) | + (readw(®_resp[7]) << 16); + } else { + cmd->resp[0] = (readw(®_resp[0]) >> 8) | + (readw(®_resp[1]) << 8) | + (readw(®_resp[2]) << 24); + } + } + +#if 0 + /* We'll only get an interrupt when all data has been transfered. + By starting to copy data when it's avaiable we can increase + throughput by up to 30%. */ + if (cmd->data && (cmd->data->flags & MMC_DATA_READ)) + schedule_work(&host->read_work); +#endif + +} + +static int glamo_mci_prepare_pio(struct glamo_mci_host *host, + struct mmc_data *data) +{ + /* set up the block info */ + glamo_reg_write(host, GLAMO_REG_MMC_DATBLKLEN, data->blksz); + glamo_reg_write(host, GLAMO_REG_MMC_DATBLKCNT, data->blocks); + + data->bytes_xfered = 0; + + /* if write, prep the write into the shared RAM before the command */ + if (data->flags & MMC_DATA_WRITE) + do_pio_write(host, data); + + dev_dbg(&host->pdev->dev, "(blksz=%d, count=%d)\n", + data->blksz, data->blocks); + return 0; +} + +static int glamo_mci_irq_poll(struct glamo_mci_host *host, + struct mmc_command *cmd) +{ + int timeout = 1000000; + uint16_t status; + /* + * if the glamo INT# line isn't wired (*cough* it can happen) + * I'm afraid we have to spin on the IRQ status bit and "be + * our own INT# line" + */ + /* + * we have faith we will get an "interrupt"... + * but something insane like suspend problems can mean + * we spin here forever, so we timeout after a LONG time + */ + do { + status = glamo_reg_read(host, GLAMO_REG_IRQ_STATUS); + } while ((--timeout) && !(status & GLAMO_IRQ_MMC)); + + if (timeout <= 0) { + if (cmd->data->error) + cmd->data->error = -ETIMEDOUT; + dev_err(&host->pdev->dev, "Payload timeout\n"); + return -ETIMEDOUT; + } + /* ack this interrupt source */ + writew(GLAMO_IRQ_MMC, host->core->base + + GLAMO_REG_IRQ_CLEAR); + + /* yay we are an interrupt controller! -- call the ISR + * it will stop clock to card + */ + glamo_mci_irq(host->irq, host); + + return 0; +} + +static void glamo_mci_send_request(struct mmc_host *mmc, + struct mmc_request *mrq) +{ + struct glamo_mci_host *host = mmc_priv(mmc); + struct mmc_command *cmd = mrq->cmd; + + glamo_mci_clock_enable(host); + host->request_counter++; + if (cmd->data) { + if (glamo_mci_prepare_pio(host, cmd->data)) { + cmd->error = -EIO; + cmd->data->error = -EIO; + goto done; + } + } + + dev_dbg(&host->pdev->dev, "cmd 0x%x, " + "arg 0x%x data=%p mrq->stop=%p flags 0x%x\n", + cmd->opcode, cmd->arg, cmd->data, cmd->mrq->stop, + cmd->flags); + + glamo_mci_send_command(host, cmd); + + /* + * if we don't have bulk data to take care of, we're done + */ + if (!cmd->data || cmd->error) + goto done; + + + if (!host->core->irq_works) { + if (glamo_mci_irq_poll(host, mrq->cmd)) + goto done; + } + + /* + * Otherwise can can use the interrupt as async completion -- + * if there is read data coming, or we wait for write data to complete, + * exit without mmc_request_done() as the payload interrupt + * will service it + */ + dev_dbg(&host->pdev->dev, "Waiting for payload data\n"); + return; +done: + if (!cmd->error) + cmd->error = glamo_mci_wait_idle(host, jiffies + 2 * HZ); + glamo_mci_request_done(host, mrq); +} + +static void glamo_mci_set_power_mode(struct glamo_mci_host *host, + unsigned char power_mode) +{ + int ret; + + if (power_mode == host->power_mode) + return; + + switch (power_mode) { + case MMC_POWER_UP: + if (host->power_mode == MMC_POWER_OFF) { + printk("ENABLE\n"); + ret = regulator_enable(host->regulator); + if (ret) + dev_err(&host->pdev->dev, + "Failed to enable regulator: %d\n", + ret); + } + break; + case MMC_POWER_ON: + break; + case MMC_POWER_OFF: + default: + printk("DISABLE\n"); + glamo_engine_disable(host->core, GLAMO_ENGINE_MMC); + + ret = regulator_disable(host->regulator); + if (ret) + dev_warn(&host->pdev->dev, + "Failed to disable regulator: %d\n", + ret); + break; + } + host->power_mode = power_mode; +} + +static void glamo_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct glamo_mci_host *host = mmc_priv(mmc); + int bus_width = 0; + int rate; + int sd_drive; + int ret; + + /* Set power */ + glamo_mci_set_power_mode(host, ios->power_mode); + + if (host->vdd != ios->vdd) { + ret = mmc_regulator_set_ocr(host->regulator, ios->vdd); + if (ret) + dev_err(&host->pdev->dev, + "Failed to set regulator voltage: %d\n", ret); + else + host->vdd = ios->vdd; + } + rate = glamo_mci_set_card_clock(host, ios->clock); + + if ((ios->power_mode == MMC_POWER_ON) || + (ios->power_mode == MMC_POWER_UP)) { + dev_info(&host->pdev->dev, + "powered (vdd = %hu) clk: %dkHz div=%hu (req: %ukHz). " + "Bus width=%d\n", ios->vdd, + rate / 1000, 0, + ios->clock / 1000, (int)ios->bus_width); + } else { + dev_info(&host->pdev->dev, "glamo_mci_set_ios: power down.\n"); + } + + /* set bus width */ + if (ios->bus_width == MMC_BUS_WIDTH_4) + bus_width = GLAMO_BASIC_MMC_EN_4BIT_DATA; + + sd_drive = (rate * 4) / host->clk_rate; + if (sd_drive > 3) + sd_drive = 3; + + glamo_reg_set_bit_mask(host, GLAMO_REG_MMC_BASIC, + GLAMO_BASIC_MMC_EN_4BIT_DATA | 0xb0, + bus_width | sd_drive << 6); +} + + +/* + * no physical write protect supported by us + */ +static int glamo_mci_get_ro(struct mmc_host *mmc) +{ + return 0; +} + +static struct mmc_host_ops glamo_mci_ops = { + .request = glamo_mci_send_request, + .set_ios = glamo_mci_set_ios, + .get_ro = glamo_mci_get_ro, +}; + +static int glamo_mci_probe(struct platform_device *pdev) +{ + struct mmc_host *mmc; + struct glamo_mci_host *host; + struct glamo_core *core = dev_get_drvdata(pdev->dev.parent); + int ret; + + dev_info(&pdev->dev, "glamo_mci driver (C)2007 Openmoko, Inc\n"); + + mmc = mmc_alloc_host(sizeof(struct glamo_mci_host), &pdev->dev); + if (!mmc) { + ret = -ENOMEM; + goto probe_out; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + host->pdev = pdev; + if (core->pdata) + host->pdata = core->pdata->mmc_data; + host->power_mode = MMC_POWER_OFF; + host->clk_enabled = 0; + host->core = core; + host->irq = platform_get_irq(pdev, 0); + + INIT_WORK(&host->irq_work, glamo_mci_irq_worker); + INIT_WORK(&host->read_work, glamo_mci_read_worker); + + host->regulator = regulator_get(pdev->dev.parent, "SD_3V3"); + if (IS_ERR(host->regulator)) { + dev_err(&pdev->dev, "Cannot proceed without regulator.\n"); + ret = PTR_ERR(host->regulator); + goto probe_free_host; + } + + host->mmio_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->mmio_mem) { + dev_err(&pdev->dev, + "failed to get io memory region resouce.\n"); + ret = -ENOENT; + goto probe_regulator_put; + } + + host->mmio_mem = request_mem_region(host->mmio_mem->start, + resource_size(host->mmio_mem), + pdev->name); + + if (!host->mmio_mem) { + dev_err(&pdev->dev, "failed to request io memory region.\n"); + ret = -ENOENT; + goto probe_regulator_put; + } + + host->mmio_base = ioremap(host->mmio_mem->start, + resource_size(host->mmio_mem)); + if (!host->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap() io memory region.\n"); + ret = -EINVAL; + goto probe_free_mem_region_mmio; + } + + + /* Get ahold of our data buffer we use for data in and out on MMC */ + host->data_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!host->data_mem) { + dev_err(&pdev->dev, + "failed to get io memory region resource.\n"); + ret = -ENOENT; + goto probe_iounmap_mmio; + } + + host->data_mem = request_mem_region(host->data_mem->start, + resource_size(host->data_mem), + pdev->name); + + if (!host->data_mem) { + dev_err(&pdev->dev, "failed to request io memory region.\n"); + ret = -ENOENT; + goto probe_iounmap_mmio; + } + host->data_base = ioremap(host->data_mem->start, + resource_size(host->data_mem)); + + if (host->data_base == 0) { + dev_err(&pdev->dev, "failed to ioremap() io memory region.\n"); + ret = -EINVAL; + goto probe_free_mem_region_data; + } + + ret = request_irq(host->irq, glamo_mci_irq, IRQF_SHARED, + pdev->name, host); + if (ret) { + dev_err(&pdev->dev, "failed to register irq.\n"); + goto probe_iounmap_data; + } + + + host->vdd = 0; + host->clk_rate = glamo_pll_rate(host->core, GLAMO_PLL1); + + /* explain our host controller capabilities */ + mmc->ops = &glamo_mci_ops; + mmc->ocr_avail = mmc_regulator_get_ocrmask(host->regulator); + mmc->caps = MMC_CAP_4_BIT_DATA | + MMC_CAP_MMC_HIGHSPEED | + MMC_CAP_SD_HIGHSPEED; + mmc->f_min = host->clk_rate / 256; + mmc->f_max = sd_max_clk; + + mmc->max_blk_count = (1 << 16) - 1; /* GLAMO_REG_MMC_RB_BLKCNT */ + mmc->max_blk_size = (1 << 12) - 1; /* GLAMO_REG_MMC_RB_BLKLEN */ + mmc->max_req_size = resource_size(host->data_mem); + mmc->max_seg_size = mmc->max_req_size; + mmc->max_phys_segs = 128; + mmc->max_hw_segs = 128; + + if (mmc->ocr_avail < 0) { + dev_warn(&pdev->dev, + "Failed to get ocr list for regulator: %d.\n", + mmc->ocr_avail); + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + } + + platform_set_drvdata(pdev, mmc); + + glamo_engine_enable(host->core, GLAMO_ENGINE_MMC); + glamo_mci_reset(host); + glamo_engine_disable(host->core, GLAMO_ENGINE_MMC); + + setup_timer(&host->disable_timer, glamo_mci_disable_timer, + (unsigned long)host); + + ret = mmc_add_host(mmc); + if (ret) { + dev_err(&pdev->dev, "failed to add mmc host.\n"); + goto probe_freeirq; + } + + return 0; + +probe_freeirq: + free_irq(host->irq, host); +probe_iounmap_data: + iounmap(host->data_base); +probe_free_mem_region_data: + release_mem_region(host->data_mem->start, + resource_size(host->data_mem)); +probe_iounmap_mmio: + iounmap(host->mmio_base); +probe_free_mem_region_mmio: + release_mem_region(host->mmio_mem->start, + resource_size(host->mmio_mem)); +probe_regulator_put: + regulator_put(host->regulator); +probe_free_host: + mmc_free_host(mmc); +probe_out: + return ret; +} + +static int glamo_mci_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct glamo_mci_host *host = mmc_priv(mmc); + + free_irq(host->irq, host); + + mmc_remove_host(mmc); + iounmap(host->mmio_base); + iounmap(host->data_base); + release_mem_region(host->mmio_mem->start, + resource_size(host->mmio_mem)); + release_mem_region(host->data_mem->start, + resource_size(host->data_mem)); + + regulator_put(host->regulator); + + mmc_free_host(mmc); + + glamo_engine_disable(host->core, GLAMO_ENGINE_MMC); + return 0; +} + + +#ifdef CONFIG_PM + +static int glamo_mci_suspend(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct glamo_mci_host *host = mmc_priv(mmc); + int ret; + + cancel_work_sync(&host->irq_work); + + ret = mmc_suspend_host(mmc, PMSG_SUSPEND); + + return ret; +} + +static int glamo_mci_resume(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct glamo_mci_host *host = mmc_priv(mmc); + int ret; + + + glamo_mci_reset(host); + glamo_engine_enable(host->core, GLAMO_ENGINE_MMC); + mdelay(10); + + ret = mmc_resume_host(host->mmc); + + return 0; +} + +static struct dev_pm_ops glamo_mci_pm_ops = { + .suspend = glamo_mci_suspend, + .resume = glamo_mci_resume, + .freeze = glamo_mci_suspend, + .thaw = glamo_mci_resume, +}; +#define GLAMO_MCI_PM_OPS (&glamo_mci_pm_ops) + +#else /* CONFIG_PM */ +#define GLAMO_MCI_PM_OPS NULL +#endif /* CONFIG_PM */ + + +static struct platform_driver glamo_mci_driver = { + .probe = glamo_mci_probe, + .remove = glamo_mci_remove, + .driver = { + .name = "glamo-mci", + .owner = THIS_MODULE, + .pm = GLAMO_MCI_PM_OPS, + }, +}; + +static int __init glamo_mci_init(void) +{ + platform_driver_register(&glamo_mci_driver); + return 0; +} +module_init(glamo_mci_init); + +static void __exit glamo_mci_exit(void) +{ + platform_driver_unregister(&glamo_mci_driver); +} +module_exit(glamo_mci_exit); + +MODULE_DESCRIPTION("Glamo MMC/SD Card Interface driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andy Green <andy@openmoko.com>"); diff --git a/drivers/mfd/glamo/glamo-regs.h b/drivers/mfd/glamo/glamo-regs.h new file mode 100644 index 00000000000..59848e1122e --- /dev/null +++ b/drivers/mfd/glamo/glamo-regs.h @@ -0,0 +1,630 @@ +#ifndef _GLAMO_REGS_H +#define _GLAMO_REGS_H + +/* Smedia Glamo 336x/337x driver + * + * (C) 2007 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * All rights reserved. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +enum glamo_regster_offsets { + GLAMO_REGOFS_GENERIC = 0x0000, + GLAMO_REGOFS_HOSTBUS = 0x0200, + GLAMO_REGOFS_MEMORY = 0x0300, + GLAMO_REGOFS_VIDCAP = 0x0400, + GLAMO_REGOFS_ISP = 0x0500, + GLAMO_REGOFS_JPEG = 0x0800, + GLAMO_REGOFS_MPEG = 0x0c00, + GLAMO_REGOFS_LCD = 0x1100, + GLAMO_REGOFS_MMC = 0x1400, + GLAMO_REGOFS_MPROC0 = 0x1500, + GLAMO_REGOFS_MPROC1 = 0x1580, + GLAMO_REGOFS_CMDQUEUE = 0x1600, + GLAMO_REGOFS_RISC = 0x1680, + GLAMO_REGOFS_2D = 0x1700, + GLAMO_REGOFS_3D = 0x1b00, + GLAMO_REGOFS_END = 0x2400, +}; + + +enum glamo_register_generic { + GLAMO_REG_GCONF1 = 0x0000, + GLAMO_REG_GCONF2 = 0x0002, +#define GLAMO_REG_DEVICE_ID GLAMO_REG_GCONF2 + GLAMO_REG_GCONF3 = 0x0004, +#define GLAMO_REG_REVISION_ID GLAMO_REG_GCONF3 + GLAMO_REG_IRQ_GEN1 = 0x0006, +#define GLAMO_REG_IRQ_ENABLE GLAMO_REG_IRQ_GEN1 + GLAMO_REG_IRQ_GEN2 = 0x0008, +#define GLAMO_REG_IRQ_SET GLAMO_REG_IRQ_GEN2 + GLAMO_REG_IRQ_GEN3 = 0x000a, +#define GLAMO_REG_IRQ_CLEAR GLAMO_REG_IRQ_GEN3 + GLAMO_REG_IRQ_GEN4 = 0x000c, +#define GLAMO_REG_IRQ_STATUS GLAMO_REG_IRQ_GEN4 + GLAMO_REG_CLOCK_HOST = 0x0010, + GLAMO_REG_CLOCK_MEMORY = 0x0012, + GLAMO_REG_CLOCK_LCD = 0x0014, + GLAMO_REG_CLOCK_MMC = 0x0016, + GLAMO_REG_CLOCK_ISP = 0x0018, + GLAMO_REG_CLOCK_JPEG = 0x001a, + GLAMO_REG_CLOCK_3D = 0x001c, + GLAMO_REG_CLOCK_2D = 0x001e, + GLAMO_REG_CLOCK_RISC1 = 0x0020, /* 3365 only? */ + GLAMO_REG_CLOCK_RISC2 = 0x0022, /* 3365 only? */ + GLAMO_REG_CLOCK_MPEG = 0x0024, + GLAMO_REG_CLOCK_MPROC = 0x0026, + + GLAMO_REG_CLOCK_GEN5_1 = 0x0030, + GLAMO_REG_CLOCK_GEN5_2 = 0x0032, + GLAMO_REG_CLOCK_GEN6 = 0x0034, + GLAMO_REG_CLOCK_GEN7 = 0x0036, + GLAMO_REG_CLOCK_GEN8 = 0x0038, + GLAMO_REG_CLOCK_GEN9 = 0x003a, + GLAMO_REG_CLOCK_GEN10 = 0x003c, + GLAMO_REG_CLOCK_GEN11 = 0x003e, + GLAMO_REG_PLL_GEN1 = 0x0040, + GLAMO_REG_PLL_GEN2 = 0x0042, + GLAMO_REG_PLL_GEN3 = 0x0044, + GLAMO_REG_PLL_GEN4 = 0x0046, + GLAMO_REG_PLL_GEN5 = 0x0048, + GLAMO_REG_GPIO_GEN1 = 0x0050, + GLAMO_REG_GPIO_GEN2 = 0x0052, + GLAMO_REG_GPIO_GEN3 = 0x0054, + GLAMO_REG_GPIO_GEN4 = 0x0056, + GLAMO_REG_GPIO_GEN5 = 0x0058, + GLAMO_REG_GPIO_GEN6 = 0x005a, + GLAMO_REG_GPIO_GEN7 = 0x005c, + GLAMO_REG_GPIO_GEN8 = 0x005e, + GLAMO_REG_GPIO_GEN9 = 0x0060, + GLAMO_REG_GPIO_GEN10 = 0x0062, + GLAMO_REG_DFT_GEN1 = 0x0070, + GLAMO_REG_DFT_GEN2 = 0x0072, + GLAMO_REG_DFT_GEN3 = 0x0074, + GLAMO_REG_DFT_GEN4 = 0x0076, + + GLAMO_REG_DFT_GEN5 = 0x01e0, + GLAMO_REG_DFT_GEN6 = 0x01f0, +}; + +#define GLAMO_REG_HOSTBUS(x) (GLAMO_REGOFS_HOSTBUS-2+(x*2)) + +#define REG_MEM(x) (GLAMO_REGOFS_MEMORY+(x)) +#define GLAMO_REG_MEM_TIMING(x) (GLAMO_REG_MEM_TIMING1-2+(x*2)) + +enum glamo_register_mem { + GLAMO_REG_MEM_TYPE = REG_MEM(0x00), + GLAMO_REG_MEM_GEN = REG_MEM(0x02), + GLAMO_REG_MEM_TIMING1 = REG_MEM(0x04), + GLAMO_REG_MEM_TIMING2 = REG_MEM(0x06), + GLAMO_REG_MEM_TIMING3 = REG_MEM(0x08), + GLAMO_REG_MEM_TIMING4 = REG_MEM(0x0a), + GLAMO_REG_MEM_TIMING5 = REG_MEM(0x0c), + GLAMO_REG_MEM_TIMING6 = REG_MEM(0x0e), + GLAMO_REG_MEM_TIMING7 = REG_MEM(0x10), + GLAMO_REG_MEM_TIMING8 = REG_MEM(0x12), + GLAMO_REG_MEM_TIMING9 = REG_MEM(0x14), + GLAMO_REG_MEM_TIMING10 = REG_MEM(0x16), + GLAMO_REG_MEM_TIMING11 = REG_MEM(0x18), + GLAMO_REG_MEM_POWER1 = REG_MEM(0x1a), + GLAMO_REG_MEM_POWER2 = REG_MEM(0x1c), + GLAMO_REG_MEM_LCD_BUF1 = REG_MEM(0x1e), + GLAMO_REG_MEM_LCD_BUF2 = REG_MEM(0x20), + GLAMO_REG_MEM_LCD_BUF3 = REG_MEM(0x22), + GLAMO_REG_MEM_LCD_BUF4 = REG_MEM(0x24), + GLAMO_REG_MEM_BIST1 = REG_MEM(0x26), + GLAMO_REG_MEM_BIST2 = REG_MEM(0x28), + GLAMO_REG_MEM_BIST3 = REG_MEM(0x2a), + GLAMO_REG_MEM_BIST4 = REG_MEM(0x2c), + GLAMO_REG_MEM_BIST5 = REG_MEM(0x2e), + GLAMO_REG_MEM_MAH1 = REG_MEM(0x30), + GLAMO_REG_MEM_MAH2 = REG_MEM(0x32), + GLAMO_REG_MEM_DRAM1 = REG_MEM(0x34), + GLAMO_REG_MEM_DRAM2 = REG_MEM(0x36), + GLAMO_REG_MEM_CRC = REG_MEM(0x38), +}; + +#define GLAMO_MEM_TYPE_MASK 0x03 + +enum glamo_reg_mem_dram1 { + /* b0 - b10 == refresh period, 1 -> 2048 clocks */ + GLAMO_MEM_DRAM1_EN_GATE_CLK = (1 << 11), + GLAMO_MEM_DRAM1_SELF_REFRESH = (1 << 12), + GLAMO_MEM_DRAM1_EN_GATE_CKE = (1 << 13), + GLAMO_MEM_DRAM1_EN_DRAM_REFRESH = (1 << 14), + GLAMO_MEM_DRAM1_EN_MODEREG_SET = (1 << 15), +}; + +enum glamo_reg_mem_dram2 { + GLAMO_MEM_DRAM2_DEEP_PWRDOWN = (1 << 12), +}; + +enum glamo_irq_index { + GLAMO_IRQIDX_HOSTBUS = 0, + GLAMO_IRQIDX_JPEG = 1, + GLAMO_IRQIDX_MPEG = 2, + GLAMO_IRQIDX_MPROC1 = 3, + GLAMO_IRQIDX_MPROC0 = 4, + GLAMO_IRQIDX_CMDQUEUE = 5, + GLAMO_IRQIDX_2D = 6, + GLAMO_IRQIDX_MMC = 7, + GLAMO_IRQIDX_RISC = 8, +}; + +enum glamo_irq { + GLAMO_IRQ_HOSTBUS = (1 << GLAMO_IRQIDX_HOSTBUS), + GLAMO_IRQ_JPEG = (1 << GLAMO_IRQIDX_JPEG), + GLAMO_IRQ_MPEG = (1 << GLAMO_IRQIDX_MPEG), + GLAMO_IRQ_MPROC1 = (1 << GLAMO_IRQIDX_MPROC1), + GLAMO_IRQ_MPROC0 = (1 << GLAMO_IRQIDX_MPROC0), + GLAMO_IRQ_CMDQUEUE = (1 << GLAMO_IRQIDX_CMDQUEUE), + GLAMO_IRQ_2D = (1 << GLAMO_IRQIDX_2D), + GLAMO_IRQ_MMC = (1 << GLAMO_IRQIDX_MMC), + GLAMO_IRQ_RISC = (1 << GLAMO_IRQIDX_RISC), +}; + +enum glamo_reg_clock_host { + GLAMO_CLOCK_HOST_DG_BCLK = 0x0001, + GLAMO_CLOCK_HOST_DG_M0CLK = 0x0004, + GLAMO_CLOCK_HOST_RESET = 0x1000, +}; + +enum glamo_reg_clock_mem { + GLAMO_CLOCK_MEM_DG_M1CLK = 0x0001, + GLAMO_CLOCK_MEM_EN_M1CLK = 0x0002, + GLAMO_CLOCK_MEM_DG_MOCACLK = 0x0004, + GLAMO_CLOCK_MEM_EN_MOCACLK = 0x0008, + GLAMO_CLOCK_MEM_RESET = 0x1000, + GLAMO_CLOCK_MOCA_RESET = 0x2000, +}; + +enum glamo_reg_clock_lcd { + GLAMO_CLOCK_LCD_DG_DCLK = 0x0001, + GLAMO_CLOCK_LCD_EN_DCLK = 0x0002, + GLAMO_CLOCK_LCD_DG_DMCLK = 0x0004, + GLAMO_CLOCK_LCD_EN_DMCLK = 0x0008, + GLAMO_CLOCK_LCD_EN_DHCLK = 0x0020, + GLAMO_CLOCK_LCD_DG_M5CLK = 0x0040, + GLAMO_CLOCK_LCD_EN_M5CLK = 0x0080, + GLAMO_CLOCK_LCD_RESET = 0x1000, +}; + +enum glamo_reg_clock_mmc { + GLAMO_CLOCK_MMC_DG_TCLK = 0x0001, + GLAMO_CLOCK_MMC_EN_TCLK = 0x0002, + GLAMO_CLOCK_MMC_DG_M9CLK = 0x0004, + GLAMO_CLOCK_MMC_EN_M9CLK = 0x0008, + GLAMO_CLOCK_MMC_RESET = 0x1000, +}; + +enum glamo_reg_basic_mmc { + /* set to disable CRC error rejection */ + GLAMO_BASIC_MMC_DISABLE_CRC = 0x0001, + /* enable completion interrupt */ + GLAMO_BASIC_MMC_EN_COMPL_INT = 0x0002, + /* stop MMC clock while enforced idle waiting for data from card */ + GLAMO_BASIC_MMC_NO_CLK_RD_WAIT = 0x0004, + /* 0 = 1-bit bus to card, 1 = use 4-bit bus (has to be negotiated) */ + GLAMO_BASIC_MMC_EN_4BIT_DATA = 0x0008, + /* enable 75K pullups on D3..D0 */ + GLAMO_BASIC_MMC_EN_DATA_PUPS = 0x0010, + /* enable 75K pullup on CMD */ + GLAMO_BASIC_MMC_EN_CMD_PUP = 0x0020, + /* IO drive strength 00=weak -> 11=strongest */ + GLAMO_BASIC_MMC_EN_DR_STR0 = 0x0040, + GLAMO_BASIC_MMC_EN_DR_STR1 = 0x0080, + /* TCLK delay stage A, 0000 = 500ps --> 1111 = 8ns */ + GLAMO_BASIC_MMC_EN_TCLK_DLYA0 = 0x0100, + GLAMO_BASIC_MMC_EN_TCLK_DLYA1 = 0x0200, + GLAMO_BASIC_MMC_EN_TCLK_DLYA2 = 0x0400, + GLAMO_BASIC_MMC_EN_TCLK_DLYA3 = 0x0800, + /* TCLK delay stage B (cumulative), 0000 = 500ps --> 1111 = 8ns */ + GLAMO_BASIC_MMC_EN_TCLK_DLYB0 = 0x1000, + GLAMO_BASIC_MMC_EN_TCLK_DLYB1 = 0x2000, + GLAMO_BASIC_MMC_EN_TCLK_DLYB2 = 0x4000, + GLAMO_BASIC_MMC_EN_TCLK_DLYB3 = 0x8000, +}; + +enum glamo_reg_stat1_mmc { + /* command "counter" (really: toggle) */ + GLAMO_STAT1_MMC_CMD_CTR = 0x8000, + /* engine is idle */ + GLAMO_STAT1_MMC_IDLE = 0x4000, + /* readback response is ready */ + GLAMO_STAT1_MMC_RB_RRDY = 0x0200, + /* readback data is ready */ + GLAMO_STAT1_MMC_RB_DRDY = 0x0100, + /* no response timeout */ + GLAMO_STAT1_MMC_RTOUT = 0x0020, + /* no data timeout */ + GLAMO_STAT1_MMC_DTOUT = 0x0010, + /* CRC error on block write */ + GLAMO_STAT1_MMC_BWERR = 0x0004, + /* CRC error on block read */ + GLAMO_STAT1_MMC_BRERR = 0x0002 +}; + +enum glamo_reg_fire_mmc { + /* command "counter" (really: toggle) + * the STAT1 register reflects this so you can ensure you don't look + * at status for previous command + */ + GLAMO_FIRE_MMC_CMD_CTR = 0x8000, + /* sets kind of response expected */ + GLAMO_FIRE_MMC_RES_MASK = 0x0700, + /* sets command type */ + GLAMO_FIRE_MMC_TYP_MASK = 0x00C0, + /* sets command class */ + GLAMO_FIRE_MMC_CLS_MASK = 0x000F, +}; + +enum glamo_fire_mmc_response_types { + GLAMO_FIRE_MMC_RSPT_R1 = 0x0000, + GLAMO_FIRE_MMC_RSPT_R1b = 0x0100, + GLAMO_FIRE_MMC_RSPT_R2 = 0x0200, + GLAMO_FIRE_MMC_RSPT_R3 = 0x0300, + GLAMO_FIRE_MMC_RSPT_R4 = 0x0400, + GLAMO_FIRE_MMC_RSPT_R5 = 0x0500, +}; + +enum glamo_fire_mmc_command_types { + /* broadcast, no response */ + GLAMO_FIRE_MMC_CMDT_BNR = 0x0000, + /* broadcast, with response */ + GLAMO_FIRE_MMC_CMDT_BR = 0x0040, + /* addressed, no data */ + GLAMO_FIRE_MMC_CMDT_AND = 0x0080, + /* addressed, with data */ + GLAMO_FIRE_MMC_CMDT_AD = 0x00C0, +}; + +enum glamo_fire_mmc_command_class { + /* "Stream Read" */ + GLAMO_FIRE_MMC_CC_STRR = 0x0000, + /* Single Block Read */ + GLAMO_FIRE_MMC_CC_SBR = 0x0001, + /* Multiple Block Read With Stop */ + GLAMO_FIRE_MMC_CC_MBRS = 0x0002, + /* Multiple Block Read No Stop */ + GLAMO_FIRE_MMC_CC_MBRNS = 0x0003, + /* RESERVED for "Stream Write" */ + GLAMO_FIRE_MMC_CC_STRW = 0x0004, + /* "Stream Write" */ + GLAMO_FIRE_MMC_CC_SBW = 0x0005, + /* RESERVED for Multiple Block Write With Stop */ + GLAMO_FIRE_MMC_CC_MBWS = 0x0006, + /* Multiple Block Write No Stop */ + GLAMO_FIRE_MMC_CC_MBWNS = 0x0007, + /* STOP command */ + GLAMO_FIRE_MMC_CC_STOP = 0x0008, + /* Cancel on Running Command */ + GLAMO_FIRE_MMC_CC_CANCL = 0x0009, + /* "Basic Command" */ + GLAMO_FIRE_MMC_CC_BASIC = 0x000a, +}; + +/* these are offsets from the start of the MMC register region */ +enum glamo_register_mmc { + /* MMC command, b15..8 = cmd arg b7..0; b7..1 = CRC; b0 = end bit */ + GLAMO_REG_MMC_CMD_REG1 = 0x00, + /* MMC command, b15..0 = cmd arg b23 .. 8 */ + GLAMO_REG_MMC_CMD_REG2 = 0x02, + /* MMC command, b15=start, b14=transmission, + * b13..8=cmd idx, b7..0=cmd arg b31..24 + */ + GLAMO_REG_MMC_CMD_REG3 = 0x04, + GLAMO_REG_MMC_CMD_FIRE = 0x06, + GLAMO_REG_MMC_CMD_RSP1 = 0x10, + GLAMO_REG_MMC_CMD_RSP2 = 0x12, + GLAMO_REG_MMC_CMD_RSP3 = 0x14, + GLAMO_REG_MMC_CMD_RSP4 = 0x16, + GLAMO_REG_MMC_CMD_RSP5 = 0x18, + GLAMO_REG_MMC_CMD_RSP6 = 0x1a, + GLAMO_REG_MMC_CMD_RSP7 = 0x1c, + GLAMO_REG_MMC_CMD_RSP8 = 0x1e, + GLAMO_REG_MMC_RB_STAT1 = 0x20, + GLAMO_REG_MMC_RB_BLKCNT = 0x22, + GLAMO_REG_MMC_RB_BLKLEN = 0x24, + GLAMO_REG_MMC_BASIC = 0x30, + GLAMO_REG_MMC_RDATADS1 = 0x34, + GLAMO_REG_MMC_RDATADS2 = 0x36, + GLAMO_REG_MMC_WDATADS1 = 0x38, + GLAMO_REG_MMC_WDATADS2 = 0x3a, + GLAMO_REG_MMC_DATBLKCNT = 0x3c, + GLAMO_REG_MMC_DATBLKLEN = 0x3e, + GLAMO_REG_MMC_TIMEOUT = 0x40, + +}; + +enum glamo_reg_clock_isp { + GLAMO_CLOCK_ISP_DG_I1CLK = 0x0001, + GLAMO_CLOCK_ISP_EN_I1CLK = 0x0002, + GLAMO_CLOCK_ISP_DG_CCLK = 0x0004, + GLAMO_CLOCK_ISP_EN_CCLK = 0x0008, + GLAMO_CLOCK_ISP_EN_SCLK = 0x0020, + GLAMO_CLOCK_ISP_DG_M2CLK = 0x0040, + GLAMO_CLOCK_ISP_EN_M2CLK = 0x0080, + GLAMO_CLOCK_ISP_DG_M15CLK = 0x0100, + GLAMO_CLOCK_ISP_EN_M15CLK = 0x0200, + GLAMO_CLOCK_ISP1_RESET = 0x1000, + GLAMO_CLOCK_ISP2_RESET = 0x2000, +}; + +enum glamo_reg_clock_jpeg { + GLAMO_CLOCK_JPEG_DG_JCLK = 0x0001, + GLAMO_CLOCK_JPEG_EN_JCLK = 0x0002, + GLAMO_CLOCK_JPEG_DG_M3CLK = 0x0004, + GLAMO_CLOCK_JPEG_EN_M3CLK = 0x0008, + GLAMO_CLOCK_JPEG_RESET = 0x1000, +}; + +enum glamo_reg_clock_2d { + GLAMO_CLOCK_2D_DG_GCLK = 0x0001, + GLAMO_CLOCK_2D_EN_GCLK = 0x0002, + GLAMO_CLOCK_2D_DG_M7CLK = 0x0004, + GLAMO_CLOCK_2D_EN_M7CLK = 0x0008, + GLAMO_CLOCK_2D_DG_M6CLK = 0x0010, + GLAMO_CLOCK_2D_EN_M6CLK = 0x0020, + GLAMO_CLOCK_2D_RESET = 0x1000, + GLAMO_CLOCK_2D_CQ_RESET = 0x2000, +}; + +enum glamo_reg_clock_3d { + GLAMO_CLOCK_3D_DG_ECLK = 0x0001, + GLAMO_CLOCK_3D_EN_ECLK = 0x0002, + GLAMO_CLOCK_3D_DG_RCLK = 0x0004, + GLAMO_CLOCK_3D_EN_RCLK = 0x0008, + GLAMO_CLOCK_3D_DG_M8CLK = 0x0010, + GLAMO_CLOCK_3D_EN_M8CLK = 0x0020, + GLAMO_CLOCK_3D_BACK_RESET = 0x1000, + GLAMO_CLOCK_3D_FRONT_RESET = 0x2000, +}; + +enum glamo_reg_clock_mpeg { + GLAMO_CLOCK_MPEG_DG_X0CLK = 0x0001, + GLAMO_CLOCK_MPEG_EN_X0CLK = 0x0002, + GLAMO_CLOCK_MPEG_DG_X1CLK = 0x0004, + GLAMO_CLOCK_MPEG_EN_X1CLK = 0x0008, + GLAMO_CLOCK_MPEG_DG_X2CLK = 0x0010, + GLAMO_CLOCK_MPEG_EN_X2CLK = 0x0020, + GLAMO_CLOCK_MPEG_DG_X3CLK = 0x0040, + GLAMO_CLOCK_MPEG_EN_X3CLK = 0x0080, + GLAMO_CLOCK_MPEG_DG_X4CLK = 0x0100, + GLAMO_CLOCK_MPEG_EN_X4CLK = 0x0200, + GLAMO_CLOCK_MPEG_DG_X6CLK = 0x0400, + GLAMO_CLOCK_MPEG_EN_X6CLK = 0x0800, + GLAMO_CLOCK_MPEG_ENC_RESET = 0x1000, + GLAMO_CLOCK_MPEG_DEC_RESET = 0x2000, +}; + +enum glamo_reg_clock51 { + GLAMO_CLOCK_GEN51_EN_DIV_MCLK = 0x0001, + GLAMO_CLOCK_GEN51_EN_DIV_SCLK = 0x0002, + GLAMO_CLOCK_GEN51_EN_DIV_JCLK = 0x0004, + GLAMO_CLOCK_GEN51_EN_DIV_DCLK = 0x0008, + GLAMO_CLOCK_GEN51_EN_DIV_DMCLK = 0x0010, + GLAMO_CLOCK_GEN51_EN_DIV_DHCLK = 0x0020, + GLAMO_CLOCK_GEN51_EN_DIV_GCLK = 0x0040, + GLAMO_CLOCK_GEN51_EN_DIV_TCLK = 0x0080, + /* FIXME: higher bits */ +}; + +enum glamo_reg_hostbus2 { + GLAMO_HOSTBUS2_MMIO_EN_ISP = 0x0001, + GLAMO_HOSTBUS2_MMIO_EN_JPEG = 0x0002, + GLAMO_HOSTBUS2_MMIO_EN_MPEG = 0x0004, + GLAMO_HOSTBUS2_MMIO_EN_LCD = 0x0008, + GLAMO_HOSTBUS2_MMIO_EN_MMC = 0x0010, + GLAMO_HOSTBUS2_MMIO_EN_MICROP0 = 0x0020, + GLAMO_HOSTBUS2_MMIO_EN_MICROP1 = 0x0040, + GLAMO_HOSTBUS2_MMIO_EN_CQ = 0x0080, + GLAMO_HOSTBUS2_MMIO_EN_RISC = 0x0100, + GLAMO_HOSTBUS2_MMIO_EN_2D = 0x0200, + GLAMO_HOSTBUS2_MMIO_EN_3D = 0x0400, +}; + +/* LCD Controller */ + +#define REG_LCD(x) (x) +enum glamo_reg_lcd { + GLAMO_REG_LCD_MODE1 = REG_LCD(0x00), + GLAMO_REG_LCD_MODE2 = REG_LCD(0x02), + GLAMO_REG_LCD_MODE3 = REG_LCD(0x04), + GLAMO_REG_LCD_WIDTH = REG_LCD(0x06), + GLAMO_REG_LCD_HEIGHT = REG_LCD(0x08), + GLAMO_REG_LCD_POLARITY = REG_LCD(0x0a), + GLAMO_REG_LCD_A_BASE1 = REG_LCD(0x0c), + GLAMO_REG_LCD_A_BASE2 = REG_LCD(0x0e), + GLAMO_REG_LCD_B_BASE1 = REG_LCD(0x10), + GLAMO_REG_LCD_B_BASE2 = REG_LCD(0x12), + GLAMO_REG_LCD_C_BASE1 = REG_LCD(0x14), + GLAMO_REG_LCD_C_BASE2 = REG_LCD(0x16), + GLAMO_REG_LCD_PITCH = REG_LCD(0x18), + /* RES */ + GLAMO_REG_LCD_HORIZ_TOTAL = REG_LCD(0x1c), + /* RES */ + GLAMO_REG_LCD_HORIZ_RETR_START = REG_LCD(0x20), + /* RES */ + GLAMO_REG_LCD_HORIZ_RETR_END = REG_LCD(0x24), + /* RES */ + GLAMO_REG_LCD_HORIZ_DISP_START = REG_LCD(0x28), + /* RES */ + GLAMO_REG_LCD_HORIZ_DISP_END = REG_LCD(0x2c), + /* RES */ + GLAMO_REG_LCD_VERT_TOTAL = REG_LCD(0x30), + /* RES */ + GLAMO_REG_LCD_VERT_RETR_START = REG_LCD(0x34), + /* RES */ + GLAMO_REG_LCD_VERT_RETR_END = REG_LCD(0x38), + /* RES */ + GLAMO_REG_LCD_VERT_DISP_START = REG_LCD(0x3c), + /* RES */ + GLAMO_REG_LCD_VERT_DISP_END = REG_LCD(0x40), + /* RES */ + GLAMO_REG_LCD_POL = REG_LCD(0x44), + GLAMO_REG_LCD_DATA_START = REG_LCD(0x46), + GLAMO_REG_LCD_FRATE_CONTRO = REG_LCD(0x48), + GLAMO_REG_LCD_DATA_CMD_HDR = REG_LCD(0x4a), + GLAMO_REG_LCD_SP_START = REG_LCD(0x4c), + GLAMO_REG_LCD_SP_END = REG_LCD(0x4e), + GLAMO_REG_LCD_CURSOR_BASE1 = REG_LCD(0x50), + GLAMO_REG_LCD_CURSOR_BASE2 = REG_LCD(0x52), + GLAMO_REG_LCD_CURSOR_PITCH = REG_LCD(0x54), + GLAMO_REG_LCD_CURSOR_X_SIZE = REG_LCD(0x56), + GLAMO_REG_LCD_CURSOR_Y_SIZE = REG_LCD(0x58), + GLAMO_REG_LCD_CURSOR_X_POS = REG_LCD(0x5a), + GLAMO_REG_LCD_CURSOR_Y_POS = REG_LCD(0x5c), + GLAMO_REG_LCD_CURSOR_PRESET = REG_LCD(0x5e), + GLAMO_REG_LCD_CURSOR_FG_COLOR = REG_LCD(0x60), + /* RES */ + GLAMO_REG_LCD_CURSOR_BG_COLOR = REG_LCD(0x64), + /* RES */ + GLAMO_REG_LCD_CURSOR_DST_COLOR = REG_LCD(0x68), + /* RES */ + GLAMO_REG_LCD_STATUS1 = REG_LCD(0x80), + GLAMO_REG_LCD_STATUS2 = REG_LCD(0x82), + GLAMO_REG_LCD_STATUS3 = REG_LCD(0x84), + GLAMO_REG_LCD_STATUS4 = REG_LCD(0x86), + /* RES */ + GLAMO_REG_LCD_COMMAND1 = REG_LCD(0xa0), + GLAMO_REG_LCD_COMMAND2 = REG_LCD(0xa2), + /* RES */ + GLAMO_REG_LCD_WFORM_DELAY1 = REG_LCD(0xb0), + GLAMO_REG_LCD_WFORM_DELAY2 = REG_LCD(0xb2), + /* RES */ + GLAMO_REG_LCD_GAMMA_CORR = REG_LCD(0x100), + /* RES */ + GLAMO_REG_LCD_GAMMA_R_ENTRY01 = REG_LCD(0x110), + GLAMO_REG_LCD_GAMMA_R_ENTRY23 = REG_LCD(0x112), + GLAMO_REG_LCD_GAMMA_R_ENTRY45 = REG_LCD(0x114), + GLAMO_REG_LCD_GAMMA_R_ENTRY67 = REG_LCD(0x116), + GLAMO_REG_LCD_GAMMA_R_ENTRY8 = REG_LCD(0x118), + /* RES */ + GLAMO_REG_LCD_GAMMA_G_ENTRY01 = REG_LCD(0x130), + GLAMO_REG_LCD_GAMMA_G_ENTRY23 = REG_LCD(0x132), + GLAMO_REG_LCD_GAMMA_G_ENTRY45 = REG_LCD(0x134), + GLAMO_REG_LCD_GAMMA_G_ENTRY67 = REG_LCD(0x136), + GLAMO_REG_LCD_GAMMA_G_ENTRY8 = REG_LCD(0x138), + /* RES */ + GLAMO_REG_LCD_GAMMA_B_ENTRY01 = REG_LCD(0x150), + GLAMO_REG_LCD_GAMMA_B_ENTRY23 = REG_LCD(0x152), + GLAMO_REG_LCD_GAMMA_B_ENTRY45 = REG_LCD(0x154), + GLAMO_REG_LCD_GAMMA_B_ENTRY67 = REG_LCD(0x156), + GLAMO_REG_LCD_GAMMA_B_ENTRY8 = REG_LCD(0x158), + /* RES */ + GLAMO_REG_LCD_SRAM_DRIVING1 = REG_LCD(0x160), + GLAMO_REG_LCD_SRAM_DRIVING2 = REG_LCD(0x162), + GLAMO_REG_LCD_SRAM_DRIVING3 = REG_LCD(0x164), +}; + +enum glamo_reg_lcd_mode1 { + GLAMO_LCD_MODE1_PWRSAVE = 0x0001, + GLAMO_LCD_MODE1_PARTIAL_PRT = 0x0002, + GLAMO_LCD_MODE1_HWFLIP = 0x0004, + GLAMO_LCD_MODE1_LCD2 = 0x0008, + /* RES */ + GLAMO_LCD_MODE1_PARTIAL_MODE = 0x0020, + GLAMO_LCD_MODE1_CURSOR_DSTCOLOR = 0x0040, + GLAMO_LCD_MODE1_PARTIAL_ENABLE = 0x0080, + GLAMO_LCD_MODE1_TVCLK_IN_ENABLE = 0x0100, + GLAMO_LCD_MODE1_HSYNC_HIGH_ACT = 0x0200, + GLAMO_LCD_MODE1_VSYNC_HIGH_ACT = 0x0400, + GLAMO_LCD_MODE1_HSYNC_FLIP = 0x0800, + GLAMO_LCD_MODE1_GAMMA_COR_EN = 0x1000, + GLAMO_LCD_MODE1_DITHER_EN = 0x2000, + GLAMO_LCD_MODE1_CURSOR_EN = 0x4000, + GLAMO_LCD_MODE1_ROTATE_EN = 0x8000, +}; + +enum glamo_reg_lcd_mode2 { + GLAMO_LCD_MODE2_CRC_CHECK_EN = 0x0001, + GLAMO_LCD_MODE2_DCMD_PER_LINE = 0x0002, + GLAMO_LCD_MODE2_NOUSE_BDEF = 0x0004, + GLAMO_LCD_MODE2_OUT_POS_MODE = 0x0008, + GLAMO_LCD_MODE2_FRATE_CTRL_EN = 0x0010, + GLAMO_LCD_MODE2_SINGLE_BUFFER = 0x0020, + GLAMO_LCD_MODE2_SER_LSB_TO_MSB = 0x0040, + /* FIXME */ +}; + +enum glamo_reg_lcd_mode3 { + /* LCD color source data format */ + GLAMO_LCD_SRC_RGB565 = 0x0000, + GLAMO_LCD_SRC_ARGB1555 = 0x4000, + GLAMO_LCD_SRC_ARGB4444 = 0x8000, + /* interface type */ + GLAMO_LCD_MODE3_LCD = 0x1000, + GLAMO_LCD_MODE3_RGB = 0x0800, + GLAMO_LCD_MODE3_CPU = 0x0000, + /* mode */ + GLAMO_LCD_MODE3_RGB332 = 0x0000, + GLAMO_LCD_MODE3_RGB444 = 0x0100, + GLAMO_LCD_MODE3_RGB565 = 0x0200, + GLAMO_LCD_MODE3_RGB666 = 0x0300, + /* depth */ + GLAMO_LCD_MODE3_6BITS = 0x0000, + GLAMO_LCD_MODE3_8BITS = 0x0010, + GLAMO_LCD_MODE3_9BITS = 0x0020, + GLAMO_LCD_MODE3_16BITS = 0x0030, + GLAMO_LCD_MODE3_18BITS = 0x0040, +}; + +enum glamo_lcd_rot_mode { + GLAMO_LCD_ROT_MODE_0 = 0x0000, + GLAMO_LCD_ROT_MODE_180 = 0x2000, + GLAMO_LCD_ROT_MODE_MIRROR = 0x4000, + GLAMO_LCD_ROT_MODE_FLIP = 0x6000, + GLAMO_LCD_ROT_MODE_90 = 0x8000, + GLAMO_LCD_ROT_MODE_270 = 0xa000, +}; +#define GLAMO_LCD_ROT_MODE_MASK 0xe000 + +enum glamo_lcd_cmd_type { + GLAMO_LCD_CMD_TYPE_DISP = 0x0000, + GLAMO_LCD_CMD_TYPE_PARALLEL = 0x4000, + GLAMO_LCD_CMD_TYPE_SERIAL = 0x8000, + GLAMO_LCD_CMD_TYPE_SERIAL_DIRECT = 0xc000, +}; +#define GLAMO_LCD_CMD_TYPE_MASK 0xc000 + +enum glamo_lcd_cmds { + GLAMO_LCD_CMD_DATA_DISP_FIRE = 0x00, + GLAMO_LCD_CMD_DATA_DISP_SYNC = 0x01, /* RGB only */ + /* switch to command mode, no display */ + GLAMO_LCD_CMD_DATA_FIRE_NO_DISP = 0x02, + /* display until VSYNC, switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_VSYNC = 0x11, + /* display until HSYNC, switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_HSYNC = 0x12, + /* display until VSYNC, 1 black frame, VSYNC, switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_VSYNC_B = 0x13, + /* don't care about display and switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_FREE = 0x14, /* RGB only */ + /* don't care about display, keep data display but disable data, + * and switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_FREE_D = 0x15, /* RGB only */ +}; + +enum glamo_core_revisions { + GLAMO_CORE_REV_A0 = 0x0000, + GLAMO_CORE_REV_A1 = 0x0001, + GLAMO_CORE_REV_A2 = 0x0002, + GLAMO_CORE_REV_A3 = 0x0003, +}; + +#endif /* _GLAMO_REGS_H */ diff --git a/drivers/mfd/mc13783-core.c b/drivers/mfd/mc13783-core.c new file mode 100644 index 00000000000..e354d2912ef --- /dev/null +++ b/drivers/mfd/mc13783-core.c @@ -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"); + diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 54ddf3772e0..ae15e495e20 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -25,7 +25,7 @@ static int mfd_add_device(struct device *parent, int id, int ret = -ENOMEM; int r; - pdev = platform_device_alloc(cell->name, id); + pdev = platform_device_alloc(cell->name, id + cell->id); if (!pdev) goto fail_alloc; diff --git a/drivers/mfd/pcf50633-adc.c b/drivers/mfd/pcf50633-adc.c index c2d05becfa9..6d2e8466df1 100644 --- a/drivers/mfd/pcf50633-adc.c +++ b/drivers/mfd/pcf50633-adc.c @@ -73,15 +73,10 @@ static void trigger_next_adc_job_if_any(struct pcf50633 *pcf) struct pcf50633_adc *adc = __to_adc(pcf); int head; - mutex_lock(&adc->queue_mutex); - head = adc->queue_head; - if (!adc->queue[head]) { - mutex_unlock(&adc->queue_mutex); + if (!adc->queue[head]) return; - } - mutex_unlock(&adc->queue_mutex); adc_setup(pcf, adc->queue[head]->mux, adc->queue[head]->avg); } @@ -99,16 +94,17 @@ adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req) if (adc->queue[tail]) { mutex_unlock(&adc->queue_mutex); + dev_err(pcf->dev, "ADC queue is full, dropping request\n"); return -EBUSY; } adc->queue[tail] = req; + if (head == tail) + trigger_next_adc_job_if_any(pcf); adc->queue_tail = (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1); mutex_unlock(&adc->queue_mutex); - trigger_next_adc_job_if_any(pcf); - return 0; } @@ -124,6 +120,7 @@ pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result) int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg) { struct pcf50633_adc_request *req; + int err; /* req is freed when the result is ready, in interrupt handler */ req = kzalloc(sizeof(*req), GFP_KERNEL); @@ -136,9 +133,13 @@ int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg) req->callback_param = req; init_completion(&req->completion); - adc_enqueue_request(pcf, req); + err = adc_enqueue_request(pcf, req); + if (err) + return err; + wait_for_completion(&req->completion); + /* FIXME by this time req might be already freed */ return req->result; } EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read); @@ -159,9 +160,7 @@ int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg, req->callback = callback; req->callback_param = callback_param; - adc_enqueue_request(pcf, req); - - return 0; + return adc_enqueue_request(pcf, req); } EXPORT_SYMBOL_GPL(pcf50633_adc_async_read); @@ -184,7 +183,7 @@ static void pcf50633_adc_irq(int irq, void *data) struct pcf50633_adc *adc = data; struct pcf50633 *pcf = adc->pcf; struct pcf50633_adc_request *req; - int head; + int head, res; mutex_lock(&adc->queue_mutex); head = adc->queue_head; @@ -199,27 +198,27 @@ static void pcf50633_adc_irq(int irq, void *data) adc->queue_head = (head + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1); + res = adc_result(pcf); + trigger_next_adc_job_if_any(pcf); + mutex_unlock(&adc->queue_mutex); - req->callback(pcf, req->callback_param, adc_result(pcf)); + req->callback(pcf, req->callback_param, res); kfree(req); - - trigger_next_adc_job_if_any(pcf); } static int __devinit pcf50633_adc_probe(struct platform_device *pdev) { - struct pcf50633_subdev_pdata *pdata = pdev->dev.platform_data; struct pcf50633_adc *adc; adc = kzalloc(sizeof(*adc), GFP_KERNEL); if (!adc) return -ENOMEM; - adc->pcf = pdata->pcf; + adc->pcf = dev_to_pcf50633(pdev->dev.parent); platform_set_drvdata(pdev, adc); - pcf50633_register_irq(pdata->pcf, PCF50633_IRQ_ADCRDY, + pcf50633_register_irq(adc->pcf, PCF50633_IRQ_ADCRDY, pcf50633_adc_irq, adc); mutex_init(&adc->queue_mutex); diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 8d3c38bf971..41ac3a52df1 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -290,7 +290,7 @@ out: int pcf50633_irq_mask(struct pcf50633 *pcf, int irq) { - dev_info(pcf->dev, "Masking IRQ %d\n", irq); + dev_dbg(pcf->dev, "Masking IRQ %d\n", irq); return __pcf50633_irq_mask_set(pcf, irq, 1); } @@ -298,7 +298,7 @@ EXPORT_SYMBOL_GPL(pcf50633_irq_mask); int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq) { - dev_info(pcf->dev, "Unmasking IRQ %d\n", irq); + dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq); return __pcf50633_irq_mask_set(pcf, irq, 0); } @@ -345,23 +345,26 @@ static void pcf50633_irq_worker(struct work_struct *work) goto out; } + /* defeat 8s death from lowsys on A5 */ + pcf50633_reg_write(pcf, PCF50633_REG_OOCSHDWN, 0x04); + /* We immediately read the usb and adapter status. We thus make sure * only of USBINS/USBREM IRQ handlers are called */ if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) { chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); if (chgstat & (0x3 << 4)) - pcf_int[0] &= ~(1 << PCF50633_INT1_USBREM); + pcf_int[0] &= ~PCF50633_INT1_USBREM; else - pcf_int[0] &= ~(1 << PCF50633_INT1_USBINS); + pcf_int[0] &= ~PCF50633_INT1_USBINS; } /* Make sure only one of ADPINS or ADPREM is set */ if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) { chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); if (chgstat & (0x3 << 4)) - pcf_int[0] &= ~(1 << PCF50633_INT1_ADPREM); + pcf_int[0] &= ~PCF50633_INT1_ADPREM; else - pcf_int[0] &= ~(1 << PCF50633_INT1_ADPINS); + pcf_int[0] &= ~PCF50633_INT1_ADPINS; } dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x " @@ -444,7 +447,7 @@ static irqreturn_t pcf50633_irq(int irq, void *data) get_device(pcf->dev); disable_irq_nosync(pcf->irq); - schedule_work(&pcf->irq_work); + queue_work(pcf->work_queue, &pcf->irq_work); return IRQ_HANDLED; } @@ -453,7 +456,6 @@ static void pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, struct platform_device **pdev) { - struct pcf50633_subdev_pdata *subdev_pdata; int ret; *pdev = platform_device_alloc(name, -1); @@ -462,15 +464,6 @@ pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, return; } - subdev_pdata = kmalloc(sizeof(*subdev_pdata), GFP_KERNEL); - if (!subdev_pdata) { - dev_err(pcf->dev, "Error allocating subdev pdata\n"); - platform_device_put(*pdev); - } - - subdev_pdata->pcf = pcf; - platform_device_add_data(*pdev, subdev_pdata, sizeof(*subdev_pdata)); - (*pdev)->dev.parent = pcf->dev; ret = platform_device_add(*pdev); @@ -482,13 +475,13 @@ pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, } #ifdef CONFIG_PM -static int pcf50633_suspend(struct device *dev, pm_message_t state) +static int pcf50633_suspend(struct i2c_client *client, pm_message_t state) { struct pcf50633 *pcf; int ret = 0, i; u8 res[5]; - pcf = dev_get_drvdata(dev); + pcf = i2c_get_clientdata(client); /* Make sure our interrupt handlers are not called * henceforth */ @@ -523,12 +516,12 @@ out: return ret; } -static int pcf50633_resume(struct device *dev) +static int pcf50633_resume(struct i2c_client *client) { struct pcf50633 *pcf; int ret; - pcf = dev_get_drvdata(dev); + pcf = i2c_get_clientdata(client); /* Write the saved mask registers */ ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, @@ -560,9 +553,14 @@ static int __devinit pcf50633_probe(struct i2c_client *client, { struct pcf50633 *pcf; struct pcf50633_platform_data *pdata = client->dev.platform_data; - int i, ret = 0; + int i, ret; int version, variant; + if (!client->irq) { + dev_err(&client->dev, "Missing IRQ\n"); + return -ENOENT; + } + pcf = kzalloc(sizeof(*pcf), GFP_KERNEL); if (!pcf) return -ENOMEM; @@ -575,6 +573,12 @@ static int __devinit pcf50633_probe(struct i2c_client *client, pcf->dev = &client->dev; pcf->i2c_client = client; pcf->irq = client->irq; + pcf->work_queue = create_singlethread_workqueue("pcf50633"); + + if (!pcf->work_queue) { + dev_err(&client->dev, "Failed to alloc workqueue\n"); + goto err_free; + } INIT_WORK(&pcf->irq_work, pcf50633_irq_worker); @@ -583,7 +587,7 @@ static int __devinit pcf50633_probe(struct i2c_client *client, if (version < 0 || variant < 0) { dev_err(pcf->dev, "Unable to probe pcf50633\n"); ret = -ENODEV; - goto err; + goto err_destroy_workqueue; } dev_info(pcf->dev, "Probed device version %d variant %d\n", @@ -597,6 +601,15 @@ static int __devinit pcf50633_probe(struct i2c_client *client, pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00); pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00); + set_irq_handler(client->irq, handle_level_irq); + ret = request_irq(client->irq, pcf50633_irq, + IRQF_TRIGGER_LOW, "pcf50633", pcf); + + if (ret) { + dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); + goto err_destroy_workqueue; + } + /* Create sub devices */ pcf50633_client_dev_register(pcf, "pcf50633-input", &pcf->input_pdev); @@ -606,52 +619,46 @@ static int __devinit pcf50633_probe(struct i2c_client *client, &pcf->mbc_pdev); pcf50633_client_dev_register(pcf, "pcf50633-adc", &pcf->adc_pdev); + pcf50633_client_dev_register(pcf, "pcf50633-backlight", + &pcf->bl_pdev); + for (i = 0; i < PCF50633_NUM_REGULATORS; i++) { struct platform_device *pdev; pdev = platform_device_alloc("pcf50633-regltr", i); if (!pdev) { - dev_err(pcf->dev, "Cannot create regulator\n"); + dev_err(pcf->dev, "Cannot create regulator %d\n", i); continue; } pdev->dev.parent = pcf->dev; - pdev->dev.platform_data = &pdata->reg_init_data[i]; - dev_set_drvdata(&pdev->dev, pcf); + platform_device_add_data(pdev, &pdata->reg_init_data[i], + sizeof(pdata->reg_init_data[i])); pcf->regulator_pdev[i] = pdev; platform_device_add(pdev); } - if (client->irq) { - ret = request_irq(client->irq, pcf50633_irq, - IRQF_TRIGGER_LOW, "pcf50633", pcf); - - if (ret) { - dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); - goto err; - } - } else { - dev_err(pcf->dev, "No IRQ configured\n"); - goto err; - } - if (enable_irq_wake(client->irq) < 0) - dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source" + dev_info(pcf->dev, "IRQ %u cannot be enabled as wake-up source" "in this hardware revision", client->irq); ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group); if (ret) - dev_err(pcf->dev, "error creating sysfs entries\n"); + dev_info(pcf->dev, "Failed to create sysfs entries\n"); if (pdata->probe_done) pdata->probe_done(pcf); return 0; -err: +err_destroy_workqueue: + destroy_workqueue(pcf->work_queue); +err_free: + i2c_set_clientdata(client, NULL); kfree(pcf); + return ret; } @@ -661,6 +668,7 @@ static int __devexit pcf50633_remove(struct i2c_client *client) int i; free_irq(pcf->irq, pcf); + destroy_workqueue(pcf->work_queue); platform_device_unregister(pcf->input_pdev); platform_device_unregister(pcf->rtc_pdev); @@ -683,12 +691,12 @@ static struct i2c_device_id pcf50633_id_table[] = { static struct i2c_driver pcf50633_driver = { .driver = { .name = "pcf50633", - .suspend = pcf50633_suspend, - .resume = pcf50633_resume, }, .id_table = pcf50633_id_table, .probe = pcf50633_probe, .remove = __devexit_p(pcf50633_remove), + .suspend = pcf50633_suspend, + .resume = pcf50633_resume, }; static int __init pcf50633_init(void) diff --git a/drivers/mfd/twl4030-core.c b/drivers/mfd/twl4030-core.c index ca54996ffd0..a1c47ee95c0 100644 --- a/drivers/mfd/twl4030-core.c +++ b/drivers/mfd/twl4030-core.c @@ -89,6 +89,12 @@ #define twl_has_madc() false #endif +#ifdef CONFIG_TWL4030_POWER +#define twl_has_power() true +#else +#define twl_has_power() false +#endif + #if defined(CONFIG_RTC_DRV_TWL4030) || defined(CONFIG_RTC_DRV_TWL4030_MODULE) #define twl_has_rtc() true #else @@ -115,6 +121,12 @@ #define TWL4030_NUM_SLAVES 4 +#if defined(CONFIG_INPUT_TWL4030_PWRBUTTON) \ + || defined(CONFIG_INPUT_TWL4030_PWBUTTON_MODULE) +#define twl_has_pwrbutton() true +#else +#define twl_has_pwrbutton() false +#endif /* Base Address defns for twl4030_map[] */ @@ -468,7 +480,6 @@ static int add_children(struct twl4030_platform_data *pdata, unsigned long features) { struct device *child; - struct device *usb_transceiver = NULL; if (twl_has_bci() && pdata->bci && !(features & TPS_SUBSET)) { child = add_child(3, "twl4030_bci", @@ -520,16 +531,61 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) } if (twl_has_usb() && pdata->usb) { + + static struct regulator_consumer_supply usb1v5 = { + .supply = "usb1v5", + }; + static struct regulator_consumer_supply usb1v8 = { + .supply = "usb1v8", + }; + static struct regulator_consumer_supply usb3v1 = { + .supply = "usb3v1", + }; + + /* First add the regulators so that they can be used by transceiver */ + if (twl_has_regulator()) { + /* this is a template that gets copied */ + struct regulator_init_data usb_fixed = { + .constraints.valid_modes_mask = + REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .constraints.valid_ops_mask = + REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }; + + child = add_regulator_linked(TWL4030_REG_VUSB1V5, + &usb_fixed, &usb1v5, 1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator_linked(TWL4030_REG_VUSB1V8, + &usb_fixed, &usb1v8, 1); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator_linked(TWL4030_REG_VUSB3V1, + &usb_fixed, &usb3v1, 1); + if (IS_ERR(child)) + return PTR_ERR(child); + + } + child = add_child(0, "twl4030_usb", pdata->usb, sizeof(*pdata->usb), true, /* irq0 = USB_PRES, irq1 = USB */ pdata->irq_base + 8 + 2, pdata->irq_base + 4); + if (IS_ERR(child)) return PTR_ERR(child); /* we need to connect regulators to this transceiver */ - usb_transceiver = child; + if (twl_has_regulator() && child) { + usb1v5.dev = child; + usb1v8.dev = child; + usb3v1.dev = child; + } } if (twl_has_watchdog()) { @@ -538,6 +594,13 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) return PTR_ERR(child); } + if (twl_has_pwrbutton()) { + child = add_child(1, "twl4030_pwrbutton", + NULL, 0, true, pdata->irq_base + 8 + 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + if (twl_has_regulator()) { /* child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1); @@ -561,47 +624,6 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) return PTR_ERR(child); } - if (twl_has_regulator() && usb_transceiver) { - static struct regulator_consumer_supply usb1v5 = { - .supply = "usb1v5", - }; - static struct regulator_consumer_supply usb1v8 = { - .supply = "usb1v8", - }; - static struct regulator_consumer_supply usb3v1 = { - .supply = "usb3v1", - }; - - /* this is a template that gets copied */ - struct regulator_init_data usb_fixed = { - .constraints.valid_modes_mask = - REGULATOR_MODE_NORMAL - | REGULATOR_MODE_STANDBY, - .constraints.valid_ops_mask = - REGULATOR_CHANGE_MODE - | REGULATOR_CHANGE_STATUS, - }; - - usb1v5.dev = usb_transceiver; - usb1v8.dev = usb_transceiver; - usb3v1.dev = usb_transceiver; - - child = add_regulator_linked(TWL4030_REG_VUSB1V5, &usb_fixed, - &usb1v5, 1); - if (IS_ERR(child)) - return PTR_ERR(child); - - child = add_regulator_linked(TWL4030_REG_VUSB1V8, &usb_fixed, - &usb1v8, 1); - if (IS_ERR(child)) - return PTR_ERR(child); - - child = add_regulator_linked(TWL4030_REG_VUSB3V1, &usb_fixed, - &usb3v1, 1); - if (IS_ERR(child)) - return PTR_ERR(child); - } - /* maybe add LDOs that are omitted on cost-reduced parts */ if (twl_has_regulator() && !(features & TPS_SUBSET)) { child = add_regulator(TWL4030_REG_VPLL2, pdata->vpll2); @@ -773,7 +795,7 @@ twl4030_probe(struct i2c_client *client, const struct i2c_device_id *id) twl->client = i2c_new_dummy(client->adapter, twl->address); if (!twl->client) { - dev_err(&twl->client->dev, + dev_err(&client->dev, "can't attach client %d\n", i); status = -ENOMEM; goto fail; @@ -788,6 +810,10 @@ twl4030_probe(struct i2c_client *client, const struct i2c_device_id *id) /* setup clock framework */ clocks_init(&client->dev); + /* load power event scripts */ + if (twl_has_power() && pdata->power) + twl4030_power_init(pdata->power); + /* Maybe init the T2 Interrupt subsystem */ if (client->irq && pdata->irq_base diff --git a/drivers/mfd/twl4030-irq.c b/drivers/mfd/twl4030-irq.c index 7d430835655..fb194fe244c 100644 --- a/drivers/mfd/twl4030-irq.c +++ b/drivers/mfd/twl4030-irq.c @@ -424,7 +424,7 @@ static void twl4030_sih_do_edge(struct work_struct *work) /* see what work we have */ spin_lock_irq(&sih_agent_lock); edge_change = agent->edge_change; - agent->edge_change = 0;; + agent->edge_change = 0; sih = edge_change ? agent->sih : NULL; spin_unlock_irq(&sih_agent_lock); if (!sih) diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c new file mode 100644 index 00000000000..d423e0c4176 --- /dev/null +++ b/drivers/mfd/twl4030-power.c @@ -0,0 +1,472 @@ +/* + * linux/drivers/i2c/chips/twl4030-power.c + * + * Handle TWL4030 Power initialization + * + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2006 Texas Instruments, Inc + * + * Written by Kalle Jokiniemi + * Peter De Schrijver <peter.de-schrijver@nokia.com> + * Several fixes by Amit Kucheria <amit.kucheria@verdurent.com> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/i2c/twl4030.h> +#include <linux/platform_device.h> + +#include <asm/mach-types.h> + +static u8 twl4030_start_script_address = 0x2b; + +#define PWR_P1_SW_EVENTS 0x10 +#define PWR_DEVOFF (1<<0) + +#define PHY_TO_OFF_PM_MASTER(p) (p - 0x36) +#define PHY_TO_OFF_PM_RECEIVER(p) (p - 0x5b) + +/* resource - hfclk */ +#define R_HFCLKOUT_DEV_GRP PHY_TO_OFF_PM_RECEIVER(0xe6) + +/* PM events */ +#define R_P1_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x46) +#define R_P2_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x47) +#define R_P3_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x48) +#define R_CFG_P1_TRANSITION PHY_TO_OFF_PM_MASTER(0x36) +#define R_CFG_P2_TRANSITION PHY_TO_OFF_PM_MASTER(0x37) +#define R_CFG_P3_TRANSITION PHY_TO_OFF_PM_MASTER(0x38) + +#define LVL_WAKEUP 0x08 + +#define ENABLE_WARMRESET (1<<4) + +#define END_OF_SCRIPT 0x3f + +#define R_SEQ_ADD_A2S PHY_TO_OFF_PM_MASTER(0x55) +#define R_SEQ_ADD_S2A12 PHY_TO_OFF_PM_MASTER(0x56) +#define R_SEQ_ADD_S2A3 PHY_TO_OFF_PM_MASTER(0x57) +#define R_SEQ_ADD_WARM PHY_TO_OFF_PM_MASTER(0x58) +#define R_MEMORY_ADDRESS PHY_TO_OFF_PM_MASTER(0x59) +#define R_MEMORY_DATA PHY_TO_OFF_PM_MASTER(0x5a) + +#define R_PROTECT_KEY 0x0E +#define R_KEY_1 0xC0 +#define R_KEY_2 0x0C + +/* resource configuration registers */ + +#define DEVGROUP_OFFSET 0 +#define TYPE_OFFSET 1 + +/* Bit positions */ +#define DEVGROUP_SHIFT 5 +#define DEVGROUP_MASK (7 << DEVGROUP_SHIFT) +#define TYPE_SHIFT 0 +#define TYPE_MASK (7 << TYPE_SHIFT) +#define TYPE2_SHIFT 3 +#define TYPE2_MASK (3 << TYPE2_SHIFT) + +static u8 res_config_addrs[] = { + [RES_VAUX1] = 0x17, + [RES_VAUX2] = 0x1b, + [RES_VAUX3] = 0x1f, + [RES_VAUX4] = 0x23, + [RES_VMMC1] = 0x27, + [RES_VMMC2] = 0x2b, + [RES_VPLL1] = 0x2f, + [RES_VPLL2] = 0x33, + [RES_VSIM] = 0x37, + [RES_VDAC] = 0x3b, + [RES_VINTANA1] = 0x3f, + [RES_VINTANA2] = 0x43, + [RES_VINTDIG] = 0x47, + [RES_VIO] = 0x4b, + [RES_VDD1] = 0x55, + [RES_VDD2] = 0x63, + [RES_VUSB_1V5] = 0x71, + [RES_VUSB_1V8] = 0x74, + [RES_VUSB_3V1] = 0x77, + [RES_VUSBCP] = 0x7a, + [RES_REGEN] = 0x7f, + [RES_NRES_PWRON] = 0x82, + [RES_CLKEN] = 0x85, + [RES_SYSEN] = 0x88, + [RES_HFCLKOUT] = 0x8b, + [RES_32KCLKOUT] = 0x8e, + [RES_RESET] = 0x91, + [RES_Main_Ref] = 0x94, +}; + +static int __init twl4030_write_script_byte(u8 address, u8 byte) +{ + int err; + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_MEMORY_ADDRESS); + if (err) + goto out; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, byte, + R_MEMORY_DATA); +out: + return err; +} + +static int __init twl4030_write_script_ins(u8 address, u16 pmb_message, + u8 delay, u8 next) +{ + int err; + + address *= 4; + err = twl4030_write_script_byte(address++, pmb_message >> 8); + if (err) + goto out; + err = twl4030_write_script_byte(address++, pmb_message & 0xff); + if (err) + goto out; + err = twl4030_write_script_byte(address++, delay); + if (err) + goto out; + err = twl4030_write_script_byte(address++, next); +out: + return err; +} + +static int __init twl4030_write_script(u8 address, struct twl4030_ins *script, + int len) +{ + int err; + + for (; len; len--, address++, script++) { + if (len == 1) { + err = twl4030_write_script_ins(address, + script->pmb_message, + script->delay, + END_OF_SCRIPT); + if (err) + break; + } else { + err = twl4030_write_script_ins(address, + script->pmb_message, + script->delay, + address + 1); + if (err) + break; + } + } + return err; +} + +static int __init twl4030_config_wakeup3_sequence(u8 address) +{ + int err; + u8 data; + + /* Set SLEEP to ACTIVE SEQ address for P3 */ + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_S2A3); + if (err) + goto out; + + /* P3 LVL_WAKEUP should be on LEVEL */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_P3_SW_EVENTS); + if (err) + goto out; + data |= LVL_WAKEUP; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data, + R_P3_SW_EVENTS); +out: + if (err) + pr_err("TWL4030 wakeup sequence for P3 config error\n"); + return err; +} + +static int __init twl4030_config_wakeup12_sequence(u8 address) +{ + int err = 0; + u8 data; + + /* Set SLEEP to ACTIVE SEQ address for P1 and P2 */ + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_S2A12); + if (err) + goto out; + + /* P1/P2 LVL_WAKEUP should be on LEVEL */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_P1_SW_EVENTS); + if (err) + goto out; + + data |= LVL_WAKEUP; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data, + R_P1_SW_EVENTS); + if (err) + goto out; + + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_P2_SW_EVENTS); + if (err) + goto out; + + data |= LVL_WAKEUP; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data, + R_P2_SW_EVENTS); + if (err) + goto out; + + if (machine_is_omap_3430sdp() || machine_is_omap_ldp()) { + /* Disabling AC charger effect on sleep-active transitions */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_CFG_P1_TRANSITION); + if (err) + goto out; + data &= ~(1<<1); + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data , + R_CFG_P1_TRANSITION); + if (err) + goto out; + } + +out: + if (err) + pr_err("TWL4030 wakeup sequence for P1 and P2" \ + "config error\n"); + return err; +} + +static int __init twl4030_config_sleep_sequence(u8 address) +{ + int err; + + /* Set ACTIVE to SLEEP SEQ address in T2 memory*/ + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_A2S); + + if (err) + pr_err("TWL4030 sleep sequence config error\n"); + + return err; +} + +static int __init twl4030_config_warmreset_sequence(u8 address) +{ + int err; + u8 rd_data; + + /* Set WARM RESET SEQ address for P1 */ + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_WARM); + if (err) + goto out; + + /* P1/P2/P3 enable WARMRESET */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P1_SW_EVENTS); + if (err) + goto out; + + rd_data |= ENABLE_WARMRESET; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P1_SW_EVENTS); + if (err) + goto out; + + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P2_SW_EVENTS); + if (err) + goto out; + + rd_data |= ENABLE_WARMRESET; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P2_SW_EVENTS); + if (err) + goto out; + + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P3_SW_EVENTS); + if (err) + goto out; + + rd_data |= ENABLE_WARMRESET; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P3_SW_EVENTS); +out: + if (err) + pr_err("TWL4030 warmreset seq config error\n"); + return err; +} + +static int __init twl4030_configure_resource(struct twl4030_resconfig *rconfig) +{ + int rconfig_addr; + int err; + u8 type; + u8 grp; + + if (rconfig->resource > TOTAL_RESOURCES) { + pr_err("TWL4030 Resource %d does not exist\n", + rconfig->resource); + return -EINVAL; + } + + rconfig_addr = res_config_addrs[rconfig->resource]; + + /* Set resource group */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &grp, + rconfig_addr + DEVGROUP_OFFSET); + if (err) { + pr_err("TWL4030 Resource %d group could not be read\n", + rconfig->resource); + return err; + } + + if (rconfig->devgroup >= 0) { + grp &= ~DEVGROUP_MASK; + grp |= rconfig->devgroup << DEVGROUP_SHIFT; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + grp, rconfig_addr + DEVGROUP_OFFSET); + if (err < 0) { + pr_err("TWL4030 failed to program devgroup\n"); + return err; + } + } + + /* Set resource types */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &type, + rconfig_addr + TYPE_OFFSET); + if (err < 0) { + pr_err("TWL4030 Resource %d type could not be read\n", + rconfig->resource); + return err; + } + + if (rconfig->type >= 0) { + type &= ~TYPE_MASK; + type |= rconfig->type << TYPE_SHIFT; + } + + if (rconfig->type2 >= 0) { + type &= ~TYPE2_MASK; + type |= rconfig->type2 << TYPE2_SHIFT; + } + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + type, rconfig_addr + TYPE_OFFSET); + if (err < 0) { + pr_err("TWL4030 failed to program resource type\n"); + return err; + } + + return 0; +} + +static int __init load_twl4030_script(struct twl4030_script *tscript, + u8 address) +{ + int err; + static int order; + + /* Make sure the script isn't going beyond last valid address (0x3f) */ + if ((address + tscript->size) > END_OF_SCRIPT) { + pr_err("TWL4030 scripts too big error\n"); + return -EINVAL; + } + + err = twl4030_write_script(address, tscript->script, tscript->size); + if (err) + goto out; + + if (tscript->flags & TWL4030_WRST_SCRIPT) { + err = twl4030_config_warmreset_sequence(address); + if (err) + goto out; + } + if (tscript->flags & TWL4030_WAKEUP12_SCRIPT) { + err = twl4030_config_wakeup12_sequence(address); + if (err) + goto out; + order = 1; + } + if (tscript->flags & TWL4030_WAKEUP3_SCRIPT) { + err = twl4030_config_wakeup3_sequence(address); + if (err) + goto out; + } + if (tscript->flags & TWL4030_SLEEP_SCRIPT) + if (order) + pr_warning("TWL4030: Bad order of scripts (sleep "\ + "script before wakeup) Leads to boot"\ + "failure on some boards\n"); + err = twl4030_config_sleep_sequence(address); +out: + return err; +} + +void __init twl4030_power_init(struct twl4030_power_data *twl4030_scripts) +{ + int err = 0; + int i; + struct twl4030_resconfig *resconfig; + u8 address = twl4030_start_script_address; + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_1, + R_PROTECT_KEY); + if (err) + goto unlock; + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_2, + R_PROTECT_KEY); + if (err) + goto unlock; + + for (i = 0; i < twl4030_scripts->num; i++) { + err = load_twl4030_script(twl4030_scripts->scripts[i], address); + if (err) + goto load; + address += twl4030_scripts->scripts[i]->size; + } + + resconfig = twl4030_scripts->resource_config; + if (resconfig) { + while (resconfig->resource) { + err = twl4030_configure_resource(resconfig); + if (err) + goto resource; + resconfig++; + + } + } + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, R_PROTECT_KEY); + if (err) + pr_err("TWL4030 Unable to relock registers\n"); + return; + +unlock: + if (err) + pr_err("TWL4030 Unable to unlock registers\n"); + return; +load: + if (err) + pr_err("TWL4030 failed to load scripts\n"); + return; +resource: + if (err) + pr_err("TWL4030 failed to configure resource\n"); + return; +} diff --git a/drivers/mfd/ucb1400_core.c b/drivers/mfd/ucb1400_core.c index 78c2135c5de..fa294b6d600 100644 --- a/drivers/mfd/ucb1400_core.c +++ b/drivers/mfd/ucb1400_core.c @@ -21,6 +21,7 @@ */ #include <linux/module.h> +#include <linux/sched.h> #include <linux/ucb1400.h> unsigned int ucb1400_adc_read(struct snd_ac97 *ac97, u16 adc_channel, @@ -48,9 +49,11 @@ static int ucb1400_core_probe(struct device *dev) int err; struct ucb1400 *ucb; struct ucb1400_ts ucb_ts; + struct ucb1400_gpio ucb_gpio; struct snd_ac97 *ac97; memset(&ucb_ts, 0, sizeof(ucb_ts)); + memset(&ucb_gpio, 0, sizeof(ucb_gpio)); ucb = kzalloc(sizeof(struct ucb1400), GFP_KERNEL); if (!ucb) { @@ -68,25 +71,44 @@ static int ucb1400_core_probe(struct device *dev) goto err0; } + /* GPIO */ + ucb_gpio.ac97 = ac97; + ucb->ucb1400_gpio = platform_device_alloc("ucb1400_gpio", -1); + if (!ucb->ucb1400_gpio) { + err = -ENOMEM; + goto err0; + } + err = platform_device_add_data(ucb->ucb1400_gpio, &ucb_gpio, + sizeof(ucb_gpio)); + if (err) + goto err1; + err = platform_device_add(ucb->ucb1400_gpio); + if (err) + goto err1; + /* TOUCHSCREEN */ ucb_ts.ac97 = ac97; ucb->ucb1400_ts = platform_device_alloc("ucb1400_ts", -1); if (!ucb->ucb1400_ts) { err = -ENOMEM; - goto err0; + goto err2; } err = platform_device_add_data(ucb->ucb1400_ts, &ucb_ts, sizeof(ucb_ts)); if (err) - goto err1; + goto err3; err = platform_device_add(ucb->ucb1400_ts); if (err) - goto err1; + goto err3; return 0; -err1: +err3: platform_device_put(ucb->ucb1400_ts); +err2: + platform_device_unregister(ucb->ucb1400_gpio); +err1: + platform_device_put(ucb->ucb1400_gpio); err0: kfree(ucb); err: @@ -98,6 +120,8 @@ static int ucb1400_core_remove(struct device *dev) struct ucb1400 *ucb = dev_get_drvdata(dev); platform_device_unregister(ucb->ucb1400_ts); + platform_device_unregister(ucb->ucb1400_gpio); + kfree(ucb); return 0; } diff --git a/drivers/mfd/ucb1x00-core.c b/drivers/mfd/ucb1x00-core.c index fea9085fe52..60c3988f3cf 100644 --- a/drivers/mfd/ucb1x00-core.c +++ b/drivers/mfd/ucb1x00-core.c @@ -18,6 +18,7 @@ */ #include <linux/module.h> #include <linux/kernel.h> +#include <linux/sched.h> #include <linux/slab.h> #include <linux/init.h> #include <linux/errno.h> diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c new file mode 100644 index 00000000000..7f27576ca04 --- /dev/null +++ b/drivers/mfd/wm831x-core.c @@ -0,0 +1,1549 @@ +/* + * wm831x-core.c -- Device access for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/bcd.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/pdata.h> +#include <linux/mfd/wm831x/irq.h> +#include <linux/mfd/wm831x/auxadc.h> +#include <linux/mfd/wm831x/otp.h> +#include <linux/mfd/wm831x/regulator.h> + +/* Current settings - values are 2*2^(reg_val/4) microamps. These are + * exported since they are used by multiple drivers. + */ +int wm831x_isinkv_values[WM831X_ISINK_MAX_ISEL + 1] = { + 2, + 2, + 3, + 3, + 4, + 5, + 6, + 7, + 8, + 10, + 11, + 13, + 16, + 19, + 23, + 27, + 32, + 38, + 45, + 54, + 64, + 76, + 91, + 108, + 128, + 152, + 181, + 215, + 256, + 304, + 362, + 431, + 512, + 609, + 724, + 861, + 1024, + 1218, + 1448, + 1722, + 2048, + 2435, + 2896, + 3444, + 4096, + 4871, + 5793, + 6889, + 8192, + 9742, + 11585, + 13777, + 16384, + 19484, + 23170, + 27554, +}; +EXPORT_SYMBOL_GPL(wm831x_isinkv_values); + +enum wm831x_parent { + WM8310 = 0, + WM8311 = 1, + WM8312 = 2, +}; + +static int wm831x_reg_locked(struct wm831x *wm831x, unsigned short reg) +{ + if (!wm831x->locked) + return 0; + + switch (reg) { + case WM831X_WATCHDOG: + case WM831X_DC4_CONTROL: + case WM831X_ON_PIN_CONTROL: + case WM831X_BACKUP_CHARGER_CONTROL: + case WM831X_CHARGER_CONTROL_1: + case WM831X_CHARGER_CONTROL_2: + return 1; + + default: + return 0; + } +} + +/** + * wm831x_reg_unlock: Unlock user keyed registers + * + * The WM831x has a user key preventing writes to particularly + * critical registers. This function locks those registers, + * allowing writes to them. + */ +void wm831x_reg_lock(struct wm831x *wm831x) +{ + int ret; + + ret = wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0); + if (ret == 0) { + dev_vdbg(wm831x->dev, "Registers locked\n"); + + mutex_lock(&wm831x->io_lock); + WARN_ON(wm831x->locked); + wm831x->locked = 1; + mutex_unlock(&wm831x->io_lock); + } else { + dev_err(wm831x->dev, "Failed to lock registers: %d\n", ret); + } + +} +EXPORT_SYMBOL_GPL(wm831x_reg_lock); + +/** + * wm831x_reg_unlock: Unlock user keyed registers + * + * The WM831x has a user key preventing writes to particularly + * critical registers. This function locks those registers, + * preventing spurious writes. + */ +int wm831x_reg_unlock(struct wm831x *wm831x) +{ + int ret; + + /* 0x9716 is the value required to unlock the registers */ + ret = wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0x9716); + if (ret == 0) { + dev_vdbg(wm831x->dev, "Registers unlocked\n"); + + mutex_lock(&wm831x->io_lock); + WARN_ON(!wm831x->locked); + wm831x->locked = 0; + mutex_unlock(&wm831x->io_lock); + } + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_reg_unlock); + +static int wm831x_read(struct wm831x *wm831x, unsigned short reg, + int bytes, void *dest) +{ + int ret, i; + u16 *buf = dest; + + BUG_ON(bytes % 2); + BUG_ON(bytes <= 0); + + ret = wm831x->read_dev(wm831x, reg, bytes, dest); + if (ret < 0) + return ret; + + for (i = 0; i < bytes / 2; i++) { + buf[i] = be16_to_cpu(buf[i]); + + dev_vdbg(wm831x->dev, "Read %04x from R%d(0x%x)\n", + buf[i], reg + i, reg + i); + } + + return 0; +} + +/** + * wm831x_reg_read: Read a single WM831x register. + * + * @wm831x: Device to read from. + * @reg: Register to read. + */ +int wm831x_reg_read(struct wm831x *wm831x, unsigned short reg) +{ + unsigned short val; + int ret; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_read(wm831x, reg, 2, &val); + + mutex_unlock(&wm831x->io_lock); + + if (ret < 0) + return ret; + else + return val; +} +EXPORT_SYMBOL_GPL(wm831x_reg_read); + +/** + * wm831x_bulk_read: Read multiple WM831x registers + * + * @wm831x: Device to read from + * @reg: First register + * @count: Number of registers + * @buf: Buffer to fill. + */ +int wm831x_bulk_read(struct wm831x *wm831x, unsigned short reg, + int count, u16 *buf) +{ + int ret; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_read(wm831x, reg, count * 2, buf); + + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_bulk_read); + +static int wm831x_write(struct wm831x *wm831x, unsigned short reg, + int bytes, void *src) +{ + u16 *buf = src; + int i; + + BUG_ON(bytes % 2); + BUG_ON(bytes <= 0); + + for (i = 0; i < bytes / 2; i++) { + if (wm831x_reg_locked(wm831x, reg)) + return -EPERM; + + dev_vdbg(wm831x->dev, "Write %04x to R%d(0x%x)\n", + buf[i], reg + i, reg + i); + + buf[i] = cpu_to_be16(buf[i]); + } + + return wm831x->write_dev(wm831x, reg, bytes, src); +} + +/** + * wm831x_reg_write: Write a single WM831x register. + * + * @wm831x: Device to write to. + * @reg: Register to write to. + * @val: Value to write. + */ +int wm831x_reg_write(struct wm831x *wm831x, unsigned short reg, + unsigned short val) +{ + int ret; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_write(wm831x, reg, 2, &val); + + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_reg_write); + +/** + * wm831x_set_bits: Set the value of a bitfield in a WM831x register + * + * @wm831x: Device to write to. + * @reg: Register to write to. + * @mask: Mask of bits to set. + * @val: Value to set (unshifted) + */ +int wm831x_set_bits(struct wm831x *wm831x, unsigned short reg, + unsigned short mask, unsigned short val) +{ + int ret; + u16 r; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_read(wm831x, reg, 2, &r); + if (ret < 0) + goto out; + + r &= ~mask; + r |= val; + + ret = wm831x_write(wm831x, reg, 2, &r); + +out: + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_set_bits); + +/** + * wm831x_auxadc_read: Read a value from the WM831x AUXADC + * + * @wm831x: Device to read from. + * @input: AUXADC input to read. + */ +int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) +{ + int tries = 10; + int ret, src; + + mutex_lock(&wm831x->auxadc_lock); + + ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, + WM831X_AUX_ENA, WM831X_AUX_ENA); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to enable AUXADC: %d\n", ret); + goto out; + } + + /* We force a single source at present */ + src = input; + ret = wm831x_reg_write(wm831x, WM831X_AUXADC_SOURCE, + 1 << src); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to set AUXADC source: %d\n", ret); + goto out; + } + + ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, + WM831X_AUX_CVT_ENA, WM831X_AUX_CVT_ENA); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to start AUXADC: %d\n", ret); + goto disable; + } + + do { + msleep(1); + + ret = wm831x_reg_read(wm831x, WM831X_AUXADC_CONTROL); + if (ret < 0) + ret = WM831X_AUX_CVT_ENA; + } while ((ret & WM831X_AUX_CVT_ENA) && --tries); + + if (ret & WM831X_AUX_CVT_ENA) { + dev_err(wm831x->dev, "Timed out reading AUXADC\n"); + ret = -EBUSY; + goto disable; + } + + ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read AUXADC data: %d\n", ret); + } else { + src = ((ret & WM831X_AUX_DATA_SRC_MASK) + >> WM831X_AUX_DATA_SRC_SHIFT) - 1; + + if (src == 14) + src = WM831X_AUX_CAL; + + if (src != input) { + dev_err(wm831x->dev, "Data from source %d not %d\n", + src, input); + ret = -EINVAL; + } else { + ret &= WM831X_AUX_DATA_MASK; + } + } + +disable: + wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_ENA, 0); +out: + mutex_unlock(&wm831x->auxadc_lock); + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_auxadc_read); + +/** + * wm831x_auxadc_read_uv: Read a voltage from the WM831x AUXADC + * + * @wm831x: Device to read from. + * @input: AUXADC input to read. + */ +int wm831x_auxadc_read_uv(struct wm831x *wm831x, enum wm831x_auxadc input) +{ + int ret; + + ret = wm831x_auxadc_read(wm831x, input); + if (ret < 0) + return ret; + + ret *= 1465; + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_auxadc_read_uv); + +static struct resource wm831x_dcdc1_resources[] = { + { + .start = WM831X_DC1_CONTROL_1, + .end = WM831X_DC1_DVS_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC1, + .end = WM831X_IRQ_UV_DC1, + .flags = IORESOURCE_IRQ, + }, + { + .name = "HC", + .start = WM831X_IRQ_HC_DC1, + .end = WM831X_IRQ_HC_DC1, + .flags = IORESOURCE_IRQ, + }, +}; + + +static struct resource wm831x_dcdc2_resources[] = { + { + .start = WM831X_DC2_CONTROL_1, + .end = WM831X_DC2_DVS_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC2, + .end = WM831X_IRQ_UV_DC2, + .flags = IORESOURCE_IRQ, + }, + { + .name = "HC", + .start = WM831X_IRQ_HC_DC2, + .end = WM831X_IRQ_HC_DC2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_dcdc3_resources[] = { + { + .start = WM831X_DC3_CONTROL_1, + .end = WM831X_DC3_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC3, + .end = WM831X_IRQ_UV_DC3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_dcdc4_resources[] = { + { + .start = WM831X_DC4_CONTROL, + .end = WM831X_DC4_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC4, + .end = WM831X_IRQ_UV_DC4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_gpio_resources[] = { + { + .start = WM831X_IRQ_GPIO_1, + .end = WM831X_IRQ_GPIO_16, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_isink1_resources[] = { + { + .start = WM831X_CURRENT_SINK_1, + .end = WM831X_CURRENT_SINK_1, + .flags = IORESOURCE_IO, + }, + { + .start = WM831X_IRQ_CS1, + .end = WM831X_IRQ_CS1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_isink2_resources[] = { + { + .start = WM831X_CURRENT_SINK_2, + .end = WM831X_CURRENT_SINK_2, + .flags = IORESOURCE_IO, + }, + { + .start = WM831X_IRQ_CS2, + .end = WM831X_IRQ_CS2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo1_resources[] = { + { + .start = WM831X_LDO1_CONTROL, + .end = WM831X_LDO1_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO1, + .end = WM831X_IRQ_UV_LDO1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo2_resources[] = { + { + .start = WM831X_LDO2_CONTROL, + .end = WM831X_LDO2_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO2, + .end = WM831X_IRQ_UV_LDO2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo3_resources[] = { + { + .start = WM831X_LDO3_CONTROL, + .end = WM831X_LDO3_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO3, + .end = WM831X_IRQ_UV_LDO3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo4_resources[] = { + { + .start = WM831X_LDO4_CONTROL, + .end = WM831X_LDO4_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO4, + .end = WM831X_IRQ_UV_LDO4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo5_resources[] = { + { + .start = WM831X_LDO5_CONTROL, + .end = WM831X_LDO5_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO5, + .end = WM831X_IRQ_UV_LDO5, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo6_resources[] = { + { + .start = WM831X_LDO6_CONTROL, + .end = WM831X_LDO6_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO6, + .end = WM831X_IRQ_UV_LDO6, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo7_resources[] = { + { + .start = WM831X_LDO7_CONTROL, + .end = WM831X_LDO7_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO7, + .end = WM831X_IRQ_UV_LDO7, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo8_resources[] = { + { + .start = WM831X_LDO8_CONTROL, + .end = WM831X_LDO8_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO8, + .end = WM831X_IRQ_UV_LDO8, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo9_resources[] = { + { + .start = WM831X_LDO9_CONTROL, + .end = WM831X_LDO9_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO9, + .end = WM831X_IRQ_UV_LDO9, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo10_resources[] = { + { + .start = WM831X_LDO10_CONTROL, + .end = WM831X_LDO10_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO10, + .end = WM831X_IRQ_UV_LDO10, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo11_resources[] = { + { + .start = WM831X_LDO11_ON_CONTROL, + .end = WM831X_LDO11_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource wm831x_on_resources[] = { + { + .start = WM831X_IRQ_ON, + .end = WM831X_IRQ_ON, + .flags = IORESOURCE_IRQ, + }, +}; + + +static struct resource wm831x_power_resources[] = { + { + .name = "SYSLO", + .start = WM831X_IRQ_PPM_SYSLO, + .end = WM831X_IRQ_PPM_SYSLO, + .flags = IORESOURCE_IRQ, + }, + { + .name = "PWR SRC", + .start = WM831X_IRQ_PPM_PWR_SRC, + .end = WM831X_IRQ_PPM_PWR_SRC, + .flags = IORESOURCE_IRQ, + }, + { + .name = "USB CURR", + .start = WM831X_IRQ_PPM_USB_CURR, + .end = WM831X_IRQ_PPM_USB_CURR, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT HOT", + .start = WM831X_IRQ_CHG_BATT_HOT, + .end = WM831X_IRQ_CHG_BATT_HOT, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT COLD", + .start = WM831X_IRQ_CHG_BATT_COLD, + .end = WM831X_IRQ_CHG_BATT_COLD, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT FAIL", + .start = WM831X_IRQ_CHG_BATT_FAIL, + .end = WM831X_IRQ_CHG_BATT_FAIL, + .flags = IORESOURCE_IRQ, + }, + { + .name = "OV", + .start = WM831X_IRQ_CHG_OV, + .end = WM831X_IRQ_CHG_OV, + .flags = IORESOURCE_IRQ, + }, + { + .name = "END", + .start = WM831X_IRQ_CHG_END, + .end = WM831X_IRQ_CHG_END, + .flags = IORESOURCE_IRQ, + }, + { + .name = "TO", + .start = WM831X_IRQ_CHG_TO, + .end = WM831X_IRQ_CHG_TO, + .flags = IORESOURCE_IRQ, + }, + { + .name = "MODE", + .start = WM831X_IRQ_CHG_MODE, + .end = WM831X_IRQ_CHG_MODE, + .flags = IORESOURCE_IRQ, + }, + { + .name = "START", + .start = WM831X_IRQ_CHG_START, + .end = WM831X_IRQ_CHG_START, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_rtc_resources[] = { + { + .name = "PER", + .start = WM831X_IRQ_RTC_PER, + .end = WM831X_IRQ_RTC_PER, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ALM", + .start = WM831X_IRQ_RTC_ALM, + .end = WM831X_IRQ_RTC_ALM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_status1_resources[] = { + { + .start = WM831X_STATUS_LED_1, + .end = WM831X_STATUS_LED_1, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource wm831x_status2_resources[] = { + { + .start = WM831X_STATUS_LED_2, + .end = WM831X_STATUS_LED_2, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource wm831x_touch_resources[] = { + { + .name = "TCHPD", + .start = WM831X_IRQ_TCHPD, + .end = WM831X_IRQ_TCHPD, + .flags = IORESOURCE_IRQ, + }, + { + .name = "TCHDATA", + .start = WM831X_IRQ_TCHDATA, + .end = WM831X_IRQ_TCHDATA, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_wdt_resources[] = { + { + .start = WM831X_IRQ_WDOG_TO, + .end = WM831X_IRQ_WDOG_TO, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell wm8310_devs[] = { + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-ldo", + .id = 6, + .num_resources = ARRAY_SIZE(wm831x_ldo6_resources), + .resources = wm831x_ldo6_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-aldo", + .id = 8, + .num_resources = ARRAY_SIZE(wm831x_ldo8_resources), + .resources = wm831x_ldo8_resources, + }, + { + .name = "wm831x-aldo", + .id = 9, + .num_resources = ARRAY_SIZE(wm831x_ldo9_resources), + .resources = wm831x_ldo9_resources, + }, + { + .name = "wm831x-aldo", + .id = 10, + .num_resources = ARRAY_SIZE(wm831x_ldo10_resources), + .resources = wm831x_ldo10_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static struct mfd_cell wm8311_devs[] = { + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-touch", + .num_resources = ARRAY_SIZE(wm831x_touch_resources), + .resources = wm831x_touch_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static struct mfd_cell wm8312_devs[] = { + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-ldo", + .id = 6, + .num_resources = ARRAY_SIZE(wm831x_ldo6_resources), + .resources = wm831x_ldo6_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-aldo", + .id = 8, + .num_resources = ARRAY_SIZE(wm831x_ldo8_resources), + .resources = wm831x_ldo8_resources, + }, + { + .name = "wm831x-aldo", + .id = 9, + .num_resources = ARRAY_SIZE(wm831x_ldo9_resources), + .resources = wm831x_ldo9_resources, + }, + { + .name = "wm831x-aldo", + .id = 10, + .num_resources = ARRAY_SIZE(wm831x_ldo10_resources), + .resources = wm831x_ldo10_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-touch", + .num_resources = ARRAY_SIZE(wm831x_touch_resources), + .resources = wm831x_touch_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static struct mfd_cell backlight_devs[] = { + { + .name = "wm831x-backlight", + }, +}; + +/* + * Instantiate the generic non-control parts of the device. + */ +static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) +{ + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int rev; + enum wm831x_parent parent; + int ret; + + mutex_init(&wm831x->io_lock); + mutex_init(&wm831x->key_lock); + mutex_init(&wm831x->auxadc_lock); + dev_set_drvdata(wm831x->dev, wm831x); + + ret = wm831x_reg_read(wm831x, WM831X_PARENT_ID); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read parent ID: %d\n", ret); + goto err; + } + if (ret != 0x6204) { + dev_err(wm831x->dev, "Device is not a WM831x: ID %x\n", ret); + ret = -EINVAL; + goto err; + } + + ret = wm831x_reg_read(wm831x, WM831X_REVISION); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read revision: %d\n", ret); + goto err; + } + rev = (ret & WM831X_PARENT_REV_MASK) >> WM831X_PARENT_REV_SHIFT; + + ret = wm831x_reg_read(wm831x, WM831X_RESET_ID); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read device ID: %d\n", ret); + goto err; + } + + switch (ret) { + case 0x8310: + parent = WM8310; + switch (rev) { + case 0: + dev_info(wm831x->dev, "WM8310 revision %c\n", + 'A' + rev); + break; + } + break; + + case 0x8311: + parent = WM8311; + switch (rev) { + case 0: + dev_info(wm831x->dev, "WM8311 revision %c\n", + 'A' + rev); + break; + } + break; + + case 0x8312: + parent = WM8312; + switch (rev) { + case 0: + dev_info(wm831x->dev, "WM8312 revision %c\n", + 'A' + rev); + break; + } + break; + + case 0: + /* Some engineering samples do not have the ID set, + * rely on the device being registered correctly. + * This will need revisiting for future devices with + * multiple dies. + */ + parent = id; + switch (rev) { + case 0: + dev_info(wm831x->dev, "WM831%d ES revision %c\n", + parent, 'A' + rev); + break; + } + break; + + default: + dev_err(wm831x->dev, "Unknown WM831x device %04x\n", ret); + ret = -EINVAL; + goto err; + } + + /* This will need revisiting in future but is OK for all + * current parts. + */ + if (parent != id) + dev_warn(wm831x->dev, "Device was registered as a WM831%lu\n", + id); + + /* Bootstrap the user key */ + ret = wm831x_reg_read(wm831x, WM831X_SECURITY_KEY); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read security key: %d\n", ret); + goto err; + } + if (ret != 0) { + dev_warn(wm831x->dev, "Security key had non-zero value %x\n", + ret); + wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0); + } + wm831x->locked = 1; + + if (pdata && pdata->pre_init) { + ret = pdata->pre_init(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "pre_init() failed: %d\n", ret); + goto err; + } + } + + ret = wm831x_irq_init(wm831x, irq); + if (ret != 0) + goto err; + + /* The core device is up, instantiate the subdevices. */ + switch (parent) { + case WM8310: + ret = mfd_add_devices(wm831x->dev, -1, + wm8310_devs, ARRAY_SIZE(wm8310_devs), + NULL, 0); + break; + + case WM8311: + ret = mfd_add_devices(wm831x->dev, -1, + wm8311_devs, ARRAY_SIZE(wm8311_devs), + NULL, 0); + break; + + case WM8312: + ret = mfd_add_devices(wm831x->dev, -1, + wm8312_devs, ARRAY_SIZE(wm8312_devs), + NULL, 0); + break; + + default: + /* If this happens the bus probe function is buggy */ + BUG(); + } + + if (ret != 0) { + dev_err(wm831x->dev, "Failed to add children\n"); + goto err_irq; + } + + if (pdata && pdata->backlight) { + /* Treat errors as non-critical */ + ret = mfd_add_devices(wm831x->dev, -1, backlight_devs, + ARRAY_SIZE(backlight_devs), NULL, 0); + if (ret < 0) + dev_err(wm831x->dev, "Failed to add backlight: %d\n", + ret); + } + + wm831x_otp_init(wm831x); + + if (pdata && pdata->post_init) { + ret = pdata->post_init(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "post_init() failed: %d\n", ret); + goto err_irq; + } + } + + return 0; + +err_irq: + wm831x_irq_exit(wm831x); +err: + mfd_remove_devices(wm831x->dev); + kfree(wm831x); + return ret; +} + +static void wm831x_device_exit(struct wm831x *wm831x) +{ + wm831x_otp_exit(wm831x); + mfd_remove_devices(wm831x->dev); + wm831x_irq_exit(wm831x); + kfree(wm831x); +} + +static int wm831x_i2c_read_device(struct wm831x *wm831x, unsigned short reg, + int bytes, void *dest) +{ + struct i2c_client *i2c = wm831x->control_data; + int ret; + u16 r = cpu_to_be16(reg); + + ret = i2c_master_send(i2c, (unsigned char *)&r, 2); + if (ret < 0) + return ret; + if (ret != 2) + return -EIO; + + ret = i2c_master_recv(i2c, dest, bytes); + if (ret < 0) + return ret; + if (ret != bytes) + return -EIO; + return 0; +} + +/* Currently we allocate the write buffer on the stack; this is OK for + * small writes - if we need to do large writes this will need to be + * revised. + */ +static int wm831x_i2c_write_device(struct wm831x *wm831x, unsigned short reg, + int bytes, void *src) +{ + struct i2c_client *i2c = wm831x->control_data; + unsigned char msg[bytes + 2]; + int ret; + + reg = cpu_to_be16(reg); + memcpy(&msg[0], ®, 2); + memcpy(&msg[2], src, bytes); + + ret = i2c_master_send(i2c, msg, bytes + 2); + if (ret < 0) + return ret; + if (ret < bytes + 2) + return -EIO; + + return 0; +} + +static int wm831x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm831x *wm831x; + + wm831x = kzalloc(sizeof(struct wm831x), GFP_KERNEL); + if (wm831x == NULL) { + kfree(i2c); + return -ENOMEM; + } + + i2c_set_clientdata(i2c, wm831x); + wm831x->dev = &i2c->dev; + wm831x->control_data = i2c; + wm831x->read_dev = wm831x_i2c_read_device; + wm831x->write_dev = wm831x_i2c_write_device; + + return wm831x_device_init(wm831x, id->driver_data, i2c->irq); +} + +static int wm831x_i2c_remove(struct i2c_client *i2c) +{ + struct wm831x *wm831x = i2c_get_clientdata(i2c); + + wm831x_device_exit(wm831x); + + return 0; +} + +static const struct i2c_device_id wm831x_i2c_id[] = { + { "wm8310", WM8310 }, + { "wm8311", WM8311 }, + { "wm8312", WM8312 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm831x_i2c_id); + + +static struct i2c_driver wm831x_i2c_driver = { + .driver = { + .name = "wm831x", + .owner = THIS_MODULE, + }, + .probe = wm831x_i2c_probe, + .remove = wm831x_i2c_remove, + .id_table = wm831x_i2c_id, +}; + +static int __init wm831x_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&wm831x_i2c_driver); + if (ret != 0) + pr_err("Failed to register wm831x I2C driver: %d\n", ret); + + return ret; +} +subsys_initcall(wm831x_i2c_init); + +static void __exit wm831x_i2c_exit(void) +{ + i2c_del_driver(&wm831x_i2c_driver); +} +module_exit(wm831x_i2c_exit); + +MODULE_DESCRIPTION("I2C support for the WM831X AudioPlus PMIC"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown"); diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c new file mode 100644 index 00000000000..ac056ea6b66 --- /dev/null +++ b/drivers/mfd/wm831x-irq.c @@ -0,0 +1,560 @@ +/* + * wm831x-irq.c -- Interrupt controller support for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/interrupt.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/pdata.h> +#include <linux/mfd/wm831x/irq.h> + +#include <linux/delay.h> + +/* + * Since generic IRQs don't currently support interrupt controllers on + * interrupt driven buses we don't use genirq but instead provide an + * interface that looks very much like the standard ones. This leads + * to some bodges, including storing interrupt handler information in + * the static irq_data table we use to look up the data for individual + * interrupts, but hopefully won't last too long. + */ + +struct wm831x_irq_data { + int primary; + int reg; + int mask; + irq_handler_t handler; + void *handler_data; +}; + +static struct wm831x_irq_data wm831x_irqs[] = { + [WM831X_IRQ_TEMP_THW] = { + .primary = WM831X_TEMP_INT, + .reg = 1, + .mask = WM831X_TEMP_THW_EINT, + }, + [WM831X_IRQ_GPIO_1] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP1_EINT, + }, + [WM831X_IRQ_GPIO_2] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP2_EINT, + }, + [WM831X_IRQ_GPIO_3] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP3_EINT, + }, + [WM831X_IRQ_GPIO_4] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP4_EINT, + }, + [WM831X_IRQ_GPIO_5] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP5_EINT, + }, + [WM831X_IRQ_GPIO_6] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP6_EINT, + }, + [WM831X_IRQ_GPIO_7] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP7_EINT, + }, + [WM831X_IRQ_GPIO_8] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP8_EINT, + }, + [WM831X_IRQ_GPIO_9] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP9_EINT, + }, + [WM831X_IRQ_GPIO_10] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP10_EINT, + }, + [WM831X_IRQ_GPIO_11] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP11_EINT, + }, + [WM831X_IRQ_GPIO_12] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP12_EINT, + }, + [WM831X_IRQ_GPIO_13] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP13_EINT, + }, + [WM831X_IRQ_GPIO_14] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP14_EINT, + }, + [WM831X_IRQ_GPIO_15] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP15_EINT, + }, + [WM831X_IRQ_GPIO_16] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP16_EINT, + }, + [WM831X_IRQ_ON] = { + .primary = WM831X_ON_PIN_INT, + .reg = 1, + .mask = WM831X_ON_PIN_EINT, + }, + [WM831X_IRQ_PPM_SYSLO] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_SYSLO_EINT, + }, + [WM831X_IRQ_PPM_PWR_SRC] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_PWR_SRC_EINT, + }, + [WM831X_IRQ_PPM_USB_CURR] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_USB_CURR_EINT, + }, + [WM831X_IRQ_WDOG_TO] = { + .primary = WM831X_WDOG_INT, + .reg = 1, + .mask = WM831X_WDOG_TO_EINT, + }, + [WM831X_IRQ_RTC_PER] = { + .primary = WM831X_RTC_INT, + .reg = 1, + .mask = WM831X_RTC_PER_EINT, + }, + [WM831X_IRQ_RTC_ALM] = { + .primary = WM831X_RTC_INT, + .reg = 1, + .mask = WM831X_RTC_ALM_EINT, + }, + [WM831X_IRQ_CHG_BATT_HOT] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_HOT_EINT, + }, + [WM831X_IRQ_CHG_BATT_COLD] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_COLD_EINT, + }, + [WM831X_IRQ_CHG_BATT_FAIL] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_FAIL_EINT, + }, + [WM831X_IRQ_CHG_OV] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_OV_EINT, + }, + [WM831X_IRQ_CHG_END] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_END_EINT, + }, + [WM831X_IRQ_CHG_TO] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_TO_EINT, + }, + [WM831X_IRQ_CHG_MODE] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_MODE_EINT, + }, + [WM831X_IRQ_CHG_START] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_START_EINT, + }, + [WM831X_IRQ_TCHDATA] = { + .primary = WM831X_TCHDATA_INT, + .reg = 1, + .mask = WM831X_TCHDATA_EINT, + }, + [WM831X_IRQ_TCHPD] = { + .primary = WM831X_TCHPD_INT, + .reg = 1, + .mask = WM831X_TCHPD_EINT, + }, + [WM831X_IRQ_AUXADC_DATA] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DATA_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP1] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP1_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP2] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP2_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP3] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP3_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP4] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP4_EINT, + }, + [WM831X_IRQ_CS1] = { + .primary = WM831X_CS_INT, + .reg = 2, + .mask = WM831X_CS1_EINT, + }, + [WM831X_IRQ_CS2] = { + .primary = WM831X_CS_INT, + .reg = 2, + .mask = WM831X_CS2_EINT, + }, + [WM831X_IRQ_HC_DC1] = { + .primary = WM831X_HC_INT, + .reg = 4, + .mask = WM831X_HC_DC1_EINT, + }, + [WM831X_IRQ_HC_DC2] = { + .primary = WM831X_HC_INT, + .reg = 4, + .mask = WM831X_HC_DC2_EINT, + }, + [WM831X_IRQ_UV_LDO1] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO1_EINT, + }, + [WM831X_IRQ_UV_LDO2] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO2_EINT, + }, + [WM831X_IRQ_UV_LDO3] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO3_EINT, + }, + [WM831X_IRQ_UV_LDO4] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO4_EINT, + }, + [WM831X_IRQ_UV_LDO5] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO5_EINT, + }, + [WM831X_IRQ_UV_LDO6] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO6_EINT, + }, + [WM831X_IRQ_UV_LDO7] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO7_EINT, + }, + [WM831X_IRQ_UV_LDO8] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO8_EINT, + }, + [WM831X_IRQ_UV_LDO9] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO9_EINT, + }, + [WM831X_IRQ_UV_LDO10] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO10_EINT, + }, + [WM831X_IRQ_UV_DC1] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC1_EINT, + }, + [WM831X_IRQ_UV_DC2] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC2_EINT, + }, + [WM831X_IRQ_UV_DC3] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC3_EINT, + }, + [WM831X_IRQ_UV_DC4] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC4_EINT, + }, +}; + +static inline int irq_data_to_status_reg(struct wm831x_irq_data *irq_data) +{ + return WM831X_INTERRUPT_STATUS_1 - 1 + irq_data->reg; +} + +static inline int irq_data_to_mask_reg(struct wm831x_irq_data *irq_data) +{ + return WM831X_INTERRUPT_STATUS_1_MASK - 1 + irq_data->reg; +} + +static void __wm831x_enable_irq(struct wm831x *wm831x, int irq) +{ + struct wm831x_irq_data *irq_data = &wm831x_irqs[irq]; + + wm831x->irq_masks[irq_data->reg - 1] &= ~irq_data->mask; + wm831x_reg_write(wm831x, irq_data_to_mask_reg(irq_data), + wm831x->irq_masks[irq_data->reg - 1]); +} + +void wm831x_enable_irq(struct wm831x *wm831x, int irq) +{ + mutex_lock(&wm831x->irq_lock); + __wm831x_enable_irq(wm831x, irq); + mutex_unlock(&wm831x->irq_lock); +} +EXPORT_SYMBOL_GPL(wm831x_enable_irq); + +static void __wm831x_disable_irq(struct wm831x *wm831x, int irq) +{ + struct wm831x_irq_data *irq_data = &wm831x_irqs[irq]; + + wm831x->irq_masks[irq_data->reg - 1] |= irq_data->mask; + wm831x_reg_write(wm831x, irq_data_to_mask_reg(irq_data), + wm831x->irq_masks[irq_data->reg - 1]); +} + +void wm831x_disable_irq(struct wm831x *wm831x, int irq) +{ + mutex_lock(&wm831x->irq_lock); + __wm831x_disable_irq(wm831x, irq); + mutex_unlock(&wm831x->irq_lock); +} +EXPORT_SYMBOL_GPL(wm831x_disable_irq); + +int wm831x_request_irq(struct wm831x *wm831x, + unsigned int irq, irq_handler_t handler, + unsigned long flags, const char *name, + void *dev) +{ + int ret = 0; + + if (irq < 0 || irq >= WM831X_NUM_IRQS) + return -EINVAL; + + mutex_lock(&wm831x->irq_lock); + + if (wm831x_irqs[irq].handler) { + dev_err(wm831x->dev, "Already have handler for IRQ %d\n", irq); + ret = -EINVAL; + goto out; + } + + wm831x_irqs[irq].handler = handler; + wm831x_irqs[irq].handler_data = dev; + + __wm831x_enable_irq(wm831x, irq); + +out: + mutex_unlock(&wm831x->irq_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_request_irq); + +void wm831x_free_irq(struct wm831x *wm831x, unsigned int irq, void *data) +{ + if (irq < 0 || irq >= WM831X_NUM_IRQS) + return; + + mutex_lock(&wm831x->irq_lock); + + wm831x_irqs[irq].handler = NULL; + wm831x_irqs[irq].handler_data = NULL; + + __wm831x_disable_irq(wm831x, irq); + + mutex_unlock(&wm831x->irq_lock); +} +EXPORT_SYMBOL_GPL(wm831x_free_irq); + + +static void wm831x_handle_irq(struct wm831x *wm831x, int irq, int status) +{ + struct wm831x_irq_data *irq_data = &wm831x_irqs[irq]; + + if (irq_data->handler) { + irq_data->handler(irq, irq_data->handler_data); + wm831x_reg_write(wm831x, irq_data_to_status_reg(irq_data), + irq_data->mask); + } else { + dev_err(wm831x->dev, "Unhandled IRQ %d, masking\n", irq); + __wm831x_disable_irq(wm831x, irq); + } +} + +/* Main interrupt handling occurs in a workqueue since we need + * interrupts enabled to interact with the chip. */ +static void wm831x_irq_worker(struct work_struct *work) +{ + struct wm831x *wm831x = container_of(work, struct wm831x, irq_work); + unsigned int i; + int primary; + int status_regs[5]; + int read[5] = { 0 }; + int *status; + + primary = wm831x_reg_read(wm831x, WM831X_SYSTEM_INTERRUPTS); + if (primary < 0) { + dev_err(wm831x->dev, "Failed to read system interrupt: %d\n", + primary); + goto out; + } + + mutex_lock(&wm831x->irq_lock); + + for (i = 0; i < ARRAY_SIZE(wm831x_irqs); i++) { + int offset = wm831x_irqs[i].reg - 1; + + if (!(primary & wm831x_irqs[i].primary)) + continue; + + status = &status_regs[offset]; + + /* Hopefully there should only be one register to read + * each time otherwise we ought to do a block read. */ + if (!read[offset]) { + *status = wm831x_reg_read(wm831x, + irq_data_to_status_reg(&wm831x_irqs[i])); + if (*status < 0) { + dev_err(wm831x->dev, + "Failed to read IRQ status: %d\n", + *status); + goto out_lock; + } + + /* Mask out the disabled IRQs */ + *status &= ~wm831x->irq_masks[offset]; + read[offset] = 1; + } + + if (*status & wm831x_irqs[i].mask) + wm831x_handle_irq(wm831x, i, *status); + } + +out_lock: + mutex_unlock(&wm831x->irq_lock); +out: + enable_irq(wm831x->irq); +} + + +static irqreturn_t wm831x_cpu_irq(int irq, void *data) +{ + struct wm831x *wm831x = data; + + /* Shut the interrupt to the CPU up and schedule the actual + * handler; we can't check that the IRQ is asserted. */ + disable_irq_nosync(irq); + + queue_work(wm831x->irq_wq, &wm831x->irq_work); + + return IRQ_HANDLED; +} + +int wm831x_irq_init(struct wm831x *wm831x, int irq) +{ + int i, ret; + + mutex_init(&wm831x->irq_lock); + + if (!irq) { + dev_warn(wm831x->dev, + "No interrupt specified - functionality limited\n"); + return 0; + } + + + wm831x->irq_wq = create_singlethread_workqueue("wm831x-irq"); + if (!wm831x->irq_wq) { + dev_err(wm831x->dev, "Failed to allocate IRQ worker\n"); + return -ESRCH; + } + + wm831x->irq = irq; + INIT_WORK(&wm831x->irq_work, wm831x_irq_worker); + + /* Mask the individual interrupt sources */ + for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks); i++) { + wm831x->irq_masks[i] = 0xffff; + wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i, + 0xffff); + } + + /* Enable top level interrupts, we mask at secondary level */ + wm831x_reg_write(wm831x, WM831X_SYSTEM_INTERRUPTS_MASK, 0); + + /* We're good to go. We set IRQF_SHARED since there's a + * chance the driver will interoperate with another driver but + * the need to disable the IRQ while handing via I2C/SPI means + * that this may break and performance will be impacted. If + * this does happen it's a hardware design issue and the only + * other alternative would be polling. + */ + ret = request_irq(irq, wm831x_cpu_irq, IRQF_TRIGGER_LOW | IRQF_SHARED, + "wm831x", wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "Failed to request IRQ %d: %d\n", + irq, ret); + return ret; + } + + return 0; +} + +void wm831x_irq_exit(struct wm831x *wm831x) +{ + if (wm831x->irq) + free_irq(wm831x->irq, wm831x); +} diff --git a/drivers/mfd/wm831x-otp.c b/drivers/mfd/wm831x-otp.c new file mode 100644 index 00000000000..f742745ff35 --- /dev/null +++ b/drivers/mfd/wm831x-otp.c @@ -0,0 +1,83 @@ +/* + * wm831x-otp.c -- OTP for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/bcd.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/otp.h> + +/* In bytes */ +#define WM831X_UNIQUE_ID_LEN 16 + +/* Read the unique ID from the chip into id */ +static int wm831x_unique_id_read(struct wm831x *wm831x, char *id) +{ + int i, val; + + for (i = 0; i < WM831X_UNIQUE_ID_LEN / 2; i++) { + val = wm831x_reg_read(wm831x, WM831X_UNIQUE_ID_1 + i); + if (val < 0) + return val; + + id[i * 2] = (val >> 8) & 0xff; + id[(i * 2) + 1] = val & 0xff; + } + + return 0; +} + +static ssize_t wm831x_unique_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + int i, rval; + char id[WM831X_UNIQUE_ID_LEN]; + ssize_t ret = 0; + + rval = wm831x_unique_id_read(wm831x, id); + if (rval < 0) + return 0; + + for (i = 0; i < WM831X_UNIQUE_ID_LEN; i++) + ret += sprintf(&buf[ret], "%02x", buf[i]); + + ret += sprintf(&buf[ret], "\n"); + + return ret; +} + +static DEVICE_ATTR(unique_id, 0444, wm831x_unique_id_show, NULL); + +int wm831x_otp_init(struct wm831x *wm831x) +{ + int ret; + + ret = device_create_file(wm831x->dev, &dev_attr_unique_id); + if (ret != 0) + dev_err(wm831x->dev, "Unique ID attribute not created: %d\n", + ret); + + return ret; +} + +void wm831x_otp_exit(struct wm831x *wm831x) +{ + device_remove_file(wm831x->dev, &dev_attr_unique_id); +} + diff --git a/drivers/mfd/wm8350-core.c b/drivers/mfd/wm8350-core.c index fe24079387c..ba27c9dc1ad 100644 --- a/drivers/mfd/wm8350-core.c +++ b/drivers/mfd/wm8350-core.c @@ -353,15 +353,15 @@ static void wm8350_irq_call_handler(struct wm8350 *wm8350, int irq) } /* - * wm8350_irq_worker actually handles the interrupts. Since all + * This is a threaded IRQ handler so can access I2C/SPI. Since all * interrupts are clear on read the IRQ line will be reasserted and * the physical IRQ will be handled again if another interrupt is * asserted while we run - in the normal course of events this is a * rare occurrence so we save I2C/SPI reads. */ -static void wm8350_irq_worker(struct work_struct *work) +static irqreturn_t wm8350_irq(int irq, void *data) { - struct wm8350 *wm8350 = container_of(work, struct wm8350, irq_work); + struct wm8350 *wm8350 = data; u16 level_one, status1, status2, comp; /* TODO: Use block reads to improve performance? */ @@ -552,16 +552,6 @@ static void wm8350_irq_worker(struct work_struct *work) } } - enable_irq(wm8350->chip_irq); -} - -static irqreturn_t wm8350_irq(int irq, void *data) -{ - struct wm8350 *wm8350 = data; - - disable_irq_nosync(irq); - schedule_work(&wm8350->irq_work); - return IRQ_HANDLED; } @@ -1428,9 +1418,8 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq, mutex_init(&wm8350->auxadc_mutex); mutex_init(&wm8350->irq_mutex); - INIT_WORK(&wm8350->irq_work, wm8350_irq_worker); if (irq) { - int flags = 0; + int flags = IRQF_ONESHOT; if (pdata && pdata->irq_high) { flags |= IRQF_TRIGGER_HIGH; @@ -1444,8 +1433,8 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq, WM8350_IRQ_POL); } - ret = request_irq(irq, wm8350_irq, flags, - "wm8350", wm8350); + ret = request_threaded_irq(irq, NULL, wm8350_irq, flags, + "wm8350", wm8350); if (ret != 0) { dev_err(wm8350->dev, "Failed to request IRQ: %d\n", ret); @@ -1472,6 +1461,8 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq, &(wm8350->codec.pdev)); wm8350_client_dev_register(wm8350, "wm8350-gpio", &(wm8350->gpio.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-hwmon", + &(wm8350->hwmon.pdev)); wm8350_client_dev_register(wm8350, "wm8350-power", &(wm8350->power.pdev)); wm8350_client_dev_register(wm8350, "wm8350-rtc", &(wm8350->rtc.pdev)); @@ -1498,11 +1489,11 @@ void wm8350_device_exit(struct wm8350 *wm8350) platform_device_unregister(wm8350->wdt.pdev); platform_device_unregister(wm8350->rtc.pdev); platform_device_unregister(wm8350->power.pdev); + platform_device_unregister(wm8350->hwmon.pdev); platform_device_unregister(wm8350->gpio.pdev); platform_device_unregister(wm8350->codec.pdev); free_irq(wm8350->chip_irq, wm8350); - flush_work(&wm8350->irq_work); kfree(wm8350->reg_cache); } EXPORT_SYMBOL_GPL(wm8350_device_exit); |