Patchwork [1/3] x86: Add basic support for the Congatec CGEB BIOS interface

login
register
mail settings
Submitter Sascha Hauer
Date Feb. 12, 2013, 10:02 a.m.
Message ID <1360663374-30951-2-git-send-email-s.hauer@pengutronix.de>
Download mbox | patch
Permalink /patch/219756/
State Not Applicable
Headers show

Comments

Sascha Hauer - Feb. 12, 2013, 10:02 a.m.
The Congatec CGEB is a BIOS interface found on some Congatec x86
modules. It provides access to on board peripherals like I2C busses
and watchdogs. This driver contains the basic support for accessing
the CGEB interface and registers the child devices.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 arch/x86/Kconfig                           |    9 +
 arch/x86/include/asm/congatec-cgeb.h       |  105 +++++
 arch/x86/platform/Makefile                 |    1 +
 arch/x86/platform/congatec/congatec-cgeb.c |  590 ++++++++++++++++++++++++++++
 4 files changed, 705 insertions(+)
 create mode 100644 arch/x86/include/asm/congatec-cgeb.h
 create mode 100644 arch/x86/platform/congatec/congatec-cgeb.c
Christian Gmeiner - March 25, 2013, 8:31 a.m.
Hi Sascha


> +struct cgeb_pdata {
> +       struct cgeb_board_data          *board;
> +       int unit;
> +};
> +
> +#endif /* __CONGATEC_CGEB_H */
> diff --git a/arch/x86/platform/Makefile b/arch/x86/platform/Makefile
> index 8d87439..9de1789 100644
> --- a/arch/x86/platform/Makefile
> +++ b/arch/x86/platform/Makefile
> @@ -1,5 +1,6 @@
>  # Platform specific code goes here
>  obj-y  += ce4100/
> +obj-y  += congatec/
>  obj-y  += efi/
>  obj-y  += geode/
>  obj-y  += iris/


Fails to build


christian@chgm-pc:~/kernel/linux_mainline$ make
make[1]: Nothing to be done for `all'.
make[1]: Nothing to be done for `relocs'.
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
  CALL    scripts/checksyscalls.sh
  CC      scripts/mod/devicetable-offsets.s
  GEN     scripts/mod/devicetable-offsets.h
  HOSTCC  scripts/mod/file2alias.o
  HOSTLD  scripts/mod/modpost
  CHK     include/generated/compile.h
scripts/Makefile.build:44:
/home/christian/kernel/linux_mainline/arch/x86/platform/congatec/Makefile:
No such file or directory
make[3]: *** No rule to make target
`/home/christian/kernel/linux_mainline/arch/x86/platform/congatec/Makefile'.
 Stop.
make[2]: *** [arch/x86/platform/congatec] Error 2
make[1]: *** [arch/x86/platform] Error 2
make: *** [arch/x86] Error 2


--
Christian Gmeiner, MSc
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Sascha Hauer - March 26, 2013, 8:54 a.m.
On Mon, Mar 25, 2013 at 09:31:35AM +0100, Christian Gmeiner wrote:
> Hi Sascha
> 
> 
> > +struct cgeb_pdata {
> > +       struct cgeb_board_data          *board;
> > +       int unit;
> > +};
> > +
> > +#endif /* __CONGATEC_CGEB_H */
> > diff --git a/arch/x86/platform/Makefile b/arch/x86/platform/Makefile
> > index 8d87439..9de1789 100644
> > --- a/arch/x86/platform/Makefile
> > +++ b/arch/x86/platform/Makefile
> > @@ -1,5 +1,6 @@
> >  # Platform specific code goes here
> >  obj-y  += ce4100/
> > +obj-y  += congatec/
> >  obj-y  += efi/
> >  obj-y  += geode/
> >  obj-y  += iris/
> 
> 
> Fails to build

Ups, sorry. It seems I forgot to git add
arch/x86/platform/congatec/Makefile. It should look like this:

obj-$(CONFIG_CONGATEC_CGEB) += congatec-cgeb.o

I will include this next time.


Any other comments to this series? Is the base cgeb support ok to
include?

Sascha
Wolfram Sang - March 26, 2013, 9:24 a.m.
Hi Sascha,

> Any other comments to this series? Is the base cgeb support ok to
> include?

I had a few comments to the I2C driver.

All the best,

   Wolfram
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Sascha Hauer - March 26, 2013, 9:31 a.m.
Hi Wolfram,

On Tue, Mar 26, 2013 at 10:24:35AM +0100, Wolfram Sang wrote:
> Hi Sascha,
> 
> > Any other comments to this series? Is the base cgeb support ok to
> > include?
> 
> I had a few comments to the I2C driver.

Yes, thanks, I have seen this and will address them. Atm I'm more
interested in the base support though, since without it there won't be a
need for the i2c driver ;)

Regards
 Sascha
Christian Gmeiner - March 26, 2013, 9:38 a.m.
2013/3/26 Sascha Hauer <s.hauer@pengutronix.de>:
> Hi Wolfram,
>
> On Tue, Mar 26, 2013 at 10:24:35AM +0100, Wolfram Sang wrote:
>> Hi Sascha,
>>
>> > Any other comments to this series? Is the base cgeb support ok to
>> > include?
>>
>> I had a few comments to the I2C driver.
>
> Yes, thanks, I have seen this and will address them. Atm I'm more
> interested in the base support though, since without it there won't be a
> need for the i2c driver ;)
>

vis@OT:~$ dmesg | grep CGEB
[    2.466519] CGEB: Board name: QBRA


root@OT:/home/vis# i2cdetect -l
i2c-0   i2c             cgeb-i2c                                I2C adapter
i2c-1   i2c             Radeon i2c bit bus 0x90                 I2C adapter
i2c-2   i2c             Radeon i2c bit bus 0x91                 I2C adapter
i2c-3   i2c             Radeon i2c bit bus 0x92                 I2C adapter
i2c-4   i2c             Radeon i2c bit bus 0x93                 I2C adapter
i2c-5   i2c             Radeon i2c bit bus 0x94                 I2C adapter
i2c-6   i2c             Radeon i2c bit bus 0x95                 I2C adapter
i2c-7   i2c             Radeon i2c bit bus 0x96                 I2C adapter
i2c-8   i2c             Radeon i2c bit bus 0x97                 I2C adapter
i2c-9   i2c             Radeon aux bus DP-auxch                 I2C adapter
root@OT:/home/vis# i2cdetect 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0.
I will probe address range 0x03-0x77.
Continue? [Y/n]
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- 21 -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: 50 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

I hope to see this patches soon in mainline - I have some Congatec
devices where I
am using an early version of this patch set.

Btw I have a cgeb-backlight driver lying here :)


Tested-by: Christian Gmeiner <christian.gmeiner@gmail.com>
Acked-by: Christian Gmeiner <christian.gmeiner@gmail.com>

--
Christian Gmeiner, MSc
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Christian Gmeiner - April 11, 2013, 7:14 a.m.
2013/3/26 Christian Gmeiner <christian.gmeiner@gmail.com>:
> 2013/3/26 Sascha Hauer <s.hauer@pengutronix.de>:
>> Hi Wolfram,
>>
>> On Tue, Mar 26, 2013 at 10:24:35AM +0100, Wolfram Sang wrote:
>>> Hi Sascha,
>>>
>>> > Any other comments to this series? Is the base cgeb support ok to
>>> > include?
>>>
>>> I had a few comments to the I2C driver.
>>
>> Yes, thanks, I have seen this and will address them. Atm I'm more
>> interested in the base support though, since without it there won't be a
>> need for the i2c driver ;)
>>
>
> vis@OT:~$ dmesg | grep CGEB
> [    2.466519] CGEB: Board name: QBRA
>
>
> root@OT:/home/vis# i2cdetect -l
> i2c-0   i2c             cgeb-i2c                                I2C adapter
> i2c-1   i2c             Radeon i2c bit bus 0x90                 I2C adapter
> i2c-2   i2c             Radeon i2c bit bus 0x91                 I2C adapter
> i2c-3   i2c             Radeon i2c bit bus 0x92                 I2C adapter
> i2c-4   i2c             Radeon i2c bit bus 0x93                 I2C adapter
> i2c-5   i2c             Radeon i2c bit bus 0x94                 I2C adapter
> i2c-6   i2c             Radeon i2c bit bus 0x95                 I2C adapter
> i2c-7   i2c             Radeon i2c bit bus 0x96                 I2C adapter
> i2c-8   i2c             Radeon i2c bit bus 0x97                 I2C adapter
> i2c-9   i2c             Radeon aux bus DP-auxch                 I2C adapter
> root@OT:/home/vis# i2cdetect 0
> WARNING! This program can confuse your I2C bus, cause data loss and worse!
> I will probe file /dev/i2c-0.
> I will probe address range 0x03-0x77.
> Continue? [Y/n]
>      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
> 00:          -- -- -- -- -- -- -- -- -- -- -- -- --
> 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
> 20: -- 21 -- -- -- -- -- -- -- -- -- -- -- -- -- --
> 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
> 40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
> 50: 50 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
> 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
> 70: -- -- -- -- -- -- -- --
>
> I hope to see this patches soon in mainline - I have some Congatec
> devices where I
> am using an early version of this patch set.
>
> Btw I have a cgeb-backlight driver lying here :)
>
>
> Tested-by: Christian Gmeiner <christian.gmeiner@gmail.com>
> Acked-by: Christian Gmeiner <christian.gmeiner@gmail.com>
>


Hi Wolfram,

any update on your patch series to support CGEB? I have some time to work
on these patches but I don't want to duplicate the work. I really want
this watches
to hit mainline soon.

--
Christian Gmeiner, MSc
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Wolfram Sang - April 11, 2013, 11:20 a.m.
> any update on your patch series to support CGEB? I have some time to
> work on these patches but I don't want to duplicate the work. I really
> want this watches to hit mainline soon.

I acked the I2C patch and this is all I can do for this series.

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 225543b..bd68c87 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -2188,6 +2188,15 @@  config GEOS
 	---help---
 	  This option enables system support for the Traverse Technologies GEOS.
 
+config CONGATEC_CGEB
+	bool "Support for the Congatec CGEB BIOS interface"
+	---help---
+	  The Congatec CGEB BIOS interface provides access to onboard
+	  peripherals like I2C busses and watchdogs. additional drivers must be
+	  enabled in order to use the functionality of the device.
+	  Say y or m here if you are using a congatec module with CGEB interface,
+	  otherwise say n.
+
 endif # X86_32
 
 config AMD_NB
diff --git a/arch/x86/include/asm/congatec-cgeb.h b/arch/x86/include/asm/congatec-cgeb.h
new file mode 100644
index 0000000..71e1bf5
--- /dev/null
+++ b/arch/x86/include/asm/congatec-cgeb.h
@@ -0,0 +1,105 @@ 
+#ifndef __CONGATEC_CGEB_H
+#define __CONGATEC_CGEB_H
+
+/* CGEB interface functions */
+enum cgeb_function {
+	CgebGetCgebVersion =		0,
+	CgebGetSysBiosVersion =		1,
+	CgebGetVgaBiosVersion =		2,
+	CgebGetDataSize =		3,
+	CgebOpen =			4,
+	CgebClose =			5,
+	CgebMapGetMem =			6,
+	CgebMapChanged =		7,
+	CgebMapGetPorts =		8,
+	CgebDelayUs =			9,
+	CgebCgbcReadWrite =		10,
+	CgebCgbcSetControl =		11,
+	CgebCgbcGetInfo =		12,
+	CgebCgbcHandleCommand =		13,
+	CgebBoardGetInfo =		14,
+	CgebBoardGetBootCounter	=	15,
+	CgebBoardGetRunningTimeMeter =	16,
+	CgebBoardGetBootErrorLog =	17,
+	CgebVgaCount =			18,
+	CgebVgaGetInfo =		19,
+	CgebVgaGetContrast =		20,
+	CgebVgaSetContrast =		21,
+	CgebVgaGetContrastEnable =	22,
+	CgebVgaSetContrastEnable =	23,
+	CgebVgaGetBacklight =		24,
+	CgebVgaSetBacklight =		25,
+	CgebVgaGetBacklightEnable =	26,
+	CgebVgaSetBacklightEnable =	27,
+	CgebVgaEndDarkBoot =		28,
+	CgebStorageAreaCount =		29,
+	CgebStorageAreaGetInfo =	30,
+	CgebStorageAreaRead =		31,
+	CgebStorageAreaWrite =		32,
+	CgebStorageAreaErase =		33,
+	CgebStorageAreaEraseStatus =	34,
+	CgebI2CCount =			35,
+	CgebI2CGetInfo =		36,
+	CgebI2CGetAddrList =		37,
+	CgebI2CTransfer =		38,
+	CgebI2CGetFrequency =		39,
+	CgebI2CSetFrequency =		40,
+	CgebIOCount =			41,
+	CgebIOGetInfo =			42,
+	CgebIORead =			43,
+	CgebIOWrite =			44,
+	CgebIOGetDirection =		45,
+	CgebIOSetDirection =		46,
+	CgebWDogCount =			47,
+	CgebWDogGetInfo =		48,
+	CgebWDogTrigger =		49,
+	CgebWDogGetConfig =		50,
+	CgebWDogSetConfig =		51,
+	CgebPerformanceGetCurrent =	52,
+	CgebPerformanceSetCurrent =	53,
+	CgebPerformanceGetPolicyCaps =	54,
+	CgebPerformanceGetPolicy =	55,
+	CgebPerformanceSetPolicy =	56,
+	CgebTemperatureCount =		57,
+	CgebTemperatureGetInfo =	58,
+	CgebTemperatureGetCurrent =	59,
+	CgebTemperatureSetLimits =	60,
+	CgebFanCount =			61,
+	CgebFanGetInfo =		62,
+	CgebFanGetCurrent =		63,
+	CgebFanSetLimits =		64,
+	CgebVoltageCount =		65,
+	CgebVoltageGetInfo =		66,
+	CgebVoltageGetCurrent =		67,
+	CgebVoltageSetLimits =		68,
+	CgebStorageAreaLock =		69,
+	CgebStorageAreaUnlock =		70,
+	CgebStorageAreaIsLocked =	71,
+};
+
+struct cgeb_function_parameters {
+	u32 unit;		/* unit number or type */
+	u32 pars[4];		/* input parameters */
+	u32 rets[2];		/* return parameters */
+	void *iptr;		/* input pointer */
+	void *optr;		/* output pointer */
+};
+
+struct cgeb_board_data;
+
+unsigned int cgeb_call(struct cgeb_board_data *,
+		struct cgeb_function_parameters *, enum cgeb_function);
+
+int cgeb_call_simple(struct cgeb_board_data *,
+		enum cgeb_function, unsigned long,
+		unsigned long *, unsigned long *);
+
+/*
+ * Platform data for child devices
+ */
+struct cgeb_pdata {
+	struct cgeb_board_data		*board;
+	int unit;
+};
+
+#endif /* __CONGATEC_CGEB_H */
diff --git a/arch/x86/platform/Makefile b/arch/x86/platform/Makefile
index 8d87439..9de1789 100644
--- a/arch/x86/platform/Makefile
+++ b/arch/x86/platform/Makefile
@@ -1,5 +1,6 @@ 
 # Platform specific code goes here
 obj-y	+= ce4100/
+obj-y	+= congatec/
 obj-y	+= efi/
 obj-y	+= geode/
 obj-y	+= iris/
diff --git a/arch/x86/platform/congatec/congatec-cgeb.c b/arch/x86/platform/congatec/congatec-cgeb.c
new file mode 100644
index 0000000..5b5af26
--- /dev/null
+++ b/arch/x86/platform/congatec/congatec-cgeb.c
@@ -0,0 +1,590 @@ 
+/*
+ * CGEB driver
+ *
+ * (c) 2011 Sascha Hauer, Pengutronix
+ *
+ * Based on code from Congatec AG.
+ *
+ * CGEB is a BIOS interface found on congatech modules. It consists of
+ * code found in the BIOS memory map which is called in a ioctl like
+ * fashion. This file contains the basic driver for this interface
+ * which provides access to the GCEB interface and registers the child
+ * devices like I2C busses and watchdogs.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <asm/congatec-cgeb.h>
+
+#include <generated/autoconf.h>
+#include <stddef.h>
+
+#define CGOS_BOARD_MAX_SIZE_ID_STRING 16
+
+#define CGEB_VERSION_MAJOR 1
+
+#define CGEB_GET_VERSION_MAJOR(v) (((unsigned long)(v))>>24)
+
+/* CGEB Low Descriptor located in 0xc0000-0xfffff */
+#define CGEB_LD_MAGIC "$CGEBLD$"
+
+struct cgeb_low_desc {
+	char magic[8];		/* descriptor magic string */
+	u16 size;		/* size of this descriptor */
+	u16 reserved;
+	char bios_name[8];	/* BIOS name and revision "ppppRvvv" */
+	u32 hi_desc_phys_addr;	/* phys addr of the high descriptor, can be 0 */
+};
+
+/* CGEB High Descriptor located in 0xfff00000-0xffffffff */
+#define CGEB_HD_MAGIC "$CGEBHD$"
+
+struct cgeb_high_desc {
+	char magic[8];		/* descriptor magic string */
+	u16 size;		/* size of this descriptor */
+	u16 reserved;
+	u32 data_size;		/* CGEB data area size */
+	u32 code_size;		/* CGEB code area size */
+	u32 entry_rel;		/* CGEB entry point relative to start */
+};
+
+struct cgeb_far_ptr {
+	u32 off;
+	u16 seg;
+	u16 pad;
+};
+
+struct cgeb_fps {
+	u32 size;		/* size of the parameter structure */
+	u32 fct;		/* function number */
+	struct cgeb_far_ptr data;	/* CGEB data area */
+	u32 cont;		/* private continuation pointer */
+	u32 subfps;		/* private sub function parameter
+				 * structure pointer
+				 */
+	u32 subfct;		/* sub function pointer */
+	u32 status;		/* result codes of the function */
+	u32 unit;		/* unit number or type */
+	u32 pars[4];		/* input parameters */
+	u32 rets[2];		/* return parameters */
+	void *iptr;		/* input pointer */
+	void *optr;		/* output pointer */
+};
+
+/* continuation status codes */
+#define CGEB_SUCCESS            0
+#define CGEB_NEXT               1
+#define CGEB_DELAY              2
+#define CGEB_NOIRQS             3
+
+#define CGEB_DBG_STR        0x100
+#define CGEB_DBG_HEX        0x101
+#define CGEB_DBG_DEC        0x102
+
+struct cgeb_map_mem {
+	unsigned long phys;	/* physical address */
+	unsigned long size;	/* size in bytes */
+	struct cgeb_far_ptr virt;
+};
+
+struct cgeb_map_mem_list {
+	unsigned long count;	/* number of memory map entries */
+	struct cgeb_map_mem entries[];
+};
+
+struct cgeb_boardinfo {
+	unsigned long size;
+	unsigned long flags;
+	unsigned long classes;
+	unsigned long primary_class;
+	char board[CGOS_BOARD_MAX_SIZE_ID_STRING];
+	/* optional */
+	char vendor[CGOS_BOARD_MAX_SIZE_ID_STRING];
+};
+
+struct cgeb_i2c_info {
+	unsigned long size;
+	unsigned long type;
+	unsigned long frequency;
+	unsigned long maxFrequency;
+};
+
+/* I2C Types */
+#define CGEB_I2C_TYPE_UNKNOWN 0
+#define CGEB_I2C_TYPE_PRIMARY 1
+#define CGEB_I2C_TYPE_SMB     2
+#define CGEB_I2C_TYPE_DDC     3
+#define CGEB_I2C_TYPE_BC      4
+
+struct cgeb_board_data {
+	void *code;
+	void *data;
+	unsigned short ds;
+	struct cgeb_map_mem_list *map_mem;
+	struct platform_device **devices;
+	int num_devices;
+
+	/*
+	 * entry points to a bimodal C style function that expects a far pointer
+	 * to a fps. If cs is 0 then it does a near return, otherwise a far
+	 * return. If we ever need a far return then we must not pass cs at all.
+	 * parameters are removed by the caller.
+	 */
+	void __attribute__((regparm(0)))(*entry)(unsigned short,
+			struct cgeb_fps *, unsigned short);
+};
+
+static unsigned short get_data_segment(void)
+{
+	unsigned short ret;
+
+	asm volatile("mov %%ds, %0\n"
+			: "=r"(ret)
+			:
+			: "memory"
+	);
+
+	return ret;
+}
+
+/*
+ * cgeb_invoke - invoke CGEB BIOS call.
+ *
+ * @board:	board context data
+ * @p:		CGEB parameters for this call
+ * @fct:	CGEB function code
+ * @return:	0 on success or negative error code on failure.
+ *
+ * Call the CGEB BIOS code with the given parameters.
+ */
+unsigned int cgeb_call(struct cgeb_board_data *board,
+		struct cgeb_function_parameters *p, enum cgeb_function fct)
+{
+	struct cgeb_fps fps;
+	int i;
+
+	memset(&fps, 0, sizeof(fps));
+
+	fps.size = sizeof(fps);
+	fps.fct = fct;
+	fps.data.off = (unsigned long) board->data;
+	fps.data.seg = board->ds;
+	fps.data.pad = 0;
+	fps.status = 0;
+	fps.cont = fps.subfct = fps.subfps = 0;
+	fps.unit = p->unit;
+	for (i = 0; i < 4; i++)
+		fps.pars[i] = p->pars[i];
+	fps.iptr = p->iptr;
+	fps.optr = p->optr;
+
+	while (1) {
+		pr_debug("CGEB: CGEB: ->  size %02x, fct %02x, data %04x:%08x, status %08x\n",
+				fps.size, fps.fct, fps.data.seg, fps.data.off,
+				fps.status);
+
+		board->entry(0, &fps, fps.data.seg);
+
+		switch (fps.status) {
+		case CGEB_SUCCESS:
+			goto out;
+		case CGEB_NEXT:
+			break;	/* simply call again */
+		case CGEB_NOIRQS:
+			current->state = TASK_INTERRUPTIBLE;
+			schedule_timeout(0);
+			break;
+		case CGEB_DELAY:
+			usleep_range(fps.rets[0], fps.rets[0] + 1000);
+			break;
+		case CGEB_DBG_STR:
+			if (fps.optr)
+				pr_debug("CGEB: %s\n", (char *)fps.optr);
+			break;
+		case CGEB_DBG_HEX:
+			pr_debug("CGEB: 0x%08x\n", fps.rets[0]);
+			break;
+		case CGEB_DBG_DEC:
+			pr_debug("CGEB: %d\n", fps.rets[0]);
+			break;
+
+		default:
+			/* unknown continuation code */
+			return -EINVAL;
+		}
+	}
+out:
+	for (i = 0; i < 2; i++)
+		p->rets[i] = fps.rets[i];
+	p->optr = fps.optr;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cgeb_call);
+
+/*
+ * cgeb_call_simple - convenience wrapper for cgeb_call
+ *
+ * @board:	board context data
+ * @p:		CGEB parameters for this call
+ * @fct:	CGEB function code
+ * @return:	0 on success or negative error code on failure.
+ *
+ * Call the CGEB BIOS code with the given parameters.
+ */
+int cgeb_call_simple(struct cgeb_board_data *board,
+		enum cgeb_function fct, unsigned long unit,
+		unsigned long *optr, unsigned long *result)
+{
+	struct cgeb_function_parameters p;
+	unsigned int ret;
+
+	memset(&p, 0, sizeof(p));
+	p.unit = unit;
+	p.optr = optr;
+
+	ret = cgeb_call(board, &p, fct);
+	if (optr)
+		*optr = (unsigned long)p.optr;
+	if (result)
+		*result = p.rets[0];
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(cgeb_call_simple);
+
+static void *cgeb_find_magic(void *_mem, size_t len, char *magic)
+{
+	unsigned long magic0 = ((unsigned long *) magic)[0];
+	unsigned long magic1 = ((unsigned long *) magic)[1];
+	int i = 0;
+
+	while (i < len) {
+		u32 *mem = _mem + i;
+		if (mem[0] == magic0 && mem[1] == magic1)
+			return mem;
+		i += 16;
+	}
+
+	return NULL;
+}
+
+static void cgeb_unmap_memory(struct cgeb_board_data *board)
+{
+	struct cgeb_map_mem_list *pmm;
+	struct cgeb_map_mem *pmme;
+	unsigned long i;
+
+	if (!board->map_mem)
+		return;
+
+	pmm = board->map_mem;
+	pmme = pmm->entries;
+	for (i = 0; i < pmm->count; i++, pmme++) {
+		if (pmme->virt.off)
+			iounmap((void *)pmme->virt.off);
+		pmme->virt.off = 0;
+		pmme->virt.seg = 0;
+	}
+}
+
+static int cgeb_map_memory(struct cgeb_board_data *board)
+{
+	struct cgeb_map_mem_list *pmm;
+	struct cgeb_map_mem *pmme;
+	int i;
+	int ret;
+
+	ret = cgeb_call_simple(board, CgebMapGetMem, 0, (void *)&board->map_mem,
+			NULL);
+	if (ret)
+		return ret;
+	if (!board->map_mem)
+		return 0;
+
+	pmm = board->map_mem;
+	pmme = pmm->entries;
+
+	pr_debug("CGEB: Memory Map with %lu entries\n", pmm->count);
+
+	for (i = 0; i < pmm->count; i++, pmme++) {
+		if (pmme->phys && pmme->size) {
+			pmme->virt.off =
+			    (unsigned long) ioremap_cache(pmme->phys,
+							  pmme->size);
+			if (!pmme->virt.off)
+				return -ENOMEM;
+		} else {
+			pmme->virt.off = 0;
+		}
+
+		pmme->virt.seg = (pmme->virt.off) ? board->ds : 0;
+
+		pr_debug("CGEB:   Map phys %08lx, size %08lx, virt %04x:%08x\n",
+		      pmme->phys, pmme->size, pmme->virt.seg,
+		      pmme->virt.off);
+	}
+
+	return cgeb_call_simple(board, CgebMapChanged, 0, NULL, NULL);
+}
+
+static struct cgeb_board_data *cgeb_open(unsigned long base, unsigned long len)
+{
+	unsigned long dw;
+	struct cgeb_boardinfo *pbi;
+	struct cgeb_low_desc *low_desc;
+	struct cgeb_high_desc *high_desc = NULL;
+	unsigned long high_desc_phys;
+	unsigned long high_desc_len;
+	void __iomem *pcur, *high_desc_virt;
+	int ret;
+
+	struct cgeb_board_data *board;
+
+	board = kzalloc(sizeof(*board), GFP_KERNEL);
+	if (!board)
+		return NULL;
+
+	pcur = ioremap_cache(base, len);
+	if (!pcur)
+		goto err_kfree;
+
+	/* look for the CGEB descriptor */
+	low_desc = cgeb_find_magic(pcur, len, CGEB_LD_MAGIC);
+	if (!low_desc)
+		goto err_kfree;
+
+	pr_debug("CGEB: Found CGEB_LD_MAGIC\n");
+
+	if (low_desc->size < sizeof(struct cgeb_low_desc) - sizeof(long))
+		goto err_kfree;
+
+	if (low_desc->size >= sizeof(struct cgeb_low_desc)
+			&& low_desc->hi_desc_phys_addr)
+		high_desc_phys = low_desc->hi_desc_phys_addr;
+	else
+		high_desc_phys = 0xfff00000;
+
+	high_desc_len = (unsigned long) -(long) high_desc_phys;
+
+	pr_debug("CGEB: Looking for CGEB hi desc between phys 0x%lx and 0x%x\n",
+	      high_desc_phys, -1);
+
+	high_desc_virt = ioremap_cache(high_desc_phys, high_desc_len);
+	if (!high_desc_virt)
+		goto err_kfree;
+
+	pr_debug("CGEB: Looking for CGEB hi desc between virt 0x%p and 0x%p\n",
+	      high_desc_virt, high_desc_virt + high_desc_len - 1);
+
+	high_desc = cgeb_find_magic(high_desc_virt, high_desc_len,
+					 CGEB_HD_MAGIC);
+	if (!high_desc)
+		goto err_iounmap;
+
+	pr_debug("CGEB: Found CGEB_HD_MAGIC\n");
+
+	if (high_desc->size < sizeof(struct cgeb_high_desc))
+		goto err_iounmap;
+
+	pr_debug("CGEB: data_size %u, code_size %u, entry_rel %u\n",
+	      high_desc->data_size, high_desc->code_size, high_desc->entry_rel);
+
+	board->code = __vmalloc(high_desc->code_size, GFP_KERNEL,
+			PAGE_KERNEL_EXEC);
+	if (!board->code)
+		goto err_iounmap;
+
+	memcpy(board->code, high_desc, high_desc->code_size);
+
+	high_desc = board->code;
+
+	board->entry = board->code + high_desc->entry_rel;
+
+	board->ds = get_data_segment();
+
+	ret = cgeb_call_simple(board, CgebGetCgebVersion, 0, NULL, &dw);
+	if (ret)
+		goto err_vfree;
+
+	if (CGEB_GET_VERSION_MAJOR(dw) != CGEB_VERSION_MAJOR)
+		goto err_vfree;
+
+	pr_debug("CGEB: BIOS interface revision: %ld.%ld\n",
+			dw >> 16, dw & 0xffff);
+
+	if (high_desc->data_size) {
+		board->data = vmalloc(high_desc->data_size);
+		if (!board->data)
+			goto err_vfree;
+	} else {
+		ret = cgeb_call_simple(board, CgebGetDataSize, 0, NULL, &dw);
+		if (!ret && dw) {
+			board->data = vmalloc(dw);
+			if (!board->data)
+				goto err_vfree;
+		}
+	}
+
+	ret = cgeb_call_simple(board, CgebOpen, 0, NULL, NULL);
+	if (ret)
+		goto err_vfree_data;
+
+	ret = cgeb_map_memory(board);
+	if (ret)
+		goto err_free_map;
+
+	ret = cgeb_call_simple(board, CgebBoardGetInfo, 0, &dw, NULL);
+	if (ret)
+		goto err_free_map;
+
+	pbi = (struct cgeb_boardinfo *) dw;
+
+	pr_info("CGEB: Board name: %c%c%c%c\n",
+			pbi->board[0], pbi->board[1],
+			pbi->board[2], pbi->board[3]);
+
+	iounmap(high_desc_virt);
+
+	return board;
+
+err_free_map:
+	cgeb_unmap_memory(board);
+err_vfree_data:
+	vfree(board->data);
+err_vfree:
+	vfree(board->code);
+err_iounmap:
+	iounmap(high_desc_virt);
+err_kfree:
+	kfree(board);
+	return NULL;
+}
+
+static void cgeb_close(struct cgeb_board_data *board)
+{
+	cgeb_call_simple(board, CgebClose, 0, NULL, NULL);
+
+	cgeb_unmap_memory(board);
+
+	vfree(board->data);
+	vfree(board->code);
+}
+
+static unsigned long cgeb_i2c_get_type(struct cgeb_board_data *brd, int unit)
+{
+	struct cgeb_i2c_info *info;
+	int ret;
+
+	ret = cgeb_call_simple(brd, CgebI2CGetInfo, unit, (void *) &info, NULL);
+	if (ret)
+		return ret;
+	if (!info)
+		return -EINVAL;
+	return info->type;
+}
+
+static struct cgeb_board_data *cgeb_board;
+
+static int __init cgeb_init(void)
+{
+	struct cgeb_board_data *board;
+	unsigned long base;
+	int i, ret;
+	struct cgeb_pdata pdata;
+	unsigned long i2c_count, watchdog_count;
+	int num_devices = 0;
+
+	for (base = 0xf0000; base >= 0xc0000; base -= 0x10000) {
+		board = cgeb_open(base, 0x10000);
+		if (board)
+			break;
+	}
+
+	if (!board)
+		return -ENODEV;
+
+	cgeb_board = board;
+
+	pdata.board = board;
+
+	cgeb_call_simple(board, CgebI2CCount, 0, NULL, &i2c_count);
+	cgeb_call_simple(board, CgebWDogCount, 0, NULL, &watchdog_count);
+
+	board->num_devices = i2c_count + watchdog_count;
+	board->devices = kzalloc(sizeof(void *) * (board->num_devices),
+			GFP_KERNEL);
+	if (!board->devices) {
+		ret = -ENOMEM;
+		goto err_out;
+	}
+
+	for (i = 0; i < i2c_count; i++) {
+		ret = cgeb_i2c_get_type(board, i);
+		if (ret != CGEB_I2C_TYPE_PRIMARY)
+			continue;
+
+		pdata.unit = i;
+
+		board->devices[num_devices] =
+			platform_device_register_data(NULL, "cgeb-i2c", i,
+					&pdata, sizeof(pdata));
+		num_devices++;
+	}
+
+	for (i = 0; i < watchdog_count; i++) {
+		board->devices[num_devices] =
+			platform_device_register_data(NULL, "cgeb-watchdog", i,
+					&pdata, sizeof(pdata));
+		pdata.unit = i;
+
+		num_devices++;
+	}
+
+	return 0;
+err_out:
+	cgeb_close(board);
+	kfree(board);
+
+	return ret;
+}
+
+static void cgeb_exit(void)
+{
+	struct cgeb_board_data *board = cgeb_board;
+	int i;
+
+	for (i = 0; i < board->num_devices; i++)
+		if (board->devices[i])
+			platform_device_unregister(board->devices[i]);
+
+	cgeb_close(board);
+
+	kfree(board->devices);
+	kfree(board);
+}
+
+module_init(cgeb_init);
+module_exit(cgeb_exit);
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("Congatec CGEB driver");
+MODULE_LICENSE("GPL");