diff options
Diffstat (limited to 'target/linux/pxa/patches-2.6.21/027-ucb1400-ac97-audio.patch')
-rw-r--r-- | target/linux/pxa/patches-2.6.21/027-ucb1400-ac97-audio.patch | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/target/linux/pxa/patches-2.6.21/027-ucb1400-ac97-audio.patch b/target/linux/pxa/patches-2.6.21/027-ucb1400-ac97-audio.patch new file mode 100644 index 0000000000..cb01d2c658 --- /dev/null +++ b/target/linux/pxa/patches-2.6.21/027-ucb1400-ac97-audio.patch @@ -0,0 +1,298 @@ +Index: linux-2.6.21gum/sound/pci/ac97/ac97_codec.c +=================================================================== +--- linux-2.6.21gum.orig/sound/pci/ac97/ac97_codec.c ++++ linux-2.6.21gum/sound/pci/ac97/ac97_codec.c +@@ -158,7 +158,7 @@ static const struct ac97_codec_id snd_ac + { 0x4e534300, 0xffffffff, "LM4540,43,45,46,48", NULL, NULL }, // only guess --jk + { 0x4e534331, 0xffffffff, "LM4549", NULL, NULL }, + { 0x4e534350, 0xffffffff, "LM4550", patch_lm4550, NULL }, // volume wrap fix +-{ 0x50534304, 0xffffffff, "UCB1400", patch_ucb1400, NULL }, ++{ 0x50534304, 0xffffffff, "UCB1400", patch_ucb1400, NULL, AC97_HAS_NO_STD_PCM }, + { 0x53494c20, 0xffffffe0, "Si3036,8", mpatch_si3036, mpatch_si3036, AC97_MODEM_PATCH }, + { 0x54524102, 0xffffffff, "TR28022", NULL, NULL }, + { 0x54524106, 0xffffffff, "TR28026", NULL, NULL }, +Index: linux-2.6.21gum/sound/pci/ac97/ac97_patch.c +=================================================================== +--- linux-2.6.21gum.orig/sound/pci/ac97/ac97_patch.c ++++ linux-2.6.21gum/sound/pci/ac97/ac97_patch.c +@@ -29,6 +29,10 @@ + #include <linux/slab.h> + #include <linux/mutex.h> + ++#include <linux/proc_fs.h> ++#include <linux/string.h> ++#include <linux/ctype.h> ++ + #include <sound/core.h> + #include <sound/pcm.h> + #include <sound/control.h> +@@ -406,6 +410,227 @@ int patch_yamaha_ymf753(struct snd_ac97 + } + + /* ++ * UCB1400 codec ++ */ ++ ++#define AC97_UCB1400_FCSR1 0x6a ++#define AC97_UCB1400_FCSR2 0x6c ++ ++static const struct snd_kcontrol_new ucb1400_snd_ac97_controls[] = { ++ AC97_SINGLE("Tone Control - Bass", AC97_UCB1400_FCSR1, 11, 4, 0), ++ AC97_SINGLE("Tone Control - Treble", AC97_UCB1400_FCSR1, 9, 2, 0), ++ AC97_SINGLE("Headphone Playback Switch", AC97_UCB1400_FCSR1, 6, 1, 0), ++ AC97_SINGLE("De-emphasis", AC97_UCB1400_FCSR1, 5, 1, 0), ++ AC97_SINGLE("DC Filter", AC97_UCB1400_FCSR1, 4, 1, 0), ++ AC97_SINGLE("Hi-pass Filter", AC97_UCB1400_FCSR1, 3, 1, 0), ++ AC97_SINGLE("ADC Filter", AC97_UCB1400_FCSR2, 12, 1, 0), ++}; ++ ++#define NUM_GPIO_LINES 10 ++ ++static struct proc_dir_entry *proc_gpio_parent; ++static struct proc_dir_entry *proc_gpios[NUM_GPIO_LINES]; ++ ++typedef struct ++{ ++ int gpio; ++ char name[32]; ++ struct snd_ac97 *ac97; ++} gpio_summary_type; ++ ++static gpio_summary_type gpio_summaries[NUM_GPIO_LINES] = ++{ ++ { 0, "UCB1400-0-0" }, ++ { 1, "UCB1400-0-1" }, ++ { 2, "UCB1400-0-2" }, ++ { 3, "UCB1400-0-3" }, ++ { 4, "UCB1400-0-4" }, ++ { 5, "UCB1400-0-5" }, ++ { 6, "UCB1400-0-6" }, ++ { 7, "UCB1400-0-7" }, ++ { 8, "UCB1400-0-8" }, ++ { 9, "UCB1400-0-9" } ++}; ++ ++ ++static int proc_ucb1400_ac97_gpio_write(struct file *file, const char __user *buf, ++ unsigned long count, void *data) ++{ ++ char *cur, lbuf[count + 1]; ++ gpio_summary_type *summary = data; ++ u32 direction_is_out, operation_is_set; ++ int i = summary->gpio; ++ u16 dir, value; ++ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EACCES; ++ ++ memset(lbuf, 0, count + 1); ++ ++ if (copy_from_user(lbuf, buf, count)) ++ return -EFAULT; ++ ++ cur = lbuf; ++ ++ // Get current values ++ direction_is_out = !!(snd_ac97_read(summary->ac97, 0x5c) & (0x0001 << i)); ++ operation_is_set = !!(snd_ac97_read(summary->ac97, 0x5a) & (0x0001 << i)); ++ while(1) ++ { ++ // We accept options: {GPIO|AF1|AF2|AF3}, {set|clear}, {in|out} ++ // Anything else is an error ++ while(cur[0] && (isspace(cur[0]) || ispunct(cur[0]))) cur = &(cur[1]); ++ ++ if('\0' == cur[0]) break; ++ ++ // Ok, so now we're pointing at the start of something ++ switch(cur[0]) ++ { ++ case 'G': ++ // Check that next is "PIO" -- '\0' will cause safe short-circuit if end of buf ++ if(!(cur[1] == 'P' && cur[2] == 'I' && cur[3] == 'O')) goto parse_error; ++ cur = &(cur[4]); ++ break; ++ case 's': ++ if(!(cur[1] == 'e' && cur[2] == 't')) goto parse_error; ++ operation_is_set = 1; ++ cur = &(cur[3]); ++ break; ++ case 'c': ++ if(!(cur[1] == 'l' && cur[2] == 'e' && cur[3] == 'a' && cur[4] == 'r')) goto ++parse_error; ++ operation_is_set = 0; ++ cur = &(cur[5]); ++ break; ++ case 'i': ++ if(!(cur[1] == 'n')) goto parse_error; ++ direction_is_out = 0; ++ cur = &(cur[2]); ++ break; ++ case 'o': ++ if(!(cur[1] == 'u' && cur[2] == 't')) goto parse_error; ++ direction_is_out = 1; ++ cur = &(cur[3]); ++ break; ++ default: goto parse_error; ++ } ++ } ++ ++ // set/get value ++ dir = snd_ac97_read(summary->ac97, 0x5c); ++ value = snd_ac97_read(summary->ac97, 0x5a); ++ if (direction_is_out) ++ { ++ dir |= 0x0001 << i; ++ if (operation_is_set) ++ { ++ value |= 0x0001 << i; ++ } ++ else ++ { ++ value &= ~(0x0001 << i); ++ } ++ ++ snd_ac97_write(summary->ac97, 0x5c, dir); ++ snd_ac97_write(summary->ac97, 0x5a, value); ++ } ++ else // direction in ++ { ++ dir &= ~(0x0001 << i); ++ snd_ac97_write(summary->ac97, 0x5c, dir); ++ operation_is_set = snd_ac97_read(summary->ac97, 0x5a) & ~(0x0001 << i); ++ } ++ ++#ifdef CONFIG_PROC_GPIO_DEBUG ++ printk(KERN_INFO "Set (%s,%s,%s) via /proc/gpio/%s\n", ++ "GPIO", ++ direction_is_out ? "out" : "in", ++ operation_is_set ? "set" : "clear", ++ summary->name); ++#endif ++ ++ return count; ++ ++parse_error: ++ printk(KERN_CRIT "Parse error: Expect \"GPIO|[set|clear]|[in|out] ...\"\n"); ++ return -EINVAL; ++} ++ ++static int proc_ucb1400_ac97_gpio_read(char *page, char **start, off_t off, ++ int count, int *eof, void *data) ++{ ++ char *p = page; ++ gpio_summary_type *summary = data; ++ int len, i; /*, af;*/ ++ i = summary->gpio; ++ ++ p += sprintf(p, "%d\t%s\t%s\t%s\n", i, ++ "GPIO", ++ (snd_ac97_read(summary->ac97, 0x5c) & (0x0001 << i)) ? "out" : "in", ++ (snd_ac97_read(summary->ac97, 0x5a) & (0x0001 << i)) ? "set" : "clear"); ++ ++ len = (p - page) - off; ++ ++ if(len < 0) ++ { ++ len = 0; ++ } ++ ++ *eof = (len <= count) ? 1 : 0; ++ *start = page + off; ++ ++ return len; ++} ++ ++int patch_ucb1400(struct snd_ac97 * ac97) ++{ ++ int err, i; ++ ++ proc_gpio_parent = proc_mkdir("gpio", NULL); ++ if(!proc_gpio_parent) return 0; ++ ++ for(i=0; i < NUM_GPIO_LINES; i++) ++ { ++ proc_gpios[i] = create_proc_entry(gpio_summaries[i].name, 0644, proc_gpio_parent); ++ if(proc_gpios[i]) ++ { ++ gpio_summaries[i].ac97 = ac97; ++ proc_gpios[i]->data = &gpio_summaries[i]; ++ proc_gpios[i]->read_proc = proc_ucb1400_ac97_gpio_read; ++ proc_gpios[i]->write_proc = proc_ucb1400_ac97_gpio_write; ++ } ++ } ++ ++ for(i = 0; i < ARRAY_SIZE(ucb1400_snd_ac97_controls); i++) { ++ if((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&ucb1400_snd_ac97_controls[i], ac97))) < 0) ++ return err; ++ } ++ ++ snd_ac97_write_cache(ac97, AC97_UCB1400_FCSR1, ++ (0 << 11) | // 0 base boost ++ (0 << 9) | // 0 treble boost ++ (0 << 7) | // Mode = flat ++ (1 << 6) | // Headphones enable ++ (0 << 5) | // De-emphasis disabled ++ (1 << 4) | // DC filter enabled ++ (1 << 3) | // Hi-pass filter enabled ++ (0 << 2) | // disable interrupt signalling via GPIO_INT ++ (1 << 0) // clear ADC overflow status if set ++ ); ++ ++ snd_ac97_write_cache(ac97, AC97_UCB1400_FCSR2, ++ (0 << 15) | // must be 0 ++ (0 << 13) | // must be 0 ++ (1 << 12) | // ADC filter enabled ++ (0 << 10) | // must be 0 ++ (0 << 4) | // Smart low power mode on neither Codec nor PLL ++ (0 << 0) // must be 0 ++ ); ++ ++ return 0; ++} ++ ++/* + * May 2, 2003 Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * removed broken wolfson00 patch. + * added support for WM9705,WM9708,WM9709,WM9710,WM9711,WM9712 and WM9717. +@@ -3408,41 +3633,3 @@ int patch_lm4550(struct snd_ac97 *ac97) + ac97->res_table = lm4550_restbl; + return 0; + } +- +-/* +- * UCB1400 codec (http://www.semiconductors.philips.com/acrobat_download/datasheets/UCB1400-02.pdf) +- */ +-static const struct snd_kcontrol_new snd_ac97_controls_ucb1400[] = { +-/* enable/disable headphone driver which allows direct connection to +- stereo headphone without the use of external DC blocking +- capacitors */ +-AC97_SINGLE("Headphone Driver", 0x6a, 6, 1, 0), +-/* Filter used to compensate the DC offset is added in the ADC to remove idle +- tones from the audio band. */ +-AC97_SINGLE("DC Filter", 0x6a, 4, 1, 0), +-/* Control smart-low-power mode feature. Allows automatic power down +- of unused blocks in the ADC analog front end and the PLL. */ +-AC97_SINGLE("Smart Low Power Mode", 0x6c, 4, 3, 0), +-}; +- +-static int patch_ucb1400_specific(struct snd_ac97 * ac97) +-{ +- int idx, err; +- for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_ucb1400); idx++) +- if ((err = snd_ctl_add(ac97->bus->card, snd_ctl_new1(&snd_ac97_controls_ucb1400[idx], ac97))) < 0) +- return err; +- return 0; +-} +- +-static struct snd_ac97_build_ops patch_ucb1400_ops = { +- .build_specific = patch_ucb1400_specific, +-}; +- +-int patch_ucb1400(struct snd_ac97 * ac97) +-{ +- ac97->build_ops = &patch_ucb1400_ops; +- /* enable headphone driver and smart low power mode by default */ +- snd_ac97_write(ac97, 0x6a, 0x0050); +- snd_ac97_write(ac97, 0x6c, 0x0030); +- return 0; +-} |