添加wm8994驱动支持

This commit is contained in:
邱建斌 2011-03-04 14:31:56 +08:00
parent 6d74f4e4f1
commit 84a568df72
25 changed files with 10045 additions and 2945 deletions

36
arch/arm/configs/rk29_phonesdk_defconfig Executable file → Normal file
View File

@ -1,7 +1,7 @@
#
# Automatically generated make config: don't edit
# Linux kernel version: 2.6.32.27
# Wed Jan 26 18:10:12 2011
# Fri Mar 4 11:17:59 2011
#
CONFIG_ARM=y
CONFIG_SYS_SUPPORTS_APM_EMULATION=y
@ -198,6 +198,7 @@ CONFIG_ARCH_RK29=y
CONFIG_WIFI_CONTROL_FUNC=y
# CONFIG_MACH_RK29SDK is not set
# CONFIG_MACH_RK29WINACCORD is not set
# CONFIG_MACH_RK29FIH is not set
# CONFIG_MACH_RK29_AIGO is not set
# CONFIG_MACH_RK29_MALATA is not set
CONFIG_MACH_RK29_PHONESDK=y
@ -607,6 +608,14 @@ CONFIG_APANIC_PLABEL="kpanic"
# CONFIG_EEPROM_MAX6875 is not set
# CONFIG_EEPROM_93CX6 is not set
# CONFIG_RK29_SUPPORT_MODEM is not set
# CONFIG_RK29_GPS is not set
#
# Motion Sensors Support
#
# CONFIG_MPU_NONE is not set
# CONFIG_SENSORS_MPU3050 is not set
# CONFIG_SENSORS_MPU6000 is not set
CONFIG_HAVE_IDE=y
# CONFIG_IDE is not set
@ -766,6 +775,7 @@ CONFIG_KEYS_RK29=y
# CONFIG_QT2160 is not set
# CONFIG_KEYBOARD_LKKBD is not set
# CONFIG_KEYBOARD_GPIO is not set
# CONFIG_KEYBOARD_WM831X_GPIO is not set
# CONFIG_KEYBOARD_MATRIX is not set
# CONFIG_KEYBOARD_MAX7359 is not set
# CONFIG_KEYBOARD_NEWTON is not set
@ -803,6 +813,7 @@ CONFIG_TOUCHSCREEN_XPT2046_SPI_NOCHOOSE=y
# CONFIG_TOUCHSCREEN_TSC2007 is not set
# CONFIG_TOUCHSCREEN_W90X900 is not set
# CONFIG_HANNSTAR_P1003 is not set
# CONFIG_ATMEL_MXT224 is not set
# CONFIG_SINTEK_3FA16 is not set
CONFIG_EETI_EGALAX=y
CONFIG_EETI_EGALAX_MAX_X=1087
@ -812,6 +823,8 @@ CONFIG_EETI_EGALAX_MAX_Y=800
CONFIG_INPUT_MISC=y
CONFIG_INPUT_LPSENSOR_ISL29028=y
# CONFIG_INPUT_LPSENSOR_CM3602 is not set
# CONFIG_INPUT_LSENSOR_CM3623 is not set
# CONFIG_INPUT_MPU3050 is not set
# CONFIG_INPUT_ATI_REMOTE is not set
# CONFIG_INPUT_ATI_REMOTE2 is not set
# CONFIG_INPUT_KEYCHORD is not set
@ -825,6 +838,7 @@ CONFIG_INPUT_LPSENSOR_ISL29028=y
CONFIG_G_SENSOR_DEVICE=y
# CONFIG_GS_MMA7660 is not set
CONFIG_GS_MMA8452=y
CONFIG_GS_FIH=y
# CONFIG_INPUT_JOGBALL is not set
#
@ -928,6 +942,7 @@ CONFIG_GPIOLIB=y
# CONFIG_GPIO_MAX732X is not set
# CONFIG_GPIO_PCA953X is not set
# CONFIG_GPIO_PCF857X is not set
# CONFIG_GPIO_WM8994 is not set
#
# PCI GPIO expanders:
@ -973,20 +988,22 @@ CONFIG_SSB_POSSIBLE=y
#
# Multifunction device drivers
#
# CONFIG_MFD_CORE is not set
CONFIG_MFD_CORE=y
# CONFIG_MFD_SM501 is not set
# CONFIG_MFD_ASIC3 is not set
# CONFIG_HTC_EGPIO is not set
# CONFIG_HTC_PASIC3 is not set
# CONFIG_TPS65010 is not set
# CONFIG_TWL4030_CORE is not set
# CONFIG_TPS65910_CORE is not set
# CONFIG_MFD_TMIO is not set
# CONFIG_MFD_T7L66XB is not set
# CONFIG_MFD_TC6387XB is not set
# CONFIG_MFD_TC6393XB is not set
# CONFIG_PMIC_DA903X is not set
CONFIG_MFD_WM8994=y
# CONFIG_MFD_WM8400 is not set
# CONFIG_MFD_WM831X is not set
# CONFIG_MFD_WM831X_I2C is not set
# CONFIG_MFD_WM8350_I2C is not set
# CONFIG_MFD_PCF50633 is not set
# CONFIG_AB3100_CORE is not set
@ -997,6 +1014,7 @@ CONFIG_REGULATOR=y
# CONFIG_REGULATOR_USERSPACE_CONSUMER is not set
# CONFIG_REGULATOR_BQ24022 is not set
# CONFIG_REGULATOR_MAX1586 is not set
# CONFIG_REGULATOR_WM8994 is not set
# CONFIG_REGULATOR_LP3971 is not set
# CONFIG_REGULATOR_TPS65023 is not set
# CONFIG_REGULATOR_TPS6507X is not set
@ -1049,6 +1067,7 @@ CONFIG_SOC_CAMERA=y
# CONFIG_SOC_CAMERA_MT9M111 is not set
# CONFIG_SOC_CAMERA_MT9T031 is not set
# CONFIG_SOC_CAMERA_MT9P111 is not set
# CONFIG_SOC_CAMERA_MT9D112 is not set
# CONFIG_SOC_CAMERA_MT9V022 is not set
# CONFIG_SOC_CAMERA_TW9910 is not set
# CONFIG_SOC_CAMERA_PLATFORM is not set
@ -1162,6 +1181,7 @@ CONFIG_FB_CFB_IMAGEBLIT=y
# Frame buffer hardware drivers
#
# CONFIG_FB_S1D13XXX is not set
# CONFIG_FB_TMIO is not set
# CONFIG_FB_RK2818 is not set
CONFIG_FB_RK29=y
# CONFIG_FB_VIRTUAL is not set
@ -1258,12 +1278,14 @@ CONFIG_SND_RK29_SOC_I2S=y
# CONFIG_SND_RK29_SOC_I2S_2CH is not set
CONFIG_SND_RK29_SOC_I2S_8CH=y
# CONFIG_SND_RK29_SOC_WM8988 is not set
CONFIG_SND_RK29_SOC_WM8900=y
# CONFIG_SND_RK29_CODEC_SOC_MASTER is not set
CONFIG_SND_RK29_CODEC_SOC_SLAVE=y
# CONFIG_SND_RK29_SOC_WM8900 is not set
CONFIG_SND_RK29_SOC_WM8994=y
CONFIG_SND_RK29_CODEC_SOC_MASTER=y
# CONFIG_SND_RK29_CODEC_SOC_SLAVE is not set
CONFIG_SND_SOC_I2C_AND_SPI=y
# CONFIG_SND_SOC_ALL_CODECS is not set
CONFIG_SND_SOC_WM8900=y
CONFIG_SND_SOC_WM_HUBS=y
CONFIG_SND_SOC_WM8994=y
# CONFIG_SOUND_PRIME is not set
# CONFIG_HID_SUPPORT is not set
CONFIG_USB_SUPPORT=y

View File

