[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
					
				
			
		
					 35 changed files with 7009 additions and 1 deletions
				
			
		| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue