LinuxPPC-Dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [RFC 4/8] snd-aoa: add i2sbus
From: Johannes Berg @ 2006-06-01 11:58 UTC (permalink / raw)
  To: alsa-devel; +Cc: linuxppc-dev
In-Reply-To: <20060601115844.343214000@sipsolutions.net>

This patch adds the required files for i2sbus (the Makefile hook and the
Kconfig item were already added in the previous patch). i2sbus is the module
that handles the i2s busses on newer Apple machines, currently only handling
those machines that have all the required information in the device tree.

--- /dev/null
+++ b/sound/aoa/soundbus/i2sbus/i2sbus-control.h
@@ -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 */
--- /dev/null
+++ b/sound/aoa/soundbus/i2sbus/i2sbus-pcm.c
@@ -0,0 +1,1008 @@
+/*
+ * i2sbus driver -- pcm routines
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+/* So apparently there's a reason for requiring driver.h
+ * to be included first, even if I don't know it... */
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <asm/macio.h>
+#include <linux/pci.h>
+#include "../soundbus.h"
+#include "i2sbus.h"
+
+static inline void get_pcm_info(struct i2sbus_dev *i2sdev, int in,
+				struct pcm_info **pi, struct pcm_info **other)
+{
+	if (in) {
+		if (pi)
+			*pi = &i2sdev->in;
+		if (other)
+			*other = &i2sdev->out;
+	} else {
+		if (pi)
+			*pi = &i2sdev->out;
+		if (other)
+			*other = &i2sdev->in;
+	}
+}
+
+static int clock_and_divisors(int mclk, int sclk, int rate, int *out)
+{
+	/* sclk must be derived from mclk! */
+	if (mclk % sclk)
+		return -1;
+	/* derive sclk register value */
+	if (i2s_sf_sclkdiv(mclk / sclk, out))
+		return -1;
+
+	if (I2S_CLOCK_SPEED_18MHz % rate == 0) {
+		if ((I2S_CLOCK_SPEED_18MHz / rate) % mclk == 0) {
+			if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_18MHz / rate / mclk, out)) {
+				*out |= I2S_SF_CLOCK_SOURCE_18MHz;
+				return 0;
+			}
+		}
+	}
+	if (I2S_CLOCK_SPEED_45MHz % rate == 0) {
+		if ((I2S_CLOCK_SPEED_45MHz / rate) % mclk == 0) {
+			if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_45MHz / rate / mclk, out)) {
+				*out |= I2S_SF_CLOCK_SOURCE_45MHz;
+				return 0;
+			}
+		}
+	}
+	if (I2S_CLOCK_SPEED_49MHz % rate == 0) {
+		if ((I2S_CLOCK_SPEED_49MHz / rate) % mclk == 0) {
+			if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_49MHz / rate / mclk, out)) {
+				*out |= I2S_SF_CLOCK_SOURCE_49MHz;
+				return 0;
+			}
+		}
+	}
+	return -1;
+}
+
+#define CHECK_RATE(rate)						\
+	do { if (rates & SNDRV_PCM_RATE_ ##rate) {			\
+		int dummy;						\
+		if (clock_and_divisors(sysclock_factor,			\
+				       bus_factor, rate, &dummy))	\
+			rates &= ~SNDRV_PCM_RATE_ ##rate;		\
+	} } while (0)
+
+static int i2sbus_pcm_open(struct i2sbus_dev *i2sdev, int in)
+{
+	struct pcm_info *pi, *other;
+	struct soundbus_dev *sdev;
+	int masks_inited = 0, err;
+	struct codec_info_item *cii;
+	struct snd_pcm_hardware *hw;
+	u64 formats = 0;
+	unsigned int rates = 0;
+	struct transfer_info v;
+	int result = 0;
+	int bus_factor = 0, sysclock_factor = 0;
+
+	mutex_lock(&i2sdev->lock);
+
+	get_pcm_info(i2sdev, in, &pi, &other);
+
+	hw = &pi->substream->runtime->hw;
+	sdev = &i2sdev->sound;
+
+	if (pi->active) {
+		/* alsa messed up */
+		result = -EBUSY;
+		goto out_unlock;
+	}
+
+	/* we now need to assign the hw */
+	list_for_each_entry(cii, &sdev->codec_list, list) {
+		struct transfer_info *ti = cii->codec->transfers;
+		bus_factor = cii->codec->bus_factor;
+		sysclock_factor = cii->codec->sysclock_factor;
+		while (ti->formats && ti->rates) {
+			v = *ti;
+			if (ti->transfer_in == in
+			    && cii->codec->usable(cii, ti, &v)) {
+				if (masks_inited) {
+					formats &= v.formats;
+					rates &= v.rates;
+				} else {
+					formats = v.formats;
+					rates = v.rates;
+					masks_inited = 1;
+				}
+			}
+			ti++;
+		}
+	}
+	if (!masks_inited || !bus_factor || !sysclock_factor) {
+		result = -ENODEV;
+		goto out_unlock;
+	}
+	/* bus dependent stuff */
+	hw->info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+		   SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME;
+
+	CHECK_RATE(5512);
+	CHECK_RATE(8000);
+	CHECK_RATE(11025);
+	CHECK_RATE(16000);
+	CHECK_RATE(22050);
+	CHECK_RATE(32000);
+	CHECK_RATE(44100);
+	CHECK_RATE(48000);
+	CHECK_RATE(64000);
+	CHECK_RATE(88200);
+	CHECK_RATE(96000);
+	CHECK_RATE(176400);
+	CHECK_RATE(192000);
+	hw->rates = rates;
+
+	/* well. the codec might want 24 bits only, and we'll
+	 * ever only transfer 24 bits, but they are top-aligned!
+	 * So for alsa, we claim that we're doing full 32 bit
+	 * while in reality we'll ignore the lower 8 bits of
+	 * that when doing playback (they're transferred as 0
+	 * as far as I know, no codecs we have are 32-bit capable
+	 * so I can't really test) and when doing recording we'll
+	 * always have those lower 8 bits recorded as 0 */
+	if (formats & SNDRV_PCM_FMTBIT_S24_BE)
+		formats |= SNDRV_PCM_FMTBIT_S32_BE;
+	if (formats & SNDRV_PCM_FMTBIT_U24_BE)
+		formats |= SNDRV_PCM_FMTBIT_U32_BE;
+	/* now mask off what we can support. I suppose we could
+	 * also support S24_3LE and some similar formats, but I
+	 * doubt there's a codec that would be able to use that,
+	 * so we don't support it here. */
+	hw->formats = formats & (SNDRV_PCM_FMTBIT_S16_BE |
+				 SNDRV_PCM_FMTBIT_U16_BE |
+				 SNDRV_PCM_FMTBIT_S32_BE |
+				 SNDRV_PCM_FMTBIT_U32_BE);
+
+	/* we need to set the highest and lowest rate possible.
+	 * These are the highest and lowest rates alsa can
+	 * support properly in its bitfield.
+	 * Below, we'll use that to restrict to the rate
+	 * currently in use (if any). */
+	hw->rate_min = 5512;
+	hw->rate_max = 192000;
+	/* if the other stream is active, then we can only
+	 * support what it is currently using.
+	 * FIXME: I lied. This comment is wrong. We can support
+	 * anything that works with the same serial format, ie.
+	 * when recording 24 bit sound we can well play 16 bit
+	 * sound at the same time iff using the same transfer mode.
+	 */
+	if (other->active) {
+		/* FIXME: is this guaranteed by the alsa api? */
+		hw->formats &= (1ULL << i2sdev->format);
+		/* see above, restrict rates to the one we already have */
+		hw->rate_min = i2sdev->rate;
+		hw->rate_max = i2sdev->rate;
+	}
+
+	hw->channels_min = 2;
+	hw->channels_max = 2;
+	/* these are somewhat arbitrary */
+	hw->buffer_bytes_max = 131072;
+	hw->period_bytes_min = 256;
+	hw->period_bytes_max = 16384;
+	hw->periods_min = 3;
+	hw->periods_max = MAX_DBDMA_COMMANDS;
+	list_for_each_entry(cii, &sdev->codec_list, list) {
+		if (cii->codec->open) {
+			err = cii->codec->open(cii, pi->substream);
+			if (err) {
+				result = err;
+				goto out_unlock;
+			}
+		}
+	}
+
+ out_unlock:
+	mutex_unlock(&i2sdev->lock);
+	return result;
+}
+
+#undef CHECK_RATE
+
+static int i2sbus_pcm_close(struct i2sbus_dev *i2sdev, int in)
+{
+	struct codec_info_item *cii;
+	struct pcm_info *pi;
+	int err = 0, tmp;
+
+	mutex_lock(&i2sdev->lock);
+
+	get_pcm_info(i2sdev, in, &pi, NULL);
+
+	list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+		if (cii->codec->close) {
+			tmp = cii->codec->close(cii, pi->substream);
+			if (tmp)
+				err = tmp;
+		}
+	}
+
+	pi->substream = NULL;
+	pi->active = 0;
+	mutex_unlock(&i2sdev->lock);
+	return err;
+}
+
+static int i2sbus_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+}
+
+static int i2sbus_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
+{
+	/* whee. Hard work now. The user has selected a bitrate
+	 * and bit format, so now we have to program our
+	 * I2S controller appropriately. */
+	struct snd_pcm_runtime *runtime;
+	struct dbdma_cmd *command;
+	int i, periodsize;
+	dma_addr_t offset;
+	struct bus_info bi;
+	struct codec_info_item *cii;
+	int sfr = 0;		/* serial format register */
+	int dws = 0;		/* data word sizes reg */
+	int input_16bit;
+	struct pcm_info *pi, *other;
+	int cnt;
+	int result = 0;
+
+	mutex_lock(&i2sdev->lock);
+
+	get_pcm_info(i2sdev, in, &pi, &other);
+
+	if (pi->dbdma_ring.running) {
+		result = -EBUSY;
+		goto out_unlock;
+	}
+
+	runtime = pi->substream->runtime;
+	pi->active = 1;
+	if (other->active &&
+	    ((i2sdev->format != runtime->format)
+	     || (i2sdev->rate != runtime->rate))) {
+		result = -EINVAL;
+		goto out_unlock;
+	}
+
+	i2sdev->format = runtime->format;
+	i2sdev->rate = runtime->rate;
+
+	periodsize = snd_pcm_lib_period_bytes(pi->substream);
+	pi->current_period = 0;
+
+	/* generate dbdma command ring first */
+	command = pi->dbdma_ring.cmds;
+	offset = runtime->dma_addr;
+	for (i = 0; i < pi->substream->runtime->periods;
+	     i++, command++, offset += periodsize) {
+		memset(command, 0, sizeof(struct dbdma_cmd));
+		command->command =
+		    cpu_to_le16((in ? INPUT_MORE : OUTPUT_MORE) | INTR_ALWAYS);
+		command->phy_addr = cpu_to_le32(offset);
+		command->req_count = cpu_to_le16(periodsize);
+		command->xfer_status = cpu_to_le16(0);
+	}
+	/* last one branches back to first */
+	command--;
+	command->command |= cpu_to_le16(BR_ALWAYS);
+	command->cmd_dep = cpu_to_le32(pi->dbdma_ring.bus_cmd_start);
+
+	/* ok, let's set the serial format and stuff */
+	switch (runtime->format) {
+	/* 16 bit formats */
+	case SNDRV_PCM_FORMAT_S16_BE:
+	case SNDRV_PCM_FORMAT_U16_BE:
+		/* FIXME: if we add different bus factors we need to
+		 * do more here!! */
+		bi.bus_factor = 0;
+		list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+			bi.bus_factor = cii->codec->bus_factor;
+			break;
+		}
+		if (!bi.bus_factor) {
+			result = -ENODEV;
+			goto out_unlock;
+		}
+		input_16bit = 1;
+		break;
+	case SNDRV_PCM_FORMAT_S32_BE:
+	case SNDRV_PCM_FORMAT_U32_BE:
+		/* force 64x bus speed, otherwise the data cannot be
+		 * transferred quickly enough! */
+		bi.bus_factor = 64;
+		input_16bit = 0;
+		break;
+	default:
+		result = -EINVAL;
+		goto out_unlock;
+	}
+	/* we assume all sysclocks are the same! */
+	list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+		bi.sysclock_factor = cii->codec->sysclock_factor;
+		break;
+	}
+
+	if (clock_and_divisors(bi.sysclock_factor,
+			       bi.bus_factor,
+			       runtime->rate,
+			       &sfr) < 0) {
+		result = -EINVAL;
+		goto out_unlock;
+	}
+	switch (bi.bus_factor) {
+	case 32:
+		sfr |= I2S_SF_SERIAL_FORMAT_I2S_32X;
+		break;
+	case 64:
+		sfr |= I2S_SF_SERIAL_FORMAT_I2S_64X;
+		break;
+	}
+	/* FIXME: THIS ASSUMES MASTER ALL THE TIME */
+	sfr |= I2S_SF_SCLK_MASTER;
+
+	list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+		int err = 0;
+		if (cii->codec->prepare)
+			err = cii->codec->prepare(cii, &bi, pi->substream);
+		if (err) {
+			result = err;
+			goto out_unlock;
+		}
+	}
+	/* codecs are fine with it, so set our clocks */
+	if (input_16bit)
+		dws =	(2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) |
+			(2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) |
+			I2S_DWS_DATA_IN_16BIT | I2S_DWS_DATA_OUT_16BIT;
+	else
+		dws =	(2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) |
+			(2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) |
+			I2S_DWS_DATA_IN_24BIT | I2S_DWS_DATA_OUT_24BIT;
+
+	/* early exit if already programmed correctly */
+	/* not locking these is fine since we touch them only in this function */
+	if (in_le32(&i2sdev->intfregs->serial_format) == sfr
+	 && in_le32(&i2sdev->intfregs->data_word_sizes) == dws)
+		goto out_unlock;
+
+	/* let's notify the codecs about clocks going away.
+	 * For now we only do mastering on the i2s cell... */
+	list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+		if (cii->codec->switch_clock)
+			cii->codec->switch_clock(cii, CLOCK_SWITCH_PREPARE_SLAVE);
+
+	i2sbus_control_enable(i2sdev->control, i2sdev);
+	i2sbus_control_cell(i2sdev->control, i2sdev, 1);
+
+	out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED);
+
+	i2sbus_control_clock(i2sdev->control, i2sdev, 0);
+
+	msleep(1);
+
+	/* wait for clock stopped. This can apparently take a while... */
+	cnt = 100;
+	while (cnt-- &&
+	    !(in_le32(&i2sdev->intfregs->intr_ctl) & I2S_PENDING_CLOCKS_STOPPED)) {
+		msleep(5);
+	}
+	out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED);
+
+	/* not locking these is fine since we touch them only in this function */
+	out_le32(&i2sdev->intfregs->serial_format, sfr);
+	out_le32(&i2sdev->intfregs->data_word_sizes, dws);
+
+        i2sbus_control_enable(i2sdev->control, i2sdev);
+        i2sbus_control_cell(i2sdev->control, i2sdev, 1);
+        i2sbus_control_clock(i2sdev->control, i2sdev, 1);
+	msleep(1);
+
+	list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+		if (cii->codec->switch_clock)
+			cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE);
+
+ out_unlock:
+	mutex_unlock(&i2sdev->lock);
+	return result;
+}
+
+static struct dbdma_cmd STOP_CMD = {
+	.command = __constant_cpu_to_le16(DBDMA_STOP),
+};
+
+static int i2sbus_pcm_trigger(struct i2sbus_dev *i2sdev, int in, int cmd)
+{
+	struct codec_info_item *cii;
+	struct pcm_info *pi;
+	int timeout;
+	struct dbdma_cmd tmp;
+	int result = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2sdev->low_lock, flags);
+
+	get_pcm_info(i2sdev, in, &pi, NULL);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (pi->dbdma_ring.running) {
+			result = -EALREADY;
+			goto out_unlock;
+		}
+		list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+			if (cii->codec->start)
+				cii->codec->start(cii, pi->substream);
+		pi->dbdma_ring.running = 1;
+
+		/* reset dma engine */
+		out_le32(&pi->dbdma->control,
+			 0 | (RUN | PAUSE | FLUSH | WAKE) << 16);
+		timeout = 100;
+		while (in_le32(&pi->dbdma->status) & RUN && timeout--)
+			udelay(1);
+		if (timeout <= 0) {
+			printk(KERN_ERR
+			       "i2sbus: error waiting for dma reset\n");
+			result = -ENXIO;
+			goto out_unlock;
+		}
+
+		/* write dma command buffer address to the dbdma chip */
+		out_le32(&pi->dbdma->cmdptr, pi->dbdma_ring.bus_cmd_start);
+		/* post PCI write */
+		mb();
+		(void)in_le32(&pi->dbdma->status);
+
+		/* change first command to STOP */
+		tmp = *pi->dbdma_ring.cmds;
+		*pi->dbdma_ring.cmds = STOP_CMD;
+
+		/* set running state, remember that the first command is STOP */
+		out_le32(&pi->dbdma->control, RUN | (RUN << 16));
+		timeout = 100;
+		/* wait for STOP to be executed */
+		while (in_le32(&pi->dbdma->status) & ACTIVE && timeout--)
+			udelay(1);
+		if (timeout <= 0) {
+			printk(KERN_ERR "i2sbus: error waiting for dma stop\n");
+			result = -ENXIO;
+			goto out_unlock;
+		}
+		/* again, write dma command buffer address to the dbdma chip,
+		 * this time of the first real command */
+		*pi->dbdma_ring.cmds = tmp;
+		out_le32(&pi->dbdma->cmdptr, pi->dbdma_ring.bus_cmd_start);
+		/* post write */
+		mb();
+		(void)in_le32(&pi->dbdma->status);
+
+		/* reset dma engine again */
+		out_le32(&pi->dbdma->control,
+			 0 | (RUN | PAUSE | FLUSH | WAKE) << 16);
+		timeout = 100;
+		while (in_le32(&pi->dbdma->status) & RUN && timeout--)
+			udelay(1);
+		if (timeout <= 0) {
+			printk(KERN_ERR
+			       "i2sbus: error waiting for dma reset\n");
+			result = -ENXIO;
+			goto out_unlock;
+		}
+
+		/* wake up the chip with the next descriptor */
+		out_le32(&pi->dbdma->control,
+			 (RUN | WAKE) | ((RUN | WAKE) << 16));
+		/* get the frame count  */
+		pi->frame_count = in_le32(&i2sdev->intfregs->frame_count);
+
+		/* off you go! */
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (!pi->dbdma_ring.running) {
+			result = -EALREADY;
+			goto out_unlock;
+		}
+
+		/* turn off all relevant bits */
+		out_le32(&pi->dbdma->control,
+			 (RUN | WAKE | FLUSH | PAUSE) << 16);
+		{
+			/* FIXME: move to own function */
+			int timeout = 5000;
+			while ((in_le32(&pi->dbdma->status) & RUN)
+			       && --timeout > 0)
+				udelay(1);
+			if (!timeout)
+				printk(KERN_ERR
+				       "i2sbus: timed out turning off dbdma engine!\n");
+		}
+
+		pi->dbdma_ring.running = 0;
+		list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+			if (cii->codec->stop)
+				cii->codec->stop(cii, pi->substream);
+		break;
+	default:
+		result = -EINVAL;
+		goto out_unlock;
+	}
+
+ out_unlock:
+	spin_unlock_irqrestore(&i2sdev->low_lock, flags);
+	return result;
+}
+
+static snd_pcm_uframes_t i2sbus_pcm_pointer(struct i2sbus_dev *i2sdev, int in)
+{
+	struct pcm_info *pi;
+	u32 fc;
+
+	get_pcm_info(i2sdev, in, &pi, NULL);
+
+	fc = in_le32(&i2sdev->intfregs->frame_count);
+	fc = fc - pi->frame_count;
+
+	return (bytes_to_frames(pi->substream->runtime,
+			       pi->current_period *
+			       snd_pcm_lib_period_bytes(pi->substream)) + fc) % pi->substream->runtime->buffer_size;
+}
+
+static inline void handle_interrupt(struct i2sbus_dev *i2sdev, int in)
+{
+	struct pcm_info *pi;
+	u32 fc;
+	u32 delta;
+
+	spin_lock(&i2sdev->low_lock);
+	get_pcm_info(i2sdev, in, &pi, NULL);
+	if (!pi->substream) {
+		printk(KERN_INFO "i2sbus: got %s irq while not active!\n",
+		       in ? "rx" : "tx");
+		goto out_unlock;
+	}
+
+	fc = in_le32(&i2sdev->intfregs->frame_count);
+	/* a counter overflow does not change the calculation. */
+	delta = fc - pi->frame_count;
+
+	/* update current_period */
+	while (delta >= pi->substream->runtime->period_size) {
+		pi->current_period++;
+		delta = delta - pi->substream->runtime->period_size;
+	}
+
+	if (unlikely(delta)) {
+		/* Some interrupt came late, so check the dbdma.
+		 * This special case exists to syncronize the frame_count with the
+		 * dbdma transfers, but is hit every once in a while. */
+		int period;
+
+		period = (in_le32(&pi->dbdma->cmdptr) - pi->dbdma_ring.bus_cmd_start) / sizeof(struct dbdma_cmd);
+		pi->current_period = pi->current_period % pi->substream->runtime->periods;
+
+		while (pi->current_period != period) {
+			pi->current_period = (pi->current_period + 1) % pi->substream->runtime->periods;
+			/* Set delta to zero, as the frame_count value is too high (otherwise the code path
+			 * will not be executed).
+			 * This is to correct the fact that the frame_count is too low at the beginning
+			 * due to the dbdma's buffer. */
+			delta = 0;
+		}
+	}
+
+	pi->frame_count = fc - delta;
+	pi->current_period = pi->current_period % pi->substream->runtime->periods;
+
+	spin_unlock(&i2sdev->low_lock);
+	/* may call _trigger again, hence needs to be unlocked */
+	snd_pcm_period_elapsed(pi->substream);
+	return;
+ out_unlock:
+	spin_unlock(&i2sdev->low_lock);
+}
+
+irqreturn_t i2sbus_tx_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	handle_interrupt((struct i2sbus_dev *)devid, 0);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t i2sbus_rx_intr(int irq, void *devid, struct pt_regs * regs)
+{
+	handle_interrupt((struct i2sbus_dev *)devid, 1);
+	return IRQ_HANDLED;
+}
+
+static int i2sbus_playback_open(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	i2sdev->out.substream = substream;
+	return i2sbus_pcm_open(i2sdev, 0);
+}
+
+static int i2sbus_playback_close(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+	int err;
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->out.substream != substream)
+		return -EINVAL;
+	err = i2sbus_pcm_close(i2sdev, 0);
+	if (!err)
+		i2sdev->out.substream = NULL;
+	return err;
+}
+
+static int i2sbus_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->out.substream != substream)
+		return -EINVAL;
+	return i2sbus_pcm_prepare(i2sdev, 0);
+}
+
+static int i2sbus_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->out.substream != substream)
+		return -EINVAL;
+	return i2sbus_pcm_trigger(i2sdev, 0, cmd);
+}
+
+static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream
+						 *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->out.substream != substream)
+		return 0;
+	return i2sbus_pcm_pointer(i2sdev, 0);
+}
+
+static struct snd_pcm_ops i2sbus_playback_ops = {
+	.open =		i2sbus_playback_open,
+	.close =	i2sbus_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	i2sbus_hw_params,
+	.hw_free =	i2sbus_hw_free,
+	.prepare =	i2sbus_playback_prepare,
+	.trigger =	i2sbus_playback_trigger,
+	.pointer =	i2sbus_playback_pointer,
+};
+
+static int i2sbus_record_open(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	i2sdev->in.substream = substream;
+	return i2sbus_pcm_open(i2sdev, 1);
+}
+
+static int i2sbus_record_close(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+	int err;
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->in.substream != substream)
+		return -EINVAL;
+	err = i2sbus_pcm_close(i2sdev, 1);
+	if (!err)
+		i2sdev->in.substream = NULL;
+	return err;
+}
+
+static int i2sbus_record_prepare(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->in.substream != substream)
+		return -EINVAL;
+	return i2sbus_pcm_prepare(i2sdev, 1);
+}
+
+static int i2sbus_record_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->in.substream != substream)
+		return -EINVAL;
+	return i2sbus_pcm_trigger(i2sdev, 1, cmd);
+}
+
+static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream
+					       *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->in.substream != substream)
+		return 0;
+	return i2sbus_pcm_pointer(i2sdev, 1);
+}
+
+static struct snd_pcm_ops i2sbus_record_ops = {
+	.open =		i2sbus_record_open,
+	.close =	i2sbus_record_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	i2sbus_hw_params,
+	.hw_free =	i2sbus_hw_free,
+	.prepare =	i2sbus_record_prepare,
+	.trigger =	i2sbus_record_trigger,
+	.pointer =	i2sbus_record_pointer,
+};
+
+static void i2sbus_private_free(struct snd_pcm *pcm)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_chip(pcm);
+	struct codec_info_item *p, *tmp;
+
+	i2sdev->sound.pcm = NULL;
+	i2sdev->out.created = 0;
+	i2sdev->in.created = 0;
+	list_for_each_entry_safe(p, tmp, &i2sdev->sound.codec_list, list) {
+		printk(KERN_ERR "i2sbus: a codec didn't unregister!\n");
+		list_del(&p->list);
+		module_put(p->codec->owner);
+		kfree(p);
+	}
+	soundbus_dev_put(&i2sdev->sound);
+	module_put(THIS_MODULE);
+}
+
+/* FIXME: this function needs an error handling strategy with labels */
+int
+i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card,
+		    struct codec_info *ci, void *data)
+{
+	int err, in = 0, out = 0;
+	struct transfer_info *tmp;
+	struct i2sbus_dev *i2sdev = soundbus_dev_to_i2sbus_dev(dev);
+	struct codec_info_item *cii;
+
+	if (!dev->pcmname || dev->pcmid == -1) {
+		printk(KERN_ERR "i2sbus: pcm name and id must be set!\n");
+	}
+
+	list_for_each_entry(cii, &dev->codec_list, list) {
+		if (cii->codec_data == data)
+			return -EALREADY;
+	}
+
+	if (!ci->transfers || !ci->transfers->formats
+	    || !ci->transfers->rates || !ci->usable)
+		return -EINVAL;
+
+	/* we currently code the i2s transfer on the clock, and support only
+	 * 32 and 64 */
+	if (ci->bus_factor != 32 && ci->bus_factor != 64)
+		return -EINVAL;
+
+	/* If you want to fix this, you need to keep track of what transport infos
+	 * are to be used, which codecs they belong to, and then fix all the
+	 * sysclock/busclock stuff above to depend on which is usable */
+	list_for_each_entry(cii, &dev->codec_list, list) {
+		if (cii->codec->sysclock_factor != ci->sysclock_factor) {
+			printk(KERN_DEBUG
+			       "cannot yet handle multiple different sysclocks!\n");
+			return -EINVAL;
+		}
+		if (cii->codec->bus_factor != ci->bus_factor) {
+			printk(KERN_DEBUG
+			       "cannot yet handle multiple different bus clocks!\n");
+			return -EINVAL;
+		}
+	}
+
+	tmp = ci->transfers;
+	while (tmp->formats && tmp->rates) {
+		if (tmp->transfer_in)
+			in = 1;
+		else
+			out = 1;
+		tmp++;
+	}
+
+	cii = kzalloc(sizeof(struct codec_info_item), GFP_KERNEL);
+	if (!cii) {
+		printk(KERN_DEBUG "i2sbus: failed to allocate cii\n");
+		return -ENOMEM;
+	}
+
+	/* use the private data to point to the codec info */
+	cii->sdev = soundbus_dev_get(dev);
+	cii->codec = ci;
+	cii->codec_data = data;
+
+	if (!cii->sdev) {
+		printk(KERN_DEBUG
+		       "i2sbus: failed to get soundbus dev reference\n");
+		kfree(cii);
+		return -ENODEV;
+	}
+
+	if (!try_module_get(THIS_MODULE)) {
+		printk(KERN_DEBUG "i2sbus: failed to get module reference!\n");
+		soundbus_dev_put(dev);
+		kfree(cii);
+		return -EBUSY;
+	}
+
+	if (!try_module_get(ci->owner)) {
+		printk(KERN_DEBUG
+		       "i2sbus: failed to get module reference to codec owner!\n");
+		module_put(THIS_MODULE);
+		soundbus_dev_put(dev);
+		kfree(cii);
+		return -EBUSY;
+	}
+
+	if (!dev->pcm) {
+		err = snd_pcm_new(card,
+				  dev->pcmname,
+				  dev->pcmid,
+				  0,
+				  0,
+				  &dev->pcm);
+		if (err) {
+			printk(KERN_DEBUG "i2sbus: failed to create pcm\n");
+			kfree(cii);
+			module_put(ci->owner);
+			soundbus_dev_put(dev);
+			module_put(THIS_MODULE);
+			return err;
+		}
+	}
+
+	/* ALSA yet again sucks.
+	 * If it is ever fixed, remove this line. See below. */
+	out = in = 1;
+
+	if (!i2sdev->out.created && out) {
+		if (dev->pcm->card != card) {
+			/* eh? */
+			printk(KERN_ERR
+			       "Can't attach same bus to different cards!\n");
+			module_put(ci->owner);
+			kfree(cii);
+			soundbus_dev_put(dev);
+			module_put(THIS_MODULE);
+			return -EINVAL;
+		}
+		if ((err =
+		     snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, 1))) {
+			module_put(ci->owner);
+			kfree(cii);
+			soundbus_dev_put(dev);
+			module_put(THIS_MODULE);
+			return err;
+		}
+		snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&i2sbus_playback_ops);
+		i2sdev->out.created = 1;
+	}
+
+	if (!i2sdev->in.created && in) {
+		if (dev->pcm->card != card) {
+			printk(KERN_ERR
+			       "Can't attach same bus to different cards!\n");
+			module_put(ci->owner);
+			kfree(cii);
+			soundbus_dev_put(dev);
+			module_put(THIS_MODULE);
+			return -EINVAL;
+		}
+		if ((err =
+		     snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, 1))) {
+			module_put(ci->owner);
+			kfree(cii);
+			soundbus_dev_put(dev);
+			module_put(THIS_MODULE);
+			return err;
+		}
+		snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&i2sbus_record_ops);
+		i2sdev->in.created = 1;
+	}
+
+	/* so we have to register the pcm after adding any substream
+	 * to it because alsa doesn't create the devices for the
+	 * substreams when we add them later.
+	 * Therefore, force in and out on both busses (above) and
+	 * register the pcm now instead of just after creating it.
+	 */
+	err = snd_device_register(card, dev->pcm);
+	if (err) {
+		printk(KERN_ERR "i2sbus: error registering new pcm\n");
+		module_put(ci->owner);
+		kfree(cii);
+		soundbus_dev_put(dev);
+		module_put(THIS_MODULE);
+		return err;
+	}
+	/* no errors any more, so let's add this to our list */
+	list_add(&cii->list, &dev->codec_list);
+
+	dev->pcm->private_data = i2sdev;
+	dev->pcm->private_free = i2sbus_private_free;
+
+	/* well, we really should support scatter/gather DMA */
+	/* FIXME FIXME FIXME: If this fails, we BUG() when the alsa layer
+	 * later tries to allocate memory. Apparently we should be setting
+	 * some device pointer for that ...
+	 */
+	snd_pcm_lib_preallocate_pages_for_all(
+		dev->pcm, SNDRV_DMA_TYPE_DEV,
+		snd_dma_pci_data(macio_get_pci_dev(i2sdev->macio)),
+		64 * 1024, 64 * 1024);
+
+	return 0;
+}
+
+void i2sbus_detach_codec(struct soundbus_dev *dev, void *data)
+{
+	struct codec_info_item *cii = NULL, *i;
+
+	list_for_each_entry(i, &dev->codec_list, list) {
+		if (i->codec_data == data) {
+			cii = i;
+			break;
+		}
+	}
+	if (cii) {
+		list_del(&cii->list);
+		module_put(cii->codec->owner);
+		kfree(cii);
+	}
+	/* no more codecs, but still a pcm? */
+	if (list_empty(&dev->codec_list) && dev->pcm) {
+		/* the actual cleanup is done by the callback above! */
+		snd_device_free(dev->pcm->card, dev->pcm);
+	}
+}
--- /dev/null
+++ b/sound/aoa/soundbus/i2sbus/i2sbus-core.c
@@ -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 = pci_alloc_consistent(macio_get_pci_dev(i2sdev->macio),
+					r->size,
+					&r->bus_addr);
+
+	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;
+	
+	pci_free_consistent(macio_get_pci_dev(i2sdev->macio),
+			    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_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);
+
+	printk(KERN_INFO "i2sbus: interrupt, intr reg is 0x%x!\n", intreg);
+
+	/* 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_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);
--- /dev/null
+++ b/sound/aoa/soundbus/i2sbus/i2sbus-interface.h
@@ -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 */
--- /dev/null
+++ b/sound/aoa/soundbus/i2sbus/i2sbus.h
@@ -0,0 +1,114 @@
+/*
+ * 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 "i2sbus-interface.h"
+#include "i2sbus-control.h"
+/* FIXME */
+#include "../soundbus.h"
+/* should go away, see comment in i2sbus_add_dev */
+#include <asm/prom.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 */
--- /dev/null
+++ b/sound/aoa/soundbus/i2sbus/Makefile
@@ -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
--- /dev/null
+++ b/sound/aoa/soundbus/i2sbus/i2sbus-control.c
@@ -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;
+}

--

^ permalink raw reply

* [RFC 2/8] snd-aoa: add aoa core
From: Johannes Berg @ 2006-06-01 11:58 UTC (permalink / raw)
  To: alsa-devel; +Cc: linuxppc-dev
In-Reply-To: <20060601115844.343214000@sipsolutions.net>

This patch adds the core of aoa, in itself pretty useless, but providing
useful functions to other modules.

--- /dev/null
+++ b/sound/aoa/core/Makefile
@@ -0,0 +1,4 @@
+obj-$(CONFIG_SND_AOA) += snd-aoa.o
+snd-aoa-objs := snd-aoa-core.o \
+		snd-aoa-alsa.o \
+		snd-aoa-gpio-pmf.o
--- /dev/null
+++ b/sound/aoa/core/snd-aoa-core.c
@@ -0,0 +1,153 @@
+/*
+ * 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 void attach_codec_to_fabric(struct aoa_codec *c)
+{
+	int err;
+
+	if (!try_module_get(c->owner))
+		return;
+	/* found_codec has to be assigned */
+	err = -ENOENT;
+	if (fabric->found_codec)
+		err = fabric->found_codec(c);
+	if (err) {
+		module_put(c->owner);
+		printk("snd-aoa: fabric didn't like codec %s\n", c->name);
+		return;
+	}
+	c->fabric = fabric;
+
+	err = 0;
+	if (c->init)
+		err = c->init(c);
+	if (err) {
+		printk("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;
+	}
+	if (fabric->attached_codec)
+		fabric->attached_codec(c);
+}
+
+int aoa_codec_register(struct aoa_codec *codec)
+{
+	list_add(&codec->list, &codec_list);
+	if (fabric)
+		attach_codec_to_fabric(codec);
+	return 0;
+}
+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);
--- /dev/null
+++ b/sound/aoa/core/snd-aoa-gpio-pmf.c
@@ -0,0 +1,231 @@
+/*
+ * 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(&notif->mutex);
+	if (notif->notify)
+		notif->notify(notif->data);
+	mutex_unlock(&notif->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);
+	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);
+}
+
+void pmf_handle_notify_irq(void *data)
+{
+	struct gpio_notification *notif = data;
+
+	schedule_work(&notif->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(&notif->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(&notif->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);
--- /dev/null
+++ b/sound/aoa/core/snd-aoa-alsa.h
@@ -0,0 +1,17 @@
+/*
+ * 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
+/* FIXME */
+#include "../aoa.h"
+
+extern int aoa_alsa_init(char *name, struct module *mod);
+extern void aoa_alsa_cleanup(void);
+
+#endif /* __SND_AOA_ALSA_H */
--- /dev/null
+++ b/sound/aoa/core/snd-aoa-alsa.c
@@ -0,0 +1,95 @@
+/*
+ * 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)-1);
+	strlcpy(alsa_card->shortname, name, sizeof(alsa_card->shortname)-1);
+	strlcpy(alsa_card->longname, name, sizeof(alsa_card->longname)-1);
+	strlcpy(alsa_card->mixername, name, sizeof(alsa_card->mixername)-1);
+	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);

--

^ permalink raw reply

* Re: [PATCH] use msleep() for RTAS delays
From: Paul Mackerras @ 2006-06-01  5:39 UTC (permalink / raw)
  To: Benjamin Herrenschmidt; +Cc: External List
In-Reply-To: <1149139866.28307.32.camel@localhost.localdomain>

Benjamin Herrenschmidt writes:

> We could either do something like
> 
> for (;;) {
> 	rc = rtas_call(...);
> 	if (!rtas_check_busy(rc))
> 		break;

rtas_check_busy might need some state, such as the number of times we
have seen an RTAS_BUSY return value.

> Or something inside rtas_call
> 
> rtas_call_waitbusy(...);

That's a good idea...

Paul.

^ permalink raw reply

* Re: [PATCH] use msleep() for RTAS delays
From: Benjamin Herrenschmidt @ 2006-06-01  5:31 UTC (permalink / raw)
  To: John Rose; +Cc: External List, Paul Mackerras
In-Reply-To: <1149103929.2524.8.camel@sinatra.austin.ibm.com>

On Wed, 2006-05-31 at 14:32 -0500, John Rose wrote:
> The current use of udelay() for RTAS extended delay conditions can
> result in CPU soft lockups.  The use of msleep() won't tie up the CPU.

What about putting this whole thing in a helper ? There is a few more
things I've seen floating around implementing the exact same logic (for
example Jake's MSI patches).

We could either do something like

for (;;) {
	rc = rtas_call(...);
	if (!rtas_check_busy(rc))
		break;
}

Or something inside rtas_call

rtas_call_waitbusy(...);

Ben.

> Signed-off-by: John Rose <johnrose@austin.ibm.com>
> 
> ---
> 
> Thanks-
> John
> 
> diff -puN arch/powerpc/kernel/rtas.c~msleep_rtas arch/powerpc/kernel/rtas.c
> --- 2_6_linus/arch/powerpc/kernel/rtas.c~msleep_rtas	2006-05-31 14:07:51.000000000 -0500
> +++ 2_6_linus-johnrose/arch/powerpc/kernel/rtas.c	2006-05-31 14:10:09.000000000 -0500
> @@ -447,10 +447,10 @@ int rtas_set_power_level(int powerdomain
>  	while (1) {
>  		rc = rtas_call(token, 2, 2, setlevel, powerdomain, level);
>  		if (rc == RTAS_BUSY)
> -			udelay(1);
> +			msleep(1);
>  		else if (rtas_is_extended_busy(rc)) {
>  			wait_time = rtas_extended_busy_delay_time(rc);
> -			udelay(wait_time * 1000);
> +			msleep(wait_time);
>  		} else
>  			break;
>  	}
> @@ -472,10 +472,10 @@ int rtas_get_sensor(int sensor, int inde
>  	while (1) {
>  		rc = rtas_call(token, 2, 2, state, sensor, index);
>  		if (rc == RTAS_BUSY)
> -			udelay(1);
> +			msleep(1);
>  		else if (rtas_is_extended_busy(rc)) {
>  			wait_time = rtas_extended_busy_delay_time(rc);
> -			udelay(wait_time * 1000);
> +			msleep(wait_time);
>  		} else
>  			break;
>  	}
> @@ -497,12 +497,11 @@ int rtas_set_indicator(int indicator, in
>  	while (1) {
>  		rc = rtas_call(token, 3, 1, NULL, indicator, index, new_value);
>  		if (rc == RTAS_BUSY)
> -			udelay(1);
> +			msleep(1);
>  		else if (rtas_is_extended_busy(rc)) {
>  			wait_time = rtas_extended_busy_delay_time(rc);
> -			udelay(wait_time * 1000);
> -		}
> -		else
> +			msleep(wait_time);
> +		} else
>  			break;
>  	}
>  
> diff -puN arch/powerpc/kernel/rtas_flash.c~msleep_rtas arch/powerpc/kernel/rtas_flash.c
> --- 2_6_linus/arch/powerpc/kernel/rtas_flash.c~msleep_rtas	2006-05-31 14:10:47.000000000 -0500
> +++ 2_6_linus-johnrose/arch/powerpc/kernel/rtas_flash.c	2006-05-31 14:19:30.000000000 -0500
> @@ -16,7 +16,7 @@
>  #include <linux/module.h>
>  #include <linux/init.h>
>  #include <linux/proc_fs.h>
> -#include <asm/delay.h>
> +#include <linux/delay.h>
>  #include <asm/uaccess.h>
>  #include <asm/rtas.h>
>  #include <asm/abs_addr.h>
> @@ -372,10 +372,10 @@ static void manage_flash(struct rtas_man
>  		rc = rtas_call(rtas_token("ibm,manage-flash-image"), 1, 
>  			       1, NULL, args_buf->op);
>  		if (rc == RTAS_RC_BUSY)
> -			udelay(1);
> +			msleep(1);
>  		else if (rtas_is_extended_busy(rc)) {
>  			wait_time = rtas_extended_busy_delay_time(rc);
> -			udelay(wait_time * 1000);
> +			msleep(wait_time);
>  		} else
>  			break;
>  	}
> @@ -465,10 +465,10 @@ static void validate_flash(struct rtas_v
>  		spin_unlock(&rtas_data_buf_lock);
>  			
>  		if (rc == RTAS_RC_BUSY)
> -			udelay(1);
> +			msleep(1);
>  		else if (rtas_is_extended_busy(rc)) {
>  			wait_time = rtas_extended_busy_delay_time(rc);
> -			udelay(wait_time * 1000);
> +			msleep(wait_time);
>  		} else
>  			break;
>  	}
> 
> _
> 
> _______________________________________________
> Linuxppc-dev mailing list
> Linuxppc-dev@ozlabs.org
> https://ozlabs.org/mailman/listinfo/linuxppc-dev

^ permalink raw reply

* Re: Collecting hypervisor call stats
From: Benjamin Herrenschmidt @ 2006-06-01  5:26 UTC (permalink / raw)
  To: Mike Kravetz; +Cc: linuxppc-dev, Paul Mackerras
In-Reply-To: <20060531225813.GC7909@w-mikek2.ibm.com>

On Wed, 2006-05-31 at 15:58 -0700, Mike Kravetz wrote:
> On Thu, Jun 01, 2006 at 08:40:50AM +1000, Paul Mackerras wrote:
> > I think that gathering those statistics would be useful.  I suggest
> > you use a per-cpu array for them, to reduce the performance impact.
> 
> Yes, that is a must do.
> 
> We need to get a timestamp before and after the call.  mftb should do
> the trick.  Also, I'd prefer to have the code that stuffs the values
> into the array be C.  So, the decision is to have the assembly code
> call out to the C routine -OR- create wrappers for the assembly routines.
> I much prefer C wrappers to touching the assembly.

Argh... yet another use of mftb for which I'll need a cell specific
workaround :(

Ben.

^ permalink raw reply

* Re: Collecting hypervisor call stats
From: Christopher Yeoh @ 2006-06-01  5:12 UTC (permalink / raw)
  To: Mike Kravetz; +Cc: Chris Yeoh, Bryan Rosenburg, linuxppc-dev
In-Reply-To: <AEFF6654-C958-42F7-93CF-64103B015CB1@watson.ibm.com>

At 2006/6/1 00:34-0400  Jimi Xenidis writes:
> IIRC, chris and bryan have done some work to track this, they may  
> have some code to contribute.
> On May 31, 2006, at 4:41 PM, Mike Kravetz wrote:
> 

Hi Mike,

Here's a patch we've used for collecting hcall counts and times.  I
think I have a perl script lying around somewhere that helps process
the (per cpu) data a bit. I'm not sure where I put it at the moment,
but give me a ping if you're interested and I'll have another look.

Regards,

Chris
-- 
cyeoh@au.ibm.com
IBM OzLabs Linux Development Group
Canberra, Australia
diff -urpN -X linux-2.6.15.6/Documentation/dontdiff linux-2.6.15.6.ref/arch/powerpc/kernel/sysfs.c linux-2.6.15.6.hcall/arch/powerpc/kernel/sysfs.c
--- linux-2.6.15.6.ref/arch/powerpc/kernel/sysfs.c	2006-03-06 06:07:54.000000000 +1100
+++ linux-2.6.15.6.hcall/arch/powerpc/kernel/sysfs.c	2006-04-05 13:56:47.000000000 +1000
@@ -338,6 +338,213 @@ static ssize_t show_physical_id(struct s
 }
 static SYSDEV_ATTR(physical_id, 0444, show_physical_id, NULL);
 
+
+DECLARE_PER_CPU(unsigned long[NUM_HCALL_TYPES], hcall_type_count);
+DECLARE_PER_CPU(unsigned long[NUM_HCALL_TYPES], hcall_type_time);
+
+static ssize_t store_hcall_stats(struct sys_device *dev,
+				 const char *buf,
+				 size_t count)
+{
+    struct cpu *cpu = container_of(dev, struct cpu, sysdev);
+    int i;
+
+    // Clear counters
+    for (i=0; i<NUM_HCALL_TYPES; i++) {
+	per_cpu(hcall_type_count, cpu->sysdev.id)[i] = 0;
+	per_cpu(hcall_type_time, cpu->sysdev.id)[i] = 0;
+    }
+    return count;
+}
+
+static ssize_t show_hcall_stats(struct sys_device *dev, char *buf)
+{
+    struct cpu *cpu = container_of(dev, struct cpu, sysdev);
+
+    return snprintf(buf, PAGE_SIZE,
+		    "H_REMOVE\t%lu\t%lu\n"
+		    "H_ENTER\t%lu\t%lu\n"
+		    "H_READ\t%lu\t%lu\n"
+		    "H_CLEAR_MOD\t%lu\t%lu\n"
+		    "H_CLEAR_REF\t%lu\t%lu\n"
+		    "H_PROTECT\t%lu\t%lu\n"
+		    "H_GET_TCE\t%lu\t%lu\n"
+		    "H_PUT_TCE\t%lu\t%lu\n"
+		    "H_SET_SPRG0\t%lu\t%lu\n"
+		    "H_SET_DABR\t%lu\t%lu\n"
+		    "H_PAGE_INIT\t%lu\t%lu\n"
+		    "H_SET_ASR\t%lu\t%lu\n"
+		    "H_ASR_ON\t%lu\t%lu\n"
+		    "H_ASR_OFF\t%lu\t%lu\n"
+		    "H_LOGICAL_CI_LOAD\t%lu\t%lu\n"
+		    "H_LOGICAL_CI_STORE\t%lu\t%lu\n"
+		    "H_LOGICAL_CACHE_LOAD\t%lu\t%lu\n"
+		    "H_LOGICAL_CACHE_STORE\t%lu\t%lu\n"
+		    "H_LOGICAL_ICBI\t%lu\t%lu\n"
+		    "H_LOGICAL_DCBF\t%lu\t%lu\n"
+		    "H_GET_TERM_CHAR\t%lu\t%lu\n"
+		    "H_PUT_TERM_CHAR\t%lu\t%lu\n"
+		    "H_REAL_TO_LOGICAL\t%lu\t%lu\n"
+		    "H_HYPERVISOR_DATA\t%lu\t%lu\n"
+		    "H_EOI\t%lu\t%lu\n"
+		    "H_CPPR\t%lu\t%lu\n"
+		    "H_IPI\t%lu\t%lu\n"
+		    "H_IPOLL\t%lu\t%lu\n"
+		    "H_XIRR\t%lu\t%lu\n"
+		    "H_PERFMON\t%lu\t%lu\n"
+		    "H_MIGRATE_DMA\t%lu\t%lu\n"
+		    "H_REGISTER_VPA\t%lu\t%lu\n"
+		    "H_CEDE\t%lu\t%lu\n"
+		    "H_CONFER\t%lu\t%lu\n"
+		    "H_PROD\t%lu\t%lu\n"
+		    "H_GET_PPP\t%lu\t%lu\n"
+		    "H_SET_PPP\t%lu\t%lu\n"
+		    "H_PURR\t%lu\t%lu\n"
+		    "H_PIC\t%lu\t%lu\n"
+		    "H_REG_CRQ\t%lu\t%lu\n"
+		    "H_FREE_CRQ\t%lu\t%lu\n"
+		    "H_VIO_SIGNAL\t%lu\t%lu\n"
+		    "H_SEND_CRQ\t%lu\t%lu\n"
+		    "H_COPY_RDMA\t%lu\t%lu\n"
+		    "H_STUFF_TCE\t%lu\t%lu\n"
+		    "H_PUT_TCE_INDIRECT\t%lu\t%lu\n"
+		    "H_VTERM_PARTNER_INFO\t%lu\t%lu\n"
+		    "H_REGISTER_VTERM\t%lu\t%lu\n"
+		    "H_FREE_VTERM\t%lu\t%lu\n"
+		    "H_POLL_PENDING\t%lu\t%lu\n"
+		    "H_REGISTER_LOGICAL_LAN\t%lu\t%lu\n"
+		    "H_FREE_LOGICAL_LAN\t%lu\t%lu\n"
+		    "H_ADD_LOGICAL_LAN_BUFFER\t%lu\t%lu\n"
+		    "H_SEND_LOGICAL_LAN\t%lu\t%lu\n"
+		    "H_MULTICAST_CTRL\t%lu\t%lu\n"
+		    "H_CHANGE_LOGICAL_LAN_MAC\t%lu\t%lu\n",
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[0],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[0],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[1],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[1],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[2],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[2],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[3],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[3],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[4],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[4],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[5],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[5],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[6],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[6],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[7],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[7],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[8],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[8],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[9],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[9],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[10],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[10],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[11],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[11],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[12],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[12],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[13],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[13],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[14],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[14],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[15],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[15],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[16],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[16],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[17],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[17],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[18],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[18],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[19],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[19],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[20],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[20],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[21],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[21],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[22],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[22],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[23],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[23],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[24],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[24],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[25],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[25],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[26],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[26],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[27],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[27],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[28],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[28],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[29],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[29],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[30],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[30],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[31],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[31],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[32],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[32],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[33],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[33],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[34],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[34],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[35],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[35],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[36],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[36],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[37],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[37],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[38],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[38],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[39],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[39],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[40],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[40],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[41],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[41],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[42],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[42],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[43],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[43],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[44],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[44],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[45],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[45],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[46],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[46],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[47],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[47],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[48],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[48],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[49],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[49],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[50],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[50],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[51],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[51],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[52],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[52],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[53],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[53],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[54],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[54],
+		    per_cpu(hcall_type_count,cpu->sysdev.id)[55],
+		    per_cpu(hcall_type_time,cpu->sysdev.id)[55]);
+}
+
+static SYSDEV_ATTR(hcall_stats, 0666, show_hcall_stats, store_hcall_stats);
+
+static int __init hcall_stats_data_init(void)
+{
+    int cpu;
+
+    for_each_cpu(cpu) {
+		struct cpu *c = &per_cpu(cpu_devices, cpu);
+		sysdev_create_file(&c->sysdev, &attr_hcall_stats);
+    }
+    return 0;
+}
+
 static int __init topology_init(void)
 {
 	int cpu;
@@ -378,6 +585,7 @@ static int __init topology_init(void)
 			register_cpu_online(cpu);
 	}
 
-	return 0;
+	return hcall_stats_data_init();
 }
 __initcall(topology_init);
+
diff -urpN -X linux-2.6.15.6/Documentation/dontdiff linux-2.6.15.6.ref/arch/powerpc/platforms/pseries/hvCall.S linux-2.6.15.6.hcall/arch/powerpc/platforms/pseries/hvCall.S
--- linux-2.6.15.6.ref/arch/powerpc/platforms/pseries/hvCall.S	2006-03-06 06:07:54.000000000 +1100
+++ linux-2.6.15.6.hcall/arch/powerpc/platforms/pseries/hvCall.S	2006-04-05 13:52:20.000000000 +1000
@@ -27,7 +27,7 @@
 			unsigned long *out2,		R9
 			unsigned long *out3);		R10
  */
-_GLOBAL(plpar_hcall)
+_GLOBAL(plpar_hcall_real)
 	HMT_MEDIUM
 
 	mfcr	r0
@@ -53,10 +53,15 @@ _GLOBAL(plpar_hcall)
 	blr				/* return r3 = status */
 
 
-/* Simple interface with no output values (other than status) */
 _GLOBAL(plpar_hcall_norets)
 	HMT_MEDIUM
 
+	b plpar_hcall_norets_C
+	
+/* Simple interface with no output values (other than status) */
+_GLOBAL(plpar_hcall_norets_real)
+	HMT_MEDIUM
+
 	mfcr	r0
 	stw	r0,8(r1)
 
@@ -78,7 +83,7 @@ _GLOBAL(plpar_hcall_norets)
 			unsigned long arg8,		112(R1)
 			unsigned long *out1);		120(R1)
  */
-_GLOBAL(plpar_hcall_8arg_2ret)
+_GLOBAL(plpar_hcall_8arg_2ret_real)
 	HMT_MEDIUM
 
 	mfcr	r0
@@ -104,7 +109,7 @@ _GLOBAL(plpar_hcall_8arg_2ret)
 		 	unsigned long *out3,		R10
 		 	unsigned long *out4);		112(R1)
  */
-_GLOBAL(plpar_hcall_4out)
+_GLOBAL(plpar_hcall_4out_real)
 	HMT_MEDIUM
 
 	mfcr	r0
diff -urpN -X linux-2.6.15.6/Documentation/dontdiff linux-2.6.15.6.ref/arch/powerpc/platforms/pseries/lpar.c linux-2.6.15.6.hcall/arch/powerpc/platforms/pseries/lpar.c
--- linux-2.6.15.6.ref/arch/powerpc/platforms/pseries/lpar.c	2006-03-06 06:07:54.000000000 +1100
+++ linux-2.6.15.6.hcall/arch/powerpc/platforms/pseries/lpar.c	2006-04-05 13:54:48.000000000 +1000
@@ -532,3 +532,286 @@ void hpte_init_lpar(void)
 
 	htab_finish_init();
 }
+
+DEFINE_PER_CPU(unsigned long[NUM_HCALL_TYPES], hcall_type_count);
+DEFINE_PER_CPU(unsigned long[NUM_HCALL_TYPES], hcall_type_time);
+
+/* From ibmveth.h */
+/* hcall numbers */
+#define H_VIO_SIGNAL             0x104
+#define H_REGISTER_LOGICAL_LAN   0x114
+#define H_FREE_LOGICAL_LAN       0x118
+#define H_ADD_LOGICAL_LAN_BUFFER 0x11C
+#define H_SEND_LOGICAL_LAN       0x120
+#define H_MULTICAST_CTRL         0x130
+#define H_CHANGE_LOGICAL_LAN_MAC 0x14C
+
+inline int map_hcall_to_index(unsigned long opcode)
+{
+    switch (opcode) {
+    case H_REMOVE:
+	return 0;
+    case H_ENTER:
+	return 1;
+    case H_READ:
+	return 2;
+    case H_CLEAR_MOD:
+	return 3;
+    case H_CLEAR_REF:
+	return 4;
+    case H_PROTECT:
+	return 5;
+    case H_GET_TCE:
+	return 6;
+    case H_PUT_TCE:
+	return 7;
+    case H_SET_SPRG0:
+	return 8;
+    case H_SET_DABR:
+	return 9;
+    case H_PAGE_INIT:
+	return 10;
+    case H_SET_ASR:
+	return 11;
+    case H_ASR_ON:
+	return 12;
+    case H_ASR_OFF:
+	return 13;
+    case H_LOGICAL_CI_LOAD:
+	return 14;
+    case H_LOGICAL_CI_STORE:
+	return 15;
+    case H_LOGICAL_CACHE_LOAD:
+	return 16;
+    case H_LOGICAL_CACHE_STORE:
+	return 17;
+    case H_LOGICAL_ICBI:
+	return 18;
+    case H_LOGICAL_DCBF:
+	return 19;
+    case H_GET_TERM_CHAR:
+	return 20;
+    case H_PUT_TERM_CHAR:
+	return 21;
+    case H_REAL_TO_LOGICAL:
+	return 22;
+    case H_HYPERVISOR_DATA:
+	return 23;
+    case H_EOI:
+	return 24;
+    case H_CPPR:
+	return 25;
+    case H_IPI:
+	return 26;
+    case H_IPOLL:
+	return 27;
+    case H_XIRR:
+	return 28;
+    case H_PERFMON:
+	return 29;
+    case H_MIGRATE_DMA:
+	return 30;
+    case H_REGISTER_VPA:
+	return 31;
+    case H_CEDE:
+	return 32;
+    case H_CONFER:
+	return 33;
+    case H_PROD:
+	return 34;
+    case H_GET_PPP:
+	return 35;
+    case H_SET_PPP:
+	return 36;
+    case H_PURR:
+	return 37;
+    case H_PIC:
+	return 38;
+    case H_REG_CRQ:
+	return 39;
+    case H_FREE_CRQ:
+	return 40;
+    case H_VIO_SIGNAL:
+	return 41;
+    case H_SEND_CRQ:
+	return 42;
+    case H_COPY_RDMA:
+	return 43;
+    case H_STUFF_TCE:
+	return 44;
+    case H_PUT_TCE_INDIRECT:
+	return 45;
+    case H_VTERM_PARTNER_INFO:
+	return 46;
+    case H_REGISTER_VTERM:
+	return 47;
+    case H_FREE_VTERM:
+	return 48;
+    case H_POLL_PENDING:
+	return 49;
+    case H_REGISTER_LOGICAL_LAN:
+	return 50;
+    case H_FREE_LOGICAL_LAN:
+	return 51;
+    case H_ADD_LOGICAL_LAN_BUFFER:
+	return 52;
+    case H_SEND_LOGICAL_LAN:
+	return 53;
+    case H_MULTICAST_CTRL:
+	return 54;
+    case H_CHANGE_LOGICAL_LAN_MAC:
+	return 55;
+    default:
+	printk("Unknown hcall %ld\n", opcode);
+	return 0;
+    }
+}
+
+extern long plpar_hcall_real(unsigned long opcode,
+			     unsigned long arg1,
+			     unsigned long arg2,
+			     unsigned long arg3,
+			     unsigned long arg4,
+			     unsigned long *out1,
+			     unsigned long *out2,
+			     unsigned long *out3);
+
+long plpar_hcall(unsigned long opcode, unsigned long arg1,
+			unsigned long arg2, unsigned long arg3,
+			unsigned long arg4, unsigned long *out1,
+			unsigned long *out2, unsigned long *out3)
+{
+    long retcode;
+    unsigned long t_entry;
+    int opcode_index;
+    
+    opcode_index = map_hcall_to_index(opcode);
+    
+    t_entry = mfspr(SPRN_PURR);
+    barrier();
+    
+    retcode = plpar_hcall_real(opcode, arg1, arg2, arg3, arg4,
+			       out1, out2, out3);
+    
+    barrier();
+    get_cpu_var(hcall_type_count)[opcode_index]++;
+    put_cpu_var(hcall_type_count);
+    get_cpu_var(hcall_type_time)[opcode_index] += mfspr(SPRN_PURR) - t_entry;
+    put_cpu_var(hcall_type_time);
+    
+    return retcode;
+};
+
+extern long plpar_hcall_norets_real(unsigned long opcode, ...);
+
+long plpar_hcall_norets_C(unsigned long opcode,
+			  unsigned long arg1,
+			  unsigned long arg2,
+			  unsigned long arg3,
+			  unsigned long arg4,
+			  unsigned long arg5,
+			  unsigned long arg6) 
+{
+    long retcode;
+    unsigned long t_entry;
+    int opcode_index;
+    
+    opcode_index = map_hcall_to_index(opcode);
+    t_entry = mfspr(SPRN_PURR);
+    barrier();
+
+    retcode = plpar_hcall_norets_real(opcode, arg1, arg2, arg3, arg4,
+				      arg5, arg6);
+
+    barrier();
+    get_cpu_var(hcall_type_count)[opcode_index]++;
+    put_cpu_var(hcall_type_count);
+    get_cpu_var(hcall_type_time)[opcode_index] += mfspr(SPRN_PURR) - t_entry;
+    put_cpu_var(hcall_type_time);
+
+    return retcode;
+}
+
+extern long plpar_hcall_8arg_2ret_real(unsigned long opcode,
+				       unsigned long arg1,
+				       unsigned long arg2,
+				       unsigned long arg3,
+				       unsigned long arg4,
+				       unsigned long arg5,
+				       unsigned long arg6,
+				       unsigned long arg7,
+				       unsigned long arg8,
+				       unsigned long *out1);
+
+long plpar_hcall_8arg_2ret(unsigned long opcode,
+			   unsigned long arg1,
+		  	   unsigned long arg2,
+			   unsigned long arg3,
+			   unsigned long arg4,
+			   unsigned long arg5,
+			   unsigned long arg6,
+			   unsigned long arg7,
+			   unsigned long arg8,
+			   unsigned long *out1)
+{
+    long retcode;
+    unsigned long t_entry;
+    int opcode_index;
+    
+    opcode_index = map_hcall_to_index(opcode);
+    t_entry = mfspr(SPRN_PURR);
+    barrier();
+
+    retcode = plpar_hcall_8arg_2ret_real(opcode, arg1, arg2, arg3, arg4,
+					 arg5, arg6, arg7, arg8, out1);
+
+    barrier();
+    get_cpu_var(hcall_type_count)[opcode_index]++;
+    put_cpu_var(hcall_type_count);
+    get_cpu_var(hcall_type_time)[opcode_index] += mfspr(SPRN_PURR) - t_entry;
+    put_cpu_var(hcall_type_time);
+
+    return retcode;
+}
+
+extern long plpar_hcall_4out_real(unsigned long opcode,
+				  unsigned long arg1,
+				  unsigned long arg2,
+				  unsigned long arg3,
+				  unsigned long arg4,
+				  unsigned long *out1,
+				  unsigned long *out2,
+				  unsigned long *out3,
+				  unsigned long *out4);
+
+long plpar_hcall_4out(unsigned long opcode,
+		      unsigned long arg1,
+		      unsigned long arg2,
+		      unsigned long arg3,
+		      unsigned long arg4,
+		      unsigned long *out1,
+		      unsigned long *out2,
+		      unsigned long *out3,
+		      unsigned long *out4)
+{
+    long retcode;
+    unsigned long t_entry;
+    int opcode_index;
+    
+    opcode_index = map_hcall_to_index(opcode);
+    t_entry = mfspr(SPRN_PURR);
+    barrier();
+
+    retcode = plpar_hcall_4out_real(opcode, arg1, arg2, arg3, arg4,
+				    out1, out2, out3, out4);
+
+    barrier();
+    get_cpu_var(hcall_type_count)[opcode_index]++;
+    put_cpu_var(hcall_type_count);
+    get_cpu_var(hcall_type_time)[opcode_index] += mfspr(SPRN_PURR) - t_entry;
+    put_cpu_var(hcall_type_time);
+
+    return retcode;
+}
+
+
diff -urpN -X linux-2.6.15.6/Documentation/dontdiff linux-2.6.15.6.ref/include/asm-powerpc/hvcall.h linux-2.6.15.6.hcall/include/asm-powerpc/hvcall.h
--- linux-2.6.15.6.ref/include/asm-powerpc/hvcall.h	2006-03-06 06:07:54.000000000 +1100
+++ linux-2.6.15.6.hcall/include/asm-powerpc/hvcall.h	2006-04-05 13:59:22.000000000 +1000
@@ -116,6 +116,11 @@
 
 #ifndef __ASSEMBLY__
 