@ -1295,6 +1295,13 @@ static struct i2c_board_info __initdata board_i2c0_devices[] = {
.flags = 0,
},
#endif
#if defined (CONFIG_SND_SOC_WM8994)
{
.type = "wm8994",
.addr = 0x1A,
.flags = 0,
},
#endif
#if defined (CONFIG_BATTERY_STC3100)
{
.type = "stc3100",

View File

@ -161,6 +161,13 @@ config GPIO_TPS65910
help
Say yes here to access the GPIO signal of TPS65910x multi-function
power management chips from Texas Instruments.
#add by qjb
config GPIO_WM8994
tristate "WM8994 GPIOs"
depends on MFD_WM8994
help
Say yes here to access the GPIO signals of WM8994 audio hub
CODECs from Wolfson Microelectronics.
config GPIO_WM831X
tristate "WM831x GPIOs"

View File

@ -23,3 +23,5 @@ obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o
obj-$(CONFIG_GPIO_PCA9554) += pca9554.o
obj-$(CONFIG_IOEXTEND_TCA6424) += tca6424.o
obj-$(CONFIG_EXPAND_GPIO_SOFT_INTERRUPT) += expand_gpio_soft_interrupt.o
#add by qjb
obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o

205
drivers/gpio/wm8994-gpio.c Normal file
View File

@ -0,0 +1,205 @@
/*
* wm8994-gpio.c -- gpiolib support for Wolfson WM8994
*
* 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/slab.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/mfd/core.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/mfd/wm8994/core.h>
#include <linux/mfd/wm8994/pdata.h>
#include <linux/mfd/wm8994/gpio.h>
#include <linux/mfd/wm8994/registers.h>
struct wm8994_gpio {
struct wm8994 *wm8994;
struct gpio_chip gpio_chip;
};
static inline struct wm8994_gpio *to_wm8994_gpio(struct gpio_chip *chip)
{
return container_of(chip, struct wm8994_gpio, gpio_chip);
}
static int wm8994_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
{
struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip);
struct wm8994 *wm8994 = wm8994_gpio->wm8994;
return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset,
WM8994_GPN_DIR, WM8994_GPN_DIR);
}
static int wm8994_gpio_get(struct gpio_chip *chip, unsigned offset)
{
struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip);
struct wm8994 *wm8994 = wm8994_gpio->wm8994;
int ret;
ret = wm8994_reg_read(wm8994, WM8994_GPIO_1 + offset);
if (ret < 0)
return ret;
if (ret & WM8994_GPN_LVL)
return 1;
else
return 0;
}
static int wm8994_gpio_direction_out(struct gpio_chip *chip,
unsigned offset, int value)
{
struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip);
struct wm8994 *wm8994 = wm8994_gpio->wm8994;
return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset,
WM8994_GPN_DIR, 0);
}
static void wm8994_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
{
struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip);
struct wm8994 *wm8994 = wm8994_gpio->wm8994;
if (value)
value = WM8994_GPN_LVL;
wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, WM8994_GPN_LVL, value);
}
#ifdef CONFIG_DEBUG_FS
static void wm8994_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
{
struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip);
struct wm8994 *wm8994 = wm8994_gpio->wm8994;
int i;
for (i = 0; i < chip->ngpio; i++) {
int gpio = i + chip->base;
int reg;
const char *label;
/* We report the GPIO even if it's not requested since
* we're also reporting things like alternate
* functions which apply even when the GPIO is not in
* use as a GPIO.
*/
label = gpiochip_is_requested(chip, i);
if (!label)
label = "Unrequested";
seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label);
reg = wm8994_reg_read(wm8994, WM8994_GPIO_1 + i);
if (reg < 0) {
dev_err(wm8994->dev,
"GPIO control %d read failed: %d\n",
gpio, reg);
seq_printf(s, "\n");
continue;
}
/* No decode yet; note that GPIO2 is special */
seq_printf(s, "(%x)\n", reg);
}
}
#else
#define wm8994_gpio_dbg_show NULL
#endif
static struct gpio_chip template_chip = {
.label = "wm8994",
.owner = THIS_MODULE,
.direction_input = wm8994_gpio_direction_in,
.get = wm8994_gpio_get,
.direction_output = wm8994_gpio_direction_out,
.set = wm8994_gpio_set,
.dbg_show = wm8994_gpio_dbg_show,
.can_sleep = 1,
};
static int __devinit wm8994_gpio_probe(struct platform_device *pdev)
{
struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent);
struct wm8994_pdata *pdata = wm8994->dev->platform_data;
struct wm8994_gpio *wm8994_gpio;
int ret;
wm8994_gpio = kzalloc(sizeof(*wm8994_gpio), GFP_KERNEL);
if (wm8994_gpio == NULL)
return -ENOMEM;
wm8994_gpio->wm8994 = wm8994;
wm8994_gpio->gpio_chip = template_chip;
wm8994_gpio->gpio_chip.ngpio = WM8994_GPIO_MAX;
wm8994_gpio->gpio_chip.dev = &pdev->dev;
if (pdata && pdata->gpio_base)
wm8994_gpio->gpio_chip.base = pdata->gpio_base;
else
wm8994_gpio->gpio_chip.base = -1;
ret = gpiochip_add(&wm8994_gpio->gpio_chip);
if (ret < 0) {
dev_err(&pdev->dev, "Could not register gpiochip, %d\n",
ret);
goto err;
}
platform_set_drvdata(pdev, wm8994_gpio);
return ret;
err:
kfree(wm8994_gpio);
return ret;
}
static int __devexit wm8994_gpio_remove(struct platform_device *pdev)
{
struct wm8994_gpio *wm8994_gpio = platform_get_drvdata(pdev);
int ret;
ret = gpiochip_remove(&wm8994_gpio->gpio_chip);
if (ret == 0)
kfree(wm8994_gpio);
return ret;
}
static struct platform_driver wm8994_gpio_driver = {
.driver.name = "wm8994-gpio",
.driver.owner = THIS_MODULE,
.probe = wm8994_gpio_probe,
.remove = __devexit_p(wm8994_gpio_remove),
};
static int __init wm8994_gpio_init(void)
{
return platform_driver_register(&wm8994_gpio_driver);
}
subsys_initcall(wm8994_gpio_init);
static void __exit wm8994_gpio_exit(void)
{
platform_driver_unregister(&wm8994_gpio_driver);
}
module_exit(wm8994_gpio_exit);
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_DESCRIPTION("GPIO interface for WM8994");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:wm8994-gpio");

View File

@ -171,6 +171,18 @@ config PMIC_DA903X
the I2C driver and the core APIs _only_, you have to select
individual components like LCD backlight, voltage regulators,
LEDs and battery-charger under the corresponding menus.
##and by qjb
config MFD_WM8994
tristate "Support Wolfson Microelectronics WM8994"
select MFD_CORE
depends on I2C
help
The WM8994 is a highly integrated hi-fi CODEC designed for
smartphone applicatiosn. As well as audio functionality it
has on board GPIO and regulator functionality which is
supported via the relevant subsystems. This driver provides
core support for the WM8994, in order to use the actual
functionaltiy of the device other drivers must be enabled.
config MFD_WM8400
tristate "Support Wolfson Microelectronics WM8400"

View File

@ -54,3 +54,6 @@ 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
#add by qjb
obj-$(CONFIG_MFD_WM8994) += wm8994-core.o

538
drivers/mfd/wm8994-core.c Executable file
View File

@ -0,0 +1,538 @@
/*
* wm8994-core.c -- Device access for Wolfson WM8994
*
* 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/slab.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/mfd/core.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/machine.h>
#include <linux/mfd/wm8994/core.h>
#include <linux/mfd/wm8994/pdata.h>
#include <linux/mfd/wm8994/registers.h>
static int wm8994_read(struct wm8994 *wm8994, unsigned short reg,
int bytes, void *dest)
{
int ret, i;
u16 *buf = dest;
BUG_ON(bytes % 2);
BUG_ON(bytes <= 0);
ret = wm8994->read_dev(wm8994, reg, bytes, dest);
if (ret < 0)
return ret;
for (i = 0; i < bytes / 2; i++) {
buf[i] = be16_to_cpu(buf[i]);
dev_vdbg(wm8994->dev, "Read %04x from R%d(0x%x)\n",
buf[i], reg + i, reg + i);
}
return 0;
}
/**
* wm8994_reg_read: Read a single WM8994 register.
*
* @wm8994: Device to read from.
* @reg: Register to read.
*/
int wm8994_reg_read(struct wm8994 *wm8994, unsigned short reg)
{
unsigned short val;
int ret;
mutex_lock(&wm8994->io_lock);
ret = wm8994_read(wm8994, reg, 2, &val);
mutex_unlock(&wm8994->io_lock);
if (ret < 0)
return ret;
else
return val;
}
EXPORT_SYMBOL_GPL(wm8994_reg_read);
/**
* wm8994_bulk_read: Read multiple WM8994 registers
*
* @wm8994: Device to read from
* @reg: First register
* @count: Number of registers
* @buf: Buffer to fill.
*/
int wm8994_bulk_read(struct wm8994 *wm8994, unsigned short reg,
int count, u16 *buf)
{
int ret;
mutex_lock(&wm8994->io_lock);
ret = wm8994_read(wm8994, reg, count * 2, buf);
mutex_unlock(&wm8994->io_lock);
return ret;
}
EXPORT_SYMBOL_GPL(wm8994_bulk_read);
static int wm8994_write(struct wm8994 *wm8994, 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++) {
dev_vdbg(wm8994->dev, "Write %04x to R%d(0x%x)\n",
buf[i], reg + i, reg + i);
buf[i] = cpu_to_be16(buf[i]);
}
return wm8994->write_dev(wm8994, reg, bytes, src);
}
/**
* wm8994_reg_write: Write a single WM8994 register.
*
* @wm8994: Device to write to.
* @reg: Register to write to.
* @val: Value to write.
*/
int wm8994_reg_write(struct wm8994 *wm8994, unsigned short reg,
unsigned short val)
{
int ret;
mutex_lock(&wm8994->io_lock);
ret = wm8994_write(wm8994, reg, 2, &val);
mutex_unlock(&wm8994->io_lock);
return ret;
}
EXPORT_SYMBOL_GPL(wm8994_reg_write);
/**
* wm8994_set_bits: Set the value of a bitfield in a WM8994 register
*
* @wm8994: Device to write to.
* @reg: Register to write to.
* @mask: Mask of bits to set.
* @val: Value to set (unshifted)
*/
int wm8994_set_bits(struct wm8994 *wm8994, unsigned short reg,
unsigned short mask, unsigned short val)
{
int ret;
u16 r;
mutex_lock(&wm8994->io_lock);
ret = wm8994_read(wm8994, reg, 2, &r);
if (ret < 0)
goto out;
r &= ~mask;
r |= val;
ret = wm8994_write(wm8994, reg, 2, &r);
out:
mutex_unlock(&wm8994->io_lock);
return ret;
}
EXPORT_SYMBOL_GPL(wm8994_set_bits);
static struct mfd_cell wm8994_regulator_devs[] = {
{ .name = "wm8994-ldo", .id = 1 },
{ .name = "wm8994-ldo", .id = 2 },
};
static struct mfd_cell wm8994_devs[] = {
{ .name = "wm8994-codec" },
{ .name = "wm8994-gpio" },
};
/*
* Supplies for the main bulk of CODEC; the LDO supplies are ignored
* and should be handled via the standard regulator API supply
* management.
*/
static const char *wm8994_main_supplies[] = {
// "DBVDD",
// "DCVDD",
// "AVDD1",
// "AVDD2",
// "CPVDD",
// "SPKVDD1",
// "SPKVDD2",
};
#ifdef CONFIG_PM
static int wm8994_device_suspend(struct device *dev)
{
struct wm8994 *wm8994 = dev_get_drvdata(dev);
int ret;
/* GPIO configuration state is saved here since we may be configuring
* the GPIO alternate functions even if we're not using the gpiolib
* driver for them.
*/
ret = wm8994_read(wm8994, WM8994_GPIO_1, WM8994_NUM_GPIO_REGS * 2,
&wm8994->gpio_regs);
if (ret < 0)
dev_err(dev, "Failed to save GPIO registers: %d\n", ret);
/* For similar reasons we also stash the regulator states */
ret = wm8994_read(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2,
&wm8994->ldo_regs);
if (ret < 0)
dev_err(dev, "Failed to save LDO registers: %d\n", ret);
ret = regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies),
wm8994->supplies);
if (ret != 0) {
dev_err(dev, "Failed to disable supplies: %d\n", ret);
return ret;
}
return 0;
}
static int wm8994_device_resume(struct device *dev)
{
struct wm8994 *wm8994 = dev_get_drvdata(dev);
int ret;
ret = regulator_bulk_enable(ARRAY_SIZE(wm8994_main_supplies),
wm8994->supplies);
if (ret != 0) {
dev_err(dev, "Failed to enable supplies: %d\n", ret);
return ret;
}
ret = wm8994_write(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2,
&wm8994->ldo_regs);
if (ret < 0)
dev_err(dev, "Failed to restore LDO registers: %d\n", ret);
ret = wm8994_write(wm8994, WM8994_GPIO_1, WM8994_NUM_GPIO_REGS * 2,
&wm8994->gpio_regs);
if (ret < 0)
dev_err(dev, "Failed to restore GPIO registers: %d\n", ret);
return 0;
}
#endif
#ifdef CONFIG_REGULATOR
static int wm8994_ldo_in_use(struct wm8994_pdata *pdata, int ldo)
{
struct wm8994_ldo_pdata *ldo_pdata;
if (!pdata)
return 0;
ldo_pdata = &pdata->ldo[ldo];
if (!ldo_pdata->init_data)
return 0;
return ldo_pdata->init_data->num_consumer_supplies != 0;
}
#else
static int wm8994_ldo_in_use(struct wm8994_pdata *pdata, int ldo)
{
return 0;
}
#endif
/*
* Instantiate the generic non-control parts of the device.
*/
static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq)
{
struct wm8994_pdata *pdata = wm8994->dev->platform_data;
int ret, i;
mutex_init(&wm8994->io_lock);
dev_set_drvdata(wm8994->dev, wm8994);
/* Add the on-chip regulators first for bootstrapping */
ret = mfd_add_devices(wm8994->dev, -1,
wm8994_regulator_devs,
ARRAY_SIZE(wm8994_regulator_devs),
NULL, 0);
if (ret != 0) {
dev_err(wm8994->dev, "Failed to add children: %d\n", ret);
goto err;
}
wm8994->supplies = kzalloc(sizeof(struct regulator_bulk_data) *
ARRAY_SIZE(wm8994_main_supplies),
GFP_KERNEL);
if (!wm8994->supplies)
goto err;
for (i = 0; i < ARRAY_SIZE(wm8994_main_supplies); i++)
wm8994->supplies[i].supply = wm8994_main_supplies[i];
ret = regulator_bulk_get(wm8994->dev, ARRAY_SIZE(wm8994_main_supplies),
wm8994->supplies);
if (ret != 0) {
dev_err(wm8994->dev, "Failed to get supplies: %d\n", ret);
goto err_supplies;
}
ret = regulator_bulk_enable(ARRAY_SIZE(wm8994_main_supplies),
wm8994->supplies);
if (ret != 0) {
dev_err(wm8994->dev, "Failed to enable supplies: %d\n", ret);
goto err_get;
}
ret = wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET);
if (ret < 0) {
dev_err(wm8994->dev, "Failed to read ID register\n");
goto err_enable;
}
if (ret != 0x8994) {
dev_err(wm8994->dev, "Device is not a WM8994, ID is %x\n",
ret);
ret = -EINVAL;
goto err_enable;
}
ret = wm8994_reg_read(wm8994, WM8994_CHIP_REVISION);
if (ret < 0) {
dev_err(wm8994->dev, "Failed to read revision register: %d\n",
ret);
goto err_enable;
}
switch (ret) {
case 0:
case 1:
dev_warn(wm8994->dev, "revision %c not fully supported\n",
'A' + ret);
break;
default:
dev_info(wm8994->dev, "revision %c\n", 'A' + ret);
break;
}
if (pdata) {
wm8994->gpio_base = pdata->gpio_base;
/* GPIO configuration is only applied if it's non-zero */
for (i = 0; i < ARRAY_SIZE(pdata->gpio_defaults); i++) {
if (pdata->gpio_defaults[i]) {
wm8994_set_bits(wm8994, WM8994_GPIO_1 + i,
0xffff,
pdata->gpio_defaults[i]);
}
}
}
/* In some system designs where the regulators are not in use,
* we can achieve a small reduction in leakage currents by
* floating LDO outputs. This bit makes no difference if the
* LDOs are enabled, it only affects cases where the LDOs were
* in operation and are then disabled.
*/
for (i = 0; i < WM8994_NUM_LDO_REGS; i++) {
if (wm8994_ldo_in_use(pdata, i))
wm8994_set_bits(wm8994, WM8994_LDO_1 + i,
WM8994_LDO1_DISCH, WM8994_LDO1_DISCH);
else
wm8994_set_bits(wm8994, WM8994_LDO_1 + i,
WM8994_LDO1_DISCH, 0);
}
ret = mfd_add_devices(wm8994->dev, -1,
wm8994_devs, ARRAY_SIZE(wm8994_devs),
NULL, 0);
if (ret != 0) {
dev_err(wm8994->dev, "Failed to add children: %d\n", ret);
goto err_enable;
}
return 0;
err_enable:
regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies),
wm8994->supplies);
err_get:
regulator_bulk_free(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies);
err_supplies:
kfree(wm8994->supplies);
err:
mfd_remove_devices(wm8994->dev);
kfree(wm8994);
return ret;
}
static void wm8994_device_exit(struct wm8994 *wm8994)
{
mfd_remove_devices(wm8994->dev);
regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies),
wm8994->supplies);
regulator_bulk_free(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies);
kfree(wm8994->supplies);
kfree(wm8994);
}
static int wm8994_i2c_read_device(struct wm8994 *wm8994, unsigned short reg,
int bytes, void *dest)
{
struct i2c_client *i2c = wm8994->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 wm8994_i2c_write_device(struct wm8994 *wm8994, unsigned short reg,
int bytes, void *src)
{
struct i2c_client *i2c = wm8994->control_data;
unsigned char msg[bytes + 2];
int ret;
reg = cpu_to_be16(reg);
memcpy(&msg[0], &reg, 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 wm8994_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct wm8994 *wm8994;
wm8994 = kzalloc(sizeof(struct wm8994), GFP_KERNEL);
if (wm8994 == NULL) {
kfree(i2c);
return -ENOMEM;
}
i2c_set_clientdata(i2c, wm8994);
wm8994->dev = &i2c->dev;
wm8994->control_data = i2c;
wm8994->read_dev = wm8994_i2c_read_device;
wm8994->write_dev = wm8994_i2c_write_device;
return wm8994_device_init(wm8994, id->driver_data, i2c->irq);
}
static int wm8994_i2c_remove(struct i2c_client *i2c)
{
struct wm8994 *wm8994 = i2c_get_clientdata(i2c);
wm8994_device_exit(wm8994);
return 0;
}
#ifdef CONFIG_PM
static int wm8994_i2c_suspend(struct i2c_client *i2c, pm_message_t state)
{
return wm8994_device_suspend(&i2c->dev);
}
static int wm8994_i2c_resume(struct i2c_client *i2c)
{
return wm8994_device_resume(&i2c->dev);
}
#else
#define wm8994_i2c_suspend NULL
#define wm8994_i2c_resume NULL
#endif
static const struct i2c_device_id wm8994_i2c_id[] = {
{ "wm8994", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, wm8994_i2c_id);
static struct i2c_driver wm8994_i2c_driver = {
.driver = {
.name = "wm8994",
.owner = THIS_MODULE,
},
.probe = wm8994_i2c_probe,
.remove = wm8994_i2c_remove,
.suspend = wm8994_i2c_suspend,
.resume = wm8994_i2c_resume,
.id_table = wm8994_i2c_id,
};
static int __init wm8994_i2c_init(void)
{
int ret;
ret = i2c_add_driver(&wm8994_i2c_driver);
if (ret != 0)
pr_err("Failed to register wm8994 I2C driver: %d\n", ret);
return ret;
}
module_init(wm8994_i2c_init);
static void __exit wm8994_i2c_exit(void)
{
i2c_del_driver(&wm8994_i2c_driver);
}
module_exit(wm8994_i2c_exit);
MODULE_DESCRIPTION("Core support for the WM8994 audio CODEC");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");

View File

@ -83,6 +83,14 @@ config REGULATOR_TPS65910
This driver supports the voltage regulators provided by
this family of companion chips.
#add by qjb
config REGULATOR_WM8994
tristate "Wolfson Microelectronics WM8994 CODEC"
depends on MFD_WM8994
help
This driver provides support for the voltage regulators on the
WM8994 CODEC.
config REGULATOR_WM831X
tristate "Wolfson Microelcronics WM831x PMIC regulators"
depends on MFD_WM831X

View File

@ -29,5 +29,7 @@ obj-$(CONFIG_RK29_PWM_REGULATOR) += rk29-pwm-regulator.o
obj-$(CONFIG_REGULATOR_TPS65023) += tps65023-regulator.o
obj-$(CONFIG_REGULATOR_TPS6507X) += tps6507x-regulator.o
#add by qjb
obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o
ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG

View File

@ -0,0 +1,308 @@
/*
* wm8994-regulator.c -- Regulator driver for the WM8994
*
* 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/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/mfd/wm8994/core.h>
#include <linux/mfd/wm8994/registers.h>
#include <linux/mfd/wm8994/pdata.h>
struct wm8994_ldo {
int enable;
bool is_enabled;
struct regulator_dev *regulator;
struct wm8994 *wm8994;
};
#define WM8994_LDO1_MAX_SELECTOR 0x7
#define WM8994_LDO2_MAX_SELECTOR 0x3
static int wm8994_ldo_enable(struct regulator_dev *rdev)
{
struct wm8994_ldo *ldo = rdev_get_drvdata(rdev);
/* If we have no soft control assume that the LDO is always enabled. */
if (!ldo->enable)
return 0;
gpio_set_value(ldo->enable, 1);
ldo->is_enabled = true;
return 0;
}
static int wm8994_ldo_disable(struct regulator_dev *rdev)
{
struct wm8994_ldo *ldo = rdev_get_drvdata(rdev);
/* If we have no soft control assume that the LDO is always enabled. */
if (!ldo->enable)
return -EINVAL;
gpio_set_value(ldo->enable, 0);
ldo->is_enabled = false;
return 0;
}
static int wm8994_ldo_is_enabled(struct regulator_dev *rdev)
{
struct wm8994_ldo *ldo = rdev_get_drvdata(rdev);
return ldo->is_enabled;
}
static int wm8994_ldo_enable_time(struct regulator_dev *rdev)
{
/* 3ms is fairly conservative but this shouldn't be too performance
* critical; can be tweaked per-system if required. */
return 3000;
}
static int wm8994_ldo1_list_voltage(struct regulator_dev *rdev,
unsigned int selector)
{
if (selector > WM8994_LDO1_MAX_SELECTOR)
return -EINVAL;
return (selector * 100000) + 2400000;
}
static int wm8994_ldo1_get_voltage(struct regulator_dev *rdev)
{
struct wm8994_ldo *ldo = rdev_get_drvdata(rdev);
int val;
val = wm8994_reg_read(ldo->wm8994, WM8994_LDO_1);
if (val < 0)
return val;
val = (val & WM8994_LDO1_VSEL_MASK) >> WM8994_LDO1_VSEL_SHIFT;
return wm8994_ldo1_list_voltage(rdev, val);
}
static int wm8994_ldo1_set_voltage(struct regulator_dev *rdev,
int min_uV, int max_uV)
{
struct wm8994_ldo *ldo = rdev_get_drvdata(rdev);
int selector, v;
selector = (min_uV - 2400000) / 100000;
v = wm8994_ldo1_list_voltage(rdev, selector);
if (v < 0 || v > max_uV)
return -EINVAL;
selector <<= WM8994_LDO1_VSEL_SHIFT;
return wm8994_set_bits(ldo->wm8994, WM8994_LDO_1,
WM8994_LDO1_VSEL_MASK, selector);
}
static struct regulator_ops wm8994_ldo1_ops = {
.enable = wm8994_ldo_enable,
.disable = wm8994_ldo_disable,
.is_enabled = wm8994_ldo_is_enabled,
// .enable_time = wm8994_ldo_enable_time,
.list_voltage = wm8994_ldo1_list_voltage,
.get_voltage = wm8994_ldo1_get_voltage,
.set_voltage = wm8994_ldo1_set_voltage,
};
static int wm8994_ldo2_list_voltage(struct regulator_dev *rdev,
unsigned int selector)
{
if (selector > WM8994_LDO2_MAX_SELECTOR)
return -EINVAL;
return (selector * 100000) + 900000;
}
static int wm8994_ldo2_get_voltage(struct regulator_dev *rdev)
{
struct wm8994_ldo *ldo = rdev_get_drvdata(rdev);
int val;
val = wm8994_reg_read(ldo->wm8994, WM8994_LDO_2);
if (val < 0)
return val;
val = (val & WM8994_LDO2_VSEL_MASK) >> WM8994_LDO2_VSEL_SHIFT;
return wm8994_ldo2_list_voltage(rdev, val);
}
static int wm8994_ldo2_set_voltage(struct regulator_dev *rdev,
int min_uV, int max_uV)
{
struct wm8994_ldo *ldo = rdev_get_drvdata(rdev);
int selector, v;
selector = (min_uV - 900000) / 100000;
v = wm8994_ldo2_list_voltage(rdev, selector);
if (v < 0 || v > max_uV)
return -EINVAL;
selector <<= WM8994_LDO2_VSEL_SHIFT;
return wm8994_set_bits(ldo->wm8994, WM8994_LDO_2,
WM8994_LDO2_VSEL_MASK, selector);
}
static struct regulator_ops wm8994_ldo2_ops = {
.enable = wm8994_ldo_enable,
.disable = wm8994_ldo_disable,
.is_enabled = wm8994_ldo_is_enabled,
// .enable_time = wm8994_ldo_enable_time,
.list_voltage = wm8994_ldo2_list_voltage,
.get_voltage = wm8994_ldo2_get_voltage,
.set_voltage = wm8994_ldo2_set_voltage,
};
static struct regulator_desc wm8994_ldo_desc[] = {
{
.name = "LDO1",
.id = 1,
.type = REGULATOR_VOLTAGE,
.n_voltages = WM8994_LDO1_MAX_SELECTOR + 1,
.ops = &wm8994_ldo1_ops,
.owner = THIS_MODULE,
},
{
.name = "LDO2",
.id = 2,
.type = REGULATOR_VOLTAGE,
.n_voltages = WM8994_LDO2_MAX_SELECTOR + 1,
.ops = &wm8994_ldo2_ops,
.owner = THIS_MODULE,
},
};
static __devinit int wm8994_ldo_probe(struct platform_device *pdev)
{
struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent);
struct wm8994_pdata *pdata = wm8994->dev->platform_data;
int id = pdev->id % ARRAY_SIZE(pdata->ldo);
struct wm8994_ldo *ldo;
int ret;
dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1);
if (!pdata)
return -ENODEV;
ldo = kzalloc(sizeof(struct wm8994_ldo), GFP_KERNEL);
if (ldo == NULL) {
dev_err(&pdev->dev, "Unable to allocate private data\n");
return -ENOMEM;
}
ldo->wm8994 = wm8994;
ldo->is_enabled = true;
if (pdata->ldo[id].enable && gpio_is_valid(pdata->ldo[id].enable)) {
ldo->enable = pdata->ldo[id].enable;
ret = gpio_request(ldo->enable, "WM8994 LDO enable");
if (ret < 0) {
dev_err(&pdev->dev, "Failed to get enable GPIO: %d\n",
ret);
goto err;
}
ret = gpio_direction_output(ldo->enable, ldo->is_enabled);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to set GPIO up: %d\n",
ret);
goto err_gpio;
}
}
ldo->regulator = regulator_register(&wm8994_ldo_desc[id], &pdev->dev,
pdata->ldo[id].init_data, ldo);
if (IS_ERR(ldo->regulator)) {
ret = PTR_ERR(ldo->regulator);
dev_err(wm8994->dev, "Failed to register LDO%d: %d\n",
id + 1, ret);
goto err_gpio;
}
platform_set_drvdata(pdev, ldo);
return 0;
err_gpio:
if (gpio_is_valid(ldo->enable))
gpio_free(ldo->enable);
err:
kfree(ldo);
return ret;
}
static __devexit int wm8994_ldo_remove(struct platform_device *pdev)
{
struct wm8994_ldo *ldo = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
regulator_unregister(ldo->regulator);
if (gpio_is_valid(ldo->enable))
gpio_free(ldo->enable);
kfree(ldo);
return 0;
}
static struct platform_driver wm8994_ldo_driver = {
.probe = wm8994_ldo_probe,
.remove = __devexit_p(wm8994_ldo_remove),
.driver = {
.name = "wm8994-ldo",
.owner = THIS_MODULE,
},
};
static int __init wm8994_ldo_init(void)
{
int ret;
ret = platform_driver_register(&wm8994_ldo_driver);
if (ret != 0)
pr_err("Failed to register Wm8994 GP LDO driver: %d\n", ret);
return ret;
}
subsys_initcall(wm8994_ldo_init);
static void __exit wm8994_ldo_exit(void)
{
platform_driver_unregister(&wm8994_ldo_driver);
}
module_exit(wm8994_ldo_exit);
/* Module information */
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_DESCRIPTION("WM8994 LDO driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:wm8994-ldo");

View File

@ -0,0 +1,54 @@
/*
* include/linux/mfd/wm8994/core.h -- Core interface for WM8994
*
* 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.
*
*/
#ifndef __MFD_WM8994_CORE_H__
#define __MFD_WM8994_CORE_H__
struct regulator_dev;
struct regulator_bulk_data;
#define WM8994_NUM_GPIO_REGS 11
#define WM8994_NUM_LDO_REGS 2
struct wm8994 {
struct mutex io_lock;
struct device *dev;
int (*read_dev)(struct wm8994 *wm8994, unsigned short reg,
int bytes, void *dest);
int (*write_dev)(struct wm8994 *wm8994, unsigned short reg,
int bytes, void *src);
void *control_data;
int gpio_base;
/* Used over suspend/resume */
u16 ldo_regs[WM8994_NUM_LDO_REGS];
u16 gpio_regs[WM8994_NUM_GPIO_REGS];
struct regulator_dev *dbvdd;
struct regulator_bulk_data *supplies;
};
/* Device I/O API */
int wm8994_reg_read(struct wm8994 *wm8994, unsigned short reg);
int wm8994_reg_write(struct wm8994 *wm8994, unsigned short reg,
unsigned short val);
int wm8994_set_bits(struct wm8994 *wm8994, unsigned short reg,
unsigned short mask, unsigned short val);
int wm8994_bulk_read(struct wm8994 *wm8994, unsigned short reg,
int count, u16 *buf);
#endif

