[ALSA] snd-aoa: add snd-aoa
This large patch adds all of snd-aoa. Consisting of many modules, it currently replaces snd-powermac for all layout-id based machines and handles many more (for example new powerbooks and powermacs with digital output that previously couldn't be used at all). It also has support for all layout-IDs that Apple has (judging from their Info.plist file) but not all are tested. The driver currently has 2 known regressions over snd-powermac: * it doesn't handle powermac 7,2 and 7,3 * it doesn't have a DRC control on snapper-based machines I will fix those during the 2.6.18 development cycle. Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
		
							parent
							
								
									41f0cd3a0c
								
							
						
					
					
						commit
						f3d9478b2c
					
				| @ -58,6 +58,8 @@ source "sound/pci/Kconfig" | ||||
| 
 | ||||
| source "sound/ppc/Kconfig" | ||||
| 
 | ||||
| source "sound/aoa/Kconfig" | ||||
| 
 | ||||
| source "sound/arm/Kconfig" | ||||
| 
 | ||||
| source "sound/mips/Kconfig" | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| obj-$(CONFIG_SOUND) += soundcore.o | ||||
| obj-$(CONFIG_SOUND_PRIME) += oss/ | ||||
| obj-$(CONFIG_DMASOUND) += oss/ | ||||
| obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/ usb/ sparc/ parisc/ pcmcia/ mips/ | ||||
| obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/ usb/ sparc/ parisc/ pcmcia/ mips/ aoa/ | ||||
| 
 | ||||
| ifeq ($(CONFIG_SND),y) | ||||
|   obj-y += last.o | ||||
|  | ||||
							
								
								
									
										17
									
								
								sound/aoa/Kconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								sound/aoa/Kconfig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| menu "Apple Onboard Audio driver" | ||||
| 	depends on SND != n && PPC | ||||
| 
 | ||||
| config SND_AOA | ||||
| 	tristate "Apple Onboard Audio driver" | ||||
| 	depends on SOUND && SND_PCM | ||||
| 	---help--- | ||||
| 	This option enables the new driver for the various | ||||
| 	Apple Onboard Audio components. | ||||
| 
 | ||||
| source "sound/aoa/fabrics/Kconfig" | ||||
| 
 | ||||
| source "sound/aoa/codecs/Kconfig" | ||||
| 
 | ||||
| source "sound/aoa/soundbus/Kconfig" | ||||
| 
 | ||||
| endmenu | ||||
							
								
								
									
										4
									
								
								sound/aoa/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								sound/aoa/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| obj-$(CONFIG_SND_AOA) += core/ | ||||
| obj-$(CONFIG_SND_AOA) += codecs/ | ||||
| obj-$(CONFIG_SND_AOA) += fabrics/ | ||||
| obj-$(CONFIG_SND_AOA_SOUNDBUS) += soundbus/ | ||||
							
								
								
									
										81
									
								
								sound/aoa/aoa-gpio.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								sound/aoa/aoa-gpio.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio GPIO definitions | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| 
 | ||||
| #ifndef __AOA_GPIO_H | ||||
| #define __AOA_GPIO_H | ||||
| #include <linux/workqueue.h> | ||||
| #include <linux/mutex.h> | ||||
| #include <asm/prom.h> | ||||
| 
 | ||||
| typedef void (*notify_func_t)(void *data); | ||||
| 
 | ||||
| enum notify_type { | ||||
| 	AOA_NOTIFY_HEADPHONE, | ||||
| 	AOA_NOTIFY_LINE_IN, | ||||
| 	AOA_NOTIFY_LINE_OUT, | ||||
| }; | ||||
| 
 | ||||
| struct gpio_runtime; | ||||
| struct gpio_methods { | ||||
| 	/* for initialisation/de-initialisation of the GPIO layer */ | ||||
| 	void (*init)(struct gpio_runtime *rt); | ||||
| 	void (*exit)(struct gpio_runtime *rt); | ||||
| 
 | ||||
| 	/* turn off headphone, speakers, lineout */ | ||||
| 	void (*all_amps_off)(struct gpio_runtime *rt); | ||||
| 	/* turn headphone, speakers, lineout back to previous setting */ | ||||
| 	void (*all_amps_restore)(struct gpio_runtime *rt); | ||||
| 
 | ||||
| 	void (*set_headphone)(struct gpio_runtime *rt, int on); | ||||
| 	void (*set_speakers)(struct gpio_runtime *rt, int on); | ||||
| 	void (*set_lineout)(struct gpio_runtime *rt, int on); | ||||
| 
 | ||||
| 	int (*get_headphone)(struct gpio_runtime *rt); | ||||
| 	int (*get_speakers)(struct gpio_runtime *rt); | ||||
| 	int (*get_lineout)(struct gpio_runtime *rt); | ||||
| 
 | ||||
| 	void (*set_hw_reset)(struct gpio_runtime *rt, int on); | ||||
| 
 | ||||
| 	/* use this to be notified of any events. The notification
 | ||||
| 	 * function is passed the data, and is called in process | ||||
| 	 * context by the use of schedule_work. | ||||
| 	 * The interface for it is that setting a function to NULL | ||||
| 	 * removes it, and they return 0 if the operation succeeded, | ||||
| 	 * and -EBUSY if the notification is already assigned by | ||||
| 	 * someone else. */ | ||||
| 	int (*set_notify)(struct gpio_runtime *rt, | ||||
| 			  enum notify_type type, | ||||
| 			  notify_func_t notify, | ||||
| 			  void *data); | ||||
| 	/* returns 0 if not plugged in, 1 if plugged in
 | ||||
| 	 * or a negative error code */ | ||||
| 	int (*get_detect)(struct gpio_runtime *rt, | ||||
| 			  enum notify_type type); | ||||
| }; | ||||
| 
 | ||||
| struct gpio_notification { | ||||
| 	notify_func_t notify; | ||||
| 	void *data; | ||||
| 	void *gpio_private; | ||||
| 	struct work_struct work; | ||||
| 	struct mutex mutex; | ||||
| }; | ||||
| 
 | ||||
| struct gpio_runtime { | ||||
| 	/* to be assigned by fabric */ | ||||
| 	struct device_node *node; | ||||
| 	/* since everyone needs this pointer anyway... */ | ||||
| 	struct gpio_methods *methods; | ||||
| 	/* to be used by the gpio implementation */ | ||||
| 	int implementation_private; | ||||
| 	struct gpio_notification headphone_notify; | ||||
| 	struct gpio_notification line_in_notify; | ||||
| 	struct gpio_notification line_out_notify; | ||||
| }; | ||||
| 
 | ||||
| #endif /* __AOA_GPIO_H */ | ||||
							
								
								
									
										131
									
								
								sound/aoa/aoa.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								sound/aoa/aoa.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio definitions | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| 
 | ||||
| #ifndef __AOA_H | ||||
| #define __AOA_H | ||||
| #include <asm/prom.h> | ||||
| #include <linux/module.h> | ||||
| /* So apparently there's a reason for requiring driver.h to be included first! */ | ||||
| #include <sound/driver.h> | ||||
| #include <sound/core.h> | ||||
| #include <sound/asound.h> | ||||
| #include <sound/control.h> | ||||
| #include "aoa-gpio.h" | ||||
| #include "soundbus/soundbus.h" | ||||
| 
 | ||||
| #define MAX_CODEC_NAME_LEN	32 | ||||
| 
 | ||||
| struct aoa_codec { | ||||
| 	char	name[MAX_CODEC_NAME_LEN]; | ||||
| 
 | ||||
| 	struct module *owner; | ||||
| 
 | ||||
| 	/* called when the fabric wants to init this codec.
 | ||||
| 	 * Do alsa card manipulations from here. */ | ||||
| 	int (*init)(struct aoa_codec *codec); | ||||
| 
 | ||||
| 	/* called when the fabric is done with the codec.
 | ||||
| 	 * The alsa card will be cleaned up so don't bother. */ | ||||
| 	void (*exit)(struct aoa_codec *codec); | ||||
| 
 | ||||
| 	/* May be NULL, but can be used by the fabric.
 | ||||
| 	 * Refcounting is the codec driver's responsibility */ | ||||
| 	struct device_node *node; | ||||
| 
 | ||||
| 	/* assigned by fabric before init() is called, points
 | ||||
| 	 * to the soundbus device. Cannot be NULL. */ | ||||
| 	struct soundbus_dev *soundbus_dev; | ||||
| 
 | ||||
| 	/* assigned by the fabric before init() is called, points
 | ||||
| 	 * to the fabric's gpio runtime record for the relevant | ||||
| 	 * device. */ | ||||
| 	struct gpio_runtime *gpio; | ||||
| 
 | ||||
| 	/* assigned by the fabric before init() is called, contains
 | ||||
| 	 * a codec specific bitmask of what outputs and inputs are | ||||
| 	 * actually connected */ | ||||
| 	u32 connected; | ||||
| 
 | ||||
| 	/* data the fabric can associate with this structure */ | ||||
| 	void *fabric_data; | ||||
| 
 | ||||
| 	/* private! */ | ||||
| 	struct list_head list; | ||||
| 	struct aoa_fabric *fabric; | ||||
| }; | ||||
| 
 | ||||
| /* return 0 on success */ | ||||
| extern int | ||||
| aoa_codec_register(struct aoa_codec *codec); | ||||
| extern void | ||||
| aoa_codec_unregister(struct aoa_codec *codec); | ||||
| 
 | ||||
| #define MAX_LAYOUT_NAME_LEN	32 | ||||
| 
 | ||||
| struct aoa_fabric { | ||||
| 	char	name[MAX_LAYOUT_NAME_LEN]; | ||||
| 
 | ||||
| 	struct module *owner; | ||||
| 
 | ||||
| 	/* once codecs register, they are passed here after.
 | ||||
| 	 * They are of course not initialised, since the | ||||
| 	 * fabric is responsible for initialising some fields | ||||
| 	 * in the codec structure! */ | ||||
| 	int (*found_codec)(struct aoa_codec *codec); | ||||
| 	/* called for each codec when it is removed,
 | ||||
| 	 * also in the case that aoa_fabric_unregister | ||||
| 	 * is called and all codecs are removed | ||||
| 	 * from this fabric. | ||||
| 	 * Also called if found_codec returned 0 but | ||||
| 	 * the codec couldn't initialise. */ | ||||
| 	void (*remove_codec)(struct aoa_codec *codec); | ||||
| 	/* If found_codec returned 0, and the codec
 | ||||
| 	 * could be initialised, this is called. */ | ||||
| 	void (*attached_codec)(struct aoa_codec *codec); | ||||
| }; | ||||
| 
 | ||||
| /* return 0 on success, -EEXIST if another fabric is
 | ||||
|  * registered, -EALREADY if the same fabric is registered. | ||||
|  * Passing NULL can be used to test for the presence | ||||
|  * of another fabric, if -EALREADY is returned there is | ||||
|  * no other fabric present. | ||||
|  * In the case that the function returns -EALREADY | ||||
|  * and the fabric passed is not NULL, all codecs | ||||
|  * that are not assigned yet are passed to the fabric | ||||
|  * again for reconsideration. */ | ||||
| extern int | ||||
| aoa_fabric_register(struct aoa_fabric *fabric); | ||||
| 
 | ||||
| /* it is vital to call this when the fabric exits!
 | ||||
|  * When calling, the remove_codec will be called | ||||
|  * for all codecs, unless it is NULL. */ | ||||
| extern void | ||||
| aoa_fabric_unregister(struct aoa_fabric *fabric); | ||||
| 
 | ||||
| /* if for some reason you want to get rid of a codec
 | ||||
|  * before the fabric is removed, use this. | ||||
|  * Note that remove_codec is called for it! */ | ||||
| extern void | ||||
| aoa_fabric_unlink_codec(struct aoa_codec *codec); | ||||
| 
 | ||||
| /* alsa help methods */ | ||||
| struct aoa_card { | ||||
| 	struct snd_card *alsa_card; | ||||
| }; | ||||
|          | ||||
| extern int aoa_snd_device_new(snd_device_type_t type, | ||||
| 	void * device_data, struct snd_device_ops * ops); | ||||
| extern struct snd_card *aoa_get_card(void); | ||||
| extern int aoa_snd_ctl_add(struct snd_kcontrol* control); | ||||
| 
 | ||||
| /* GPIO stuff */ | ||||
| extern struct gpio_methods *pmf_gpio_methods; | ||||
| extern struct gpio_methods *ftr_gpio_methods; | ||||
| /* extern struct gpio_methods *map_gpio_methods; */ | ||||
| 
 | ||||
| #endif /* __AOA_H */ | ||||
							
								
								
									
										32
									
								
								sound/aoa/codecs/Kconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								sound/aoa/codecs/Kconfig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| config SND_AOA_ONYX | ||||
| 	tristate "support Onyx chip" | ||||
| 	depends on SND_AOA | ||||
| 	---help--- | ||||
| 	This option enables support for the Onyx (pcm3052) | ||||
| 	codec chip found in the latest Apple machines | ||||
| 	(most of those with digital audio output). | ||||
| 
 | ||||
| #config SND_AOA_TOPAZ | ||||
| #	tristate "support Topaz chips" | ||||
| #	depends on SND_AOA | ||||
| #	---help--- | ||||
| #	This option enables support for the Topaz (CS84xx) | ||||
| #	codec chips found in the latest Apple machines, | ||||
| #	these chips do the digital input and output on | ||||
| #	some PowerMacs. | ||||
| 
 | ||||
| config SND_AOA_TAS | ||||
| 	tristate "support TAS chips" | ||||
| 	depends on SND_AOA | ||||
| 	---help--- | ||||
| 	This option enables support for the tas chips | ||||
| 	found in a lot of Apple Machines, especially | ||||
| 	iBooks and PowerBooks without digital. | ||||
| 
 | ||||
| config SND_AOA_TOONIE | ||||
| 	tristate "support Toonie chip" | ||||
| 	depends on SND_AOA | ||||
| 	---help--- | ||||
| 	This option enables support for the toonie codec | ||||
| 	found in the Mac Mini. If you have a Mac Mini and | ||||
| 	want to hear sound, select this option. | ||||
							
								
								
									
										3
									
								
								sound/aoa/codecs/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								sound/aoa/codecs/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| obj-$(CONFIG_SND_AOA_ONYX) += snd-aoa-codec-onyx.o | ||||
| obj-$(CONFIG_SND_AOA_TAS) += snd-aoa-codec-tas.o | ||||
| obj-$(CONFIG_SND_AOA_TOONIE) += snd-aoa-codec-toonie.o | ||||
							
								
								
									
										1113
									
								
								sound/aoa/codecs/snd-aoa-codec-onyx.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1113
									
								
								sound/aoa/codecs/snd-aoa-codec-onyx.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										76
									
								
								sound/aoa/codecs/snd-aoa-codec-onyx.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								sound/aoa/codecs/snd-aoa-codec-onyx.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio driver for Onyx codec (header) | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| #ifndef __SND_AOA_CODEC_ONYX_H | ||||
| #define __SND_AOA_CODEC_ONYX_H | ||||
| #include <stddef.h> | ||||
| #include <linux/i2c.h> | ||||
| #include <linux/i2c-dev.h> | ||||
| #include <asm/pmac_low_i2c.h> | ||||
| #include <asm/prom.h> | ||||
| 
 | ||||
| /* PCM3052 register definitions */ | ||||
| 
 | ||||
| /* the attenuation registers take values from
 | ||||
|  * -1 (0dB) to -127 (-63.0 dB) or others (muted) */ | ||||
| #define ONYX_REG_DAC_ATTEN_LEFT		65 | ||||
| #define FIRSTREGISTER			ONYX_REG_DAC_ATTEN_LEFT | ||||
| #define ONYX_REG_DAC_ATTEN_RIGHT	66 | ||||
| 
 | ||||
| #define ONYX_REG_CONTROL		67 | ||||
| #	define ONYX_MRST		(1<<7) | ||||
| #	define ONYX_SRST		(1<<6) | ||||
| #	define ONYX_ADPSV		(1<<5) | ||||
| #	define ONYX_DAPSV		(1<<4) | ||||
| #	define ONYX_SILICONVERSION	(1<<0) | ||||
| /* all others reserved */ | ||||
| 
 | ||||
| #define ONYX_REG_DAC_CONTROL		68 | ||||
| #	define ONYX_OVR1		(1<<6) | ||||
| #	define ONYX_MUTE_RIGHT		(1<<1) | ||||
| #	define ONYX_MUTE_LEFT		(1<<0) | ||||
| 
 | ||||
| #define ONYX_REG_DAC_DEEMPH		69 | ||||
| #	define ONYX_DIGDEEMPH_SHIFT	5 | ||||
| #	define ONYX_DIGDEEMPH_MASK	(3<<ONYX_DIGDEEMPH_SHIFT) | ||||
| #	define ONYX_DIGDEEMPH_CTRL	(1<<4) | ||||
| 
 | ||||
| #define ONYX_REG_DAC_FILTER		70 | ||||
| #	define ONYX_ROLLOFF_FAST	(1<<5) | ||||
| #	define ONYX_DAC_FILTER_ALWAYS	(1<<2) | ||||
| 
 | ||||
| #define	ONYX_REG_DAC_OUTPHASE		71 | ||||
| #	define ONYX_OUTPHASE_INVERTED	(1<<0) | ||||
| 
 | ||||
| #define ONYX_REG_ADC_CONTROL		72 | ||||
| #	define ONYX_ADC_INPUT_MIC	(1<<5) | ||||
| /* 8 + input gain in dB, valid range for input gain is -4 .. 20 dB */ | ||||
| #	define ONYX_ADC_PGA_GAIN_MASK	0x1f | ||||
| 
 | ||||
| #define ONYX_REG_ADC_HPF_BYPASS		75 | ||||
| #	define ONYX_HPF_DISABLE		(1<<3) | ||||
| #	define ONYX_ADC_HPF_ALWAYS	(1<<2) | ||||
| 
 | ||||
| #define ONYX_REG_DIG_INFO1		77 | ||||
| #	define ONYX_MASK_DIN_TO_BPZ	(1<<7) | ||||
| /* bits 1-5 control channel bits 1-5 */ | ||||
| #	define ONYX_DIGOUT_DISABLE	(1<<0) | ||||
| 
 | ||||
| #define ONYX_REG_DIG_INFO2		78 | ||||
| /* controls channel bits 8-15 */ | ||||
| 
 | ||||
| #define ONYX_REG_DIG_INFO3		79 | ||||
| /* control channel bits 24-29, high 2 bits reserved */ | ||||
| 
 | ||||
| #define ONYX_REG_DIG_INFO4		80 | ||||
| #	define ONYX_VALIDL		(1<<7) | ||||
| #	define ONYX_VALIDR		(1<<6) | ||||
| #	define ONYX_SPDIF_ENABLE	(1<<5) | ||||
| /* lower 4 bits control bits 32-35 of channel control and word length */ | ||||
| #	define ONYX_WORDLEN_MASK	(0xF) | ||||
| 
 | ||||
| #endif /* __SND_AOA_CODEC_ONYX_H */ | ||||
							
								
								
									
										209
									
								
								sound/aoa/codecs/snd-aoa-codec-tas-gain-table.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								sound/aoa/codecs/snd-aoa-codec-tas-gain-table.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,209 @@ | ||||
| /*
 | ||||
|  This is the program used to generate below table. | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <math.h> | ||||
| int main() { | ||||
|   int dB2; | ||||
|   printf("/" "* This file is only included exactly once!\n"); | ||||
|   printf(" *\n"); | ||||
|   printf(" * If they'd only tell us that generating this table was\n"); | ||||
|   printf(" * as easy as calculating\n"); | ||||
|   printf(" *      hwvalue = 1048576.0*exp(0.057564628*dB*2)\n"); | ||||
|   printf(" * :) *" "/\n"); | ||||
|   printf("static int tas_gaintable[] = {\n"); | ||||
|   printf("	0x000000, /" "* -infinity dB *" "/\n"); | ||||
|   for (dB2=-140;dB2<=36;dB2++) | ||||
|     printf("	0x%.6x, /" "* %-02.1f dB *" "/\n", (int)(1048576.0*exp(0.057564628*dB2)), dB2/2.0); | ||||
|   printf("};\n\n"); | ||||
| } | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| /* This file is only included exactly once!
 | ||||
|  * | ||||
|  * If they'd only tell us that generating this table was | ||||
|  * as easy as calculating | ||||
|  *      hwvalue = 1048576.0*exp(0.057564628*dB*2) | ||||
|  * :) */ | ||||
| static int tas_gaintable[] = { | ||||
| 	0x000000, /* -infinity dB */ | ||||
| 	0x00014b, /* -70.0 dB */ | ||||
| 	0x00015f, /* -69.5 dB */ | ||||
| 	0x000174, /* -69.0 dB */ | ||||
| 	0x00018a, /* -68.5 dB */ | ||||
| 	0x0001a1, /* -68.0 dB */ | ||||
| 	0x0001ba, /* -67.5 dB */ | ||||
| 	0x0001d4, /* -67.0 dB */ | ||||
| 	0x0001f0, /* -66.5 dB */ | ||||
| 	0x00020d, /* -66.0 dB */ | ||||
| 	0x00022c, /* -65.5 dB */ | ||||
| 	0x00024d, /* -65.0 dB */ | ||||
| 	0x000270, /* -64.5 dB */ | ||||
| 	0x000295, /* -64.0 dB */ | ||||
| 	0x0002bc, /* -63.5 dB */ | ||||
| 	0x0002e6, /* -63.0 dB */ | ||||
| 	0x000312, /* -62.5 dB */ | ||||
| 	0x000340, /* -62.0 dB */ | ||||
| 	0x000372, /* -61.5 dB */ | ||||
| 	0x0003a6, /* -61.0 dB */ | ||||
| 	0x0003dd, /* -60.5 dB */ | ||||
| 	0x000418, /* -60.0 dB */ | ||||
| 	0x000456, /* -59.5 dB */ | ||||
| 	0x000498, /* -59.0 dB */ | ||||
| 	0x0004de, /* -58.5 dB */ | ||||
| 	0x000528, /* -58.0 dB */ | ||||
| 	0x000576, /* -57.5 dB */ | ||||
| 	0x0005c9, /* -57.0 dB */ | ||||
| 	0x000620, /* -56.5 dB */ | ||||
| 	0x00067d, /* -56.0 dB */ | ||||
| 	0x0006e0, /* -55.5 dB */ | ||||
| 	0x000748, /* -55.0 dB */ | ||||
| 	0x0007b7, /* -54.5 dB */ | ||||
| 	0x00082c, /* -54.0 dB */ | ||||
| 	0x0008a8, /* -53.5 dB */ | ||||
| 	0x00092b, /* -53.0 dB */ | ||||
| 	0x0009b6, /* -52.5 dB */ | ||||
| 	0x000a49, /* -52.0 dB */ | ||||
| 	0x000ae5, /* -51.5 dB */ | ||||
| 	0x000b8b, /* -51.0 dB */ | ||||
| 	0x000c3a, /* -50.5 dB */ | ||||
| 	0x000cf3, /* -50.0 dB */ | ||||
| 	0x000db8, /* -49.5 dB */ | ||||
| 	0x000e88, /* -49.0 dB */ | ||||
| 	0x000f64, /* -48.5 dB */ | ||||
| 	0x00104e, /* -48.0 dB */ | ||||
| 	0x001145, /* -47.5 dB */ | ||||
| 	0x00124b, /* -47.0 dB */ | ||||
| 	0x001361, /* -46.5 dB */ | ||||
| 	0x001487, /* -46.0 dB */ | ||||
| 	0x0015be, /* -45.5 dB */ | ||||
| 	0x001708, /* -45.0 dB */ | ||||
| 	0x001865, /* -44.5 dB */ | ||||
| 	0x0019d8, /* -44.0 dB */ | ||||
| 	0x001b60, /* -43.5 dB */ | ||||
| 	0x001cff, /* -43.0 dB */ | ||||
| 	0x001eb7, /* -42.5 dB */ | ||||
| 	0x002089, /* -42.0 dB */ | ||||
| 	0x002276, /* -41.5 dB */ | ||||
| 	0x002481, /* -41.0 dB */ | ||||
| 	0x0026ab, /* -40.5 dB */ | ||||
| 	0x0028f5, /* -40.0 dB */ | ||||
| 	0x002b63, /* -39.5 dB */ | ||||
| 	0x002df5, /* -39.0 dB */ | ||||
| 	0x0030ae, /* -38.5 dB */ | ||||
| 	0x003390, /* -38.0 dB */ | ||||
| 	0x00369e, /* -37.5 dB */ | ||||
| 	0x0039db, /* -37.0 dB */ | ||||
| 	0x003d49, /* -36.5 dB */ | ||||
| 	0x0040ea, /* -36.0 dB */ | ||||
| 	0x0044c3, /* -35.5 dB */ | ||||
| 	0x0048d6, /* -35.0 dB */ | ||||
| 	0x004d27, /* -34.5 dB */ | ||||
| 	0x0051b9, /* -34.0 dB */ | ||||
| 	0x005691, /* -33.5 dB */ | ||||
| 	0x005bb2, /* -33.0 dB */ | ||||
| 	0x006121, /* -32.5 dB */ | ||||
| 	0x0066e3, /* -32.0 dB */ | ||||
| 	0x006cfb, /* -31.5 dB */ | ||||
| 	0x007370, /* -31.0 dB */ | ||||
| 	0x007a48, /* -30.5 dB */ | ||||
| 	0x008186, /* -30.0 dB */ | ||||
| 	0x008933, /* -29.5 dB */ | ||||
| 	0x009154, /* -29.0 dB */ | ||||
| 	0x0099f1, /* -28.5 dB */ | ||||
| 	0x00a310, /* -28.0 dB */ | ||||
| 	0x00acba, /* -27.5 dB */ | ||||
| 	0x00b6f6, /* -27.0 dB */ | ||||
| 	0x00c1cd, /* -26.5 dB */ | ||||
| 	0x00cd49, /* -26.0 dB */ | ||||
| 	0x00d973, /* -25.5 dB */ | ||||
| 	0x00e655, /* -25.0 dB */ | ||||
| 	0x00f3fb, /* -24.5 dB */ | ||||
| 	0x010270, /* -24.0 dB */ | ||||
| 	0x0111c0, /* -23.5 dB */ | ||||
| 	0x0121f9, /* -23.0 dB */ | ||||
| 	0x013328, /* -22.5 dB */ | ||||
| 	0x01455b, /* -22.0 dB */ | ||||
| 	0x0158a2, /* -21.5 dB */ | ||||
| 	0x016d0e, /* -21.0 dB */ | ||||
| 	0x0182af, /* -20.5 dB */ | ||||
| 	0x019999, /* -20.0 dB */ | ||||
| 	0x01b1de, /* -19.5 dB */ | ||||
| 	0x01cb94, /* -19.0 dB */ | ||||
| 	0x01e6cf, /* -18.5 dB */ | ||||
| 	0x0203a7, /* -18.0 dB */ | ||||
| 	0x022235, /* -17.5 dB */ | ||||
| 	0x024293, /* -17.0 dB */ | ||||
| 	0x0264db, /* -16.5 dB */ | ||||
| 	0x02892c, /* -16.0 dB */ | ||||
| 	0x02afa3, /* -15.5 dB */ | ||||
| 	0x02d862, /* -15.0 dB */ | ||||
| 	0x03038a, /* -14.5 dB */ | ||||
| 	0x033142, /* -14.0 dB */ | ||||
| 	0x0361af, /* -13.5 dB */ | ||||
| 	0x0394fa, /* -13.0 dB */ | ||||
| 	0x03cb50, /* -12.5 dB */ | ||||
| 	0x0404de, /* -12.0 dB */ | ||||
| 	0x0441d5, /* -11.5 dB */ | ||||
| 	0x048268, /* -11.0 dB */ | ||||
| 	0x04c6d0, /* -10.5 dB */ | ||||
| 	0x050f44, /* -10.0 dB */ | ||||
| 	0x055c04, /* -9.5 dB */ | ||||
| 	0x05ad50, /* -9.0 dB */ | ||||
| 	0x06036e, /* -8.5 dB */ | ||||
| 	0x065ea5, /* -8.0 dB */ | ||||
| 	0x06bf44, /* -7.5 dB */ | ||||
| 	0x07259d, /* -7.0 dB */ | ||||
| 	0x079207, /* -6.5 dB */ | ||||
| 	0x0804dc, /* -6.0 dB */ | ||||
| 	0x087e80, /* -5.5 dB */ | ||||
| 	0x08ff59, /* -5.0 dB */ | ||||
| 	0x0987d5, /* -4.5 dB */ | ||||
| 	0x0a1866, /* -4.0 dB */ | ||||
| 	0x0ab189, /* -3.5 dB */ | ||||
| 	0x0b53be, /* -3.0 dB */ | ||||
| 	0x0bff91, /* -2.5 dB */ | ||||
| 	0x0cb591, /* -2.0 dB */ | ||||
| 	0x0d765a, /* -1.5 dB */ | ||||
| 	0x0e4290, /* -1.0 dB */ | ||||
| 	0x0f1adf, /* -0.5 dB */ | ||||
| 	0x100000, /* 0.0 dB */ | ||||
| 	0x10f2b4, /* 0.5 dB */ | ||||
| 	0x11f3c9, /* 1.0 dB */ | ||||
| 	0x13041a, /* 1.5 dB */ | ||||
| 	0x14248e, /* 2.0 dB */ | ||||
| 	0x15561a, /* 2.5 dB */ | ||||
| 	0x1699c0, /* 3.0 dB */ | ||||
| 	0x17f094, /* 3.5 dB */ | ||||
| 	0x195bb8, /* 4.0 dB */ | ||||
| 	0x1adc61, /* 4.5 dB */ | ||||
| 	0x1c73d5, /* 5.0 dB */ | ||||
| 	0x1e236d, /* 5.5 dB */ | ||||
| 	0x1fec98, /* 6.0 dB */ | ||||
| 	0x21d0d9, /* 6.5 dB */ | ||||
| 	0x23d1cd, /* 7.0 dB */ | ||||
| 	0x25f125, /* 7.5 dB */ | ||||
| 	0x2830af, /* 8.0 dB */ | ||||
| 	0x2a9254, /* 8.5 dB */ | ||||
| 	0x2d1818, /* 9.0 dB */ | ||||
| 	0x2fc420, /* 9.5 dB */ | ||||
| 	0x3298b0, /* 10.0 dB */ | ||||
| 	0x35982f, /* 10.5 dB */ | ||||
| 	0x38c528, /* 11.0 dB */ | ||||
| 	0x3c224c, /* 11.5 dB */ | ||||
| 	0x3fb278, /* 12.0 dB */ | ||||
| 	0x4378b0, /* 12.5 dB */ | ||||
| 	0x477829, /* 13.0 dB */ | ||||
| 	0x4bb446, /* 13.5 dB */ | ||||
| 	0x5030a1, /* 14.0 dB */ | ||||
| 	0x54f106, /* 14.5 dB */ | ||||
| 	0x59f980, /* 15.0 dB */ | ||||
| 	0x5f4e52, /* 15.5 dB */ | ||||
| 	0x64f403, /* 16.0 dB */ | ||||
| 	0x6aef5e, /* 16.5 dB */ | ||||
| 	0x714575, /* 17.0 dB */ | ||||
| 	0x77fbaa, /* 17.5 dB */ | ||||
| 	0x7f17af, /* 18.0 dB */ | ||||
| }; | ||||
| 
 | ||||
							
								
								
									
										654
									
								
								sound/aoa/codecs/snd-aoa-codec-tas.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										654
									
								
								sound/aoa/codecs/snd-aoa-codec-tas.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,654 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio driver for tas codec | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  * | ||||
|  * Open questions: | ||||
|  *  - How to distinguish between 3004 and versions? | ||||
|  * | ||||
|  * FIXMEs: | ||||
|  *  - This codec driver doesn't honour the 'connected' | ||||
|  *    property of the aoa_codec struct, hence if | ||||
|  *    it is used in machines where not everything is | ||||
|  *    connected it will display wrong mixer elements. | ||||
|  *  - Driver assumes that the microphone is always | ||||
|  *    monaureal and connected to the right channel of | ||||
|  *    the input. This should also be a codec-dependent | ||||
|  *    flag, maybe the codec should have 3 different | ||||
|  *    bits for the three different possibilities how | ||||
|  *    it can be hooked up... | ||||
|  *    But as long as I don't see any hardware hooked | ||||
|  *    up that way... | ||||
|  *  - As Apple notes in their code, the tas3004 seems | ||||
|  *    to delay the right channel by one sample. You can | ||||
|  *    see this when for example recording stereo in | ||||
|  *    audacity, or recording the tas output via cable | ||||
|  *    on another machine (use a sinus generator or so). | ||||
|  *    I tried programming the BiQuads but couldn't | ||||
|  *    make the delay work, maybe someone can read the | ||||
|  *    datasheet and fix it. The relevant Apple comment | ||||
|  *    is in AppleTAS3004Audio.cpp lines 1637 ff. Note | ||||
|  *    that their comment describing how they program | ||||
|  *    the filters sucks... | ||||
|  * | ||||
|  * Other things: | ||||
|  *  - this should actually register *two* aoa_codec | ||||
|  *    structs since it has two inputs. Then it must | ||||
|  *    use the prepare callback to forbid running the | ||||
|  *    secondary output on a different clock. | ||||
|  *    Also, whatever bus knows how to do this must | ||||
|  *    provide two soundbus_dev devices and the fabric | ||||
|  *    must be able to link them correctly. | ||||
|  * | ||||
|  *    I don't even know if Apple ever uses the second | ||||
|  *    port on the tas3004 though, I don't think their | ||||
|  *    i2s controllers can even do it. OTOH, they all | ||||
|  *    derive the clocks from common clocks, so it | ||||
|  *    might just be possible. The framework allows the | ||||
|  *    codec to refine the transfer_info items in the | ||||
|  *    usable callback, so we can simply remove the | ||||
|  *    rates the second instance is not using when it | ||||
|  *    actually is in use. | ||||
|  *    Maybe we'll need to make the sound busses have | ||||
|  *    a 'clock group id' value so the codec can | ||||
|  *    determine if the two outputs can be driven at | ||||
|  *    the same time. But that is likely overkill, up | ||||
|  *    to the fabric to not link them up incorrectly, | ||||
|  *    and up to the hardware designer to not wire | ||||
|  *    them up in some weird unusable way. | ||||
|  */ | ||||
| #include <stddef.h> | ||||
| #include <linux/i2c.h> | ||||
| #include <linux/i2c-dev.h> | ||||
| #include <asm/pmac_low_i2c.h> | ||||
| #include <asm/prom.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/module.h> | ||||
| MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); | ||||
| MODULE_LICENSE("GPL"); | ||||
| MODULE_DESCRIPTION("tas codec driver for snd-aoa"); | ||||
| 
 | ||||
| #include "snd-aoa-codec-tas.h" | ||||
| #include "snd-aoa-codec-tas-gain-table.h" | ||||
| #include "../aoa.h" | ||||
| #include "../soundbus/soundbus.h" | ||||
| 
 | ||||
| 
 | ||||
| #define PFX "snd-aoa-codec-tas: " | ||||
| 
 | ||||
| struct tas { | ||||
| 	struct aoa_codec	codec; | ||||
| 	struct i2c_client	i2c; | ||||
| 	u32			muted_l:1, muted_r:1, | ||||
| 				controls_created:1; | ||||
| 	u8			cached_volume_l, cached_volume_r; | ||||
| 	u8			mixer_l[3], mixer_r[3]; | ||||
| 	u8			acr; | ||||
| }; | ||||
| 
 | ||||
| static struct tas *codec_to_tas(struct aoa_codec *codec) | ||||
| { | ||||
| 	return container_of(codec, struct tas, codec); | ||||
| } | ||||
| 
 | ||||
| static inline int tas_write_reg(struct tas *tas, u8 reg, u8 len, u8 *data) | ||||
| { | ||||
| 	if (len == 1) | ||||
| 		return i2c_smbus_write_byte_data(&tas->i2c, reg, *data); | ||||
| 	else | ||||
| 		return i2c_smbus_write_i2c_block_data(&tas->i2c, reg, len, data); | ||||
| } | ||||
| 
 | ||||
| static void tas_set_volume(struct tas *tas) | ||||
| { | ||||
| 	u8 block[6]; | ||||
| 	int tmp; | ||||
| 	u8 left, right; | ||||
| 
 | ||||
| 	left = tas->cached_volume_l; | ||||
| 	right = tas->cached_volume_r; | ||||
| 
 | ||||
| 	if (left > 177) left = 177; | ||||
| 	if (right > 177) right = 177; | ||||
| 
 | ||||
| 	if (tas->muted_l) left = 0; | ||||
| 	if (tas->muted_r) right = 0; | ||||
| 
 | ||||
| 	/* analysing the volume and mixer tables shows
 | ||||
| 	 * that they are similar enough when we shift | ||||
| 	 * the mixer table down by 4 bits. The error | ||||
| 	 * is miniscule, in just one item the error | ||||
| 	 * is 1, at a value of 0x07f17b (mixer table | ||||
| 	 * value is 0x07f17a) */ | ||||
| 	tmp = tas_gaintable[left]; | ||||
| 	block[0] = tmp>>20; | ||||
| 	block[1] = tmp>>12; | ||||
| 	block[2] = tmp>>4; | ||||
| 	tmp = tas_gaintable[right]; | ||||
| 	block[3] = tmp>>20; | ||||
| 	block[4] = tmp>>12; | ||||
| 	block[5] = tmp>>4; | ||||
| 	tas_write_reg(tas, TAS_REG_VOL, 6, block); | ||||
| } | ||||
| 
 | ||||
| static void tas_set_mixer(struct tas *tas) | ||||
| { | ||||
| 	u8 block[9]; | ||||
| 	int tmp, i; | ||||
| 	u8 val; | ||||
| 
 | ||||
| 	for (i=0;i<3;i++) { | ||||
| 		val = tas->mixer_l[i]; | ||||
| 		if (val > 177) val = 177; | ||||
| 		tmp = tas_gaintable[val]; | ||||
| 		block[3*i+0] = tmp>>16; | ||||
| 		block[3*i+1] = tmp>>8; | ||||
| 		block[3*i+2] = tmp; | ||||
| 	} | ||||
| 	tas_write_reg(tas, TAS_REG_LMIX, 9, block); | ||||
| 
 | ||||
| 	for (i=0;i<3;i++) { | ||||
| 		val = tas->mixer_r[i]; | ||||
| 		if (val > 177) val = 177; | ||||
| 		tmp = tas_gaintable[val]; | ||||
| 		block[3*i+0] = tmp>>16; | ||||
| 		block[3*i+1] = tmp>>8; | ||||
| 		block[3*i+2] = tmp; | ||||
| 	} | ||||
| 	tas_write_reg(tas, TAS_REG_RMIX, 9, block); | ||||
| } | ||||
| 
 | ||||
| /* alsa stuff */ | ||||
| 
 | ||||
| static int tas_dev_register(struct snd_device *dev) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static struct snd_device_ops ops = { | ||||
| 	.dev_register = tas_dev_register, | ||||
| }; | ||||
| 
 | ||||
| static int tas_snd_vol_info(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_info *uinfo) | ||||
| { | ||||
| 	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||||
| 	uinfo->count = 2; | ||||
| 	uinfo->value.integer.min = 0; | ||||
| 	uinfo->value.integer.max = 177; | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int tas_snd_vol_get(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_value *ucontrol) | ||||
| { | ||||
| 	struct tas *tas = snd_kcontrol_chip(kcontrol); | ||||
| 
 | ||||
| 	ucontrol->value.integer.value[0] = tas->cached_volume_l; | ||||
| 	ucontrol->value.integer.value[1] = tas->cached_volume_r; | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int tas_snd_vol_put(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_value *ucontrol) | ||||
| { | ||||
| 	struct tas *tas = snd_kcontrol_chip(kcontrol); | ||||
| 
 | ||||
| 	if (tas->cached_volume_l == ucontrol->value.integer.value[0] | ||||
| 	 && tas->cached_volume_r == ucontrol->value.integer.value[1]) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	tas->cached_volume_l = ucontrol->value.integer.value[0]; | ||||
| 	tas->cached_volume_r = ucontrol->value.integer.value[1]; | ||||
| 	tas_set_volume(tas); | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| static struct snd_kcontrol_new volume_control = { | ||||
| 	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||||
| 	.name = "Master Playback Volume", | ||||
| 	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | ||||
| 	.info = tas_snd_vol_info, | ||||
| 	.get = tas_snd_vol_get, | ||||
| 	.put = tas_snd_vol_put, | ||||
| }; | ||||
| 
 | ||||
| static int tas_snd_mute_info(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_info *uinfo) | ||||
| { | ||||
| 	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; | ||||
| 	uinfo->count = 2; | ||||
| 	uinfo->value.integer.min = 0; | ||||
| 	uinfo->value.integer.max = 1; | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int tas_snd_mute_get(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_value *ucontrol) | ||||
| { | ||||
| 	struct tas *tas = snd_kcontrol_chip(kcontrol); | ||||
| 
 | ||||
| 	ucontrol->value.integer.value[0] = !tas->muted_l; | ||||
| 	ucontrol->value.integer.value[1] = !tas->muted_r; | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int tas_snd_mute_put(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_value *ucontrol) | ||||
| { | ||||
| 	struct tas *tas = snd_kcontrol_chip(kcontrol); | ||||
| 
 | ||||
| 	if (tas->muted_l == !ucontrol->value.integer.value[0] | ||||
| 	 && tas->muted_r == !ucontrol->value.integer.value[1]) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	tas->muted_l = !ucontrol->value.integer.value[0]; | ||||
| 	tas->muted_r = !ucontrol->value.integer.value[1]; | ||||
| 	tas_set_volume(tas); | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| static struct snd_kcontrol_new mute_control = { | ||||
| 	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||||
| 	.name = "Master Playback Switch", | ||||
| 	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | ||||
| 	.info = tas_snd_mute_info, | ||||
| 	.get = tas_snd_mute_get, | ||||
| 	.put = tas_snd_mute_put, | ||||
| }; | ||||
| 
 | ||||
| static int tas_snd_mixer_info(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_info *uinfo) | ||||
| { | ||||
| 	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||||
| 	uinfo->count = 2; | ||||
| 	uinfo->value.integer.min = 0; | ||||
| 	uinfo->value.integer.max = 177; | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int tas_snd_mixer_get(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_value *ucontrol) | ||||
| { | ||||
| 	struct tas *tas = snd_kcontrol_chip(kcontrol); | ||||
| 	int idx = kcontrol->private_value; | ||||
| 
 | ||||
| 	ucontrol->value.integer.value[0] = tas->mixer_l[idx]; | ||||
| 	ucontrol->value.integer.value[1] = tas->mixer_r[idx]; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int tas_snd_mixer_put(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_value *ucontrol) | ||||
| { | ||||
| 	struct tas *tas = snd_kcontrol_chip(kcontrol); | ||||
| 	int idx = kcontrol->private_value; | ||||
| 
 | ||||
| 	if (tas->mixer_l[idx] == ucontrol->value.integer.value[0] | ||||
| 	 && tas->mixer_r[idx] == ucontrol->value.integer.value[1]) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	tas->mixer_l[idx] = ucontrol->value.integer.value[0]; | ||||
| 	tas->mixer_r[idx] = ucontrol->value.integer.value[1]; | ||||
| 
 | ||||
| 	tas_set_mixer(tas); | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| #define MIXER_CONTROL(n,descr,idx)			\ | ||||
| static struct snd_kcontrol_new n##_control = {		\ | ||||
| 	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,		\ | ||||
| 	.name = descr " Playback Volume",		\ | ||||
| 	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,	\ | ||||
| 	.info = tas_snd_mixer_info,			\ | ||||
| 	.get = tas_snd_mixer_get,			\ | ||||
| 	.put = tas_snd_mixer_put,			\ | ||||
| 	.private_value = idx,				\ | ||||
| } | ||||
| 
 | ||||
| MIXER_CONTROL(pcm1, "PCM1", 0); | ||||
| MIXER_CONTROL(monitor, "Monitor", 2); | ||||
| 
 | ||||
| static int tas_snd_capture_source_info(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_info *uinfo) | ||||
| { | ||||
| 	static char *texts[] = { "Line-In", "Microphone" }; | ||||
| 
 | ||||
| 	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | ||||
| 	uinfo->count = 1; | ||||
| 	uinfo->value.enumerated.items = 2; | ||||
| 	if (uinfo->value.enumerated.item > 1) | ||||
| 		uinfo->value.enumerated.item = 1; | ||||
| 	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int tas_snd_capture_source_get(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_value *ucontrol) | ||||
| { | ||||
| 	struct tas *tas = snd_kcontrol_chip(kcontrol); | ||||
| 
 | ||||
| 	ucontrol->value.enumerated.item[0] = !!(tas->acr & TAS_ACR_INPUT_B); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int tas_snd_capture_source_put(struct snd_kcontrol *kcontrol, | ||||
| 	struct snd_ctl_elem_value *ucontrol) | ||||
| { | ||||
| 	struct tas *tas = snd_kcontrol_chip(kcontrol); | ||||
| 	int oldacr = tas->acr; | ||||
| 
 | ||||
| 	tas->acr &= ~TAS_ACR_INPUT_B; | ||||
| 	if (ucontrol->value.enumerated.item[0]) | ||||
| 		tas->acr |= TAS_ACR_INPUT_B; | ||||
| 	if (oldacr == tas->acr) | ||||
| 		return 0; | ||||
| 	tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr); | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| static struct snd_kcontrol_new capture_source_control = { | ||||
| 	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||||
| 	/* If we name this 'Input Source', it properly shows up in
 | ||||
| 	 * alsamixer as a selection, * but it's shown under the | ||||
| 	 * 'Playback' category. | ||||
| 	 * If I name it 'Capture Source', it shows up in strange | ||||
| 	 * ways (two bools of which one can be selected at a | ||||
| 	 * time) but at least it's shown in the 'Capture' | ||||
| 	 * category. | ||||
| 	 * I was told that this was due to backward compatibility, | ||||
| 	 * but I don't understand then why the mangling is *not* | ||||
| 	 * done when I name it "Input Source"..... | ||||
| 	 */ | ||||
| 	.name = "Capture Source", | ||||
| 	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | ||||
| 	.info = tas_snd_capture_source_info, | ||||
| 	.get = tas_snd_capture_source_get, | ||||
| 	.put = tas_snd_capture_source_put, | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| static struct transfer_info tas_transfers[] = { | ||||
| 	{ | ||||
| 		/* input */ | ||||
| 		.formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_BE | | ||||
| 			   SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE, | ||||
| 		.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, | ||||
| 		.transfer_in = 1, | ||||
| 	}, | ||||
| 	{ | ||||
| 		/* output */ | ||||
| 		.formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_BE | | ||||
| 			   SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE, | ||||
| 		.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, | ||||
| 		.transfer_in = 0, | ||||
| 	}, | ||||
| 	{} | ||||
| }; | ||||
| 
 | ||||
| static int tas_usable(struct codec_info_item *cii, | ||||
| 		      struct transfer_info *ti, | ||||
| 		      struct transfer_info *out) | ||||
| { | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| static int tas_reset_init(struct tas *tas) | ||||
| { | ||||
| 	u8 tmp; | ||||
| 	tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0); | ||||
| 	msleep(1); | ||||
| 	tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 1); | ||||
| 	msleep(1); | ||||
| 	tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0); | ||||
| 	msleep(1); | ||||
| 
 | ||||
| 	tas->acr &= ~TAS_ACR_ANALOG_PDOWN; | ||||
| 	tas->acr |= TAS_ACR_B_MONAUREAL | TAS_ACR_B_MON_SEL_RIGHT; | ||||
| 	if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	tmp = TAS_MCS_SCLK64 | TAS_MCS_SPORT_MODE_I2S | TAS_MCS_SPORT_WL_24BIT; | ||||
| 	if (tas_write_reg(tas, TAS_REG_MCS, 1, &tmp)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	tmp = 0; | ||||
| 	if (tas_write_reg(tas, TAS_REG_MCS2, 1, &tmp)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /* we are controlled via i2c and assume that is always up
 | ||||
|  * If that wasn't the case, we'd have to suspend once | ||||
|  * our i2c device is suspended, and then take note of that! */ | ||||
| static int tas_suspend(struct tas *tas) | ||||
| { | ||||
| 	tas->acr |= TAS_ACR_ANALOG_PDOWN; | ||||
| 	tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int tas_resume(struct tas *tas) | ||||
| { | ||||
| 	/* reset codec */ | ||||
| 	tas_reset_init(tas); | ||||
| 	tas_set_volume(tas); | ||||
| 	tas_set_mixer(tas); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| static int _tas_suspend(struct codec_info_item *cii, pm_message_t state) | ||||
| { | ||||
| 	return tas_suspend(cii->codec_data); | ||||
| } | ||||
| 
 | ||||
| static int _tas_resume(struct codec_info_item *cii) | ||||
| { | ||||
| 	return tas_resume(cii->codec_data); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| static struct codec_info tas_codec_info = { | ||||
| 	.transfers = tas_transfers, | ||||
| 	/* in theory, we can drive it at 512 too...
 | ||||
| 	 * but so far the framework doesn't allow | ||||
| 	 * for that and I don't see much point in it. */ | ||||
| 	.sysclock_factor = 256, | ||||
| 	/* same here, could be 32 for just one 16 bit format */ | ||||
| 	.bus_factor = 64, | ||||
| 	.owner = THIS_MODULE, | ||||
| 	.usable = tas_usable, | ||||
| #ifdef CONFIG_PM | ||||
| 	.suspend = _tas_suspend, | ||||
| 	.resume = _tas_resume, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| static int tas_init_codec(struct aoa_codec *codec) | ||||
| { | ||||
| 	struct tas *tas = codec_to_tas(codec); | ||||
| 	int err; | ||||
| 
 | ||||
| 	if (!tas->codec.gpio || !tas->codec.gpio->methods) { | ||||
| 		printk(KERN_ERR PFX "gpios not assigned!!\n"); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	if (tas_reset_init(tas)) { | ||||
| 		printk(KERN_ERR PFX "tas failed to initialise\n"); | ||||
| 		return -ENXIO; | ||||
| 	} | ||||
| 
 | ||||
| 	if (tas->codec.soundbus_dev->attach_codec(tas->codec.soundbus_dev, | ||||
| 						   aoa_get_card(), | ||||
| 						   &tas_codec_info, tas)) { | ||||
| 		printk(KERN_ERR PFX "error attaching tas to soundbus\n"); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, tas, &ops)) { | ||||
| 		printk(KERN_ERR PFX "failed to create tas snd device!\n"); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 	err = aoa_snd_ctl_add(snd_ctl_new1(&volume_control, tas)); | ||||
| 	if (err) | ||||
| 		goto error; | ||||
| 
 | ||||
| 	err = aoa_snd_ctl_add(snd_ctl_new1(&mute_control, tas)); | ||||
| 	if (err) | ||||
| 		goto error; | ||||
| 
 | ||||
| 	err = aoa_snd_ctl_add(snd_ctl_new1(&pcm1_control, tas)); | ||||
| 	if (err) | ||||
| 		goto error; | ||||
| 
 | ||||
| 	err = aoa_snd_ctl_add(snd_ctl_new1(&monitor_control, tas)); | ||||
| 	if (err) | ||||
| 		goto error; | ||||
| 
 | ||||
| 	err = aoa_snd_ctl_add(snd_ctl_new1(&capture_source_control, tas)); | ||||
| 	if (err) | ||||
| 		goto error; | ||||
| 
 | ||||
| 	return 0; | ||||
|  error: | ||||
| 	tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas); | ||||
| 	snd_device_free(aoa_get_card(), tas); | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static void tas_exit_codec(struct aoa_codec *codec) | ||||
| { | ||||
| 	struct tas *tas = codec_to_tas(codec); | ||||
| 
 | ||||
| 	if (!tas->codec.soundbus_dev) | ||||
| 		return; | ||||
| 	tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas); | ||||
| } | ||||
| 	 | ||||
| 
 | ||||
| static struct i2c_driver tas_driver; | ||||
| 
 | ||||
| static int tas_create(struct i2c_adapter *adapter, | ||||
| 		       struct device_node *node, | ||||
| 		       int addr) | ||||
| { | ||||
| 	struct tas *tas; | ||||
| 
 | ||||
| 	tas = kzalloc(sizeof(struct tas), GFP_KERNEL); | ||||
| 
 | ||||
| 	if (!tas) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	tas->i2c.driver = &tas_driver; | ||||
| 	tas->i2c.adapter = adapter; | ||||
| 	tas->i2c.addr = addr; | ||||
| 	strlcpy(tas->i2c.name, "tas audio codec", I2C_NAME_SIZE-1); | ||||
| 
 | ||||
| 	if (i2c_attach_client(&tas->i2c)) { | ||||
| 		printk(KERN_ERR PFX "failed to attach to i2c\n"); | ||||
| 		goto fail; | ||||
| 	} | ||||
| 
 | ||||
| 	strlcpy(tas->codec.name, "tas", MAX_CODEC_NAME_LEN-1); | ||||
| 	tas->codec.owner = THIS_MODULE; | ||||
| 	tas->codec.init = tas_init_codec; | ||||
| 	tas->codec.exit = tas_exit_codec; | ||||
| 	tas->codec.node = of_node_get(node); | ||||
| 
 | ||||
| 	if (aoa_codec_register(&tas->codec)) { | ||||
| 		goto detach; | ||||
| 	} | ||||
| 	printk(KERN_DEBUG "snd-aoa-codec-tas: created and attached tas instance\n"); | ||||
| 	return 0; | ||||
|  detach: | ||||
| 	i2c_detach_client(&tas->i2c); | ||||
|  fail: | ||||
| 	kfree(tas); | ||||
| 	return -EINVAL; | ||||
| } | ||||
| 
 | ||||
| static int tas_i2c_attach(struct i2c_adapter *adapter) | ||||
| { | ||||
| 	struct device_node *busnode, *dev = NULL; | ||||
| 	struct pmac_i2c_bus *bus; | ||||
| 
 | ||||
| 	bus = pmac_i2c_adapter_to_bus(adapter); | ||||
| 	if (bus == NULL) | ||||
| 		return -ENODEV; | ||||
| 	busnode = pmac_i2c_get_bus_node(bus); | ||||
| 
 | ||||
| 	while ((dev = of_get_next_child(busnode, dev)) != NULL) { | ||||
| 		if (device_is_compatible(dev, "tas3004")) { | ||||
| 			u32 *addr; | ||||
| 			printk(KERN_DEBUG PFX "found tas3004\n"); | ||||
| 			addr = (u32 *) get_property(dev, "reg", NULL); | ||||
| 			if (!addr) | ||||
| 				continue; | ||||
| 			return tas_create(adapter, dev, ((*addr) >> 1) & 0x7f); | ||||
| 		} | ||||
| 		/* older machines have no 'codec' node with a 'compatible'
 | ||||
| 		 * property that says 'tas3004', they just have a 'deq' | ||||
| 		 * node without any such property... */ | ||||
| 		if (strcmp(dev->name, "deq") == 0) { | ||||
| 			u32 *_addr, addr; | ||||
| 			printk(KERN_DEBUG PFX "found 'deq' node\n"); | ||||
| 			_addr = (u32 *) get_property(dev, "i2c-address", NULL); | ||||
| 			if (!_addr) | ||||
| 				continue; | ||||
| 			addr = ((*_addr) >> 1) & 0x7f; | ||||
| 			/* now, if the address doesn't match any of the two
 | ||||
| 			 * that a tas3004 can have, we cannot handle this. | ||||
| 			 * I doubt it ever happens but hey. */ | ||||
| 			if (addr != 0x34 && addr != 0x35) | ||||
| 				continue; | ||||
| 			return tas_create(adapter, dev, addr); | ||||
| 		} | ||||
| 	} | ||||
| 	return -ENODEV; | ||||
| } | ||||
| 
 | ||||
| static int tas_i2c_detach(struct i2c_client *client) | ||||
| { | ||||
| 	struct tas *tas = container_of(client, struct tas, i2c); | ||||
| 	int err; | ||||
| 	u8 tmp = TAS_ACR_ANALOG_PDOWN; | ||||
| 
 | ||||
| 	if ((err = i2c_detach_client(client))) | ||||
| 		return err; | ||||
| 	aoa_codec_unregister(&tas->codec); | ||||
| 	of_node_put(tas->codec.node); | ||||
| 
 | ||||
| 	/* power down codec chip */ | ||||
| 	tas_write_reg(tas, TAS_REG_ACR, 1, &tmp); | ||||
| 
 | ||||
| 	kfree(tas); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static struct i2c_driver tas_driver = { | ||||
| 	.driver = { | ||||
| 		.name = "aoa_codec_tas", | ||||
| 		.owner = THIS_MODULE, | ||||
| 	}, | ||||
| 	.attach_adapter = tas_i2c_attach, | ||||
| 	.detach_client = tas_i2c_detach, | ||||
| }; | ||||
| 
 | ||||
| static int __init tas_init(void) | ||||
| { | ||||
| 	return i2c_add_driver(&tas_driver); | ||||
| } | ||||
| 
 | ||||
| static void __exit tas_exit(void) | ||||
| { | ||||
| 	i2c_del_driver(&tas_driver); | ||||
| } | ||||
| 
 | ||||
| module_init(tas_init); | ||||
| module_exit(tas_exit); | ||||
							
								
								
									
										47
									
								
								sound/aoa/codecs/snd-aoa-codec-tas.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								sound/aoa/codecs/snd-aoa-codec-tas.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio driver for tas codec (header) | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| #ifndef __SND_AOA_CODECTASH | ||||
| #define __SND_AOA_CODECTASH | ||||
| 
 | ||||
| #define TAS_REG_MCS	0x01	/* main control */ | ||||
| #	define TAS_MCS_FASTLOAD		(1<<7) | ||||
| #	define TAS_MCS_SCLK64		(1<<6) | ||||
| #	define TAS_MCS_SPORT_MODE_MASK	(3<<4) | ||||
| #	define TAS_MCS_SPORT_MODE_I2S	(2<<4) | ||||
| #	define TAS_MCS_SPORT_MODE_RJ	(1<<4) | ||||
| #	define TAS_MCS_SPORT_MODE_LJ	(0<<4) | ||||
| #	define TAS_MCS_SPORT_WL_MASK	(3<<0) | ||||
| #	define TAS_MCS_SPORT_WL_16BIT	(0<<0) | ||||
| #	define TAS_MCS_SPORT_WL_18BIT	(1<<0) | ||||
| #	define TAS_MCS_SPORT_WL_20BIT	(2<<0) | ||||
| #	define TAS_MCS_SPORT_WL_24BIT	(3<<0) | ||||
| 
 | ||||
| #define TAS_REG_DRC	0x02 | ||||
| #define TAS_REG_VOL	0x04 | ||||
| #define TAS_REG_TREBLE	0x05 | ||||
| #define TAS_REG_BASS	0x06 | ||||
| #define TAS_REG_LMIX	0x07 | ||||
| #define TAS_REG_RMIX	0x08 | ||||
| 
 | ||||
| #define TAS_REG_ACR	0x40	/* analog control */ | ||||
| #	define TAS_ACR_B_MONAUREAL	(1<<7) | ||||
| #	define TAS_ACR_B_MON_SEL_RIGHT	(1<<6) | ||||
| #	define TAS_ACR_DEEMPH_MASK	(3<<2) | ||||
| #	define TAS_ACR_DEEMPH_OFF	(0<<2) | ||||
| #	define TAS_ACR_DEEMPH_48KHz	(1<<2) | ||||
| #	define TAS_ACR_DEEMPH_44KHz	(2<<2) | ||||
| #	define TAS_ACR_INPUT_B		(1<<1) | ||||
| #	define TAS_ACR_ANALOG_PDOWN	(1<<0) | ||||
| 
 | ||||
| #define TAS_REG_MCS2	0x43	/* main control 2 */ | ||||
| #	define TAS_MCS2_ALLPASS		(1<<1) | ||||
| 
 | ||||
| #define TAS_REG_LEFT_BIQUAD6	0x10 | ||||
| #define TAS_REG_RIGHT_BIQUAD6	0x19 | ||||
| 
 | ||||
| #endif /* __SND_AOA_CODECTASH */ | ||||
							
								
								
									
										141
									
								
								sound/aoa/codecs/snd-aoa-codec-toonie.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								sound/aoa/codecs/snd-aoa-codec-toonie.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,141 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio driver for Toonie codec | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  * | ||||
|  * | ||||
|  * This is a driver for the toonie codec chip. This chip is present | ||||
|  * on the Mac Mini and is nothing but a DAC. | ||||
|  */ | ||||
| #include <linux/delay.h> | ||||
| #include <linux/module.h> | ||||
| MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); | ||||
| MODULE_LICENSE("GPL"); | ||||
| MODULE_DESCRIPTION("toonie codec driver for snd-aoa"); | ||||
| 
 | ||||
| #include "../aoa.h" | ||||
| #include "../soundbus/soundbus.h" | ||||
| 
 | ||||
| 
 | ||||
| #define PFX "snd-aoa-codec-toonie: " | ||||
| 
 | ||||
| struct toonie { | ||||
| 	struct aoa_codec	codec; | ||||
| }; | ||||
| #define codec_to_toonie(c) container_of(c, struct toonie, codec) | ||||
| 
 | ||||
| static int toonie_dev_register(struct snd_device *dev) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static struct snd_device_ops ops = { | ||||
| 	.dev_register = toonie_dev_register, | ||||
| }; | ||||
| 
 | ||||
| static struct transfer_info toonie_transfers[] = { | ||||
| 	/* This thing *only* has analog output,
 | ||||
| 	 * the rates are taken from Info.plist | ||||
| 	 * from Darwin. */ | ||||
| 	{ | ||||
| 		.formats = SNDRV_PCM_FMTBIT_S16_BE | | ||||
| 			   SNDRV_PCM_FMTBIT_S24_BE, | ||||
| 		.rates = SNDRV_PCM_RATE_32000 | | ||||
| 			 SNDRV_PCM_RATE_44100 | | ||||
| 			 SNDRV_PCM_RATE_48000 | | ||||
| 			 SNDRV_PCM_RATE_88200 | | ||||
| 			 SNDRV_PCM_RATE_96000, | ||||
| 	}, | ||||
| 	{} | ||||
| }; | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| static int toonie_suspend(struct codec_info_item *cii, pm_message_t state) | ||||
| { | ||||
| 	/* can we turn it off somehow? */ | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int toonie_resume(struct codec_info_item *cii) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| #endif /* CONFIG_PM */ | ||||
| 
 | ||||
| static struct codec_info toonie_codec_info = { | ||||
| 	.transfers = toonie_transfers, | ||||
| 	.sysclock_factor = 256, | ||||
| 	.bus_factor = 64, | ||||
| 	.owner = THIS_MODULE, | ||||
| #ifdef CONFIG_PM | ||||
| 	.suspend = toonie_suspend, | ||||
| 	.resume = toonie_resume, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| static int toonie_init_codec(struct aoa_codec *codec) | ||||
| { | ||||
| 	struct toonie *toonie = codec_to_toonie(codec); | ||||
| 
 | ||||
| 	if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, toonie, &ops)) { | ||||
| 		printk(KERN_ERR PFX "failed to create toonie snd device!\n"); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	/* nothing connected? what a joke! */ | ||||
| 	if (toonie->codec.connected != 1) | ||||
| 		return -ENOTCONN; | ||||
| 
 | ||||
| 	if (toonie->codec.soundbus_dev->attach_codec(toonie->codec.soundbus_dev, | ||||
| 						     aoa_get_card(), | ||||
| 						     &toonie_codec_info, toonie)) { | ||||
| 		printk(KERN_ERR PFX "error creating toonie pcm\n"); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void toonie_exit_codec(struct aoa_codec *codec) | ||||
| { | ||||
| 	struct toonie *toonie = codec_to_toonie(codec); | ||||
| 
 | ||||
| 	if (!toonie->codec.soundbus_dev) { | ||||
| 		printk(KERN_ERR PFX "toonie_exit_codec called without soundbus_dev!\n"); | ||||
| 		return; | ||||
| 	} | ||||
| 	toonie->codec.soundbus_dev->detach_codec(toonie->codec.soundbus_dev, toonie); | ||||
| } | ||||
| 
 | ||||
| static struct toonie *toonie; | ||||
| 
 | ||||
| static int __init toonie_init(void) | ||||
| { | ||||
| 	toonie = kzalloc(sizeof(struct toonie), GFP_KERNEL); | ||||
| 
 | ||||
| 	if (!toonie) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	strlcpy(toonie->codec.name, "toonie", sizeof(toonie->codec.name)); | ||||
| 	toonie->codec.owner = THIS_MODULE; | ||||
| 	toonie->codec.init = toonie_init_codec; | ||||
| 	toonie->codec.exit = toonie_exit_codec; | ||||
|                                          | ||||
| 	if (aoa_codec_register(&toonie->codec)) { | ||||
| 		kfree(toonie); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void __exit toonie_exit(void) | ||||
| { | ||||
| 	aoa_codec_unregister(&toonie->codec); | ||||
| 	kfree(toonie); | ||||
| } | ||||
| 
 | ||||
| module_init(toonie_init); | ||||
| module_exit(toonie_exit); | ||||
							
								
								
									
										5
									
								
								sound/aoa/core/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								sound/aoa/core/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| obj-$(CONFIG_SND_AOA) += snd-aoa.o | ||||
| snd-aoa-objs := snd-aoa-core.o \
 | ||||
| 		snd-aoa-alsa.o \
 | ||||
| 		snd-aoa-gpio-pmf.o \
 | ||||
| 		snd-aoa-gpio-feature.o | ||||
							
								
								
									
										98
									
								
								sound/aoa/core/snd-aoa-alsa.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								sound/aoa/core/snd-aoa-alsa.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio Alsa helpers | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| #include <linux/module.h> | ||||
| #include "snd-aoa-alsa.h" | ||||
| 
 | ||||
| static int index = -1; | ||||
| module_param(index, int, 0444); | ||||
| MODULE_PARM_DESC(index, "index for AOA sound card."); | ||||
| 
 | ||||
| static struct aoa_card *aoa_card; | ||||
| 
 | ||||
| int aoa_alsa_init(char *name, struct module *mod) | ||||
| { | ||||
| 	struct snd_card *alsa_card; | ||||
| 	int err; | ||||
| 
 | ||||
| 	if (aoa_card) | ||||
| 		/* cannot be EEXIST due to usage in aoa_fabric_register */ | ||||
| 		return -EBUSY; | ||||
| 
 | ||||
| 	alsa_card = snd_card_new(index, name, mod, sizeof(struct aoa_card)); | ||||
| 	if (!alsa_card) | ||||
| 		return -ENOMEM; | ||||
| 	aoa_card = alsa_card->private_data; | ||||
| 	aoa_card->alsa_card = alsa_card; | ||||
| 	strlcpy(alsa_card->driver, "AppleOnbdAudio", sizeof(alsa_card->driver)); | ||||
| 	strlcpy(alsa_card->shortname, name, sizeof(alsa_card->shortname)); | ||||
| 	strlcpy(alsa_card->longname, name, sizeof(alsa_card->longname)); | ||||
| 	strlcpy(alsa_card->mixername, name, sizeof(alsa_card->mixername)); | ||||
| 	err = snd_card_register(aoa_card->alsa_card); | ||||
| 	if (err < 0) { | ||||
| 		printk(KERN_ERR "snd-aoa: couldn't register alsa card\n"); | ||||
| 		snd_card_free(aoa_card->alsa_card); | ||||
| 		aoa_card = NULL; | ||||
| 		return err; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| struct snd_card *aoa_get_card(void) | ||||
| { | ||||
| 	if (aoa_card) | ||||
| 		return aoa_card->alsa_card; | ||||
| 	return NULL; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(aoa_get_card); | ||||
| 
 | ||||
| void aoa_alsa_cleanup(void) | ||||
| { | ||||
| 	if (aoa_card) { | ||||
| 		snd_card_free(aoa_card->alsa_card); | ||||
| 		aoa_card = NULL; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| int aoa_snd_device_new(snd_device_type_t type, | ||||
|         void * device_data, struct snd_device_ops * ops) | ||||
| { | ||||
| 	struct snd_card *card = aoa_get_card(); | ||||
| 	int err; | ||||
| 	 | ||||
| 	if (!card) return -ENOMEM; | ||||
| 
 | ||||
| 	err = snd_device_new(card, type, device_data, ops); | ||||
| 	if (err) { | ||||
| 		printk(KERN_ERR "snd-aoa: failed to create snd device (%d)\n", err); | ||||
| 		return err; | ||||
| 	} | ||||
| 	err = snd_device_register(card, device_data); | ||||
| 	if (err) { | ||||
| 		printk(KERN_ERR "snd-aoa: failed to register " | ||||
| 				"snd device (%d)\n", err); | ||||
| 		printk(KERN_ERR "snd-aoa: have you forgotten the " | ||||
| 				"dev_register callback?\n"); | ||||
| 		snd_device_free(card, device_data); | ||||
| 	} | ||||
| 	return err; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(aoa_snd_device_new); | ||||
| 
 | ||||
| int aoa_snd_ctl_add(struct snd_kcontrol* control) | ||||
| { | ||||
| 	int err; | ||||
| 
 | ||||
| 	if (!aoa_card) return -ENODEV; | ||||
| 
 | ||||
| 	err = snd_ctl_add(aoa_card->alsa_card, control); | ||||
| 	if (err) | ||||
| 		printk(KERN_ERR "snd-aoa: failed to add alsa control (%d)\n", | ||||
| 		       err); | ||||
| 	return err; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(aoa_snd_ctl_add); | ||||
							
								
								
									
										16
									
								
								sound/aoa/core/snd-aoa-alsa.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sound/aoa/core/snd-aoa-alsa.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio Alsa private helpers | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| 
 | ||||
| #ifndef __SND_AOA_ALSA_H | ||||
| #define __SND_AOA_ALSA_H | ||||
| #include "../aoa.h" | ||||
| 
 | ||||
| extern int aoa_alsa_init(char *name, struct module *mod); | ||||
| extern void aoa_alsa_cleanup(void); | ||||
| 
 | ||||
| #endif /* __SND_AOA_ALSA_H */ | ||||
							
								
								
									
										162
									
								
								sound/aoa/core/snd-aoa-core.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								sound/aoa/core/snd-aoa-core.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,162 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio driver core | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/init.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/list.h> | ||||
| #include "../aoa.h" | ||||
| #include "snd-aoa-alsa.h" | ||||
| 
 | ||||
| MODULE_DESCRIPTION("Apple Onboard Audio Sound Driver"); | ||||
| MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); | ||||
| MODULE_LICENSE("GPL"); | ||||
| 
 | ||||
| /* We allow only one fabric. This simplifies things,
 | ||||
|  * and more don't really make that much sense */ | ||||
| static struct aoa_fabric *fabric; | ||||
| static LIST_HEAD(codec_list); | ||||
| 
 | ||||
| static int attach_codec_to_fabric(struct aoa_codec *c) | ||||
| { | ||||
| 	int err; | ||||
| 
 | ||||
| 	if (!try_module_get(c->owner)) | ||||
| 		return -EBUSY; | ||||
| 	/* found_codec has to be assigned */ | ||||
| 	err = -ENOENT; | ||||
| 	if (fabric->found_codec) | ||||
| 		err = fabric->found_codec(c); | ||||
| 	if (err) { | ||||
| 		module_put(c->owner); | ||||
| 		printk(KERN_ERR "snd-aoa: fabric didn't like codec %s\n", | ||||
| 				c->name); | ||||
| 		return err; | ||||
| 	} | ||||
| 	c->fabric = fabric; | ||||
| 
 | ||||
| 	err = 0; | ||||
| 	if (c->init) | ||||
| 		err = c->init(c); | ||||
| 	if (err) { | ||||
| 		printk(KERN_ERR "snd-aoa: codec %s didn't init\n", c->name); | ||||
| 		c->fabric = NULL; | ||||
| 		if (fabric->remove_codec) | ||||
| 			fabric->remove_codec(c); | ||||
| 		module_put(c->owner); | ||||
| 		return err; | ||||
| 	} | ||||
| 	if (fabric->attached_codec) | ||||
| 		fabric->attached_codec(c); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int aoa_codec_register(struct aoa_codec *codec) | ||||
| { | ||||
| 	int err = 0; | ||||
| 
 | ||||
| 	/* if there's a fabric already, we can tell if we
 | ||||
| 	 * will want to have this codec, so propagate error | ||||
| 	 * through. Otherwise, this will happen later... */ | ||||
| 	if (fabric) | ||||
| 		err = attach_codec_to_fabric(codec); | ||||
| 	if (!err) | ||||
| 		list_add(&codec->list, &codec_list); | ||||
| 	return err; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(aoa_codec_register); | ||||
| 
 | ||||
| void aoa_codec_unregister(struct aoa_codec *codec) | ||||
| { | ||||
| 	list_del(&codec->list); | ||||
| 	if (codec->fabric && codec->exit) | ||||
| 		codec->exit(codec); | ||||
| 	if (fabric && fabric->remove_codec) | ||||
| 		fabric->remove_codec(codec); | ||||
| 	codec->fabric = NULL; | ||||
| 	module_put(codec->owner); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(aoa_codec_unregister); | ||||
| 
 | ||||
| int aoa_fabric_register(struct aoa_fabric *new_fabric) | ||||
| { | ||||
| 	struct aoa_codec *c; | ||||
| 	int err; | ||||
| 
 | ||||
| 	/* allow querying for presence of fabric
 | ||||
| 	 * (i.e. do this test first!) */ | ||||
| 	if (new_fabric == fabric) { | ||||
| 		err = -EALREADY; | ||||
| 		goto attach; | ||||
| 	} | ||||
| 	if (fabric) | ||||
| 		return -EEXIST; | ||||
| 	if (!new_fabric) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	err = aoa_alsa_init(new_fabric->name, new_fabric->owner); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	fabric = new_fabric; | ||||
| 
 | ||||
|  attach: | ||||
| 	list_for_each_entry(c, &codec_list, list) { | ||||
| 		if (c->fabric != fabric) | ||||
| 			attach_codec_to_fabric(c); | ||||
| 	} | ||||
| 	return err; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(aoa_fabric_register); | ||||
| 
 | ||||
| void aoa_fabric_unregister(struct aoa_fabric *old_fabric) | ||||
| { | ||||
| 	struct aoa_codec *c; | ||||
| 
 | ||||
| 	if (fabric != old_fabric) | ||||
| 		return; | ||||
| 
 | ||||
| 	list_for_each_entry(c, &codec_list, list) { | ||||
| 		if (c->fabric) | ||||
| 			aoa_fabric_unlink_codec(c); | ||||
| 	} | ||||
| 
 | ||||
| 	aoa_alsa_cleanup(); | ||||
| 
 | ||||
| 	fabric = NULL; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(aoa_fabric_unregister); | ||||
| 
 | ||||
| void aoa_fabric_unlink_codec(struct aoa_codec *codec) | ||||
| { | ||||
| 	if (!codec->fabric) { | ||||
| 		printk(KERN_ERR "snd-aoa: fabric unassigned " | ||||
| 				"in aoa_fabric_unlink_codec\n"); | ||||
| 		dump_stack(); | ||||
| 		return; | ||||
| 	} | ||||
| 	if (codec->exit) | ||||
| 		codec->exit(codec); | ||||
| 	if (codec->fabric->remove_codec) | ||||
| 		codec->fabric->remove_codec(codec); | ||||
| 	codec->fabric = NULL; | ||||
| 	module_put(codec->owner); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(aoa_fabric_unlink_codec); | ||||
| 
 | ||||
| static int __init aoa_init(void) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void __exit aoa_exit(void) | ||||
| { | ||||
| 	aoa_alsa_cleanup(); | ||||
| } | ||||
| 
 | ||||
| module_init(aoa_init); | ||||
| module_exit(aoa_exit); | ||||
							
								
								
									
										399
									
								
								sound/aoa/core/snd-aoa-gpio-feature.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								sound/aoa/core/snd-aoa-gpio-feature.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,399 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio feature call GPIO control | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  * | ||||
|  * This file contains the GPIO control routines for  | ||||
|  * direct (through feature calls) access to the GPIO | ||||
|  * registers. | ||||
|  */ | ||||
| 
 | ||||
| #include <asm/pmac_feature.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include "../aoa.h" | ||||
| 
 | ||||
| /* TODO: these are 20 global variables
 | ||||
|  * that aren't used on most machines... | ||||
|  * Move them into a dynamically allocated | ||||
|  * structure and use that. | ||||
|  */ | ||||
| 
 | ||||
| /* these are the GPIO numbers (register addresses as offsets into
 | ||||
|  * the GPIO space) */ | ||||
| static int headphone_mute_gpio; | ||||
| static int amp_mute_gpio; | ||||
| static int lineout_mute_gpio; | ||||
| static int hw_reset_gpio; | ||||
| static int lineout_detect_gpio; | ||||
| static int headphone_detect_gpio; | ||||
| static int linein_detect_gpio; | ||||
| 
 | ||||
| /* see the SWITCH_GPIO macro */ | ||||
| static int headphone_mute_gpio_activestate; | ||||
| static int amp_mute_gpio_activestate; | ||||
| static int lineout_mute_gpio_activestate; | ||||
| static int hw_reset_gpio_activestate; | ||||
| static int lineout_detect_gpio_activestate; | ||||
| static int headphone_detect_gpio_activestate; | ||||
| static int linein_detect_gpio_activestate; | ||||
| 
 | ||||
| /* node pointers that we save when getting the GPIO number
 | ||||
|  * to get the interrupt later */ | ||||
| static struct device_node *lineout_detect_node; | ||||
| static struct device_node *linein_detect_node; | ||||
| static struct device_node *headphone_detect_node; | ||||
| 
 | ||||
| static int lineout_detect_irq; | ||||
| static int linein_detect_irq; | ||||
| static int headphone_detect_irq; | ||||
| 
 | ||||
| static struct device_node *get_gpio(char *name, | ||||
| 				    char *altname, | ||||
| 				    int *gpioptr, | ||||
| 				    int *gpioactiveptr) | ||||
| { | ||||
| 	struct device_node *np, *gpio; | ||||
| 	u32 *reg; | ||||
| 	char *audio_gpio; | ||||
| 
 | ||||
| 	*gpioptr = -1; | ||||
| 
 | ||||
| 	/* check if we can get it the easy way ... */ | ||||
| 	np = of_find_node_by_name(NULL, name); | ||||
| 	if (!np) { | ||||
| 		/* some machines have only gpioX/extint-gpioX nodes,
 | ||||
| 		 * and an audio-gpio property saying what it is ... | ||||
| 		 * So what we have to do is enumerate all children | ||||
| 		 * of the gpio node and check them all. */ | ||||
| 		gpio = of_find_node_by_name(NULL, "gpio"); | ||||
| 		if (!gpio) | ||||
| 			return NULL; | ||||
| 		while ((np = of_get_next_child(gpio, np))) { | ||||
| 			audio_gpio = get_property(np, "audio-gpio", NULL); | ||||
| 			if (!audio_gpio) | ||||
| 				continue; | ||||
| 			if (strcmp(audio_gpio, name) == 0) | ||||
| 				break; | ||||
| 			if (altname && (strcmp(audio_gpio, altname) == 0)) | ||||
| 				break; | ||||
| 		} | ||||
| 		/* still not found, assume not there */ | ||||
| 		if (!np) | ||||
| 			return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	reg = (u32 *)get_property(np, "reg", NULL); | ||||
| 	if (!reg) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	*gpioptr = *reg; | ||||
| 
 | ||||
| 	/* this is a hack, usually the GPIOs 'reg' property
 | ||||
| 	 * should have the offset based from the GPIO space | ||||
| 	 * which is at 0x50, but apparently not always... */ | ||||
| 	if (*gpioptr < 0x50) | ||||
| 		*gpioptr += 0x50; | ||||
| 
 | ||||
| 	reg = (u32 *)get_property(np, "audio-gpio-active-state", NULL); | ||||
| 	if (!reg) | ||||
| 		/* Apple seems to default to 1, but
 | ||||
| 		 * that doesn't seem right at least on most | ||||
| 		 * machines. So until proven that the opposite | ||||
| 		 * is necessary, we default to 0 | ||||
| 		 * (which, incidentally, snd-powermac also does...) */ | ||||
| 		*gpioactiveptr = 0; | ||||
| 	else | ||||
| 		*gpioactiveptr = *reg; | ||||
| 
 | ||||
| 	return np; | ||||
| } | ||||
| 
 | ||||
| static void get_irq(struct device_node * np, int *irqptr) | ||||
| { | ||||
| 	*irqptr = -1; | ||||
| 	if (!np) | ||||
| 		return; | ||||
| 	if (np->n_intrs != 1) | ||||
| 		return; | ||||
| 	*irqptr = np->intrs[0].line; | ||||
| } | ||||
| 
 | ||||
| /* 0x4 is outenable, 0x1 is out, thus 4 or 5 */ | ||||
| #define SWITCH_GPIO(name, v, on)				\ | ||||
| 	(((v)&~1) | ((on)?					\ | ||||
| 			(name##_gpio_activestate==0?4:5):	\ | ||||
| 			(name##_gpio_activestate==0?5:4))) | ||||
| 
 | ||||
| #define FTR_GPIO(name, bit)					\ | ||||
| static void ftr_gpio_set_##name(struct gpio_runtime *rt, int on)\ | ||||
| {								\ | ||||
| 	int v;							\ | ||||
| 								\ | ||||
| 	if (unlikely(!rt)) return;				\ | ||||
| 								\ | ||||
| 	if (name##_mute_gpio < 0)				\ | ||||
| 		return;						\ | ||||
| 								\ | ||||
| 	v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL,		\ | ||||
| 			      name##_mute_gpio,			\ | ||||
| 			      0);				\ | ||||
| 								\ | ||||
| 	/* muted = !on... */					\ | ||||
| 	v = SWITCH_GPIO(name##_mute, v, !on);			\ | ||||
| 								\ | ||||
| 	pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL,		\ | ||||
| 			  name##_mute_gpio, v);			\ | ||||
| 								\ | ||||
| 	rt->implementation_private &= ~(1<<bit);		\ | ||||
| 	rt->implementation_private |= (!!on << bit);		\ | ||||
| }								\ | ||||
| static int ftr_gpio_get_##name(struct gpio_runtime *rt)		\ | ||||
| {								\ | ||||
| 	if (unlikely(!rt)) return 0;				\ | ||||
| 	return (rt->implementation_private>>bit)&1;		\ | ||||
| } | ||||
| 
 | ||||
| FTR_GPIO(headphone, 0); | ||||
| FTR_GPIO(amp, 1); | ||||
| FTR_GPIO(lineout, 2); | ||||
| 
 | ||||
| static void ftr_gpio_set_hw_reset(struct gpio_runtime *rt, int on) | ||||
| { | ||||
| 	int v; | ||||
| 
 | ||||
| 	if (unlikely(!rt)) return; | ||||
| 	if (hw_reset_gpio < 0) | ||||
| 		return; | ||||
| 
 | ||||
| 	v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, | ||||
| 			      hw_reset_gpio, 0); | ||||
| 	v = SWITCH_GPIO(hw_reset, v, on); | ||||
| 	pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, | ||||
| 			  hw_reset_gpio, v); | ||||
| } | ||||
| 
 | ||||
| static void ftr_gpio_all_amps_off(struct gpio_runtime *rt) | ||||
| { | ||||
| 	int saved; | ||||
| 
 | ||||
| 	if (unlikely(!rt)) return; | ||||
| 	saved = rt->implementation_private; | ||||
| 	ftr_gpio_set_headphone(rt, 0); | ||||
| 	ftr_gpio_set_amp(rt, 0); | ||||
| 	ftr_gpio_set_lineout(rt, 0); | ||||
| 	rt->implementation_private = saved; | ||||
| } | ||||
| 
 | ||||
| static void ftr_gpio_all_amps_restore(struct gpio_runtime *rt) | ||||
| { | ||||
| 	int s; | ||||
| 
 | ||||
| 	if (unlikely(!rt)) return; | ||||
| 	s = rt->implementation_private; | ||||
| 	ftr_gpio_set_headphone(rt, (s>>0)&1); | ||||
| 	ftr_gpio_set_amp(rt, (s>>1)&1); | ||||
| 	ftr_gpio_set_lineout(rt, (s>>2)&1); | ||||
| } | ||||
| 
 | ||||
| static void ftr_handle_notify(void *data) | ||||
| { | ||||
| 	struct gpio_notification *notif = data; | ||||
| 
 | ||||
| 	mutex_lock(¬if->mutex); | ||||
| 	if (notif->notify) | ||||
| 		notif->notify(notif->data); | ||||
| 	mutex_unlock(¬if->mutex); | ||||
| } | ||||
| 
 | ||||
| static void ftr_gpio_init(struct gpio_runtime *rt) | ||||
| { | ||||
| 	get_gpio("headphone-mute", NULL, | ||||
| 		 &headphone_mute_gpio, | ||||
| 		 &headphone_mute_gpio_activestate); | ||||
| 	get_gpio("amp-mute", NULL, | ||||
| 		 &_mute_gpio, | ||||
| 		 &_mute_gpio_activestate); | ||||
| 	get_gpio("lineout-mute", NULL, | ||||
| 		 &lineout_mute_gpio, | ||||
| 		 &lineout_mute_gpio_activestate); | ||||
| 	get_gpio("hw-reset", "audio-hw-reset", | ||||
| 		 &hw_reset_gpio, | ||||
| 		 &hw_reset_gpio_activestate); | ||||
| 
 | ||||
| 	headphone_detect_node = get_gpio("headphone-detect", NULL, | ||||
| 					 &headphone_detect_gpio, | ||||
| 					 &headphone_detect_gpio_activestate); | ||||
| 	/* go Apple, and thanks for giving these different names
 | ||||
| 	 * across the board... */ | ||||
| 	lineout_detect_node = get_gpio("lineout-detect", "line-output-detect", | ||||
| 				       &lineout_detect_gpio, | ||||
| 				       &lineout_detect_gpio_activestate); | ||||
| 	linein_detect_node = get_gpio("linein-detect", "line-input-detect", | ||||
| 				      &linein_detect_gpio, | ||||
| 				      &linein_detect_gpio_activestate); | ||||
| 
 | ||||
| 	get_irq(headphone_detect_node, &headphone_detect_irq); | ||||
| 	get_irq(lineout_detect_node, &lineout_detect_irq); | ||||
| 	get_irq(linein_detect_node, &linein_detect_irq); | ||||
| 
 | ||||
| 	ftr_gpio_all_amps_off(rt); | ||||
| 	rt->implementation_private = 0; | ||||
| 	INIT_WORK(&rt->headphone_notify.work, ftr_handle_notify, | ||||
| 		  &rt->headphone_notify); | ||||
| 	INIT_WORK(&rt->line_in_notify.work, ftr_handle_notify, | ||||
| 		  &rt->line_in_notify); | ||||
| 	INIT_WORK(&rt->line_out_notify.work, ftr_handle_notify, | ||||
| 		  &rt->line_out_notify); | ||||
| 	mutex_init(&rt->headphone_notify.mutex); | ||||
| 	mutex_init(&rt->line_in_notify.mutex); | ||||
| 	mutex_init(&rt->line_out_notify.mutex); | ||||
| } | ||||
| 
 | ||||
| static void ftr_gpio_exit(struct gpio_runtime *rt) | ||||
| { | ||||
| 	ftr_gpio_all_amps_off(rt); | ||||
| 	rt->implementation_private = 0; | ||||
| 	if (rt->headphone_notify.notify) | ||||
| 		free_irq(headphone_detect_irq, &rt->headphone_notify); | ||||
| 	if (rt->line_in_notify.gpio_private) | ||||
| 		free_irq(linein_detect_irq, &rt->line_in_notify); | ||||
| 	if (rt->line_out_notify.gpio_private) | ||||
| 		free_irq(lineout_detect_irq, &rt->line_out_notify); | ||||
| 	cancel_delayed_work(&rt->headphone_notify.work); | ||||
| 	cancel_delayed_work(&rt->line_in_notify.work); | ||||
| 	cancel_delayed_work(&rt->line_out_notify.work); | ||||
| 	flush_scheduled_work(); | ||||
| 	mutex_destroy(&rt->headphone_notify.mutex); | ||||
| 	mutex_destroy(&rt->line_in_notify.mutex); | ||||
| 	mutex_destroy(&rt->line_out_notify.mutex); | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t ftr_handle_notify_irq(int xx, | ||||
| 					 void *data, | ||||
| 					 struct pt_regs *regs) | ||||
| { | ||||
| 	struct gpio_notification *notif = data; | ||||
| 
 | ||||
| 	schedule_work(¬if->work); | ||||
| 
 | ||||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| static int ftr_set_notify(struct gpio_runtime *rt, | ||||
| 			  enum notify_type type, | ||||
| 			  notify_func_t notify, | ||||
| 			  void *data) | ||||
| { | ||||
| 	struct gpio_notification *notif; | ||||
| 	notify_func_t old; | ||||
| 	int irq; | ||||
| 	char *name; | ||||
| 	int err = -EBUSY; | ||||
| 
 | ||||
| 	switch (type) { | ||||
| 	case AOA_NOTIFY_HEADPHONE: | ||||
| 		notif = &rt->headphone_notify; | ||||
| 		name = "headphone-detect"; | ||||
| 		irq = headphone_detect_irq; | ||||
| 		break; | ||||
| 	case AOA_NOTIFY_LINE_IN: | ||||
| 		notif = &rt->line_in_notify; | ||||
| 		name = "linein-detect"; | ||||
| 		irq = linein_detect_irq; | ||||
| 		break; | ||||
| 	case AOA_NOTIFY_LINE_OUT: | ||||
| 		notif = &rt->line_out_notify; | ||||
| 		name = "lineout-detect"; | ||||
| 		irq = lineout_detect_irq; | ||||
| 		break; | ||||
| 	default: | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	if (irq == -1) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	mutex_lock(¬if->mutex); | ||||
| 
 | ||||
| 	old = notif->notify; | ||||
| 
 | ||||
| 	if (!old && !notify) { | ||||
| 		err = 0; | ||||
| 		goto out_unlock; | ||||
| 	} | ||||
| 
 | ||||
| 	if (old && notify) { | ||||
| 		if (old == notify && notif->data == data) | ||||
| 			err = 0; | ||||
| 		goto out_unlock; | ||||
| 	} | ||||
| 
 | ||||
| 	if (old && !notify) | ||||
| 		free_irq(irq, notif); | ||||
| 
 | ||||
| 	if (!old && notify) { | ||||
| 		err = request_irq(irq, ftr_handle_notify_irq, 0, name, notif); | ||||
| 		if (err) | ||||
| 			goto out_unlock; | ||||
| 	} | ||||
| 
 | ||||
| 	notif->notify = notify; | ||||
| 	notif->data = data; | ||||
| 
 | ||||
| 	err = 0; | ||||
|  out_unlock: | ||||
| 	mutex_unlock(¬if->mutex); | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static int ftr_get_detect(struct gpio_runtime *rt, | ||||
| 			  enum notify_type type) | ||||
| { | ||||
| 	int gpio, ret, active; | ||||
| 
 | ||||
| 	switch (type) { | ||||
| 	case AOA_NOTIFY_HEADPHONE: | ||||
| 		gpio = headphone_detect_gpio; | ||||
| 		active = headphone_detect_gpio_activestate; | ||||
| 		break; | ||||
| 	case AOA_NOTIFY_LINE_IN: | ||||
| 		gpio = linein_detect_gpio; | ||||
| 		active = linein_detect_gpio_activestate; | ||||
| 		break; | ||||
| 	case AOA_NOTIFY_LINE_OUT: | ||||
| 		gpio = lineout_detect_gpio; | ||||
| 		active = lineout_detect_gpio_activestate; | ||||
| 		break; | ||||
| 	default: | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	if (gpio == -1) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	ret = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 	return ((ret >> 1) & 1) == active; | ||||
| } | ||||
| 
 | ||||
| static struct gpio_methods methods = { | ||||
| 	.init			= ftr_gpio_init, | ||||
| 	.exit			= ftr_gpio_exit, | ||||
| 	.all_amps_off		= ftr_gpio_all_amps_off, | ||||
| 	.all_amps_restore	= ftr_gpio_all_amps_restore, | ||||
| 	.set_headphone		= ftr_gpio_set_headphone, | ||||
| 	.set_speakers		= ftr_gpio_set_amp, | ||||
| 	.set_lineout		= ftr_gpio_set_lineout, | ||||
| 	.set_hw_reset		= ftr_gpio_set_hw_reset, | ||||
| 	.get_headphone		= ftr_gpio_get_headphone, | ||||
| 	.get_speakers		= ftr_gpio_get_amp, | ||||
| 	.get_lineout		= ftr_gpio_get_lineout, | ||||
| 	.set_notify		= ftr_set_notify, | ||||
| 	.get_detect		= ftr_get_detect, | ||||
| }; | ||||
| 
 | ||||
| struct gpio_methods *ftr_gpio_methods = &methods; | ||||
| EXPORT_SYMBOL_GPL(ftr_gpio_methods); | ||||
							
								
								
									
										246
									
								
								sound/aoa/core/snd-aoa-gpio-pmf.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								sound/aoa/core/snd-aoa-gpio-pmf.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,246 @@ | ||||
| /*
 | ||||
|  * Apple Onboard Audio pmf GPIOs | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| 
 | ||||
| #include <asm/pmac_feature.h> | ||||
| #include <asm/pmac_pfunc.h> | ||||
| #include "../aoa.h" | ||||
| 
 | ||||
| #define PMF_GPIO(name, bit)					\ | ||||
| static void pmf_gpio_set_##name(struct gpio_runtime *rt, int on)\ | ||||
| {								\ | ||||
| 	struct pmf_args args = { .count = 1, .u[0].v = !on };	\ | ||||
| 								\ | ||||
| 	if (unlikely(!rt)) return;				\ | ||||
| 	pmf_call_function(rt->node, #name "-mute", &args);	\ | ||||
| 	rt->implementation_private &= ~(1<<bit);		\ | ||||
| 	rt->implementation_private |= (!!on << bit);		\ | ||||
| }								\ | ||||
| static int pmf_gpio_get_##name(struct gpio_runtime *rt)		\ | ||||
| {								\ | ||||
| 	if (unlikely(!rt)) return 0;				\ | ||||
| 	return (rt->implementation_private>>bit)&1;		\ | ||||
| } | ||||
| 
 | ||||
| PMF_GPIO(headphone, 0); | ||||
| PMF_GPIO(amp, 1); | ||||
| PMF_GPIO(lineout, 2); | ||||
| 
 | ||||
| static void pmf_gpio_set_hw_reset(struct gpio_runtime *rt, int on) | ||||
| { | ||||
| 	struct pmf_args args = { .count = 1, .u[0].v = !!on }; | ||||
| 
 | ||||
| 	if (unlikely(!rt)) return; | ||||
| 	pmf_call_function(rt->node, "hw-reset", &args); | ||||
| } | ||||
| 
 | ||||
| static void pmf_gpio_all_amps_off(struct gpio_runtime *rt) | ||||
| { | ||||
| 	int saved; | ||||
| 
 | ||||
| 	if (unlikely(!rt)) return; | ||||
| 	saved = rt->implementation_private; | ||||
| 	pmf_gpio_set_headphone(rt, 0); | ||||
| 	pmf_gpio_set_amp(rt, 0); | ||||
| 	pmf_gpio_set_lineout(rt, 0); | ||||
| 	rt->implementation_private = saved; | ||||
| } | ||||
| 
 | ||||
| static void pmf_gpio_all_amps_restore(struct gpio_runtime *rt) | ||||
| { | ||||
| 	int s; | ||||
| 
 | ||||
| 	if (unlikely(!rt)) return; | ||||
| 	s = rt->implementation_private; | ||||
| 	pmf_gpio_set_headphone(rt, (s>>0)&1); | ||||
| 	pmf_gpio_set_amp(rt, (s>>1)&1); | ||||
| 	pmf_gpio_set_lineout(rt, (s>>2)&1); | ||||
| } | ||||
| 
 | ||||
| static void pmf_handle_notify(void *data) | ||||
| { | ||||
| 	struct gpio_notification *notif = data; | ||||
| 
 | ||||
| 	mutex_lock(¬if->mutex); | ||||
| 	if (notif->notify) | ||||
| 		notif->notify(notif->data); | ||||
| 	mutex_unlock(¬if->mutex); | ||||
| } | ||||
| 
 | ||||
| static void pmf_gpio_init(struct gpio_runtime *rt) | ||||
| { | ||||
| 	pmf_gpio_all_amps_off(rt); | ||||
| 	rt->implementation_private = 0; | ||||
| 	INIT_WORK(&rt->headphone_notify.work, pmf_handle_notify, | ||||
| 		  &rt->headphone_notify); | ||||
| 	INIT_WORK(&rt->line_in_notify.work, pmf_handle_notify, | ||||
| 		  &rt->line_in_notify); | ||||
| 	INIT_WORK(&rt->line_out_notify.work, pmf_handle_notify, | ||||
| 		  &rt->line_out_notify); | ||||
| 	mutex_init(&rt->headphone_notify.mutex); | ||||
| 	mutex_init(&rt->line_in_notify.mutex); | ||||
| 	mutex_init(&rt->line_out_notify.mutex); | ||||
| } | ||||
| 
 | ||||
| static void pmf_gpio_exit(struct gpio_runtime *rt) | ||||
| { | ||||
| 	pmf_gpio_all_amps_off(rt); | ||||
| 	rt->implementation_private = 0; | ||||
| 
 | ||||
| 	if (rt->headphone_notify.gpio_private) | ||||
| 		pmf_unregister_irq_client(rt->headphone_notify.gpio_private); | ||||
| 	if (rt->line_in_notify.gpio_private) | ||||
| 		pmf_unregister_irq_client(rt->line_in_notify.gpio_private); | ||||
| 	if (rt->line_out_notify.gpio_private) | ||||
| 		pmf_unregister_irq_client(rt->line_out_notify.gpio_private); | ||||
| 
 | ||||
| 	/* make sure no work is pending before freeing
 | ||||
| 	 * all things */ | ||||
| 	cancel_delayed_work(&rt->headphone_notify.work); | ||||
| 	cancel_delayed_work(&rt->line_in_notify.work); | ||||
| 	cancel_delayed_work(&rt->line_out_notify.work); | ||||
| 	flush_scheduled_work(); | ||||
| 
 | ||||
| 	mutex_destroy(&rt->headphone_notify.mutex); | ||||
| 	mutex_destroy(&rt->line_in_notify.mutex); | ||||
| 	mutex_destroy(&rt->line_out_notify.mutex); | ||||
| 
 | ||||
| 	if (rt->headphone_notify.gpio_private) | ||||
| 		kfree(rt->headphone_notify.gpio_private); | ||||
| 	if (rt->line_in_notify.gpio_private) | ||||
| 		kfree(rt->line_in_notify.gpio_private); | ||||
| 	if (rt->line_out_notify.gpio_private) | ||||
| 		kfree(rt->line_out_notify.gpio_private); | ||||
| } | ||||
| 
 | ||||
| static void pmf_handle_notify_irq(void *data) | ||||
| { | ||||
| 	struct gpio_notification *notif = data; | ||||
| 
 | ||||
| 	schedule_work(¬if->work); | ||||
| } | ||||
| 
 | ||||
| static int pmf_set_notify(struct gpio_runtime *rt, | ||||
| 			  enum notify_type type, | ||||
| 			  notify_func_t notify, | ||||
| 			  void *data) | ||||
| { | ||||
| 	struct gpio_notification *notif; | ||||
| 	notify_func_t old; | ||||
| 	struct pmf_irq_client *irq_client; | ||||
| 	char *name; | ||||
| 	int err = -EBUSY; | ||||
| 
 | ||||
| 	switch (type) { | ||||
| 	case AOA_NOTIFY_HEADPHONE: | ||||
| 		notif = &rt->headphone_notify; | ||||
| 		name = "headphone-detect"; | ||||
| 		break; | ||||
| 	case AOA_NOTIFY_LINE_IN: | ||||
| 		notif = &rt->line_in_notify; | ||||
| 		name = "linein-detect"; | ||||
| 		break; | ||||
| 	case AOA_NOTIFY_LINE_OUT: | ||||
| 		notif = &rt->line_out_notify; | ||||
| 		name = "lineout-detect"; | ||||
| 		break; | ||||
| 	default: | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	mutex_lock(¬if->mutex); | ||||
| 
 | ||||
| 	old = notif->notify; | ||||
| 
 | ||||
| 	if (!old && !notify) { | ||||
| 		err = 0; | ||||
| 		goto out_unlock; | ||||
| 	} | ||||
| 
 | ||||
| 	if (old && notify) { | ||||
| 		if (old == notify && notif->data == data) | ||||
| 			err = 0; | ||||
| 		goto out_unlock; | ||||
| 	} | ||||
| 
 | ||||
| 	if (old && !notify) { | ||||
| 		irq_client = notif->gpio_private; | ||||
| 		pmf_unregister_irq_client(irq_client); | ||||
| 		kfree(irq_client); | ||||
| 		notif->gpio_private = NULL; | ||||
| 	} | ||||
| 	if (!old && notify) { | ||||
| 		irq_client = kzalloc(sizeof(struct pmf_irq_client), | ||||
| 				     GFP_KERNEL); | ||||
| 		irq_client->data = notif; | ||||
| 		irq_client->handler = pmf_handle_notify_irq; | ||||
| 		irq_client->owner = THIS_MODULE; | ||||
| 		err = pmf_register_irq_client(rt->node, | ||||
| 					      name, | ||||
| 					      irq_client); | ||||
| 		if (err) { | ||||
| 			printk(KERN_ERR "snd-aoa: gpio layer failed to" | ||||
| 					" register %s irq (%d)\n", name, err); | ||||
| 			kfree(irq_client); | ||||
| 			goto out_unlock; | ||||
| 		} | ||||
| 		notif->gpio_private = irq_client; | ||||
| 	} | ||||
| 	notif->notify = notify; | ||||
| 	notif->data = data; | ||||
| 
 | ||||
| 	err = 0; | ||||
|  out_unlock: | ||||
| 	mutex_unlock(¬if->mutex); | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static int pmf_get_detect(struct gpio_runtime *rt, | ||||
| 			  enum notify_type type) | ||||
| { | ||||
| 	char *name; | ||||
| 	int err = -EBUSY, ret; | ||||
| 	struct pmf_args args = { .count = 1, .u[0].p = &ret }; | ||||
| 
 | ||||
| 	switch (type) { | ||||
| 	case AOA_NOTIFY_HEADPHONE: | ||||
| 		name = "headphone-detect"; | ||||
| 		break; | ||||
| 	case AOA_NOTIFY_LINE_IN: | ||||
| 		name = "linein-detect"; | ||||
| 		break; | ||||
| 	case AOA_NOTIFY_LINE_OUT: | ||||
| 		name = "lineout-detect"; | ||||
| 		break; | ||||
| 	default: | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	err = pmf_call_function(rt->node, name, &args); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static struct gpio_methods methods = { | ||||
| 	.init			= pmf_gpio_init, | ||||
| 	.exit			= pmf_gpio_exit, | ||||
| 	.all_amps_off		= pmf_gpio_all_amps_off, | ||||
| 	.all_amps_restore	= pmf_gpio_all_amps_restore, | ||||
| 	.set_headphone		= pmf_gpio_set_headphone, | ||||
| 	.set_speakers		= pmf_gpio_set_amp, | ||||
| 	.set_lineout		= pmf_gpio_set_lineout, | ||||
| 	.set_hw_reset		= pmf_gpio_set_hw_reset, | ||||
| 	.get_headphone		= pmf_gpio_get_headphone, | ||||
| 	.get_speakers		= pmf_gpio_get_amp, | ||||
| 	.get_lineout		= pmf_gpio_get_lineout, | ||||
| 	.set_notify		= pmf_set_notify, | ||||
| 	.get_detect		= pmf_get_detect, | ||||
| }; | ||||
| 
 | ||||
| struct gpio_methods *pmf_gpio_methods = &methods; | ||||
| EXPORT_SYMBOL_GPL(pmf_gpio_methods); | ||||
							
								
								
									
										12
									
								
								sound/aoa/fabrics/Kconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								sound/aoa/fabrics/Kconfig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| config SND_AOA_FABRIC_LAYOUT | ||||
| 	tristate "layout-id fabric" | ||||
| 	depends SND_AOA | ||||
| 	select SND_AOA_SOUNDBUS | ||||
| 	select SND_AOA_SOUNDBUS_I2S | ||||
| 	---help--- | ||||
| 	This enables the layout-id fabric for the Apple Onboard | ||||
| 	Audio driver, the module holding it all together | ||||
| 	based on the device-tree's layout-id property. | ||||
| 	 | ||||
| 	If you are unsure and have a later Apple machine, | ||||
| 	compile it as a module. | ||||
							
								
								
									
										1
									
								
								sound/aoa/fabrics/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								sound/aoa/fabrics/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| obj-$(CONFIG_SND_AOA_FABRIC_LAYOUT) += snd-aoa-fabric-layout.o | ||||
							
								
								
									
										1109
									
								
								sound/aoa/fabrics/snd-aoa-fabric-layout.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1109
									
								
								sound/aoa/fabrics/snd-aoa-fabric-layout.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										14
									
								
								sound/aoa/soundbus/Kconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								sound/aoa/soundbus/Kconfig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| config SND_AOA_SOUNDBUS | ||||
| 	tristate "Apple Soundbus support" | ||||
| 	depends on SOUND && SND_PCM && EXPERIMENTAL | ||||
| 	---help--- | ||||
| 	This option enables the generic driver for the soundbus | ||||
| 	support on Apple machines. | ||||
| 	 | ||||
| 	It is required for the sound bus implementations. | ||||
| 
 | ||||
| config SND_AOA_SOUNDBUS_I2S | ||||
| 	tristate "I2S bus support" | ||||
| 	depends on SND_AOA_SOUNDBUS && PCI | ||||
| 	---help--- | ||||
| 	This option enables support for Apple I2S busses. | ||||
							
								
								
									
										3
									
								
								sound/aoa/soundbus/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								sound/aoa/soundbus/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| obj-$(CONFIG_SND_AOA_SOUNDBUS) += snd-aoa-soundbus.o | ||||
| snd-aoa-soundbus-objs := core.o sysfs.o | ||||
| obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += i2sbus/ | ||||
							
								
								
									
										250
									
								
								sound/aoa/soundbus/core.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								sound/aoa/soundbus/core.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,250 @@ | ||||
| /*
 | ||||
|  * soundbus | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/module.h> | ||||
| #include "soundbus.h" | ||||
| 
 | ||||
| MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); | ||||
| MODULE_LICENSE("GPL"); | ||||
| MODULE_DESCRIPTION("Apple Soundbus"); | ||||
| 
 | ||||
| struct soundbus_dev *soundbus_dev_get(struct soundbus_dev *dev) | ||||
| { | ||||
| 	struct device *tmp; | ||||
| 
 | ||||
| 	if (!dev) | ||||
| 		return NULL; | ||||
| 	tmp = get_device(&dev->ofdev.dev); | ||||
| 	if (tmp) | ||||
| 		return to_soundbus_device(tmp); | ||||
| 	else | ||||
| 		return NULL; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(soundbus_dev_get); | ||||
| 
 | ||||
| void soundbus_dev_put(struct soundbus_dev *dev) | ||||
| { | ||||
| 	if (dev) | ||||
| 		put_device(&dev->ofdev.dev); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(soundbus_dev_put); | ||||
| 
 | ||||
| static int soundbus_probe(struct device *dev) | ||||
| { | ||||
| 	int error = -ENODEV; | ||||
| 	struct soundbus_driver *drv; | ||||
| 	struct soundbus_dev *soundbus_dev; | ||||
| 
 | ||||
| 	drv = to_soundbus_driver(dev->driver); | ||||
| 	soundbus_dev = to_soundbus_device(dev); | ||||
| 
 | ||||
| 	if (!drv->probe) | ||||
| 		return error; | ||||
| 
 | ||||
| 	soundbus_dev_get(soundbus_dev); | ||||
| 
 | ||||
| 	error = drv->probe(soundbus_dev); | ||||
| 	if (error) | ||||
| 		soundbus_dev_put(soundbus_dev); | ||||
| 
 | ||||
| 	return error; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static int soundbus_uevent(struct device *dev, char **envp, int num_envp, | ||||
| 			   char *buffer, int buffer_size) | ||||
| { | ||||
| 	struct soundbus_dev * soundbus_dev; | ||||
| 	struct of_device * of; | ||||
| 	char *scratch, *compat, *compat2; | ||||
| 	int i = 0; | ||||
| 	int length, cplen, cplen2, seen = 0; | ||||
| 
 | ||||
| 	if (!dev) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	soundbus_dev = to_soundbus_device(dev); | ||||
| 	if (!soundbus_dev) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	of = &soundbus_dev->ofdev; | ||||
| 
 | ||||
| 	/* stuff we want to pass to /sbin/hotplug */ | ||||
| 	envp[i++] = scratch = buffer; | ||||
| 	length = scnprintf (scratch, buffer_size, "OF_NAME=%s", of->node->name); | ||||
| 	++length; | ||||
| 	buffer_size -= length; | ||||
| 	if ((buffer_size <= 0) || (i >= num_envp)) | ||||
| 		return -ENOMEM; | ||||
| 	scratch += length; | ||||
| 
 | ||||
| 	envp[i++] = scratch; | ||||
| 	length = scnprintf (scratch, buffer_size, "OF_TYPE=%s", of->node->type); | ||||
| 	++length; | ||||
| 	buffer_size -= length; | ||||
| 	if ((buffer_size <= 0) || (i >= num_envp)) | ||||
| 		return -ENOMEM; | ||||
| 	scratch += length; | ||||
| 
 | ||||
| 	/* Since the compatible field can contain pretty much anything
 | ||||
| 	 * it's not really legal to split it out with commas. We split it | ||||
| 	 * up using a number of environment variables instead. */ | ||||
| 
 | ||||
| 	compat = (char *) get_property(of->node, "compatible", &cplen); | ||||
| 	compat2 = compat; | ||||
| 	cplen2= cplen; | ||||
| 	while (compat && cplen > 0) { | ||||
| 		envp[i++] = scratch; | ||||
| 		length = scnprintf (scratch, buffer_size, | ||||
| 				     "OF_COMPATIBLE_%d=%s", seen, compat); | ||||
| 		++length; | ||||
| 		buffer_size -= length; | ||||
| 		if ((buffer_size <= 0) || (i >= num_envp)) | ||||
| 			return -ENOMEM; | ||||
| 		scratch += length; | ||||
| 		length = strlen (compat) + 1; | ||||
| 		compat += length; | ||||
| 		cplen -= length; | ||||
| 		seen++; | ||||
| 	} | ||||
| 
 | ||||
| 	envp[i++] = scratch; | ||||
| 	length = scnprintf (scratch, buffer_size, "OF_COMPATIBLE_N=%d", seen); | ||||
| 	++length; | ||||
| 	buffer_size -= length; | ||||
| 	if ((buffer_size <= 0) || (i >= num_envp)) | ||||
| 		return -ENOMEM; | ||||
| 	scratch += length; | ||||
| 
 | ||||
| 	envp[i++] = scratch; | ||||
| 	length = scnprintf (scratch, buffer_size, "MODALIAS=%s", | ||||
| 			soundbus_dev->modalias); | ||||
| 
 | ||||
| 	buffer_size -= length; | ||||
| 	if ((buffer_size <= 0) || (i >= num_envp)) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	envp[i] = NULL; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int soundbus_device_remove(struct device *dev) | ||||
| { | ||||
| 	struct soundbus_dev * soundbus_dev = to_soundbus_device(dev); | ||||
| 	struct soundbus_driver * drv = to_soundbus_driver(dev->driver); | ||||
| 
 | ||||
| 	if (dev->driver && drv->remove) | ||||
| 		drv->remove(soundbus_dev); | ||||
| 	soundbus_dev_put(soundbus_dev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void soundbus_device_shutdown(struct device *dev) | ||||
| { | ||||
| 	struct soundbus_dev * soundbus_dev = to_soundbus_device(dev); | ||||
| 	struct soundbus_driver * drv = to_soundbus_driver(dev->driver); | ||||
| 
 | ||||
| 	if (dev->driver && drv->shutdown) | ||||
| 		drv->shutdown(soundbus_dev); | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| 
 | ||||
| static int soundbus_device_suspend(struct device *dev, pm_message_t state) | ||||
| { | ||||
| 	struct soundbus_dev * soundbus_dev = to_soundbus_device(dev); | ||||
| 	struct soundbus_driver * drv = to_soundbus_driver(dev->driver); | ||||
| 
 | ||||
| 	if (dev->driver && drv->suspend) | ||||
| 		return drv->suspend(soundbus_dev, state); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int soundbus_device_resume(struct device * dev) | ||||
| { | ||||
| 	struct soundbus_dev * soundbus_dev = to_soundbus_device(dev); | ||||
| 	struct soundbus_driver * drv = to_soundbus_driver(dev->driver); | ||||
| 
 | ||||
| 	if (dev->driver && drv->resume) | ||||
| 		return drv->resume(soundbus_dev); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| #endif /* CONFIG_PM */ | ||||
| 
 | ||||
| extern struct device_attribute soundbus_dev_attrs[]; | ||||
| 
 | ||||
| static struct bus_type soundbus_bus_type = { | ||||
| 	.name		= "aoa-soundbus", | ||||
| 	.probe		= soundbus_probe, | ||||
| 	.uevent		= soundbus_uevent, | ||||
| 	.remove		= soundbus_device_remove, | ||||
| 	.shutdown	= soundbus_device_shutdown, | ||||
| #ifdef CONFIG_PM | ||||
| 	.suspend	= soundbus_device_suspend, | ||||
| 	.resume		= soundbus_device_resume, | ||||
| #endif | ||||
| 	.dev_attrs	= soundbus_dev_attrs, | ||||
| }; | ||||
| 
 | ||||
| static int __init soundbus_init(void) | ||||
| { | ||||
| 	return bus_register(&soundbus_bus_type); | ||||
| } | ||||
| 
 | ||||
| static void __exit soundbus_exit(void) | ||||
| { | ||||
| 	bus_unregister(&soundbus_bus_type); | ||||
| } | ||||
| 
 | ||||
| int soundbus_add_one(struct soundbus_dev *dev) | ||||
| { | ||||
| 	static int devcount; | ||||
| 
 | ||||
| 	/* sanity checks */ | ||||
| 	if (!dev->attach_codec || | ||||
| 	    !dev->ofdev.node || | ||||
| 	    dev->pcmname || | ||||
| 	    dev->pcmid != -1) { | ||||
| 		printk(KERN_ERR "soundbus: adding device failed sanity check!\n"); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	snprintf(dev->ofdev.dev.bus_id, BUS_ID_SIZE, "soundbus:%x", ++devcount); | ||||
| 	dev->ofdev.dev.bus = &soundbus_bus_type; | ||||
| 	return of_device_register(&dev->ofdev); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(soundbus_add_one); | ||||
| 
 | ||||
| void soundbus_remove_one(struct soundbus_dev *dev) | ||||
| { | ||||
| 	of_device_unregister(&dev->ofdev); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(soundbus_remove_one); | ||||
| 
 | ||||
| int soundbus_register_driver(struct soundbus_driver *drv) | ||||
| { | ||||
| 	/* initialize common driver fields */ | ||||
| 	drv->driver.name = drv->name; | ||||
| 	drv->driver.bus = &soundbus_bus_type; | ||||
| 
 | ||||
| 	/* register with core */ | ||||
| 	return driver_register(&drv->driver); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(soundbus_register_driver); | ||||
| 
 | ||||
| void soundbus_unregister_driver(struct soundbus_driver *drv) | ||||
| { | ||||
| 	driver_unregister(&drv->driver); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(soundbus_unregister_driver); | ||||
| 
 | ||||
| module_init(soundbus_init); | ||||
| module_exit(soundbus_exit); | ||||
							
								
								
									
										2
									
								
								sound/aoa/soundbus/i2sbus/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								sound/aoa/soundbus/i2sbus/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += snd-aoa-i2sbus.o | ||||
| snd-aoa-i2sbus-objs := i2sbus-core.o i2sbus-pcm.o i2sbus-control.o | ||||
							
								
								
									
										192
									
								
								sound/aoa/soundbus/i2sbus/i2sbus-control.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								sound/aoa/soundbus/i2sbus/i2sbus-control.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,192 @@ | ||||
| /*
 | ||||
|  * i2sbus driver -- bus control routines | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| 
 | ||||
| #include <asm/io.h> | ||||
| #include <linux/delay.h> | ||||
| #include <asm/prom.h> | ||||
| #include <asm/macio.h> | ||||
| #include <asm/pmac_feature.h> | ||||
| #include <asm/pmac_pfunc.h> | ||||
| #include "i2sbus.h" | ||||
| 
 | ||||
| int i2sbus_control_init(struct macio_dev* dev, struct i2sbus_control **c) | ||||
| { | ||||
| 	*c = kzalloc(sizeof(struct i2sbus_control), GFP_KERNEL); | ||||
| 	if (!*c) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	INIT_LIST_HEAD(&(*c)->list); | ||||
| 
 | ||||
| 	if (of_address_to_resource(dev->ofdev.node, 0, &(*c)->rsrc)) | ||||
| 		goto err; | ||||
| 	/* we really should be using feature calls instead of mapping
 | ||||
| 	 * these registers. It's safe for now since no one else is | ||||
| 	 * touching them... */ | ||||
| 	(*c)->controlregs = ioremap((*c)->rsrc.start, | ||||
| 				    sizeof(struct i2s_control_regs)); | ||||
| 	if (!(*c)->controlregs) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	return 0; | ||||
|  err: | ||||
| 	kfree(*c); | ||||
| 	*c = NULL; | ||||
| 	return -ENODEV; | ||||
| } | ||||
| 
 | ||||
| void i2sbus_control_destroy(struct i2sbus_control *c) | ||||
| { | ||||
| 	iounmap(c->controlregs); | ||||
| 	kfree(c); | ||||
| } | ||||
| 
 | ||||
| /* this is serialised externally */ | ||||
| int i2sbus_control_add_dev(struct i2sbus_control *c, | ||||
| 			   struct i2sbus_dev *i2sdev) | ||||
| { | ||||
| 	struct device_node *np; | ||||
| 
 | ||||
| 	np = i2sdev->sound.ofdev.node; | ||||
| 	i2sdev->enable = pmf_find_function(np, "enable"); | ||||
| 	i2sdev->cell_enable = pmf_find_function(np, "cell-enable"); | ||||
| 	i2sdev->clock_enable = pmf_find_function(np, "clock-enable"); | ||||
| 	i2sdev->cell_disable = pmf_find_function(np, "cell-disable"); | ||||
| 	i2sdev->clock_disable = pmf_find_function(np, "clock-disable"); | ||||
| 
 | ||||
| 	/* if the bus number is not 0 or 1 we absolutely need to use
 | ||||
| 	 * the platform functions -- there's nothing in Darwin that | ||||
| 	 * would allow seeing a system behind what the FCRs are then, | ||||
| 	 * and I don't want to go parsing a bunch of platform functions | ||||
| 	 * by hand to try finding a system... */ | ||||
| 	if (i2sdev->bus_number != 0 && i2sdev->bus_number != 1 && | ||||
| 	    (!i2sdev->enable || | ||||
| 	     !i2sdev->cell_enable || !i2sdev->clock_enable || | ||||
| 	     !i2sdev->cell_disable || !i2sdev->clock_disable)) { | ||||
| 		pmf_put_function(i2sdev->enable); | ||||
| 		pmf_put_function(i2sdev->cell_enable); | ||||
| 		pmf_put_function(i2sdev->clock_enable); | ||||
| 		pmf_put_function(i2sdev->cell_disable); | ||||
| 		pmf_put_function(i2sdev->clock_disable); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	list_add(&i2sdev->item, &c->list); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| void i2sbus_control_remove_dev(struct i2sbus_control *c, | ||||
| 			       struct i2sbus_dev *i2sdev) | ||||
| { | ||||
| 	/* this is serialised externally */ | ||||
| 	list_del(&i2sdev->item); | ||||
| 	if (list_empty(&c->list)) | ||||
| 		i2sbus_control_destroy(c); | ||||
| } | ||||
| 
 | ||||
| int i2sbus_control_enable(struct i2sbus_control *c, | ||||
| 			  struct i2sbus_dev *i2sdev) | ||||
| { | ||||
| 	struct pmf_args args = { .count = 0 }; | ||||
| 	int cc; | ||||
| 
 | ||||
| 	if (i2sdev->enable) | ||||
| 		return pmf_call_one(i2sdev->enable, &args); | ||||
| 
 | ||||
| 	switch (i2sdev->bus_number) { | ||||
| 	case 0: | ||||
| 		cc = in_le32(&c->controlregs->cell_control); | ||||
| 		out_le32(&c->controlregs->cell_control, cc | CTRL_CLOCK_INTF_0_ENABLE); | ||||
| 		break; | ||||
| 	case 1: | ||||
| 		cc = in_le32(&c->controlregs->cell_control); | ||||
| 		out_le32(&c->controlregs->cell_control, cc | CTRL_CLOCK_INTF_1_ENABLE); | ||||
| 		break; | ||||
| 	default: | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int i2sbus_control_cell(struct i2sbus_control *c, | ||||
| 			struct i2sbus_dev *i2sdev, | ||||
| 			int enable) | ||||
| { | ||||
| 	struct pmf_args args = { .count = 0 }; | ||||
| 	int cc; | ||||
| 
 | ||||
| 	switch (enable) { | ||||
| 	case 0: | ||||
| 		if (i2sdev->cell_disable) | ||||
| 			return pmf_call_one(i2sdev->cell_disable, &args); | ||||
| 		break; | ||||
| 	case 1: | ||||
| 		if (i2sdev->cell_enable) | ||||
| 			return pmf_call_one(i2sdev->cell_enable, &args); | ||||
| 		break; | ||||
| 	default: | ||||
| 		printk(KERN_ERR "i2sbus: INVALID CELL ENABLE VALUE\n"); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 	switch (i2sdev->bus_number) { | ||||
| 	case 0: | ||||
| 		cc = in_le32(&c->controlregs->cell_control); | ||||
| 		cc &= ~CTRL_CLOCK_CELL_0_ENABLE; | ||||
| 		cc |= enable * CTRL_CLOCK_CELL_0_ENABLE; | ||||
| 		out_le32(&c->controlregs->cell_control, cc); | ||||
| 		break; | ||||
| 	case 1: | ||||
| 		cc = in_le32(&c->controlregs->cell_control); | ||||
| 		cc &= ~CTRL_CLOCK_CELL_1_ENABLE; | ||||
| 		cc |= enable * CTRL_CLOCK_CELL_1_ENABLE; | ||||
| 		out_le32(&c->controlregs->cell_control, cc); | ||||
| 		break; | ||||
| 	default: | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int i2sbus_control_clock(struct i2sbus_control *c, | ||||
| 			 struct i2sbus_dev *i2sdev, | ||||
| 			 int enable) | ||||
| { | ||||
| 	struct pmf_args args = { .count = 0 }; | ||||
| 	int cc; | ||||
| 
 | ||||
| 	switch (enable) { | ||||
| 	case 0: | ||||
| 		if (i2sdev->clock_disable) | ||||
| 			return pmf_call_one(i2sdev->clock_disable, &args); | ||||
| 		break; | ||||
| 	case 1: | ||||
| 		if (i2sdev->clock_enable) | ||||
| 			return pmf_call_one(i2sdev->clock_enable, &args); | ||||
| 		break; | ||||
| 	default: | ||||
| 		printk(KERN_ERR "i2sbus: INVALID CLOCK ENABLE VALUE\n"); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 	switch (i2sdev->bus_number) { | ||||
| 	case 0: | ||||
| 		cc = in_le32(&c->controlregs->cell_control); | ||||
| 		cc &= ~CTRL_CLOCK_CLOCK_0_ENABLE; | ||||
| 		cc |= enable * CTRL_CLOCK_CLOCK_0_ENABLE; | ||||
| 		out_le32(&c->controlregs->cell_control, cc); | ||||
| 		break; | ||||
| 	case 1: | ||||
| 		cc = in_le32(&c->controlregs->cell_control); | ||||
| 		cc &= ~CTRL_CLOCK_CLOCK_1_ENABLE; | ||||
| 		cc |= enable * CTRL_CLOCK_CLOCK_1_ENABLE; | ||||
| 		out_le32(&c->controlregs->cell_control, cc); | ||||
| 		break; | ||||
| 	default: | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
							
								
								
									
										37
									
								
								sound/aoa/soundbus/i2sbus/i2sbus-control.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								sound/aoa/soundbus/i2sbus/i2sbus-control.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| /*
 | ||||
|  * i2sbus driver -- bus register definitions | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| #ifndef __I2SBUS_CONTROLREGS_H | ||||
| #define __I2SBUS_CONTROLREGS_H | ||||
| 
 | ||||
| /* i2s control registers, at least what we know about them */ | ||||
| 
 | ||||
| #define __PAD(m,n) u8 __pad##m[n] | ||||
| #define _PAD(line, n) __PAD(line, n) | ||||
| #define PAD(n) _PAD(__LINE__, (n)) | ||||
| struct i2s_control_regs { | ||||
| 	PAD(0x38); | ||||
| 	__le32 fcr0;		/* 0x38 (unknown) */ | ||||
| 	__le32 cell_control;	/* 0x3c (fcr1) */ | ||||
| 	__le32 fcr2;		/* 0x40 (unknown) */ | ||||
| 	__le32 fcr3;		/* 0x44 (fcr3) */ | ||||
| 	__le32 clock_control;	/* 0x48 (unknown) */ | ||||
| 	PAD(4); | ||||
| 	/* total size: 0x50 bytes */ | ||||
| }  __attribute__((__packed__)); | ||||
| 
 | ||||
| #define CTRL_CLOCK_CELL_0_ENABLE	(1<<10) | ||||
| #define CTRL_CLOCK_CLOCK_0_ENABLE	(1<<12) | ||||
| #define CTRL_CLOCK_SWRESET_0		(1<<11) | ||||
| #define CTRL_CLOCK_INTF_0_ENABLE	(1<<13) | ||||
| 
 | ||||
| #define CTRL_CLOCK_CELL_1_ENABLE	(1<<17) | ||||
| #define CTRL_CLOCK_CLOCK_1_ENABLE	(1<<18) | ||||
| #define CTRL_CLOCK_SWRESET_1		(1<<19) | ||||
| #define CTRL_CLOCK_INTF_1_ENABLE	(1<<20) | ||||
| 
 | ||||
| #endif /* __I2SBUS_CONTROLREGS_H */ | ||||
							
								
								
									
										387
									
								
								sound/aoa/soundbus/i2sbus/i2sbus-core.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										387
									
								
								sound/aoa/soundbus/i2sbus/i2sbus-core.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,387 @@ | ||||
| /*
 | ||||
|  * i2sbus driver | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/module.h> | ||||
| #include <asm/macio.h> | ||||
| #include <asm/dbdma.h> | ||||
| #include <linux/pci.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <sound/driver.h> | ||||
| #include <sound/core.h> | ||||
| #include <linux/dma-mapping.h> | ||||
| #include "../soundbus.h" | ||||
| #include "i2sbus.h" | ||||
| 
 | ||||
| MODULE_LICENSE("GPL"); | ||||
| MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); | ||||
| MODULE_DESCRIPTION("Apple Soundbus: I2S support"); | ||||
| /* for auto-loading, declare that we handle this weird
 | ||||
|  * string that macio puts into the relevant device */ | ||||
| MODULE_ALIAS("of:Ni2sTi2sC"); | ||||
| 
 | ||||
| static struct of_device_id i2sbus_match[] = { | ||||
| 	{ .name = "i2s" }, | ||||
| 	{ } | ||||
| }; | ||||
| 
 | ||||
| static int alloc_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev, | ||||
| 				       struct dbdma_command_mem *r, | ||||
| 				       int numcmds) | ||||
| { | ||||
| 	/* one more for rounding */ | ||||
| 	r->size = (numcmds+1) * sizeof(struct dbdma_cmd); | ||||
| 	/* We use the PCI APIs for now until the generic one gets fixed
 | ||||
| 	 * enough or until we get some macio-specific versions | ||||
| 	 */ | ||||
| 	r->space = dma_alloc_coherent( | ||||
| 			&macio_get_pci_dev(i2sdev->macio)->dev, | ||||
| 			r->size, | ||||
| 			&r->bus_addr, | ||||
| 			GFP_KERNEL); | ||||
| 
 | ||||
| 	if (!r->space) return -ENOMEM; | ||||
| 
 | ||||
| 	memset(r->space, 0, r->size); | ||||
| 	r->cmds = (void*)DBDMA_ALIGN(r->space); | ||||
| 	r->bus_cmd_start = r->bus_addr + | ||||
| 			   (dma_addr_t)((char*)r->cmds - (char*)r->space); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void free_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev, | ||||
| 				       struct dbdma_command_mem *r) | ||||
| { | ||||
| 	if (!r->space) return; | ||||
| 	 | ||||
| 	dma_free_coherent(&macio_get_pci_dev(i2sdev->macio)->dev, | ||||
| 			    r->size, r->space, r->bus_addr); | ||||
| } | ||||
| 
 | ||||
| static void i2sbus_release_dev(struct device *dev) | ||||
| { | ||||
| 	struct i2sbus_dev *i2sdev; | ||||
| 	int i; | ||||
| 
 | ||||
| 	i2sdev = container_of(dev, struct i2sbus_dev, sound.ofdev.dev); | ||||
| 
 | ||||
|  	if (i2sdev->intfregs) iounmap(i2sdev->intfregs); | ||||
|  	if (i2sdev->out.dbdma) iounmap(i2sdev->out.dbdma); | ||||
|  	if (i2sdev->in.dbdma) iounmap(i2sdev->in.dbdma); | ||||
| 	for (i=0;i<3;i++) | ||||
| 		if (i2sdev->allocated_resource[i]) | ||||
| 			release_and_free_resource(i2sdev->allocated_resource[i]); | ||||
| 	free_dbdma_descriptor_ring(i2sdev, &i2sdev->out.dbdma_ring); | ||||
| 	free_dbdma_descriptor_ring(i2sdev, &i2sdev->in.dbdma_ring); | ||||
| 	for (i=0;i<3;i++) | ||||
| 		free_irq(i2sdev->interrupts[i], i2sdev); | ||||
| 	i2sbus_control_remove_dev(i2sdev->control, i2sdev); | ||||
| 	mutex_destroy(&i2sdev->lock); | ||||
| 	kfree(i2sdev); | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t i2sbus_bus_intr(int irq, void *devid, struct pt_regs *regs) | ||||
| { | ||||
| 	struct i2sbus_dev *dev = devid; | ||||
| 	u32 intreg; | ||||
| 
 | ||||
| 	spin_lock(&dev->low_lock); | ||||
| 	intreg = in_le32(&dev->intfregs->intr_ctl); | ||||
| 
 | ||||
| 	/* acknowledge interrupt reasons */ | ||||
| 	out_le32(&dev->intfregs->intr_ctl, intreg); | ||||
| 
 | ||||
| 	spin_unlock(&dev->low_lock); | ||||
| 
 | ||||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| static int force; | ||||
| module_param(force, int, 0444); | ||||
| MODULE_PARM_DESC(force, "Force loading i2sbus even when" | ||||
| 			" no layout-id property is present"); | ||||
| 
 | ||||
| /* FIXME: look at device node refcounting */ | ||||
| static int i2sbus_add_dev(struct macio_dev *macio, | ||||
| 			  struct i2sbus_control *control, | ||||
| 			  struct device_node *np) | ||||
| { | ||||
| 	struct i2sbus_dev *dev; | ||||
| 	struct device_node *child = NULL, *sound = NULL; | ||||
| 	int i; | ||||
| 	static const char *rnames[] = { "i2sbus: %s (control)", | ||||
| 					"i2sbus: %s (tx)", | ||||
| 					"i2sbus: %s (rx)" }; | ||||
| 	static irqreturn_t (*ints[])(int irq, void *devid, | ||||
| 				     struct pt_regs *regs) = { | ||||
| 		i2sbus_bus_intr, | ||||
| 		i2sbus_tx_intr, | ||||
| 		i2sbus_rx_intr | ||||
| 	}; | ||||
| 
 | ||||
| 	if (strlen(np->name) != 5) | ||||
| 		return 0; | ||||
| 	if (strncmp(np->name, "i2s-", 4)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (np->n_intrs != 3) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	dev = kzalloc(sizeof(struct i2sbus_dev), GFP_KERNEL); | ||||
| 	if (!dev) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	i = 0; | ||||
| 	while ((child = of_get_next_child(np, child))) { | ||||
| 		if (strcmp(child->name, "sound") == 0) { | ||||
| 			i++; | ||||
| 			sound = child; | ||||
| 		} | ||||
| 	} | ||||
| 	if (i == 1) { | ||||
| 		u32 *layout_id; | ||||
| 		layout_id = (u32*) get_property(sound, "layout-id", NULL); | ||||
| 		if (layout_id) { | ||||
| 			snprintf(dev->sound.modalias, 32, | ||||
| 				 "sound-layout-%d", *layout_id); | ||||
| 			force = 1; | ||||
| 		} | ||||
| 	} | ||||
| 	/* for the time being, until we can handle non-layout-id
 | ||||
| 	 * things in some fabric, refuse to attach if there is no | ||||
| 	 * layout-id property or we haven't been forced to attach. | ||||
| 	 * When there are two i2s busses and only one has a layout-id, | ||||
| 	 * then this depends on the order, but that isn't important | ||||
| 	 * either as the second one in that case is just a modem. */ | ||||
| 	if (!force) { | ||||
| 		kfree(dev); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	mutex_init(&dev->lock); | ||||
| 	spin_lock_init(&dev->low_lock); | ||||
| 	dev->sound.ofdev.node = np; | ||||
| 	dev->sound.ofdev.dma_mask = macio->ofdev.dma_mask; | ||||
| 	dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.dma_mask; | ||||
| 	dev->sound.ofdev.dev.parent = &macio->ofdev.dev; | ||||
| 	dev->sound.ofdev.dev.release = i2sbus_release_dev; | ||||
| 	dev->sound.attach_codec = i2sbus_attach_codec; | ||||
| 	dev->sound.detach_codec = i2sbus_detach_codec; | ||||
| 	dev->sound.pcmid = -1; | ||||
| 	dev->macio = macio; | ||||
| 	dev->control = control; | ||||
| 	dev->bus_number = np->name[4] - 'a'; | ||||
| 	INIT_LIST_HEAD(&dev->sound.codec_list); | ||||
| 
 | ||||
| 	for (i=0;i<3;i++) { | ||||
| 		dev->interrupts[i] = -1; | ||||
| 		snprintf(dev->rnames[i], sizeof(dev->rnames[i]), rnames[i], np->name); | ||||
| 	} | ||||
| 	for (i=0;i<3;i++) { | ||||
| 		if (request_irq(np->intrs[i].line, ints[i], 0, dev->rnames[i], dev)) | ||||
| 			goto err; | ||||
| 		dev->interrupts[i] = np->intrs[i].line; | ||||
| 	} | ||||
| 
 | ||||
| 	for (i=0;i<3;i++) { | ||||
| 		if (of_address_to_resource(np, i, &dev->resources[i])) | ||||
| 			goto err; | ||||
| 		/* if only we could use our resource dev->resources[i]...
 | ||||
| 		 * but request_resource doesn't know about parents and | ||||
| 		 * contained resources... */ | ||||
| 		dev->allocated_resource[i] =  | ||||
| 			request_mem_region(dev->resources[i].start, | ||||
| 					   dev->resources[i].end - | ||||
| 					   dev->resources[i].start + 1, | ||||
| 					   dev->rnames[i]); | ||||
| 		if (!dev->allocated_resource[i]) { | ||||
| 			printk(KERN_ERR "i2sbus: failed to claim resource %d!\n", i); | ||||
| 			goto err; | ||||
| 		} | ||||
| 	} | ||||
| 	/* should do sanity checking here about length of them */ | ||||
| 	dev->intfregs = ioremap(dev->resources[0].start, | ||||
| 				dev->resources[0].end-dev->resources[0].start+1); | ||||
| 	dev->out.dbdma = ioremap(dev->resources[1].start, | ||||
| 			 	 dev->resources[1].end-dev->resources[1].start+1); | ||||
| 	dev->in.dbdma = ioremap(dev->resources[2].start, | ||||
| 				dev->resources[2].end-dev->resources[2].start+1); | ||||
| 	if (!dev->intfregs || !dev->out.dbdma || !dev->in.dbdma) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	if (alloc_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring, | ||||
| 					MAX_DBDMA_COMMANDS)) | ||||
| 		goto err; | ||||
| 	if (alloc_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring, | ||||
| 					MAX_DBDMA_COMMANDS)) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	if (i2sbus_control_add_dev(dev->control, dev)) { | ||||
| 		printk(KERN_ERR "i2sbus: control layer didn't like bus\n"); | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	if (soundbus_add_one(&dev->sound)) { | ||||
| 		printk(KERN_DEBUG "i2sbus: device registration error!\n"); | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	/* enable this cell */ | ||||
| 	i2sbus_control_cell(dev->control, dev, 1); | ||||
| 	i2sbus_control_enable(dev->control, dev); | ||||
| 	i2sbus_control_clock(dev->control, dev, 1); | ||||
| 
 | ||||
| 	return 1; | ||||
|  err: | ||||
| 	for (i=0;i<3;i++) | ||||
| 		if (dev->interrupts[i] != -1) | ||||
| 			free_irq(dev->interrupts[i], dev); | ||||
| 	free_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring); | ||||
| 	free_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring); | ||||
| 	if (dev->intfregs) iounmap(dev->intfregs); | ||||
| 	if (dev->out.dbdma) iounmap(dev->out.dbdma); | ||||
| 	if (dev->in.dbdma) iounmap(dev->in.dbdma); | ||||
| 	for (i=0;i<3;i++) | ||||
| 		if (dev->allocated_resource[i]) | ||||
| 			release_and_free_resource(dev->allocated_resource[i]); | ||||
| 	mutex_destroy(&dev->lock); | ||||
| 	kfree(dev); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match) | ||||
| { | ||||
| 	struct device_node *np = NULL; | ||||
| 	int got = 0, err; | ||||
| 	struct i2sbus_control *control = NULL; | ||||
| 
 | ||||
| 	err = i2sbus_control_init(dev, &control); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 	if (!control) { | ||||
| 		printk(KERN_ERR "i2sbus_control_init API breakage\n"); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	while ((np = of_get_next_child(dev->ofdev.node, np))) { | ||||
| 		if (device_is_compatible(np, "i2sbus") || | ||||
| 		    device_is_compatible(np, "i2s-modem")) { | ||||
| 			got += i2sbus_add_dev(dev, control, np); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (!got) { | ||||
| 		/* found none, clean up */ | ||||
| 		i2sbus_control_destroy(control); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	dev->ofdev.dev.driver_data = control; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int i2sbus_remove(struct macio_dev* dev) | ||||
| { | ||||
| 	struct i2sbus_control *control = dev->ofdev.dev.driver_data; | ||||
| 	struct i2sbus_dev *i2sdev, *tmp; | ||||
| 
 | ||||
| 	list_for_each_entry_safe(i2sdev, tmp, &control->list, item) | ||||
| 		soundbus_remove_one(&i2sdev->sound); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state) | ||||
| { | ||||
| 	struct i2sbus_control *control = dev->ofdev.dev.driver_data; | ||||
| 	struct codec_info_item *cii; | ||||
| 	struct i2sbus_dev* i2sdev; | ||||
| 	int err, ret = 0; | ||||
| 
 | ||||
| 	list_for_each_entry(i2sdev, &control->list, item) { | ||||
| 		/* Notify Alsa */ | ||||
| 		if (i2sdev->sound.pcm) { | ||||
| 			/* Suspend PCM streams */ | ||||
| 			snd_pcm_suspend_all(i2sdev->sound.pcm); | ||||
| 			/* Probably useless as we handle
 | ||||
| 			 * power transitions ourselves */ | ||||
| 			snd_power_change_state(i2sdev->sound.pcm->card, | ||||
| 					       SNDRV_CTL_POWER_D3hot); | ||||
| 		} | ||||
| 		/* Notify codecs */ | ||||
| 		list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { | ||||
| 			err = 0; | ||||
| 			if (cii->codec->suspend) | ||||
| 				err = cii->codec->suspend(cii, state); | ||||
| 			if (err) | ||||
| 				ret = err; | ||||
| 		} | ||||
| 	} | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int i2sbus_resume(struct macio_dev* dev) | ||||
| { | ||||
| 	struct i2sbus_control *control = dev->ofdev.dev.driver_data; | ||||
| 	struct codec_info_item *cii; | ||||
| 	struct i2sbus_dev* i2sdev; | ||||
| 	int err, ret = 0; | ||||
| 
 | ||||
| 	list_for_each_entry(i2sdev, &control->list, item) { | ||||
| 		/* Notify codecs so they can re-initialize */ | ||||
| 		list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { | ||||
| 			err = 0; | ||||
| 			if (cii->codec->resume) | ||||
| 				err = cii->codec->resume(cii); | ||||
| 			if (err) | ||||
| 				ret = err; | ||||
| 		} | ||||
| 		/* Notify Alsa */ | ||||
| 		if (i2sdev->sound.pcm) { | ||||
| 			/* Same comment as above, probably useless */ | ||||
| 			snd_power_change_state(i2sdev->sound.pcm->card, | ||||
| 					       SNDRV_CTL_POWER_D0); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| #endif /* CONFIG_PM */ | ||||
| 
 | ||||
| static int i2sbus_shutdown(struct macio_dev* dev) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static struct macio_driver i2sbus_drv = { | ||||
| 	.name = "soundbus-i2s", | ||||
| 	.owner = THIS_MODULE, | ||||
| 	.match_table = i2sbus_match, | ||||
| 	.probe = i2sbus_probe, | ||||
| 	.remove = i2sbus_remove, | ||||
| #ifdef CONFIG_PM | ||||
| 	.suspend = i2sbus_suspend, | ||||
| 	.resume = i2sbus_resume, | ||||
| #endif | ||||
| 	.shutdown = i2sbus_shutdown, | ||||
| }; | ||||
| 
 | ||||
| static int __init soundbus_i2sbus_init(void) | ||||
| { | ||||
| 	return macio_register_driver(&i2sbus_drv); | ||||
| } | ||||
| 
 | ||||
| static void __exit soundbus_i2sbus_exit(void) | ||||
| { | ||||
| 	macio_unregister_driver(&i2sbus_drv); | ||||
| } | ||||
| 
 | ||||
| module_init(soundbus_i2sbus_init); | ||||
| module_exit(soundbus_i2sbus_exit); | ||||
							
								
								
									
										187
									
								
								sound/aoa/soundbus/i2sbus/i2sbus-interface.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								sound/aoa/soundbus/i2sbus/i2sbus-interface.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | ||||
| /*
 | ||||
|  * i2sbus driver -- interface register definitions | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| #ifndef __I2SBUS_INTERFACE_H | ||||
| #define __I2SBUS_INTERFACE_H | ||||
| 
 | ||||
| /* i2s bus control registers, at least what we know about them */ | ||||
| 
 | ||||
| #define __PAD(m,n) u8 __pad##m[n] | ||||
| #define _PAD(line, n) __PAD(line, n) | ||||
| #define PAD(n) _PAD(__LINE__, (n)) | ||||
| struct i2s_interface_regs { | ||||
| 	__le32 intr_ctl;	/* 0x00 */ | ||||
| 	PAD(12); | ||||
| 	__le32 serial_format;	/* 0x10 */ | ||||
| 	PAD(12); | ||||
| 	__le32 codec_msg_out;	/* 0x20 */ | ||||
| 	PAD(12); | ||||
| 	__le32 codec_msg_in;	/* 0x30 */ | ||||
| 	PAD(12); | ||||
| 	__le32 frame_count;	/* 0x40 */ | ||||
| 	PAD(12); | ||||
| 	__le32 frame_match;	/* 0x50 */ | ||||
| 	PAD(12); | ||||
| 	__le32 data_word_sizes;	/* 0x60 */ | ||||
| 	PAD(12); | ||||
| 	__le32 peak_level_sel;	/* 0x70 */ | ||||
| 	PAD(12); | ||||
| 	__le32 peak_level_in0;	/* 0x80 */ | ||||
| 	PAD(12); | ||||
| 	__le32 peak_level_in1;	/* 0x90 */ | ||||
| 	PAD(12); | ||||
| 	/* total size: 0x100 bytes */ | ||||
| }  __attribute__((__packed__)); | ||||
| 
 | ||||
| /* interrupt register is just a bitfield with
 | ||||
|  * interrupt enable and pending bits */ | ||||
| #define I2S_REG_INTR_CTL		0x00 | ||||
| #	define I2S_INT_FRAME_COUNT		(1<<31) | ||||
| #	define I2S_PENDING_FRAME_COUNT		(1<<30) | ||||
| #	define I2S_INT_MESSAGE_FLAG		(1<<29) | ||||
| #	define I2S_PENDING_MESSAGE_FLAG		(1<<28) | ||||
| #	define I2S_INT_NEW_PEAK			(1<<27) | ||||
| #	define I2S_PENDING_NEW_PEAK		(1<<26) | ||||
| #	define I2S_INT_CLOCKS_STOPPED		(1<<25) | ||||
| #	define I2S_PENDING_CLOCKS_STOPPED	(1<<24) | ||||
| #	define I2S_INT_EXTERNAL_SYNC_ERROR	(1<<23) | ||||
| #	define I2S_PENDING_EXTERNAL_SYNC_ERROR	(1<<22) | ||||
| #	define I2S_INT_EXTERNAL_SYNC_OK		(1<<21) | ||||
| #	define I2S_PENDING_EXTERNAL_SYNC_OK	(1<<20) | ||||
| #	define I2S_INT_NEW_SAMPLE_RATE		(1<<19) | ||||
| #	define I2S_PENDING_NEW_SAMPLE_RATE	(1<<18) | ||||
| #	define I2S_INT_STATUS_FLAG		(1<<17) | ||||
| #	define I2S_PENDING_STATUS_FLAG		(1<<16) | ||||
| 
 | ||||
| /* serial format register is more interesting :)
 | ||||
|  * It contains: | ||||
|  *  - clock source | ||||
|  *  - MClk divisor | ||||
|  *  - SClk divisor | ||||
|  *  - SClk master flag | ||||
|  *  - serial format (sony, i2s 64x, i2s 32x, dav, silabs) | ||||
|  *  - external sample frequency interrupt (don't understand) | ||||
|  *  - external sample frequency | ||||
|  */ | ||||
| #define I2S_REG_SERIAL_FORMAT		0x10 | ||||
| /* clock source. You get either 18.432, 45.1584 or 49.1520 MHz */ | ||||
| #	define I2S_SF_CLOCK_SOURCE_SHIFT	30 | ||||
| #	define I2S_SF_CLOCK_SOURCE_MASK		(3<<I2S_SF_CLOCK_SOURCE_SHIFT) | ||||
| #	define I2S_SF_CLOCK_SOURCE_18MHz	(0<<I2S_SF_CLOCK_SOURCE_SHIFT) | ||||
| #	define I2S_SF_CLOCK_SOURCE_45MHz	(1<<I2S_SF_CLOCK_SOURCE_SHIFT) | ||||
| #	define I2S_SF_CLOCK_SOURCE_49MHz	(2<<I2S_SF_CLOCK_SOURCE_SHIFT) | ||||
| /* also, let's define the exact clock speeds here, in Hz */ | ||||
| #define I2S_CLOCK_SPEED_18MHz	18432000 | ||||
| #define I2S_CLOCK_SPEED_45MHz	45158400 | ||||
| #define I2S_CLOCK_SPEED_49MHz	49152000 | ||||
| /* MClk is the clock that drives the codec, usually called its 'system clock'.
 | ||||
|  * It is derived by taking only every 'divisor' tick of the clock. | ||||
|  */ | ||||
| #	define I2S_SF_MCLKDIV_SHIFT		24 | ||||
| #	define I2S_SF_MCLKDIV_MASK		(0x1F<<I2S_SF_MCLKDIV_SHIFT) | ||||
| #	define I2S_SF_MCLKDIV_1			(0x14<<I2S_SF_MCLKDIV_SHIFT) | ||||
| #	define I2S_SF_MCLKDIV_3			(0x13<<I2S_SF_MCLKDIV_SHIFT) | ||||
| #	define I2S_SF_MCLKDIV_5			(0x12<<I2S_SF_MCLKDIV_SHIFT) | ||||
| #	define I2S_SF_MCLKDIV_14		(0x0E<<I2S_SF_MCLKDIV_SHIFT) | ||||
| #	define I2S_SF_MCLKDIV_OTHER(div)	(((div/2-1)<<I2S_SF_MCLKDIV_SHIFT)&I2S_SF_MCLKDIV_MASK) | ||||
| static inline int i2s_sf_mclkdiv(int div, int *out) | ||||
| { | ||||
| 	int d; | ||||
| 
 | ||||
| 	switch(div) { | ||||
| 	case 1: *out |= I2S_SF_MCLKDIV_1; return 0; | ||||
| 	case 3: *out |= I2S_SF_MCLKDIV_3; return 0; | ||||
| 	case 5: *out |= I2S_SF_MCLKDIV_5; return 0; | ||||
| 	case 14: *out |= I2S_SF_MCLKDIV_14; return 0; | ||||
| 	default: | ||||
| 		if (div%2) return -1; | ||||
| 		d = div/2-1; | ||||
| 		if (d == 0x14 || d == 0x13 || d == 0x12 || d == 0x0E) | ||||
| 			return -1; | ||||
| 		*out |= I2S_SF_MCLKDIV_OTHER(div); | ||||
| 		return 0; | ||||
| 	} | ||||
| } | ||||
| /* SClk is the clock that drives the i2s wire bus. Note that it is
 | ||||
|  * derived from the MClk above by taking only every 'divisor' tick | ||||
|  * of MClk. | ||||
|  */ | ||||
| #	define I2S_SF_SCLKDIV_SHIFT		20 | ||||
| #	define I2S_SF_SCLKDIV_MASK		(0xF<<I2S_SF_SCLKDIV_SHIFT) | ||||
| #	define I2S_SF_SCLKDIV_1			(8<<I2S_SF_SCLKDIV_SHIFT) | ||||
| #	define I2S_SF_SCLKDIV_3			(9<<I2S_SF_SCLKDIV_SHIFT) | ||||
| #	define I2S_SF_SCLKDIV_OTHER(div)	(((div/2-1)<<I2S_SF_SCLKDIV_SHIFT)&I2S_SF_SCLKDIV_MASK) | ||||
| static inline int i2s_sf_sclkdiv(int div, int *out) | ||||
| { | ||||
| 	int d; | ||||
| 
 | ||||
| 	switch(div) { | ||||
| 	case 1: *out |= I2S_SF_SCLKDIV_1; return 0; | ||||
| 	case 3: *out |= I2S_SF_SCLKDIV_3; return 0; | ||||
| 	default: | ||||
| 		if (div%2) return -1; | ||||
| 		d = div/2-1; | ||||
| 		if (d == 8 || d == 9) return -1; | ||||
| 		*out |= I2S_SF_SCLKDIV_OTHER(div); | ||||
| 		return 0; | ||||
| 	} | ||||
| } | ||||
| #	define I2S_SF_SCLK_MASTER		(1<<19) | ||||
| /* serial format is the way the data is put to the i2s wire bus */ | ||||
| #	define I2S_SF_SERIAL_FORMAT_SHIFT	16 | ||||
| #	define I2S_SF_SERIAL_FORMAT_MASK	(7<<I2S_SF_SERIAL_FORMAT_SHIFT) | ||||
| #	define I2S_SF_SERIAL_FORMAT_SONY	(0<<I2S_SF_SERIAL_FORMAT_SHIFT) | ||||
| #	define I2S_SF_SERIAL_FORMAT_I2S_64X	(1<<I2S_SF_SERIAL_FORMAT_SHIFT) | ||||
| #	define I2S_SF_SERIAL_FORMAT_I2S_32X	(2<<I2S_SF_SERIAL_FORMAT_SHIFT) | ||||
| #	define I2S_SF_SERIAL_FORMAT_I2S_DAV	(4<<I2S_SF_SERIAL_FORMAT_SHIFT) | ||||
| #	define I2S_SF_SERIAL_FORMAT_I2S_SILABS	(5<<I2S_SF_SERIAL_FORMAT_SHIFT) | ||||
| /* unknown */ | ||||
| #	define I2S_SF_EXT_SAMPLE_FREQ_INT_SHIFT	12 | ||||
| #	define I2S_SF_EXT_SAMPLE_FREQ_INT_MASK	(0xF<<I2S_SF_SAMPLE_FREQ_INT_SHIFT) | ||||
| /* probably gives external frequency? */ | ||||
| #	define I2S_SF_EXT_SAMPLE_FREQ_MASK	0xFFF | ||||
| 
 | ||||
| /* used to send codec messages, but how isn't clear */ | ||||
| #define I2S_REG_CODEC_MSG_OUT		0x20 | ||||
| 
 | ||||
| /* used to receive codec messages, but how isn't clear */ | ||||
| #define I2S_REG_CODEC_MSG_IN		0x30 | ||||
| 
 | ||||
| /* frame count reg isn't clear to me yet, but probably useful */ | ||||
| #define I2S_REG_FRAME_COUNT		0x40 | ||||
| 
 | ||||
| /* program to some value, and get interrupt if frame count reaches it */ | ||||
| #define I2S_REG_FRAME_MATCH		0x50 | ||||
| 
 | ||||
| /* this register describes how the bus transfers data */ | ||||
| #define I2S_REG_DATA_WORD_SIZES		0x60 | ||||
| /* number of interleaved input channels */ | ||||
| #	define I2S_DWS_NUM_CHANNELS_IN_SHIFT	24 | ||||
| #	define I2S_DWS_NUM_CHANNELS_IN_MASK	(0x1F<<I2S_DWS_NUM_CHANNELS_IN_SHIFT) | ||||
| /* word size of input data */ | ||||
| #	define I2S_DWS_DATA_IN_SIZE_SHIFT	16 | ||||
| #	define I2S_DWS_DATA_IN_16BIT		(0<<I2S_DWS_DATA_IN_SIZE_SHIFT) | ||||
| #	define I2S_DWS_DATA_IN_24BIT		(3<<I2S_DWS_DATA_IN_SIZE_SHIFT) | ||||
| /* number of interleaved output channels */ | ||||
| #	define I2S_DWS_NUM_CHANNELS_OUT_SHIFT	8 | ||||
| #	define I2S_DWS_NUM_CHANNELS_OUT_MASK	(0x1F<<I2S_DWS_NUM_CHANNELS_OUT_SHIFT) | ||||
| /* word size of output data */ | ||||
| #	define I2S_DWS_DATA_OUT_SIZE_SHIFT	0 | ||||
| #	define I2S_DWS_DATA_OUT_16BIT		(0<<I2S_DWS_DATA_OUT_SIZE_SHIFT) | ||||
| #	define I2S_DWS_DATA_OUT_24BIT		(3<<I2S_DWS_DATA_OUT_SIZE_SHIFT) | ||||
| 
 | ||||
| 
 | ||||
| /* unknown */ | ||||
| #define I2S_REG_PEAK_LEVEL_SEL		0x70 | ||||
| 
 | ||||
| /* unknown */ | ||||
| #define I2S_REG_PEAK_LEVEL_IN0		0x80 | ||||
| 
 | ||||
| /* unknown */ | ||||
| #define I2S_REG_PEAK_LEVEL_IN1		0x90 | ||||
| 
 | ||||
| #endif /* __I2SBUS_INTERFACE_H */ | ||||
							
								
								
									
										1021
									
								
								sound/aoa/soundbus/i2sbus/i2sbus-pcm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1021
									
								
								sound/aoa/soundbus/i2sbus/i2sbus-pcm.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										112
									
								
								sound/aoa/soundbus/i2sbus/i2sbus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								sound/aoa/soundbus/i2sbus/i2sbus.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | ||||
| /*
 | ||||
|  * i2sbus driver -- private definitions | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| #ifndef __I2SBUS_H | ||||
| #define __I2SBUS_H | ||||
| #include <asm/dbdma.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <sound/pcm.h> | ||||
| #include <linux/spinlock.h> | ||||
| #include <linux/mutex.h> | ||||
| #include <asm/prom.h> | ||||
| #include "i2sbus-interface.h" | ||||
| #include "i2sbus-control.h" | ||||
| #include "../soundbus.h" | ||||
| 
 | ||||
| struct i2sbus_control { | ||||
| 	volatile struct i2s_control_regs __iomem *controlregs; | ||||
| 	struct resource rsrc; | ||||
| 	struct list_head list; | ||||
| }; | ||||
| 
 | ||||
| #define MAX_DBDMA_COMMANDS	32 | ||||
| 
 | ||||
| struct dbdma_command_mem { | ||||
| 	dma_addr_t bus_addr; | ||||
| 	dma_addr_t bus_cmd_start; | ||||
| 	struct dbdma_cmd *cmds; | ||||
| 	void *space; | ||||
| 	int size; | ||||
| 	u32 running:1; | ||||
| }; | ||||
| 
 | ||||
| struct pcm_info { | ||||
| 	u32 created:1, /* has this direction been created with alsa? */ | ||||
| 	    active:1;  /* is this stream active? */ | ||||
| 	/* runtime information */ | ||||
| 	struct snd_pcm_substream *substream; | ||||
| 	int current_period; | ||||
| 	u32 frame_count; | ||||
| 	struct dbdma_command_mem dbdma_ring; | ||||
| 	volatile struct dbdma_regs __iomem *dbdma; | ||||
| }; | ||||
| 
 | ||||
| struct i2sbus_dev { | ||||
| 	struct soundbus_dev sound; | ||||
| 	struct macio_dev *macio; | ||||
| 	struct i2sbus_control *control; | ||||
| 	volatile struct i2s_interface_regs __iomem *intfregs; | ||||
| 
 | ||||
| 	struct resource resources[3]; | ||||
| 	struct resource *allocated_resource[3]; | ||||
| 	int interrupts[3]; | ||||
| 	char rnames[3][32]; | ||||
| 
 | ||||
| 	/* info about currently active substreams */ | ||||
| 	struct pcm_info out, in; | ||||
| 	snd_pcm_format_t format; | ||||
| 	unsigned int rate; | ||||
| 
 | ||||
| 	/* list for a single controller */ | ||||
| 	struct list_head item; | ||||
| 	/* number of bus on controller */ | ||||
| 	int bus_number; | ||||
| 	/* for use by control layer */ | ||||
| 	struct pmf_function *enable, | ||||
| 			    *cell_enable, | ||||
| 			    *cell_disable, | ||||
| 			    *clock_enable, | ||||
| 			    *clock_disable; | ||||
| 
 | ||||
| 	/* locks */ | ||||
| 	/* spinlock for low-level interrupt locking */ | ||||
| 	spinlock_t low_lock; | ||||
| 	/* mutex for high-level consistency */ | ||||
| 	struct mutex lock; | ||||
| }; | ||||
| 
 | ||||
| #define soundbus_dev_to_i2sbus_dev(sdev) \ | ||||
| 		container_of(sdev, struct i2sbus_dev, sound) | ||||
| 
 | ||||
| /* pcm specific functions */ | ||||
| extern int | ||||
| i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card, | ||||
| 		    struct codec_info *ci, void *data); | ||||
| extern void | ||||
| i2sbus_detach_codec(struct soundbus_dev *dev, void *data); | ||||
| extern irqreturn_t | ||||
| i2sbus_tx_intr(int irq, void *devid, struct pt_regs *regs); | ||||
| extern irqreturn_t | ||||
| i2sbus_rx_intr(int irq, void *devid, struct pt_regs *regs); | ||||
| 
 | ||||
| /* control specific functions */ | ||||
| extern int i2sbus_control_init(struct macio_dev* dev, | ||||
| 			       struct i2sbus_control **c); | ||||
| extern void i2sbus_control_destroy(struct i2sbus_control *c); | ||||
| extern int i2sbus_control_add_dev(struct i2sbus_control *c, | ||||
| 				  struct i2sbus_dev *i2sdev); | ||||
| extern void i2sbus_control_remove_dev(struct i2sbus_control *c, | ||||
| 				      struct i2sbus_dev *i2sdev); | ||||
| extern int i2sbus_control_enable(struct i2sbus_control *c, | ||||
| 				 struct i2sbus_dev *i2sdev); | ||||
| extern int i2sbus_control_cell(struct i2sbus_control *c, | ||||
| 			       struct i2sbus_dev *i2sdev, | ||||
| 			       int enable); | ||||
| extern int i2sbus_control_clock(struct i2sbus_control *c, | ||||
| 				struct i2sbus_dev *i2sdev, | ||||
| 				int enable); | ||||
| #endif /* __I2SBUS_H */ | ||||
							
								
								
									
										202
									
								
								sound/aoa/soundbus/soundbus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								sound/aoa/soundbus/soundbus.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,202 @@ | ||||
| /*
 | ||||
|  * soundbus generic definitions | ||||
|  * | ||||
|  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | ||||
|  * | ||||
|  * GPL v2, can be found in COPYING. | ||||
|  */ | ||||
| #ifndef __SOUNDBUS_H | ||||
| #define __SOUNDBUS_H | ||||
| 
 | ||||
| #include <asm/of_device.h> | ||||
| #include <sound/pcm.h> | ||||
| #include <linux/list.h> | ||||
| 
 | ||||
| 
 | ||||
| /* When switching from master to slave or the other way around,
 | ||||
|  * you don't want to have the codec chip acting as clock source | ||||
|  * while the bus still is. | ||||
|  * More importantly, while switch from slave to master, you need | ||||
|  * to turn off the chip's master function first, but then there's | ||||
|  * no clock for a while and other chips might reset, so we notify | ||||
|  * their drivers after having switched. | ||||
|  * The constants here are codec-point of view, so when we switch | ||||
|  * the soundbus to master we tell the codec we're going to switch | ||||
|  * and give it CLOCK_SWITCH_PREPARE_SLAVE! | ||||
|  */ | ||||
| enum clock_switch { | ||||
| 	CLOCK_SWITCH_PREPARE_SLAVE, | ||||
| 	CLOCK_SWITCH_PREPARE_MASTER, | ||||
| 	CLOCK_SWITCH_SLAVE, | ||||
| 	CLOCK_SWITCH_MASTER, | ||||
| 	CLOCK_SWITCH_NOTIFY, | ||||
| }; | ||||
| 
 | ||||
| /* information on a transfer the codec can take */ | ||||
| struct transfer_info { | ||||
| 	u64 formats;		/* SNDRV_PCM_FMTBIT_* */ | ||||
| 	unsigned int rates;	/* SNDRV_PCM_RATE_* */ | ||||
| 	/* flags */ | ||||
| 	u32 transfer_in:1, /* input = 1, output = 0 */ | ||||
| 	    must_be_clock_source:1; | ||||
| 	/* for codecs to distinguish among their TIs */ | ||||
| 	int tag; | ||||
| }; | ||||
| 
 | ||||
| struct codec_info_item { | ||||
| 	struct codec_info *codec; | ||||
| 	void *codec_data; | ||||
| 	struct soundbus_dev *sdev; | ||||
| 	/* internal, to be used by the soundbus provider */ | ||||
| 	struct list_head list; | ||||
| }; | ||||
| 
 | ||||
| /* for prepare, where the codecs need to know
 | ||||
|  * what we're going to drive the bus with */ | ||||
| struct bus_info { | ||||
| 	/* see below */ | ||||
| 	int sysclock_factor; | ||||
| 	int bus_factor; | ||||
| }; | ||||
| 
 | ||||
| /* information on the codec itself, plus function pointers */ | ||||
| struct codec_info { | ||||
| 	/* the module this lives in */ | ||||
| 	struct module *owner; | ||||
| 
 | ||||
| 	/* supported transfer possibilities, array terminated by
 | ||||
| 	 * formats or rates being 0. */ | ||||
| 	struct transfer_info *transfers; | ||||
| 
 | ||||
| 	/* Master clock speed factor
 | ||||
| 	 * to be used (master clock speed = sysclock_factor * sampling freq) | ||||
| 	 * Unused if the soundbus provider has no such notion. | ||||
| 	 */ | ||||
| 	int sysclock_factor; | ||||
| 
 | ||||
| 	/* Bus factor, bus clock speed = bus_factor * sampling freq)
 | ||||
| 	 * Unused if the soundbus provider has no such notion. | ||||
| 	 */ | ||||
| 	int bus_factor; | ||||
| 
 | ||||
| 	/* operations */ | ||||
| 	/* clock switching, see above */ | ||||
| 	int (*switch_clock)(struct codec_info_item *cii, | ||||
| 			    enum clock_switch clock); | ||||
| 
 | ||||
| 	/* called for each transfer_info when the user
 | ||||
| 	 * opens the pcm device to determine what the | ||||
| 	 * hardware can support at this point in time. | ||||
| 	 * That can depend on other user-switchable controls. | ||||
| 	 * Return 1 if usable, 0 if not. | ||||
| 	 * out points to another instance of a transfer_info | ||||
| 	 * which is initialised to the values in *ti, and | ||||
| 	 * it's format and rate values can be modified by | ||||
| 	 * the callback if it is necessary to further restrict | ||||
| 	 * the formats that can be used at the moment, for | ||||
| 	 * example when one codec has multiple logical codec | ||||
| 	 * info structs for multiple inputs. | ||||
| 	 */ | ||||
| 	int (*usable)(struct codec_info_item *cii, | ||||
| 		      struct transfer_info *ti, | ||||
| 		      struct transfer_info *out); | ||||
| 
 | ||||
| 	/* called when pcm stream is opened, probably not implemented
 | ||||
| 	 * most of the time since it isn't too useful */ | ||||
| 	int (*open)(struct codec_info_item *cii, | ||||
| 		    struct snd_pcm_substream *substream); | ||||
| 
 | ||||
| 	/* called when the pcm stream is closed, at this point
 | ||||
| 	 * the user choices can all be unlocked (see below) */ | ||||
| 	int (*close)(struct codec_info_item *cii, | ||||
| 		     struct snd_pcm_substream *substream); | ||||
| 
 | ||||
| 	/* if the codec must forbid some user choices because
 | ||||
| 	 * they are not valid with the substream/transfer info, | ||||
| 	 * it must do so here. Example: no digital output for | ||||
| 	 * incompatible framerate, say 8KHz, on Onyx. | ||||
| 	 * If the selected stuff in the substream is NOT | ||||
| 	 * compatible, you have to reject this call! */ | ||||
| 	int (*prepare)(struct codec_info_item *cii, | ||||
| 		       struct bus_info *bi, | ||||
| 		       struct snd_pcm_substream *substream); | ||||
| 
 | ||||
| 	/* start() is called before data is pushed to the codec.
 | ||||
| 	 * Note that start() must be atomic! */ | ||||
| 	int (*start)(struct codec_info_item *cii, | ||||
| 		     struct snd_pcm_substream *substream); | ||||
| 
 | ||||
| 	/* stop() is called after data is no longer pushed to the codec.
 | ||||
| 	 * Note that stop() must be atomic! */ | ||||
| 	int (*stop)(struct codec_info_item *cii, | ||||
| 		    struct snd_pcm_substream *substream); | ||||
| 
 | ||||
| 	int (*suspend)(struct codec_info_item *cii, pm_message_t state); | ||||
| 	int (*resume)(struct codec_info_item *cii); | ||||
| }; | ||||
| 
 | ||||
| /* information on a soundbus device */ | ||||
| struct soundbus_dev { | ||||
| 	/* the bus it belongs to */ | ||||
| 	struct list_head onbuslist; | ||||
| 
 | ||||
| 	/* the of device it represents */ | ||||
| 	struct of_device ofdev; | ||||
| 
 | ||||
| 	/* what modules go by */ | ||||
| 	char modalias[32]; | ||||
| 
 | ||||
| 	/* These fields must be before attach_codec can be called.
 | ||||
| 	 * They should be set by the owner of the alsa card object | ||||
| 	 * that is needed, and whoever sets them must make sure | ||||
| 	 * that they are unique within that alsa card object. */ | ||||
| 	char *pcmname; | ||||
| 	int pcmid; | ||||
| 
 | ||||
| 	/* this is assigned by the soundbus provider in attach_codec */ | ||||
| 	struct snd_pcm *pcm; | ||||
| 
 | ||||
| 	/* operations */ | ||||
| 	/* attach a codec to this soundbus, give the alsa
 | ||||
| 	 * card object the PCMs for this soundbus should be in. | ||||
| 	 * The 'data' pointer must be unique, it is used as the | ||||
| 	 * key for detach_codec(). */ | ||||
| 	int (*attach_codec)(struct soundbus_dev *dev, struct snd_card *card, | ||||
| 			    struct codec_info *ci, void *data); | ||||
| 	void (*detach_codec)(struct soundbus_dev *dev, void *data); | ||||
| 	/* TODO: suspend/resume */ | ||||
| 
 | ||||
| 	/* private for the soundbus provider */ | ||||
| 	struct list_head codec_list; | ||||
| 	u32 have_out:1, have_in:1; | ||||
| }; | ||||
| #define to_soundbus_device(d) container_of(d, struct soundbus_dev, ofdev.dev) | ||||
| #define of_to_soundbus_device(d) container_of(d, struct soundbus_dev, ofdev) | ||||
| 
 | ||||
| extern int soundbus_add_one(struct soundbus_dev *dev); | ||||
| extern void soundbus_remove_one(struct soundbus_dev *dev); | ||||
| 
 | ||||
| extern struct soundbus_dev *soundbus_dev_get(struct soundbus_dev *dev); | ||||
| extern void soundbus_dev_put(struct soundbus_dev *dev); | ||||
| 
 | ||||
| struct soundbus_driver { | ||||
| 	char *name; | ||||
| 	struct module *owner; | ||||
| 
 | ||||
| 	/* we don't implement any matching at all */ | ||||
| 
 | ||||
| 	int	(*probe)(struct soundbus_dev* dev); | ||||
| 	int	(*remove)(struct soundbus_dev* dev); | ||||
| 
 | ||||
| 	int	(*suspend)(struct soundbus_dev* dev, pm_message_t state); | ||||
| 	int	(*resume)(struct soundbus_dev* dev); | ||||
| 	int	(*shutdown)(struct soundbus_dev* dev); | ||||
| 
 | ||||
| 	struct device_driver driver; | ||||
| }; | ||||
| #define to_soundbus_driver(drv) container_of(drv,struct soundbus_driver, driver) | ||||
| 
 | ||||
| extern int soundbus_register_driver(struct soundbus_driver *drv); | ||||
| extern void soundbus_unregister_driver(struct soundbus_driver *drv); | ||||
| 
 | ||||
| #endif /* __SOUNDBUS_H */ | ||||
							
								
								
									
										43
									
								
								sound/aoa/soundbus/sysfs.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								sound/aoa/soundbus/sysfs.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| #include <linux/config.h> | ||||
| #include <linux/kernel.h> | ||||
| #include <linux/stat.h> | ||||
| /* FIX UP */ | ||||
| #include "soundbus.h" | ||||
| 
 | ||||
| #define soundbus_config_of_attr(field, format_string)			\ | ||||
| static ssize_t								\ | ||||
| field##_show (struct device *dev, struct device_attribute *attr,	\ | ||||
|               char *buf)						\ | ||||
| {									\ | ||||
| 	struct soundbus_dev *mdev = to_soundbus_device (dev);		\ | ||||
| 	return sprintf (buf, format_string, mdev->ofdev.node->field);	\ | ||||
| } | ||||
| 
 | ||||
| static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, | ||||
| 			     char *buf) | ||||
| { | ||||
| 	struct soundbus_dev *sdev = to_soundbus_device(dev); | ||||
| 	struct of_device *of = &sdev->ofdev; | ||||
| 	int length; | ||||
| 
 | ||||
| 	if (*sdev->modalias) { | ||||
| 		strlcpy(buf, sdev->modalias, sizeof(sdev->modalias) + 1); | ||||
| 		strcat(buf, "\n"); | ||||
| 		length = strlen(buf); | ||||
| 	} else { | ||||
| 		length = sprintf(buf, "of:N%sT%s\n", | ||||
| 				 of->node->name, of->node->type); | ||||
| 	} | ||||
| 
 | ||||
| 	return length; | ||||
| } | ||||
| 
 | ||||
| soundbus_config_of_attr (name, "%s\n"); | ||||
| soundbus_config_of_attr (type, "%s\n"); | ||||
| 
 | ||||
| struct device_attribute soundbus_dev_attrs[] = { | ||||
| 	__ATTR_RO(name), | ||||
| 	__ATTR_RO(type), | ||||
| 	__ATTR_RO(modalias), | ||||
| 	__ATTR_NULL | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user