+
+#define NUM_HCALL_TYPES 56
+/* Some are in ibmveth.h */
+
+
 /* plpar_hcall() -- Generic call interface using above opcodes
  *
  * The actual call interface is a hypervisor call instruction with

^ permalink raw reply

* Re: Collecting hypervisor call stats
From: Jimi Xenidis @ 2006-06-01  4:34 UTC (permalink / raw)
  To: Mike Kravetz, Chris Yeoh, Bryan Rosenburg; +Cc: linuxppc-dev
In-Reply-To: <20060531204144.GA7909@w-mikek2.ibm.com>

IIRC, chris and bryan have done some work to track this, they may  
have some code to contribute.
On May 31, 2006, at 4:41 PM, Mike Kravetz wrote:

> We would like to add code that gathers statistics about hypervisor  
> calls.
> It would keep track of things like # of calls made for each opcode  
> as well
> as time spent processing the calls.
>
> The 'obvious' place to gather such statistics would be from within the
> routines making hcalls in hvCall.S.  Once thing that I have noticed is
> the following comment at the beginning hvCall.S:
>
>  * NOTE: this file will go away when we move to inline this work.
>
> So, it appears someone thought/thinks these routines should be  
> inlined.
> I assume this is for performance reasons?  If this is the case, then I
> suspect gathering of statistics (which would introduce overhead) would
> meet resistance?
>
> Comments about the feasibility of adding such statistic gathering code
> would be appreciated.
>
> Thanks,
> -- 
> Mike
> _______________________________________________
> Linuxppc-dev mailing list
> Linuxppc-dev@ozlabs.org
> https://ozlabs.org/mailman/listinfo/linuxppc-dev

^ permalink raw reply

* Re: AW: MPC8270 on Microsys PM827 Kernel 2.6 Oops: kernel access of bad area, sig: 11 [#1]
From: Wolfgang Denk @ 2006-06-01  1:02 UTC (permalink / raw)
  To: Behre, Frederik - LT; +Cc: linuxppc-embedded
In-Reply-To: <CC692F5386B0AA47A62B7484A7CA2B6D02C4CFCC@frmx1.litef.de>

In message <CC692F5386B0AA47A62B7484A7CA2B6D02C4CFCC@frmx1.litef.de> you wrote:
> Thanks for answer me. 
> I want to install Xenomai on this mpc8270. Therefor I need 2.6.14
> ,because I need the adeos patch. 

You should be able to use a more recent kenrel tree as well.

Looking closer at your problem, it seems obvious  where  the  problem
is:

...
> RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize 
> NFTL driver: nftlcore.c $Revision: 1.97 $, nftlmount.c $Revision: 1.40 $
> Oops: kernel access of bad area, sig: 11 [#1]
> NIP: C0107A14 LR: C0106660 SP: C036FE30 REGS: c036fd80 TRAP: 0300    Not
> tainted
...

That's when the kernel attempts to access the flash devices  on  your
board.

Please use a later kernel. I just verified  that  the  2.6.15  kernel
(git tag DENX-v2.6.15 in our repo) works fine:


Linux version 2.6.15-gref: ref (wd@pollux.denx.de) (gcc version 4.0.0 (DENX ELDK 4.0 4.0.0)) #1 Thu Jun 1 02:55:30 MEST 2006
Microsys PM82x PowerPC port
arch/ppc/syslib/m82xx_pci.c: The PCI bus is 37500000 Mhz.
Waiting 0.5 seconds after deasserting RST...
Built 1 zonelists
Kernel command line: root=/dev/nfs rw nfsroot=192.168.1.1:/opt/eldk-4.0-2006-02-19/ppc_6xx ip=192.168.200.5:192.168.1.1::255.255.0.0:pm827:eth0:off panic=1 console=ttyCPM1,9600
PID hash table entries: 1024 (order: 10, 16384 bytes)
Dentry cache hash table entries: 32768 (order: 5, 131072 bytes)
Inode-cache hash table entries: 16384 (order: 4, 65536 bytes)
Memory: 127616k available (1592k kernel code, 412k data, 124k init, 0k highmem)
Mount-cache hash table entries: 512
NET: Registered protocol family 16
PCI: Probing PCI hardware
PCI: Cannot allocate resource region 0 of device 0000:00:00.0
PCI: Cannot allocate resource region 1 of device 0000:00:00.0
io scheduler noop registered
io scheduler anticipatory registered
io scheduler deadline registered
io scheduler cfq registered
Generic RTC Driver v1.07
Serial: CPM driver $Revision: 0.01 $
ttyCPM0 at MMIO 0xf0011a80 (irq = 4) is a CPM UART
ttyCPM1 at MMIO 0xf0011a90 (irq = 5) is a CPM UART
ttyCPM2 at MMIO 0xf0011a00 (irq = 40) is a CPM UART
ttyCPM3 at MMIO 0xf0011a20 (irq = 41) is a CPM UART
ttyCPM4 at MMIO 0xf0011a40 (irq = 42) is a CPM UART
ttyCPM5 at MMIO 0xf0011a60 (irq = 43) is a CPM UART
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
NFTL driver: nftlcore.c $Revision: 1.98 $, nftlmount.c $Revision: 1.41 $
Found: Intel 28F640C3B
PM82x-0: Found 4 x16 devices at 0x0 in 64-bit bank
PM82x flash bank 0: Using static image partition definition
Creating 4 MTD partitions on "PM82x-0":
0x00000000-0x00040000 : "U-Boot"
0x00040000-0x00100000 : "kernel"
0x00100000-0x00400000 : "ramdisk"
0x00400000-0x02000000 : "user"
No valid DiskOnChip devices found
eth0: FCC ENET Version 0.3, 00:40:42:81:27:0d
mii_reg: 608e78e2
eth0: Phy @ 0x1, type LXT971 (0x001378e2)
NET: Registered protocol family 2
IP route cache hash table entries: 2048 (order: 1, 8192 bytes)
TCP established hash table entries: 8192 (order: 3, 32768 bytes)
TCP bind hash table entries: 8192 (order: 3, 32768 bytes)
TCP: Hash tables configured (established 8192 bind 8192)
TCP reno registered
TCP bic registered
NET: Registered protocol family 1
NET: Registered protocol family 17
eth0: config: auto-negotiation on, 100HDX, 10HDX.
IP-Config: Complete:
      device=eth0, addr=192.168.200.5, mask=255.255.0.0, gw=255.255.255.255,
     host=pm827, domain=, nis-domain=(none),
     bootserver=192.168.1.1, rootserver=192.168.1.1, rootpath=
Looking up port of RPC 100003/2 on 192.168.1.1
Looking up port of RPC 100005/1 on 192.168.1.1
VFS: Mounted root (nfs filesystem).
Freeing unused kernel memory: 124k init
...


Also, the current  top-of-tree  version  (Linux-2.6.17-rc5-g1d475c9f)
works fine.

Please use current code.

Best regards,

Wolfgang Denk

-- 
Software Engineering:  Embedded and Realtime Systems,  Embedded Linux
Phone: (+49)-8142-66989-10 Fax: (+49)-8142-66989-80 Email: wd@denx.de
We are Microsoft. Unix is irrelevant. Openness is futile.  Prepare to
be assimilated.

^ permalink raw reply

* Re: pmf_register_irq_client gives sleep with locks held warning
From: Benjamin Herrenschmidt @ 2006-06-01  0:59 UTC (permalink / raw)
  To: Johannes Berg; +Cc: Andrew Morton, linuxppc-dev list
In-Reply-To: <1149020341.5128.7.camel@johannes>

On Tue, 2006-05-30 at 22:19 +0200, Johannes Berg wrote:
> Hi,
> 
> When testing headphone detection stuff I got this:
> 
> [  634.218762] BUG: sleeping function called from invalid context at mm/slab.c:2794
> [  634.218774] in_atomic():0, irqs_disabled():1
> [  634.218777] Call Trace:
> [  634.218779] [D67ADC00] [C00085F8] show_stack+0x50/0x190 (unreliable)
> [  634.218794] [D67ADC30] [C0024030] __might_sleep+0xcc/0xe8
> [  634.218804] [D67ADC40] [C0076C04] __kmalloc+0xf4/0xf8
> [  634.218815] [D67ADC60] [C00B37D4] proc_create+0x9c/0xe8
> [  634.218827] [D67ADC90] [C00B3930] proc_mkdir_mode+0x2c/0x88
> [  634.218833] [D67ADCB0] [C005227C] register_handler_proc+0xe4/0xfc
> [  634.218844] [D67ADD50] [C0051D70] setup_irq+0x118/0x16c
> [  634.218850] [D67ADD70] [C0051E68] request_irq+0xa4/0xb8
> [  634.218855] [D67ADDA0] [C0020BA0] macio_do_gpio_irq_enable+0x40/0x50
> [  634.218860] [D67ADDB0] [C00208BC] pmf_register_irq_client+0x88/0x9c
> [..., not important]

That's a bug in the PowerMac PMF code. What about this patch ?

This fixes request_irq() potentially called from atomic context.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>

Index: linux-work/arch/powerpc/platforms/powermac/pfunc_core.c
===================================================================
--- linux-work.orig/arch/powerpc/platforms/powermac/pfunc_core.c	2006-04-19 15:04:38.000000000 +1000
+++ linux-work/arch/powerpc/platforms/powermac/pfunc_core.c	2006-06-01 10:58:57.000000000 +1000
@@ -11,6 +11,7 @@
 #include <linux/kernel.h>
 #include <linux/spinlock.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 
 #include <asm/semaphore.h>
 #include <asm/prom.h>
@@ -546,6 +547,7 @@
 
 static LIST_HEAD(pmf_devices);
 static spinlock_t pmf_lock = SPIN_LOCK_UNLOCKED;
+static DEFINE_MUTEX(pmf_irq_mutex);
 
 static void pmf_release_device(struct kref *kref)
 {
@@ -864,15 +866,17 @@
 
 	spin_lock_irqsave(&pmf_lock, flags);
 	func = __pmf_find_function(target, name, PMF_FLAGS_INT_GEN);
-	if (func == NULL) {
-		spin_unlock_irqrestore(&pmf_lock, flags);
+	if (func)
+		func = pmf_get_function(func);
+	spin_unlock_irqrestore(&pmf_lock, flags);
+	if (func == NULL)
 		return -ENODEV;
-	}
+	mutex_lock(&pmf_irq_mutex);
 	if (list_empty(&func->irq_clients))
 		func->dev->handlers->irq_enable(func);
 	list_add(&client->link, &func->irq_clients);
 	client->func = func;
-	spin_unlock_irqrestore(&pmf_lock, flags);
+	mutex_unlock(&pmf_irq_mutex);
 
 	return 0;
 }
@@ -881,16 +885,16 @@
 void pmf_unregister_irq_client(struct pmf_irq_client *client)
 {
 	struct pmf_function *func = client->func;
-	unsigned long flags;
 
 	BUG_ON(func == NULL);
 
-	spin_lock_irqsave(&pmf_lock, flags);
+	mutex_lock(&pmf_irq_mutex);
 	client->func = NULL;
 	list_del(&client->link);
 	if (list_empty(&func->irq_clients))
 		func->dev->handlers->irq_disable(func);
-	spin_unlock_irqrestore(&pmf_lock, flags);
+	mutex_unlock(&pmf_irq_mutex);
+	pmf_put_function(func);
 }
 EXPORT_SYMBOL_GPL(pmf_unregister_irq_client);
 

^ permalink raw reply

* RE: Linux 2.4 Kernel on Xilinx Virtex4 FX100's PPC
From: Anantharaman Chetan-W16155 @ 2006-05-31 23:14 UTC (permalink / raw)
  To: Grant Likely; +Cc: linuxppc-embedded
In-Reply-To: <528646bc0605311235n171f889g135e074685b578bc@mail.gmail.com>

Grant,=20
Can you please provide a link or perhaps point me to the app node that
Xilinx has to modify linuxppc-2.4 tree for V4-FX.
Thanks,=20
Chetan

-----Original Message-----
From: glikely@gmail.com [mailto:glikely@gmail.com] On Behalf Of Grant
Likely
Sent: Wednesday, May 31, 2006 2:35 PM
To: Anantharaman Chetan-W16155
Cc: linuxppc-embedded@ozlabs.org
Subject: Re: Linux 2.4 Kernel on Xilinx Virtex4 FX100's PPC

On 5/31/06, Anantharaman Chetan-W16155
<Chetan.S.Anantharaman@motorola.com> wrote:
>
> Has anyone successfully ported a Linux 2.4 Kernel on a Xilinx Virtex-4
FX
> series FPGA's, PPC405 processor?

Linux on the V4-FX is well supported.  Xilinx has an app node
describing how to modify the linuxppc-2.4 tree to work on the V4-FX.
You can find out how to get the tree here:

http://www.penguinppc.org/kernel/

I've got both 2.4 & 2.6 happily running on my ML403 board here.

Cheers,
g.

--=20
Grant Likely, B.Sc. P.Eng.
Secret Lab Technologies Ltd.
grant.likely@secretlab.ca
(403) 399-0195

^ permalink raw reply

* Re: Setting I&D cache enable in the same mtspr instruction
From: Becky Bruce @ 2006-05-31 23:06 UTC (permalink / raw)
  To: Assaf Hoffman; +Cc: linuxppc-embedded, Rita Shtern, Ronen Shitrit
In-Reply-To: <B9FFC3F97441D04093A504CEA31B7C41A62B2F@msilexch01.marvell.com>


I think we need to hold off on this particular patch for a few days.   
I took a look at the manual section you're referencing here, and sure  
enough, it says that you shouldn't set both bits in the same mtspr  
instruction.  The manual says this in more than one place, in fact.   
However, that seemed a little bit odd to me, so I talked with a few  
of the hardware designers for the 745x family.  They assure me that  
the manual statement is false.  The normal code that sets ICE/ICFI/ 
DCE/DCFI all on one mtspr should be fully functional on this  
processor family.  I'm still working on confirming this and getting  
some history on *why* the manual says that, but for now I'd say it's  
a pretty good bet that the manual is in error.  I will let you know  
if I find out this is not the case.

You are correct, though, in that an isync is needed prior to the  
write of HID0[ICE]. It's probably missing because it's not listed in  
the synchronization table in chapter 2 of the manual.

For what it's worth, as soon as I can confirm this, I will make sure  
the publications team here at Freescale is made aware of the error so  
it can be corrected in the next printing of the manual.  I will also  
have the synchronization table updated as it also has incorrect  
information.

Thanks,
B

^ permalink raw reply

* Re: Collecting hypervisor call stats
From: Mike Kravetz @ 2006-05-31 22:58 UTC (permalink / raw)
  To: Paul Mackerras; +Cc: linuxppc-dev
In-Reply-To: <17534.7026.169023.420733@cargo.ozlabs.ibm.com>

On Thu, Jun 01, 2006 at 08:40:50AM +1000, Paul Mackerras wrote:
> I think that gathering those statistics would be useful.  I suggest
> you use a per-cpu array for them, to reduce the performance impact.

Yes, that is a must do.

We need to get a timestamp before and after the call.  mftb should do
the trick.  Also, I'd prefer to have the code that stuffs the values
into the array be C.  So, the decision is to have the assembly code
call out to the C routine -OR- create wrappers for the assembly routines.
I much prefer C wrappers to touching the assembly.

-- 
Mike

^ permalink raw reply

* Re: Collecting hypervisor call stats
From: Paul Mackerras @ 2006-05-31 22:40 UTC (permalink / raw)
  To: Mike Kravetz; +Cc: linuxppc-dev
In-Reply-To: <20060531204144.GA7909@w-mikek2.ibm.com>

Mike Kravetz writes:

> We would like to add code that gathers statistics about hypervisor calls.
> It would keep track of things like # of calls made for each opcode as well
> as time spent processing the calls.
> 
> The 'obvious' place to gather such statistics would be from within the
> routines making hcalls in hvCall.S.  Once thing that I have noticed is
> the following comment at the beginning hvCall.S:
> 
>  * NOTE: this file will go away when we move to inline this work.

I suspect that's a very old comment. :)

> So, it appears someone thought/thinks these routines should be inlined.  
> I assume this is for performance reasons?  If this is the case, then I
> suspect gathering of statistics (which would introduce overhead) would
> meet resistance?
> 
> Comments about the feasibility of adding such statistic gathering code
> would be appreciated.

I think that gathering those statistics would be useful.  I suggest
you use a per-cpu array for them, to reduce the performance impact.

Paul.

^ permalink raw reply

* Re: Linux 2.4 Kernel on Xilinx Virtex4 FX100's PPC
From: Grant Likely @ 2006-05-31 22:33 UTC (permalink / raw)
  To: Anantharaman Chetan-W16155; +Cc: linuxppc-embedded
In-Reply-To: <EDF27F298D4B03498AE0C249A941DFF803D6E8@de01exm68.ds.mot.com>

On 5/31/06, Anantharaman Chetan-W16155
<Chetan.S.Anantharaman@motorola.com> wrote:
> Was the port done on a FX100 FPGA?

No; but AFAIK, the ppc hardcore is identical for all FX parts.  The
FX100 shoudn't be a problem.

> Also, what PVR number does the PPC405 indicate?

Core#0>rd pvr
pvr: 0x20011430  536941616

^ permalink raw reply

* Re: Linux 2.4 Kernel on Xilinx Virtex4 FX100's PPC
From: Aidan Williams @ 2006-05-31 22:22 UTC (permalink / raw)
  To: Anantharaman Chetan-W16155; +Cc: linuxppc-embedded
In-Reply-To: <EDF27F298D4B03498AE0C249A941DFF803D6E6@de01exm68.ds.mot.com>

Anantharaman Chetan-W16155 wrote:
> Has anyone successfully ported a Linux 2.4 Kernel on a Xilinx Virtex-4 
> FX series FPGA’s, PPC405 processor?
> 

Yes, see 
http://ozlabs.org/pipermail/linuxppc-embedded/2006-April/022583.html

Note that there are silicon bugs that prevent caches being used in some 
chips.

> More specifically, the FX100 FPGA?

I don't have one of those, sorry.

- aidan

^ permalink raw reply

* Help with MPC5200 + Bestcomm + Local Plus Bus
From: roger blofeld @ 2006-05-31 21:42 UTC (permalink / raw)
  To: linuxppc embedded

Hi
 I have a custom board which is essentially an icecube with an FPGA on
the local bus. I have a driver which pulls data from the FPGA in
response to interrupts using memcpy_fromio(). I would like to reduce
the CPU usage.

Has anyone used bestcomm to move data from LPB to memory? Is there an
example anywhere? From the app note AN2604 it looks like writing a
bestcomm task is difficult at best. Perhaps it is possible to re-use
the ethernet rx task for this purpose?

I'm using mainline 2.6.16.18 with Sylvain's patches for
ethernet/bestcomm/ide. (BTW, will that code make it into the mainline
any time soon?)

Thanks in advance for any advice
-roger


__________________________________________________
Do You Yahoo!?
Tired of spam?  Yahoo! Mail has the best spam protection around 
http://mail.yahoo.com 

^ permalink raw reply

* RE: Linux 2.4 Kernel on Xilinx Virtex4 FX100's PPC
From: Anantharaman Chetan-W16155 @ 2006-05-31 21:47 UTC (permalink / raw)
  To: Grant Likely; +Cc: linuxppc-embedded
In-Reply-To: <528646bc0605311235n171f889g135e074685b578bc@mail.gmail.com>

Was the port done on a FX100 FPGA? Also, what PVR number does the PPC405
indicate?
Thanks,
Chetan

-----Original Message-----
From: glikely@gmail.com [mailto:glikely@gmail.com] On Behalf Of Grant
Likely
Sent: Wednesday, May 31, 2006 2:35 PM
To: Anantharaman Chetan-W16155
Cc: linuxppc-embedded@ozlabs.org
Subject: Re: Linux 2.4 Kernel on Xilinx Virtex4 FX100's PPC

On 5/31/06, Anantharaman Chetan-W16155
<Chetan.S.Anantharaman@motorola.com> wrote:
>
> Has anyone successfully ported a Linux 2.4 Kernel on a Xilinx Virtex-4
FX
> series FPGA's, PPC405 processor?

Linux on the V4-FX is well supported.  Xilinx has an app node
describing how to modify the linuxppc-2.4 tree to work on the V4-FX.
You can find out how to get the tree here:

http://www.penguinppc.org/kernel/

I've got both 2.4 & 2.6 happily running on my ML403 board here.

Cheers,
g.

--=20
Grant Likely, B.Sc. P.Eng.
Secret Lab Technologies Ltd.
grant.likely@secretlab.ca
(403) 399-0195

^ permalink raw reply

* Re: Collecting hypervisor call stats
From: Geoff Levand @ 2006-05-31 21:25 UTC (permalink / raw)
  To: Mike Kravetz; +Cc: linuxppc-dev
In-Reply-To: <20060531204144.GA7909@w-mikek2.ibm.com>

Mike Kravetz wrote:
> We would like to add code that gathers statistics about hypervisor calls.
> It would keep track of things like # of calls made for each opcode as well
> as time spent processing the calls.
> 
> The 'obvious' place to gather such statistics would be from within the
> routines making hcalls in hvCall.S.  Once thing that I have noticed is
> the following comment at the beginning hvCall.S:
> 
>  * NOTE: this file will go away when we move to inline this work.
> 
> So, it appears someone thought/thinks these routines should be inlined.  
> I assume this is for performance reasons?  If this is the case, then I
> suspect gathering of statistics (which would introduce overhead) would
> meet resistance?
> 
> Comments about the feasibility of adding such statistic gathering code
> would be appreciated.


FYI, there exists an add-on feature KFT (kernel function trace)
which you may be able to extend to do much of what you want.  It
uses gcc's -finstrument-functions option to hook into the
entry and exit of C functions.  You may able to add the hooks
by hand to the asm routines in hvCall.S and then take advantage
of the existing KFT functionality to start and stop traces, post
process the data, etc.

http://tree.celinuxforum.org/CelfPubWiki/KernelFunctionTrace

-Geoff

^ permalink raw reply

* Re: [PATCH] Provide mechanism for editing builtin command-line in zImage binary.
From: Michal Ostrowski @ 2006-05-31 21:04 UTC (permalink / raw)
  To: Matthew McClintock; +Cc: linuxppc-dev
In-Reply-To: <1149107718.8379.48.camel@localhost.localdomain>

On Wed, 2006-05-31 at 15:35 -0500, Matthew McClintock wrote:
> On Wed, 2006-05-31 at 16:26 -0400, Michal Ostrowski wrote:
> > I've had some experience with trying to edit existing OF trees (i.e.
> > take a G5 OF tree and alter it to reflect the fact that the OS has a
> > hypervisor between it and the HW).  It's not a pleasant experience.
> > 
> > Thus for OF based systems I'd be very wary of trying to edit the OF
> > tree
> > in arbitrary ways prior to Linux seeing it. 
> 
> Out of curiosity what was hard about it? 

Well, suppose that you want to remove a particular device from an OF
tree.  At what point are you certain that you've completely removed all
references to it?

I've always been concerned that there are some properties remaining in
the tree that may refer to the node I am removing (resulting in an
inconsistent tree).  If you're working with one particular FW provider
then you may come up with code that does it right, but such code may not
necessarily catch all the extensions provided by another FW provider.  

I've found Apple and IBM FW like to do things in different ways.  In
particular IBM FW likes to add "ibm,*" properties and you'd have to know
the meaning of all such properties to ensure you've caught all
references to the device you're pruning.

Like with most things, getting a solution to solve your immediate
problem is easy; a perfect, general solution is much, much more
difficult.


(Granted, some things, such as adding a new "memory" node are pretty
easy to do.)

> Also it is worth mentioning
> some systems don't have to privilege of having a OF tree ready to go by
> the time Linux starts.

For such systems I think the right approach is to provide a
DTC-generated OF tree (provided that one ensures that we don't skip
important parts of prom_init.c).



-- 
Michal Ostrowski <mostrows@watson.ibm.com>

^ permalink raw reply

* Collecting hypervisor call stats
From: Mike Kravetz @ 2006-05-31 20:41 UTC (permalink / raw)
  To: linuxppc-dev

We would like to add code that gathers statistics about hypervisor calls.
It would keep track of things like # of calls made for each opcode as well
as time spent processing the calls.

The 'obvious' place to gather such statistics would be from within the
routines making hcalls in hvCall.S.  Once thing that I have noticed is
the following comment at the beginning hvCall.S:

 * NOTE: this file will go away when we move to inline this work.

So, it appears someone thought/thinks these routines should be inlined.  
I assume this is for performance reasons?  If this is the case, then I
suspect gathering of statistics (which would introduce overhead) would
meet resistance?

Comments about the feasibility of adding such statistic gathering code
would be appreciated.

Thanks,
-- 
Mike

^ permalink raw reply

* Re: [PATCH] Provide mechanism for editing builtin command-line in zImage binary.
From: Matthew McClintock @ 2006-05-31 20:35 UTC (permalink / raw)
  To: Michal Ostrowski; +Cc: linuxppc-dev
In-Reply-To: <1149107204.6507.97.camel@brick>

On Wed, 2006-05-31 at 16:26 -0400, Michal Ostrowski wrote:
> I've had some experience with trying to edit existing OF trees (i.e.
> take a G5 OF tree and alter it to reflect the fact that the OS has a
> hypervisor between it and the HW).  It's not a pleasant experience.
> 
> Thus for OF based systems I'd be very wary of trying to edit the OF
> tree
> in arbitrary ways prior to Linux seeing it. 

Out of curiosity what was hard about it? Also it is worth mentioning
some systems don't have to privilege of having a OF tree ready to go by
the time Linux starts.

-Matthew

^ permalink raw reply

* Re: [PATCH] Provide mechanism for editing builtin command-line in zImage binary.
From: Michal Ostrowski @ 2006-05-31 20:26 UTC (permalink / raw)
  To: Mark A. Greer; +Cc: linuxppc-dev
In-Reply-To: <20060531200419.GA17052@mag.az.mvista.com>

On Wed, 2006-05-31 at 13:04 -0700, Mark A. Greer wrote:
> On Tue, May 30, 2006 at 05:12:37PM -0400, Michal Ostrowski wrote:
> > On Tue, 2006-05-30 at 13:41 -0700, Mark A. Greer wrote:
> > > On Mon, May 29, 2006 at 10:01:03PM -0400, mostrows@watson.ibm.com wrote:


> > I'm curious how the tacked-on dt is expected to interact with the real
> > FW dt,
> 
> That's a good question.  I was thinking that a tacked on dt would be a
> complete replacement for whatever dt came from the fw (but extracting
> some info like /memory props).  That probably works okay for non-OF
> systems but may not work for OF systems b/c there is so much more info
> in the OF dt.  Someone who knows about OF will have to speak up here.
> 

I've had some experience with trying to edit existing OF trees (i.e.
take a G5 OF tree and alter it to reflect the fact that the OS has a
hypervisor between it and the HW).  It's not a pleasant experience.

Thus for OF based systems I'd be very wary of trying to edit the OF tree
in arbitrary ways prior to Linux seeing it.

> > and in particular how you would expect the interrogation
> > of /chosen/bootargs in prom_init.c (using prom_getprop()) to pick up the
> > command line value I specify in the tacked-on dt.
> 
> If a flattened dt is used instead of an OF dt the prom_init code isn't used.

If we always skip prom_init.c code then we may skip stuff like TCE
allocation which is only ever done on the first boot (and never on
kexec).  


-- 
Michal Ostrowski <mostrows@watson.ibm.com>

^ permalink raw reply

* Re: [PATCH] Provide mechanism for editing builtin command-line in zImage binary.
From: Mark A. Greer @ 2006-05-31 20:04 UTC (permalink / raw)
  To: Michal Ostrowski; +Cc: linuxppc-dev
In-Reply-To: <1149023558.6507.15.camel@brick>

On Tue, May 30, 2006 at 05:12:37PM -0400, Michal Ostrowski wrote:
> On Tue, 2006-05-30 at 13:41 -0700, Mark A. Greer wrote:
> > On Mon, May 29, 2006 at 10:01:03PM -0400, mostrows@watson.ibm.com wrote:
> > > zImage will store the builtin command-line in a dedicated section, allowing
> > > it to be easily identified and edited with user-space tools.
> > >
> > > zImage will set /chosen/bootargs to the stored builtin command-line setting,
> > > if /chosen/bootargs is empty (emulating the behavior in prom_init.c).
> > > 
> > > Use of this mechanism avoids the need to modify firmware or rely on a
> > > bootloader to customize kernel arguments (and overall system
> > > behavior).  The command line can be edited as needed when a zImage is
> > > copied to a TFTP staging area for download by firmware.
> 
> 
> 
> 
> > Why do this?  Why not get rid of storing the cmdline in the zImage altogether?  
> > 
> 
> The CONFIG_CMDLINE that we have now is very useful if I want the
> behavior of the system to be dependent on the kernel/command line I
> specify that it used (particularly in BOOTP/TFTP scenarios).  For such
> systems I configure their firmware to not provide any arguments and to
> always download via TFTP a designated kernel image.
> 
> What this scenario does, is it allows me to specify system behavior by
> putting the right kernel with the right command line in the magic
> location from where it will be downloaded.
> 
> The problem is, that if I have some large number of machines and I want
> to use the same kernel for all of them (and simply change the command
> line between them) then I have to effectively build a custom kernel for
> each.  This patch allows me to build one kernel and then simply edit the
> command line embedded within it.
> 
> The key point is that the command line changes and I don't want to have
> to require a firmware interaction every time I change it.

Okay.  I hadn't thought of that scenario.  You're happy with the dt that
the fw gives you except that you want to change the bootargs.

I guess we can keep CONFIG_CMDLINE around then.

> > We already have equivalent functionality by storing it in the dt's
> > /chosen/bootargs so why this unnecessary complexity?
> > 
> 
> 
> > Add some code to edit the /chosen/bootargs at zImage runtime (just like
> > arch/ppc used to) and we're covered.
> 
> That is what this patch is doing.
>
> >   AFAICT, we're already adding a tool
> > to tack on flat dt's to an already existing zImage so you're not doing
> > anything we can't--or won't--do already.
> > 
> 
> Can you please point me at this code so that I can evaluate it in
> detail?

It doesn't exist yet and no one has jumped up to make that tool that I
have seen.  I've been messing with bootwrapper code and part of that
adds cmdline editing from a running bootwrapper.  We still need someone
to write this tool (assuming that's the way we're going):

http://ozlabs.org/pipermail/linuxppc-dev/2006-April/022435.html

Care to volunteer?  ;)

> I'm curious how the tacked-on dt is expected to interact with the real
> FW dt,

That's a good question.  I was thinking that a tacked on dt would be a
complete replacement for whatever dt came from the fw (but extracting
some info like /memory props).  That probably works okay for non-OF
systems but may not work for OF systems b/c there is so much more info
in the OF dt.  Someone who knows about OF will have to speak up here.

> and in particular how you would expect the interrogation
> of /chosen/bootargs in prom_init.c (using prom_getprop()) to pick up the
> command line value I specify in the tacked-on dt.

If a flattened dt is used instead of an OF dt the prom_init code isn't used.

Mark

^ permalink raw reply

* Re: Linux 2.4 Kernel on Xilinx Virtex4 FX100's PPC
From: Grant Likely @ 2006-05-31 19:35 UTC (permalink / raw)
  To: Anantharaman Chetan-W16155; +Cc: linuxppc-embedded
In-Reply-To: <EDF27F298D4B03498AE0C249A941DFF803D6E6@de01exm68.ds.mot.com>

On 5/31/06, Anantharaman Chetan-W16155
<Chetan.S.Anantharaman@motorola.com> wrote:
>
> Has anyone successfully ported a Linux 2.4 Kernel on a Xilinx Virtex-4 FX
> series FPGA's, PPC405 processor?

Linux on the V4-FX is well supported.  Xilinx has an app node
describing how to modify the linuxppc-2.4 tree to work on the V4-FX.
You can find out how to get the tree here:

http://www.penguinppc.org/kernel/

I've got both 2.4 & 2.6 happily running on my ML403 board here.

Cheers,
g.

-- 
Grant Likely, B.Sc. P.Eng.
Secret Lab Technologies Ltd.
grant.likely@secretlab.ca
(403) 399-0195

^ permalink raw reply

* [PATCH] use msleep() for RTAS delays
From: John Rose @ 2006-05-31 19:32 UTC (permalink / raw)
  To: External List; +Cc: Paul Mackerras

The current use of udelay() for RTAS extended delay conditions can
result in CPU soft lockups.  The use of msleep() won't tie up the CPU.

Signed-off-by: John Rose <johnrose@austin.ibm.com>

---

Thanks-
John

diff -puN arch/powerpc/kernel/rtas.c~msleep_rtas arch/powerpc/kernel/rtas.c
--- 2_6_linus/arch/powerpc/kernel/rtas.c~msleep_rtas	2006-05-31 14:07:51.000000000 -0500
+++ 2_6_linus-johnrose/arch/powerpc/kernel/rtas.c	2006-05-31 14:10:09.000000000 -0500
@@ -447,10 +447,10 @@ int rtas_set_power_level(int powerdomain
 	while (1) {
 		rc = rtas_call(token, 2, 2, setlevel, powerdomain, level);
 		if (rc == RTAS_BUSY)
-			udelay(1);
+			msleep(1);
 		else if (rtas_is_extended_busy(rc)) {
 			wait_time = rtas_extended_busy_delay_time(rc);
-			udelay(wait_time * 1000);
+			msleep(wait_time);
 		} else
 			break;
 	}
@@ -472,10 +472,10 @@ int rtas_get_sensor(int sensor, int inde
 	while (1) {
 		rc = rtas_call(token, 2, 2, state, sensor, index);
 		if (rc == RTAS_BUSY)
-			udelay(1);
+			msleep(1);
 		else if (rtas_is_extended_busy(rc)) {
 			wait_time = rtas_extended_busy_delay_time(rc);
-			udelay(wait_time * 1000);
+			msleep(wait_time);
 		} else
 			break;
 	}
@@ -497,12 +497,11 @@ int rtas_set_indicator(int indicator, in
 	while (1) {
 		rc = rtas_call(token, 3, 1, NULL, indicator, index, new_value);
 		if (rc == RTAS_BUSY)
-			udelay(1);
+			msleep(1);
 		else if (rtas_is_extended_busy(rc)) {
 			wait_time = rtas_extended_busy_delay_time(rc);
-			udelay(wait_time * 1000);
-		}
-		else
+			msleep(wait_time);
+		} else
 			break;
 	}
 
diff -puN arch/powerpc/kernel/rtas_flash.c~msleep_rtas arch/powerpc/kernel/rtas_flash.c
--- 2_6_linus/arch/powerpc/kernel/rtas_flash.c~msleep_rtas	2006-05-31 14:10:47.000000000 -0500
+++ 2_6_linus-johnrose/arch/powerpc/kernel/rtas_flash.c	2006-05-31 14:19:30.000000000 -0500
@@ -16,7 +16,7 @@
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/proc_fs.h>
-#include <asm/delay.h>
+#include <linux/delay.h>
 #include <asm/uaccess.h>
 #include <asm/rtas.h>
 #include <asm/abs_addr.h>
@@ -372,10 +372,10 @@ static void manage_flash(struct rtas_man
 		rc = rtas_call(rtas_token("ibm,manage-flash-image"), 1, 
 			       1, NULL, args_buf->op);
 		if (rc == RTAS_RC_BUSY)
-			udelay(1);
+			msleep(1);
 		else if (rtas_is_extended_busy(rc)) {
 			wait_time = rtas_extended_busy_delay_time(rc);
-			udelay(wait_time * 1000);
+			msleep(wait_time);
 		} else
 			break;
 	}
@@ -465,10 +465,10 @@ static void validate_flash(struct rtas_v
 		spin_unlock(&rtas_data_buf_lock);
 			
 		if (rc == RTAS_RC_BUSY)
-			udelay(1);
+			msleep(1);
 		else if (rtas_is_extended_busy(rc)) {
 			wait_time = rtas_extended_busy_delay_time(rc);
-			udelay(wait_time * 1000);
+			msleep(wait_time);
 		} else
 			break;
 	}

_

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox