mirror of
https://github.com/torvalds/linux.git
synced 2026-06-08 14:42:37 +02:00
添加wm8994驱动支持
This commit is contained in:
parent
6d74f4e4f1
commit
84a568df72
36
arch/arm/configs/rk29_phonesdk_defconfig
Executable file → Normal file
36
arch/arm/configs/rk29_phonesdk_defconfig
Executable file → Normal 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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
205
drivers/gpio/wm8994-gpio.c
Normal 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");
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
538
drivers/mfd/wm8994-core.c
Executable 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], ®, 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>");
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
308
drivers/regulator/wm8994-regulator.c
Normal file
308
drivers/regulator/wm8994-regulator.c
Normal 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");
|
||||
54
include/linux/mfd/wm8994/core.h
Normal file
54
include/linux/mfd/wm8994/core.h
Normal 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
|
||||
72
include/linux/mfd/wm8994/gpio.h
Normal file
72
include/linux/mfd/wm8994/gpio.h
Normal 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
|
||||
97
include/linux/mfd/wm8994/pdata.h
Normal file
97
include/linux/mfd/wm8994/pdata.h
Normal 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
|
||||
4292
include/linux/mfd/wm8994/registers.h
Normal file
4292
include/linux/mfd/wm8994/registers.h
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
226
sound/soc/codecs/wm_hubs.c
Normal file → Executable 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
12
sound/soc/codecs/wm_hubs.h
Normal file → Executable 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
207
sound/soc/rk29/rk29_wm8994.c
Executable 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");
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user