Re: [PATCH 1/3] mfd: Add basic support for the Congatec CGEB BIOS interface

From: Christian Gmeiner
Date: Mon Feb 06 2012 - 14:07:36 EST


> 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@xxxxxxxxxxxxxx>
> ---
> Âdrivers/mfd/Kconfig        |  10 +
> Âdrivers/mfd/Makefile       Â|  Â1 +
> Âdrivers/mfd/congatec-cgeb.c    | Â590 +++++++++++++++++++++++++++++++++++++
> Âinclude/linux/mfd/congatec-cgeb.h | Â105 +++++++
> Â4 files changed, 706 insertions(+), 0 deletions(-)
> Âcreate mode 100644 drivers/mfd/congatec-cgeb.c
> Âcreate mode 100644 include/linux/mfd/congatec-cgeb.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index f1391c2..873d04f 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -772,6 +772,16 @@ config MFD_INTEL_MSIC
> Â Â Â Â ÂPassage) chip. This chip embeds audio, battery, GPIO, etc.
> Â Â Â Â Âdevices used in Intel Medfield platforms.
>
> +config MFD_CONGATEC_CGEB
> + Â Â Â tristate "Support for the Congatec CGEB BIOS interface"
> + Â Â Â depends on X86_32
> + Â Â Â 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.
> +
> Âendmenu
> Âendif
>
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index b2292eb..cee77f7 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -104,3 +104,4 @@ obj-$(CONFIG_MFD_PM8XXX_IRQ) Â Â Â Â+= pm8xxx-irq.o
> Âobj-$(CONFIG_TPS65911_COMPARATOR) Â Â Â+= tps65911-comparator.o
> Âobj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o
> Âobj-$(CONFIG_MFD_INTEL_MSIC) Â += intel_msic.o
> +obj-$(CONFIG_MFD_CONGATEC_CGEB) Â Â Â Â+= congatec-cgeb.o
> diff --git a/drivers/mfd/congatec-cgeb.c b/drivers/mfd/congatec-cgeb.c
> new file mode 100644
> index 0000000..445a9c5
> --- /dev/null
> +++ b/drivers/mfd/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 <linux/mfd/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, cgeb_function_t 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.
> + */

description of cgeb_call_simple() is wrong regarding function
arguments.

> +int cgeb_call_simple(struct cgeb_board_data *board,
> + Â Â Â Â Â Â Â cgeb_function_t 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@xxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("CGEB driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/mfd/congatec-cgeb.h b/include/linux/mfd/congatec-cgeb.h
> new file mode 100644
> index 0000000..74069fc
> --- /dev/null
> +++ b/include/linux/mfd/congatec-cgeb.h
> @@ -0,0 +1,105 @@
> +#ifndef __CONGATEC_CGEB_H
> +#define __CONGATEC_CGEB_H
> +
> +/* CGEB interface functions */
> +typedef enum {
> + Â Â Â 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,
> +} cgeb_function_t;
> +
> +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 *, cgeb_function_t);
> +
> +int cgeb_call_simple(struct cgeb_board_data *,
> + Â Â Â Â Â Â Â cgeb_function_t, 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 */
> --
> 1.7.2.5

I have found only one small issue - see below - and did test this
driver with an Atom and E6xx based Congatec CPU module.

Tested-by: Christian Gmeiner <christian.gmeiner@xxxxxxxxx>
¢éì®&Þ~º&¶¬–+-±éÝ¥Šw®žË±Êâmébžìdz¹Þ)í…æèw*jg¬±¨¶‰šŽŠÝj/êäz¹ÞŠà2ŠÞ¨è­Ú&¢)ß«a¶Úþø®G«éh®æj:+v‰¨Šwè†Ù>Wš±êÞiÛaxPjØm¶Ÿÿà -»+ƒùdš_