View File

@ -0,0 +1,72 @@
/*
* include/linux/mfd/wm8994/gpio.h - GPIO configuration for WM8994
*
* 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.
*
*/
#ifndef __MFD_WM8994_GPIO_H__
#define __MFD_WM8994_GPIO_H__
#define WM8994_GPIO_MAX 11
#define WM8994_GP_FN_PIN_SPECIFIC 0
#define WM8994_GP_FN_GPIO 1
#define WM8994_GP_FN_SDOUT 2
#define WM8994_GP_FN_IRQ 3
#define WM8994_GP_FN_TEMPERATURE 4
#define WM8994_GP_FN_MICBIAS1_DET 5
#define WM8994_GP_FN_MICBIAS1_SHORT 6
#define WM8994_GP_FN_MICBIAS2_DET 7
#define WM8994_GP_FN_MICBIAS2_SHORT 8
#define WM8994_GP_FN_FLL1_LOCK 9
#define WM8994_GP_FN_FLL2_LOCK 10
#define WM8994_GP_FN_SRC1_LOCK 11
#define WM8994_GP_FN_SRC2_LOCK 12
#define WM8994_GP_FN_DRC1_ACT 13
#define WM8994_GP_FN_DRC2_ACT 14
#define WM8994_GP_FN_DRC3_ACT 15
#define WM8994_GP_FN_WSEQ_STATUS 16
#define WM8994_GP_FN_FIFO_ERROR 17
#define WM8994_GP_FN_OPCLK 18
#define WM8994_GPN_DIR 0x8000 /* GPN_DIR */
#define WM8994_GPN_DIR_MASK 0x8000 /* GPN_DIR */
#define WM8994_GPN_DIR_SHIFT 15 /* GPN_DIR */
#define WM8994_GPN_DIR_WIDTH 1 /* GPN_DIR */
#define WM8994_GPN_PU 0x4000 /* GPN_PU */
#define WM8994_GPN_PU_MASK 0x4000 /* GPN_PU */
#define WM8994_GPN_PU_SHIFT 14 /* GPN_PU */
#define WM8994_GPN_PU_WIDTH 1 /* GPN_PU */
#define WM8994_GPN_PD 0x2000 /* GPN_PD */
#define WM8994_GPN_PD_MASK 0x2000 /* GPN_PD */
#define WM8994_GPN_PD_SHIFT 13 /* GPN_PD */
#define WM8994_GPN_PD_WIDTH 1 /* GPN_PD */
#define WM8994_GPN_POL 0x0400 /* GPN_POL */
#define WM8994_GPN_POL_MASK 0x0400 /* GPN_POL */
#define WM8994_GPN_POL_SHIFT 10 /* GPN_POL */
#define WM8994_GPN_POL_WIDTH 1 /* GPN_POL */
#define WM8994_GPN_OP_CFG 0x0200 /* GPN_OP_CFG */
#define WM8994_GPN_OP_CFG_MASK 0x0200 /* GPN_OP_CFG */
#define WM8994_GPN_OP_CFG_SHIFT 9 /* GPN_OP_CFG */
#define WM8994_GPN_OP_CFG_WIDTH 1 /* GPN_OP_CFG */
#define WM8994_GPN_DB 0x0100 /* GPN_DB */
#define WM8994_GPN_DB_MASK 0x0100 /* GPN_DB */
#define WM8994_GPN_DB_SHIFT 8 /* GPN_DB */
#define WM8994_GPN_DB_WIDTH 1 /* GPN_DB */
#define WM8994_GPN_LVL 0x0040 /* GPN_LVL */
#define WM8994_GPN_LVL_MASK 0x0040 /* GPN_LVL */
#define WM8994_GPN_LVL_SHIFT 6 /* GPN_LVL */
#define WM8994_GPN_LVL_WIDTH 1 /* GPN_LVL */
#define WM8994_GPN_FN_MASK 0x001F /* GPN_FN - [4:0] */
#define WM8994_GPN_FN_SHIFT 0 /* GPN_FN - [4:0] */
#define WM8994_GPN_FN_WIDTH 5 /* GPN_FN - [4:0] */
#endif

View File

@ -0,0 +1,97 @@
/*
* include/linux/mfd/wm8994/pdata.h -- Platform data for WM8994
*
* 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.
*
*/
#ifndef __MFD_WM8994_PDATA_H__
#define __MFD_WM8994_PDATA_H__
#define WM8994_NUM_LDO 2
#define WM8994_NUM_GPIO 11
struct wm8994_ldo_pdata {
/** GPIOs to enable regulator, 0 or less if not available */
int enable;
const char *supply;
struct regulator_init_data *init_data;
};
#define WM8994_CONFIGURE_GPIO 0x8000
#define WM8994_DRC_REGS 5
#define WM8994_EQ_REGS 19
/**
* DRC configurations are specified with a label and a set of register
* values to write (the enable bits will be ignored). At runtime an
* enumerated control will be presented for each DRC block allowing
* the user to choose the configration to use.
*
* Configurations may be generated by hand or by using the DRC control
* panel provided by the WISCE - see http://www.wolfsonmicro.com/wisce/
* for details.
*/
struct wm8994_drc_cfg {
const char *name;
u16 regs[WM8994_DRC_REGS];
};
/**
* ReTune Mobile configurations are specified with a label, sample
* rate and set of values to write (the enable bits will be ignored).
*
* Configurations are expected to be generated using the ReTune Mobile
* control panel in WISCE - see http://www.wolfsonmicro.com/wisce/
*/
struct wm8994_retune_mobile_cfg {
const char *name;
unsigned int rate;
u16 regs[WM8994_EQ_REGS];
};
struct wm8994_pdata {
int gpio_base;
/**
* Default values for GPIOs if non-zero, WM8994_CONFIGURE_GPIO
* can be used for all zero values.
*/
int gpio_defaults[WM8994_NUM_GPIO];
struct wm8994_ldo_pdata ldo[WM8994_NUM_LDO];
int num_drc_cfgs;
struct wm8994_drc_cfg *drc_cfgs;
int num_retune_mobile_cfgs;
struct wm8994_retune_mobile_cfg *retune_mobile_cfgs;
/* LINEOUT can be differential or single ended */
unsigned int lineout1_diff:1;
unsigned int lineout2_diff:1;
/* Common mode feedback */
unsigned int lineout1fb:1;
unsigned int lineout2fb:1;
/* Microphone biases: 0=0.9*AVDD1 1=0.65*AVVD1 */
unsigned int micbias1_lvl:1;
unsigned int micbias2_lvl:1;
/* Jack detect threashold levels, see datasheet for values */
unsigned int jd_scthr:2;
unsigned int jd_thr:2;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -206,6 +206,12 @@
.get = snd_soc_dapm_get_enum_double, \
.put = snd_soc_dapm_put_enum_double, \
.private_value = (unsigned long)&xenum }
#define SOC_DAPM_ENUM_VIRT(xname, xenum) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_enum_double, \
.get = snd_soc_dapm_get_enum_virt, \
.put = snd_soc_dapm_put_enum_virt, \
.private_value = (unsigned long)&xenum }
#define SOC_DAPM_VALUE_ENUM(xname, xenum) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_enum_double, \
@ -260,6 +266,10 @@ int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_soc_dapm_get_enum_virt(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_soc_dapm_get_value_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol,

View File

@ -69,8 +69,8 @@ config SND_SOC_ALL_CODECS
config SND_SOC_WM_HUBS
tristate
default y if SND_SOC_WM8993=y
default m if SND_SOC_WM8993=m
default y if SND_SOC_WM8993=y || SND_SOC_WM8994=y
default m if SND_SOC_WM8993=m || SND_SOC_WM8994=m
config SND_SOC_AC97_CODEC
tristate

File diff suppressed because it is too large Load Diff

View File

@ -1,75 +1,26 @@
/*
* Copyright 2005 Openedhand Ltd.
*
* Author: Richard Purdie <richard@openedhand.com>
*
* Based on WM8753.h
* wm8994.h -- WM8994 Soc Audio driver
*
* 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.
*
*/
#ifndef _WM8994_H
#define _WM8994_H
/* WM8994 register space */
#include <sound/soc.h>
#define WM8994_LINVOL 0x0f
#define WM8994_RINVOL 0x01
#define WM8994_LOUT1V 0x02
#define WM8994_ROUT1V 0x03
#define WM8994_ADCDAC 0x05
#define WM8994_IFACE 0x07
#define WM8994_SRATE 0x08
#define WM8994_LDAC 0x0a
#define WM8994_RDAC 0x0b
#define WM8994_BASS 0x0c
#define WM8994_TREBLE 0x0d
#define WM8994_RESET 0x00
#define WM8994_3D 0x10
#define WM8994_ALC1 0x11
#define WM8994_ALC2 0x12
#define WM8994_ALC3 0x13
#define WM8994_NGATE 0x14
#define WM8994_LADC 0x15
#define WM8994_RADC 0x16
#define WM8994_ADCTL1 0x17
#define WM8994_ADCTL2 0x18
#define WM8994_PWR1 0x19
#define WM8994_PWR2 0x1a
#define WM8994_ADCTL3 0x1b
#define WM8994_ADCIN 0x1f
#define WM8994_LADCIN 0x20
#define WM8994_RADCIN 0x21
#define WM8994_LOUTM1 0x22
#define WM8994_LOUTM2 0x23
#define WM8994_ROUTM1 0x24
#define WM8994_ROUTM2 0x25
#define WM8994_LOUT2V 0x28
#define WM8994_ROUT2V 0x29
#define WM8994_LPPB 0x43
#define WM8994_NUM_REG 0x44
#define WM8994_SYSCLK 0
extern struct snd_soc_dai wm8994_dai;
extern struct snd_soc_codec_device soc_codec_dev_wm8994;
void wm8994_codec_set_volume(unsigned char mode,unsigned char volume);
extern struct snd_soc_dai wm8994_dai[];
struct wm8994_platform_data {
unsigned int mic_input;
unsigned int micBase_vcc;
unsigned int bb_input;
unsigned int bb_output;
unsigned int frequence;
unsigned int enable_pin;
unsigned int headset_pin;
unsigned int headset_call_vol;
unsigned int speaker_call_vol;
unsigned int earpiece_call_vol;
unsigned int bt_call_vol;
};
/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */
#define WM8994_SYSCLK_MCLK1 1
#define WM8994_SYSCLK_MCLK2 2
#define WM8994_SYSCLK_FLL1 3
#define WM8994_SYSCLK_FLL2 4
#define WM8994_FLL1 1
#define WM8994_FLL2 2
#endif

226
sound/soc/codecs/wm_hubs.c Normal file → Executable file
View File

@ -62,36 +62,108 @@ static const char *speaker_mode_text[] = {
static const struct soc_enum speaker_mode =
SOC_ENUM_SINGLE(WM8993_SPKMIXR_ATTENUATION, 8, 2, speaker_mode_text);
static void wait_for_dc_servo(struct snd_soc_codec *codec)
static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op)
{
unsigned int reg;
int count = 0;
unsigned int val;
val = op | WM8993_DCS_ENA_CHAN_0 | WM8993_DCS_ENA_CHAN_1;
/* Trigger the command */
snd_soc_write(codec, WM8993_DC_SERVO_0, val);
dev_info(codec->dev, "Waiting for DC servo...\n");
dev_dbg(codec->dev, "Waiting for DC servo...\n");
do {
count++;
msleep(1);
reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_0);
dev_dbg(codec->dev, "DC servo status: %x\n", reg);
} while ((reg & WM8993_DCS_CAL_COMPLETE_MASK)
!= WM8993_DCS_CAL_COMPLETE_MASK && count < 1000);
msleep(10);
reg = snd_soc_read(codec, WM8993_DC_SERVO_0);
dev_info(codec->dev, "DC servo: %x\n", reg);
} while (reg & op && count < 400);
if ((reg & WM8993_DCS_CAL_COMPLETE_MASK)
!= WM8993_DCS_CAL_COMPLETE_MASK)
if (reg & op)
dev_err(codec->dev, "Timed out waiting for DC Servo\n");
}
/*
* Startup calibration of the DC servo
*/
static void calibrate_dc_servo(struct snd_soc_codec *codec)
{
struct wm_hubs_data *hubs = codec->private_data;
u16 reg, reg_l, reg_r, dcs_cfg;
/* Set for 32 series updates */
snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
WM8993_DCS_SERIES_NO_01_MASK,
32 << WM8993_DCS_SERIES_NO_01_SHIFT);
wait_for_dc_servo(codec,
WM8993_DCS_TRIG_SERIES_0 | WM8993_DCS_TRIG_SERIES_1);
/* Apply correction to DC servo result */
if (hubs->dcs_codes) {
dev_dbg(codec->dev, "Applying %d code DC servo correction\n",
hubs->dcs_codes);
/* Different chips in the family support different
* readback methods.
*/
switch (hubs->dcs_readback_mode) {
case 0:
reg_l = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1)
& WM8993_DCS_INTEG_CHAN_0_MASK;;
reg_r = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2)
& WM8993_DCS_INTEG_CHAN_1_MASK;
break;
case 1:
reg = snd_soc_read(codec, WM8993_DC_SERVO_3);
reg_l = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)
>> WM8993_DCS_DAC_WR_VAL_1_SHIFT;
reg_r = reg & WM8993_DCS_DAC_WR_VAL_0_MASK;
break;
default:
WARN(1, "Unknown DCS readback method");
break;
}
/* HPOUT1L */
if (reg_l + hubs->dcs_codes > 0 &&
reg_l + hubs->dcs_codes < 0xff)
reg_l += hubs->dcs_codes;
dcs_cfg = reg_l << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
/* HPOUT1R */
if (reg_r + hubs->dcs_codes > 0 &&
reg_r + hubs->dcs_codes < 0xff)
reg_r += hubs->dcs_codes;
dcs_cfg |= reg_r;
/* Do it */
snd_soc_write(codec, WM8993_DC_SERVO_3, dcs_cfg);
wait_for_dc_servo(codec,
WM8993_DCS_TRIG_DAC_WR_0 |
WM8993_DCS_TRIG_DAC_WR_1);
}
}
/*
* Update the DC servo calibration on gain changes
*/
static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct wm_hubs_data *hubs = codec->private_data;
int ret;
ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
/* If we're applying an offset correction then updating the
* callibration would be likely to introduce further offsets. */
if (hubs->dcs_codes)
return ret;
/* Only need to do this if the outputs are active */
if (snd_soc_read(codec, WM8993_POWER_MANAGEMENT_1)
& (WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA))
@ -251,6 +323,47 @@ SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1,
line_tlv),
};
static int hp_supply_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = w->codec;
struct wm_hubs_data *hubs = codec->private_data;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
switch (hubs->hp_startup_mode) {
case 0:
break;
case 1:
/* Enable the headphone amp */
snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
WM8993_HPOUT1L_ENA |
WM8993_HPOUT1R_ENA,
WM8993_HPOUT1L_ENA |
WM8993_HPOUT1R_ENA);
/* Enable the second stage */
snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
WM8993_HPOUT1L_DLY |
WM8993_HPOUT1R_DLY,
WM8993_HPOUT1L_DLY |
WM8993_HPOUT1R_DLY);
break;
default:
dev_err(codec->dev, "Unknown HP startup mode %d\n",
hubs->hp_startup_mode);
break;
}
case SND_SOC_DAPM_PRE_PMD:
snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
WM8993_CP_ENA, 0);
break;
}
return 0;
}
static int hp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
@ -271,14 +384,11 @@ static int hp_event(struct snd_soc_dapm_widget *w,
reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY;
snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
/* Start the DC servo */
snd_soc_update_bits(codec, WM8993_DC_SERVO_0,
0xFFFF,
WM8993_DCS_ENA_CHAN_0 |
WM8993_DCS_ENA_CHAN_1 |
WM8993_DCS_TRIG_STARTUP_1 |
WM8993_DCS_TRIG_STARTUP_0);
wait_for_dc_servo(codec);
/* Smallest supported update interval */
snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
WM8993_DCS_TIMER_PERIOD_01_MASK, 1);
calibrate_dc_servo(codec);
reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT |
WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT;
@ -286,23 +396,19 @@ static int hp_event(struct snd_soc_dapm_widget *w,
break;
case SND_SOC_DAPM_PRE_PMD:
reg &= ~(WM8993_HPOUT1L_RMV_SHORT |
WM8993_HPOUT1L_DLY |
WM8993_HPOUT1L_OUTP |
WM8993_HPOUT1R_RMV_SHORT |
WM8993_HPOUT1R_DLY |
WM8993_HPOUT1R_OUTP);
snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
WM8993_HPOUT1L_DLY |
WM8993_HPOUT1R_DLY |
WM8993_HPOUT1L_RMV_SHORT |
WM8993_HPOUT1R_RMV_SHORT, 0);
snd_soc_update_bits(codec, WM8993_DC_SERVO_0,
0xffff, 0);
snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
WM8993_HPOUT1L_OUTP |
WM8993_HPOUT1R_OUTP, 0);
snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA,
0);
snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
WM8993_CP_ENA, 0);
break;
}
@ -438,11 +544,11 @@ static const struct snd_soc_dapm_widget analogue_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("IN1LN"),
SND_SOC_DAPM_INPUT("IN1LP"),
SND_SOC_DAPM_INPUT("IN2LN"),
SND_SOC_DAPM_INPUT("IN2LP/VXRN"),
SND_SOC_DAPM_INPUT("IN2LP:VXRN"),
SND_SOC_DAPM_INPUT("IN1RN"),
SND_SOC_DAPM_INPUT("IN1RP"),
SND_SOC_DAPM_INPUT("IN2RN"),
SND_SOC_DAPM_INPUT("IN2RP/VXRP"),
SND_SOC_DAPM_INPUT("IN2RP:VXRP"),
SND_SOC_DAPM_MICBIAS("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0),
SND_SOC_DAPM_MICBIAS("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0),
@ -473,6 +579,8 @@ SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0,
SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("Headphone Supply", SND_SOC_NOPM, 0, 0, hp_supply_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0,
NULL, 0,
hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
@ -537,14 +645,14 @@ static const struct snd_soc_dapm_route analogue_routes[] = {
{ "IN1R PGA", "IN1RP Switch", "IN1RP" },
{ "IN1R PGA", "IN1RN Switch", "IN1RN" },
{ "IN2L PGA", "IN2LP Switch", "IN2LP/VXRN" },
{ "IN2L PGA", "IN2LP Switch", "IN2LP:VXRN" },
{ "IN2L PGA", "IN2LN Switch", "IN2LN" },
{ "IN2R PGA", "IN2RP Switch", "IN2RP/VXRP" },
{ "IN2R PGA", "IN2RP Switch", "IN2RP:VXRP" },
{ "IN2R PGA", "IN2RN Switch", "IN2RN" },
{ "Direct Voice", NULL, "IN2LP/VXRN" },
{ "Direct Voice", NULL, "IN2RP/VXRP" },
{ "Direct Voice", NULL, "IN2LP:VXRN" },
{ "Direct Voice", NULL, "IN2RP:VXRP" },
{ "MIXINL", "IN1L Switch", "IN1L PGA" },
{ "MIXINL", "IN2L Switch", "IN2L PGA" },
@ -565,7 +673,7 @@ static const struct snd_soc_dapm_route analogue_routes[] = {
{ "Left Output Mixer", "Right Input Switch", "MIXINR" },
{ "Left Output Mixer", "IN2RN Switch", "IN2RN" },
{ "Left Output Mixer", "IN2LN Switch", "IN2LN" },
{ "Left Output Mixer", "IN2LP Switch", "IN2LP/VXRN" },
{ "Left Output Mixer", "IN2LP Switch", "IN2LP:VXRN" },
{ "Left Output Mixer", "IN1L Switch", "IN1L PGA" },
{ "Left Output Mixer", "IN1R Switch", "IN1R PGA" },
@ -573,7 +681,7 @@ static const struct snd_soc_dapm_route analogue_routes[] = {
{ "Right Output Mixer", "Right Input Switch", "MIXINR" },
{ "Right Output Mixer", "IN2LN Switch", "IN2LN" },
{ "Right Output Mixer", "IN2RN Switch", "IN2RN" },
{ "Right Output Mixer", "IN2RP Switch", "IN2RP/VXRP" },
{ "Right Output Mixer", "IN2RP Switch", "IN2RP:VXRP" },
{ "Right Output Mixer", "IN1L Switch", "IN1L PGA" },
{ "Right Output Mixer", "IN1R Switch", "IN1R PGA" },
@ -626,6 +734,7 @@ static const struct snd_soc_dapm_route analogue_routes[] = {
{ "Headphone PGA", NULL, "Left Headphone Mux" },
{ "Headphone PGA", NULL, "Right Headphone Mux" },
{ "Headphone PGA", NULL, "CLK_SYS" },
{ "Headphone PGA", NULL, "Headphone Supply" },
{ "HPOUT1L", NULL, "Headphone PGA" },
{ "HPOUT1R", NULL, "Headphone PGA" },
@ -738,6 +847,47 @@ int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec,
}
EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_routes);
int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *codec,
int lineout1_diff, int lineout2_diff,
int lineout1fb, int lineout2fb,
int jd_scthr, int jd_thr, int micbias1_lvl,
int micbias2_lvl)
{
if (!lineout1_diff)
snd_soc_update_bits(codec, WM8993_LINE_MIXER1,
WM8993_LINEOUT1_MODE,
WM8993_LINEOUT1_MODE);
if (!lineout2_diff)
snd_soc_update_bits(codec, WM8993_LINE_MIXER2,
WM8993_LINEOUT2_MODE,
WM8993_LINEOUT2_MODE);
/* If the line outputs are differential then we aren't presenting
* VMID as an output and can disable it.
*/
// if (lineout1_diff && lineout2_diff)
// codec->idle_bias_off = 1;
if (lineout1fb)
snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL,
WM8993_LINEOUT1_FB, WM8993_LINEOUT1_FB);
if (lineout2fb)
snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL,
WM8993_LINEOUT2_FB, WM8993_LINEOUT2_FB);
snd_soc_update_bits(codec, WM8993_MICBIAS,
WM8993_JD_SCTHR_MASK | WM8993_JD_THR_MASK |
WM8993_MICB1_LVL | WM8993_MICB2_LVL,
jd_scthr << WM8993_JD_SCTHR_SHIFT |
jd_thr << WM8993_JD_THR_SHIFT |
micbias1_lvl |
micbias2_lvl << WM8993_MICB2_LVL_SHIFT);
return 0;
}
EXPORT_SYMBOL_GPL(wm_hubs_handle_analogue_pdata);
MODULE_DESCRIPTION("Shared support for Wolfson hubs products");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");

12
sound/soc/codecs/wm_hubs.h Normal file → Executable file
View File

@ -18,7 +18,19 @@ struct snd_soc_codec;
extern const unsigned int wm_hubs_spkmix_tlv[];
/* This *must* be the first element of the codec->private_data struct */
struct wm_hubs_data {
int dcs_codes;
int dcs_readback_mode;
int hp_startup_mode;
};
extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *);
extern int wm_hubs_add_analogue_routes(struct snd_soc_codec *, int, int);
extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *,
int lineout1_diff, int lineout2_diff,
int lineout1fb, int lineout2fb,
int jd_scthr, int jd_thr,
int micbias1_lvl, int micbias2_lvl);
#endif

View File

@ -41,6 +41,15 @@ config SND_RK29_SOC_WM8900
Say Y if you want to add support for SoC audio on rockchip
with the WM8900.
config SND_RK29_SOC_WM8994
tristate "SoC I2S Audio support for rockchip - WM8994"
depends on SND_RK29_SOC && I2C_RK29
select SND_RK29_SOC_I2S
select SND_SOC_WM8994
help
Say Y if you want to add support for SoC audio on rockchip
with the WM8994.
config SND_RK29_SOC_RK1000
tristate "SoC I2S Audio support for rockchip - RK1000"
depends on SND_RK29_SOC && RK1000_CONTROL && I2C_RK29

View File

@ -426,7 +426,7 @@ static int rockchip_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
struct rockchip_runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
/**************add by qiuen for volume*****/
struct snd_soc_pcm_runtime *rtd = substream->private_data;
/* struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *pCodec_dai = rtd->dai->codec_dai;
int vol = 0;
int streamType = 0;
@ -438,7 +438,7 @@ static int rockchip_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
streamType = (substream->number / 100) % 100;
DBG("enter:vol=%d,streamType=%d\n",vol,streamType);
pCodec_dai->ops->set_volume(streamType, vol);
}
}*/
/****************************************************/
spin_lock(&prtd->lock);

207
sound/soc/rk29/rk29_wm8994.c Executable file
View File

@ -0,0 +1,207 @@
/*
* rk29_wm8994.c -- SoC audio for rockchip
*
* Driver for rockchip wm8994 audio
* Copyright (C) 2009 lhh
*
* 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/module.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <asm/io.h>
#include <mach/hardware.h>
#include <mach/rk29_iomap.h>
#include "../codecs/wm8994.h"
#include "rk29_pcm.h"
#include "rk29_i2s.h"
#if 0
#define DBG(x...) printk(KERN_INFO x)
#else
#define DBG(x...)
#endif
static int rk29_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
unsigned int pll_out = 0;
int ret;
DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
/*by Vincent Hsiung for EQ Vol Change*/
// #define HW_PARAMS_FLAG_EQVOL_ON 0x21
// #define HW_PARAMS_FLAG_EQVOL_OFF 0x22
// if ((params->flags == HW_PARAMS_FLAG_EQVOL_ON)||(params->flags == HW_PARAMS_FLAG_EQVOL_OFF))
// {
// ret = codec_dai->ops->hw_params(substream, params, codec_dai); //by Vincent
// }
// else
{
/* set codec DAI configuration */
#if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE)
DBG("Set codec_dai slave\n");
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
#endif
#if defined (CONFIG_SND_RK29_CODEC_SOC_MASTER)
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
DBG("Set codec_dai master\n",ret);
#endif
if (ret < 0)
return ret;
/* set cpu DAI configuration */
#if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE)
DBG("Set cpu_dai slave\n");
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
#endif
#if defined (CONFIG_SND_RK29_CODEC_SOC_MASTER)
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
DBG("Set cpu_dai master\n",ret);
#endif
if (ret < 0)
return ret;
}
switch(params_rate(params)) {
case 8000:
case 16000:
case 24000:
case 32000:
case 48000:
pll_out = 12288000;
break;
case 11025:
case 22050:
case 44100:
pll_out = 11289600;
break;
default:
DBG("Enter:%s, %d, Error rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
return -EINVAL;
break;
}
DBG("Enter:%s, %d, rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
//1、设置SYSCLK = FLL1
snd_soc_dai_set_sysclk(codec_dai,WM8994_SYSCLK_FLL1,12000000,pll_out);
//2、设置FLL1 CLK
snd_soc_dai_set_pll(codec_dai,WM8994_FLL1,12000000,pll_out);
return 0;
}
/*
static const struct snd_soc_dapm_widget rk2818_dapm_widgets[] = {
SND_SOC_DAPM_LINE("Audio Out", NULL),
SND_SOC_DAPM_LINE("Line in", NULL),
SND_SOC_DAPM_MIC("Micn", NULL),
SND_SOC_DAPM_MIC("Micp", NULL),
};
static const struct snd_soc_dapm_route audio_map[]= {
{"Audio Out", NULL, "HP_L"},
{"Audio Out", NULL, "HP_R"},
{"Line in", NULL, "RINPUT1"},
{"Line in", NULL, "LINPUT1"},
{"Micn", NULL, "RINPUT2"},
{"Micp", NULL, "LINPUT2"},
};
*/
/*
* Logic for a wm8994 as connected on a rockchip board.
codec一次?
*/
static int rk29_wm8994_init(struct snd_soc_codec *codec)
{
// struct snd_soc_dai *codec_dai = &codec->dai[0];
DBG("Enter %s::%s---%d\n",__FILE__,__FUNCTION__,__LINE__);
/* Add specific widgets */
// snd_soc_dapm_new_controls(codec, rk2818_dapm_widgets,
// ARRAY_SIZE(rk2818_dapm_widgets));
// snd_soc_dapm_nc_pin(codec, "LOUT2");
// snd_soc_dapm_nc_pin(codec, "ROUT2");
/* Set up specific audio path audio_mapnects */
// snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
// snd_soc_dapm_sync(codec);
return 0;
}
static struct snd_soc_ops rk29_ops = {
.hw_params = rk29_hw_params,
};
static struct snd_soc_dai_link rk29_dai = {
.name = "WM8994",
.stream_name = "WM8994 PCM",
.cpu_dai = &rk29_i2s_dai[0],
.codec_dai = &wm8994_dai,
.init = rk29_wm8994_init,
.ops = &rk29_ops,
};
static struct snd_soc_card snd_soc_card_rk29 = {
.name = "RK29_WM8994",
.platform = &rk29_soc_platform,
.dai_link = &rk29_dai,
.num_links = 1,
};
static struct snd_soc_device rk29_snd_devdata = {
.card = &snd_soc_card_rk29,
.codec_dev = &soc_codec_dev_wm8994,
};
static struct platform_device *rk29_snd_device;
static int __init audio_card_init(void)
{
int ret =0;
DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
rk29_snd_device = platform_device_alloc("soc-audio", -1);
if (!rk29_snd_device) {
DBG("platform device allocation failed\n");
ret = -ENOMEM;
return ret;
}
platform_set_drvdata(rk29_snd_device, &rk29_snd_devdata);
rk29_snd_devdata.dev = &rk29_snd_device->dev;
ret = platform_device_add(rk29_snd_device);
if (ret) {
DBG("platform device add failed\n");
platform_device_put(rk29_snd_device);
}
return ret;
}
static void __exit audio_card_exit(void)
{
platform_device_unregister(rk29_snd_device);
}
module_init(audio_card_init);
module_exit(audio_card_exit);
/* Module information */
MODULE_AUTHOR("rockchip");
MODULE_DESCRIPTION("ROCKCHIP i2s ASoC Interface");
MODULE_LICENSE("GPL");

View File

@ -1243,6 +1243,44 @@ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
return 0;
}
/* test and update the power status of a mux widget */
//copy on 2.6.34 by qjb
static int dapm_mux_update_power_34(struct snd_soc_dapm_widget *widget,
struct snd_kcontrol *kcontrol, int change,
int mux, struct soc_enum *e)
{
struct snd_soc_dapm_path *path;
int found = 0;
if (widget->id != snd_soc_dapm_mux &&
widget->id != snd_soc_dapm_value_mux)
return -ENODEV;
if (!change)
return 0;
/* find dapm widget path assoc with kcontrol */
list_for_each_entry(path, &widget->codec->dapm_paths, list) {
if (path->kcontrol != kcontrol)
continue;
if (!path->name || !e->texts[mux])
continue;
found = 1;
/* we now need to match the string in the enum to the path */
if (!(strcmp(path->name, e->texts[mux])))
path->connect = 1; /* new connection */
else
path->connect = 0; /* old connection must be powered down */
}
if (found)
dapm_power_widgets(widget->codec, SND_SOC_DAPM_STREAM_NOP);
return 0;
}
/* test and update the power status of a mixer or switch widget */
static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
struct snd_kcontrol *kcontrol, int reg,
@ -1807,6 +1845,56 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol,
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double);
/**
* snd_soc_dapm_get_enum_virt - Get virtual DAPM mux
* @kcontrol: mixer control
* @ucontrol: control element information
*
* Returns 0 for success.
*/
//copy on 2.6.34 by qjb
int snd_soc_dapm_get_enum_virt(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = widget->value;
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_virt);
/**
* snd_soc_dapm_put_enum_virt - Set virtual DAPM mux
* @kcontrol: mixer control
* @ucontrol: control element information
*
* Returns 0 for success.
*/
//copy on 2.6.34 by qjb
int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
struct soc_enum *e =
(struct soc_enum *)kcontrol->private_value;
int change;
int ret = 0;
if (ucontrol->value.enumerated.item[0] >= e->max)
return -EINVAL;
mutex_lock(&widget->codec->mutex);
change = widget->value != ucontrol->value.enumerated.item[0];
widget->value = ucontrol->value.enumerated.item[0];
dapm_mux_update_power_34(widget, kcontrol, change, widget->value, e);
mutex_unlock(&widget->codec->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_virt);
/**
* snd_soc_dapm_get_value_enum_double - dapm semi enumerated double mixer get
* callback