From patchwork Thu Jul 18 01:15:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mary Strodl X-Patchwork-Id: 813280 Received: from greygoose-centos7.csh.rit.edu (greygoose-centos7.csh.rit.edu [129.21.49.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C1D2F10FA; Thu, 18 Jul 2024 01:25:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=129.21.49.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1721265923; cv=none; b=GMcS9IuCQnenzjkRZcVB9xv13nTNoKpIWbcXY9A+rwBlAoMONNwquLglz2XmoS+jNJeFQdGKzrMBIQD5YOCixloUtADfwsYbGhfty7YvH0AEgGJaJmy77cDxZZvhR2p+Nbjr/K2WKmfuuO29oGuOBwitTUjjX9NH4dBt2yTIg4w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1721265923; c=relaxed/simple; bh=ToMXT8Hf3bXOS4rb9tjuA+dcjZqWXki9D94oOuilGS4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WI/5qpGqacipkXXXbLZ7adVOH2KfMf0K5YESguVGY6at9I36LgvNqovM7RWc0PAF764I1awoZsq4SCbNcUyN65P62pHXa1+KNnqyFwoC3t5Y8G0bhQaIjOfVVxK/ODvs/vj+xOm+lBoSL/2pM9lmrBaPYKwvO81c1wshGSqNLTs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=csh.rit.edu; spf=pass smtp.mailfrom=csh.rit.edu; dkim=pass (1024-bit key) header.d=csh.rit.edu header.i=@csh.rit.edu header.b=zNgSSi6i; arc=none smtp.client-ip=129.21.49.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=csh.rit.edu Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=csh.rit.edu Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=csh.rit.edu header.i=@csh.rit.edu header.b="zNgSSi6i" Received: from localhost (localhost [127.0.0.1]) by greygoose-centos7.csh.rit.edu (Postfix) with ESMTP id 87D2440D7C89; Wed, 17 Jul 2024 21:16:17 -0400 (EDT) Authentication-Results: mail.csh.rit.edu (amavisd-new); dkim=pass (1024-bit key) reason="pass (just generated, assumed good)" header.d=csh.rit.edu DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=csh.rit.edu; h= content-transfer-encoding:mime-version:references:in-reply-to :x-mailer:message-id:date:date:subject:subject:from:from :received:received; s=mail; t=1721265377; x=1723079778; bh=ToMXT 8Hf3bXOS4rb9tjuA+dcjZqWXki9D94oOuilGS4=; b=zNgSSi6ixLIxk6IlkB8Ca 6YgpHI2jdm5mdByqq6Tu9zPYU/SWXVRn04iJ2E9I1SQdlqk7nnKNlu9rbxAklfOX LPNFjaopGbBfoFQ7sN8DwdsijOkZ69vVgsW9w1ZlEDu5lXQ6KjKofjE7n5LCrv21 ZC4YoTX1covdyhsnCMlqlY= X-Virus-Scanned: amavisd-new at csh.rit.edu Received: from greygoose-centos7.csh.rit.edu ([127.0.0.1]) by localhost (mail.csh.rit.edu [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id zvkSNSZoemUH; Wed, 17 Jul 2024 21:16:17 -0400 (EDT) Received: from freedom.csh.rit.edu (freedom.csh.rit.edu [129.21.49.182]) by greygoose-centos7.csh.rit.edu (Postfix) with ESMTP id 2F12840D7C81; Wed, 17 Jul 2024 21:16:17 -0400 (EDT) From: Mary Strodl To: linux-kernel@vger.kernel.org Cc: akpm@linux-foundation.org, urezki@gmail.com, hch@infradead.org, linux-mm@kvack.org, lee@kernel.org, andi.shyti@kernel.org, linux-i2c@vger.kernel.org, s.hauer@pengutronix.de, christian.gmeiner@gmail.com, Mary Strodl Subject: [PATCH 1/3] mm: vmalloc: export __vmalloc_node_range Date: Wed, 17 Jul 2024 21:15:02 -0400 Message-ID: <20240718011504.4106163-2-mstrodl@csh.rit.edu> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20240718011504.4106163-1-mstrodl@csh.rit.edu> References: <20240718011504.4106163-1-mstrodl@csh.rit.edu> Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 After the ability to allocate PAGE_KERNEL_EXEC memory was removed from __vmalloc, this seems like the least invasive way to expose the capability to drivers that need it. Exports __vmalloc_node_range so that drivers can use it. Signed-off-by: Mary Strodl Nacked-by: Christoph Hellwig --- mm/vmalloc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/vmalloc.c b/mm/vmalloc.c index e34ea860153f..037b7e0fe430 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -3879,6 +3879,7 @@ void *__vmalloc_node_range_noprof(unsigned long size, unsigned long align, return NULL; } +EXPORT_SYMBOL(__vmalloc_node_range_noprof); /** * __vmalloc_node - allocate virtually contiguous memory From patchwork Thu Jul 18 01:15:03 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mary Strodl X-Patchwork-Id: 813279 Received: from greygoose-centos7.csh.rit.edu (greygoose-centos7.csh.rit.edu [129.21.49.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B33B5394; Thu, 18 Jul 2024 01:25:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=129.21.49.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1721265924; cv=none; b=nqX8H7tewSQrQu9oZZko/P4X3am1G+v7m1LoJiyhsTCp+VeTF3yS49/CM8ZhlD3HehmaIrjokhEINM8qjsBgg6mnDyn4JnVoo3dvSuovBTIRQTNotDt0ILCEFQx2eeG8lBOSVsIHWP1KRoVHKbmTgiG38wTKCmeiIP+l2FW0nag= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1721265924; c=relaxed/simple; bh=eKiKIc7r4fIMSIrignuV6KeMy7ZtUyOD6QPxWWr2G3c=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=tnnKl9surCAI1YKRyHPqHWaIu9frOVaPop7eSbLsN0v5kEuzVkcFNzuJ6IdS3SSxEhbw+paZm8ap2hlGII5lcu1N9IdnKJ71IT39jMdfO4ScD/tlX4iagUKplMS6uGEGZEkRnq2S5kyrn7wXDs/VjuBxrBP3gOXU3k81BHSUz6U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=csh.rit.edu; spf=pass smtp.mailfrom=csh.rit.edu; dkim=pass (1024-bit key) header.d=csh.rit.edu header.i=@csh.rit.edu header.b=DTUhhYHx; arc=none smtp.client-ip=129.21.49.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=csh.rit.edu Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=csh.rit.edu Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=csh.rit.edu header.i=@csh.rit.edu header.b="DTUhhYHx" Received: from localhost (localhost [127.0.0.1]) by greygoose-centos7.csh.rit.edu (Postfix) with ESMTP id 3C36B456D905; Wed, 17 Jul 2024 21:16:20 -0400 (EDT) Authentication-Results: mail.csh.rit.edu (amavisd-new); dkim=pass (1024-bit key) reason="pass (just generated, assumed good)" header.d=csh.rit.edu DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=csh.rit.edu; h= content-transfer-encoding:mime-version:references:in-reply-to :x-mailer:message-id:date:date:subject:subject:from:from :received:received; s=mail; t=1721265379; x=1723079780; bh=eKiKI c7r4fIMSIrignuV6KeMy7ZtUyOD6QPxWWr2G3c=; b=DTUhhYHx6zQU1nx1Aj67r N4cSJJ82DBuBHRMI6r5TjHh/APHvF4x1Sok0+1OcO2KyInzSA1fNomXx7XhUtzpX JqgwdibxvHGXaAcfm/KgYPS3DU/EwhKSwK6QrjUNLlthBe+d/OcfWzzhFDozHJkB Oy1OW3TkEUb6xT9WpnSUQU= X-Virus-Scanned: amavisd-new at csh.rit.edu Received: from greygoose-centos7.csh.rit.edu ([127.0.0.1]) by localhost (mail.csh.rit.edu [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id eyE6XJisN_wM; Wed, 17 Jul 2024 21:16:19 -0400 (EDT) Received: from freedom.csh.rit.edu (freedom.csh.rit.edu [129.21.49.182]) by greygoose-centos7.csh.rit.edu (Postfix) with ESMTP id F0FC6456E197; Wed, 17 Jul 2024 21:16:18 -0400 (EDT) From: Mary Strodl To: linux-kernel@vger.kernel.org Cc: akpm@linux-foundation.org, urezki@gmail.com, hch@infradead.org, linux-mm@kvack.org, lee@kernel.org, andi.shyti@kernel.org, linux-i2c@vger.kernel.org, s.hauer@pengutronix.de, christian.gmeiner@gmail.com, Mary Strodl Subject: [PATCH 2/3] x86: Add basic support for the Congatec CGEB BIOS interface Date: Wed, 17 Jul 2024 21:15:03 -0400 Message-ID: <20240718011504.4106163-3-mstrodl@csh.rit.edu> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20240718011504.4106163-1-mstrodl@csh.rit.edu> References: <20240718011504.4106163-1-mstrodl@csh.rit.edu> Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Sascha Hauer 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 Signed-off-by: Christian Gmeiner Signed-off-by: Mary Strodl --- drivers/mfd/Kconfig | 10 + drivers/mfd/Makefile | 1 + drivers/mfd/congatec-cgeb.c | 620 ++++++++++++++++++++++++++++++ include/linux/mfd/congatec-cgeb.h | 105 +++++ 4 files changed, 736 insertions(+) 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 266b4f54af60..fa06a9dc34f9 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1206,6 +1206,16 @@ config MFD_RT5120 is targeted at providing the CPU voltage, memory, I/O and peripheral power rails in home entertainment devices. +config MFD_CONGATEC_CGEB + tristate "Support for the Congatec CGEB BIOS interface" + depends on X86 + 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. + config MFD_RC5T583 bool "Ricoh RC5T583 Power Management system device" depends on I2C=y diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index c66f07edcd0e..38f31841ac88 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -217,6 +217,7 @@ obj-$(CONFIG_MFD_INTEL_LPSS) += intel-lpss.o obj-$(CONFIG_MFD_INTEL_LPSS_PCI) += intel-lpss-pci.o obj-$(CONFIG_MFD_INTEL_LPSS_ACPI) += intel-lpss-acpi.o obj-$(CONFIG_MFD_INTEL_PMC_BXT) += intel_pmc_bxt.o +obj-$(CONFIG_MFD_CONGATEC_CGEB) += congatec-cgeb.o obj-$(CONFIG_MFD_PALMAS) += palmas.o obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o obj-$(CONFIG_MFD_NTXEC) += ntxec.o diff --git a/drivers/mfd/congatec-cgeb.c b/drivers/mfd/congatec-cgeb.c new file mode 100644 index 000000000000..2bae1f42c357 --- /dev/null +++ b/drivers/mfd/congatec-cgeb.c @@ -0,0 +1,620 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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$" + +#pragma pack(push,4) + +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 */ +#ifdef CONFIG_X86_64 +#define CGEB_HD_MAGIC "$CGEBQD$" +#else +#define CGEB_HD_MAGIC "$CGEBHD$" +#endif + +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 { + void* 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 */ + void* cont; /* private continuation pointer */ + void* subfps; /* private sub function parameter + * structure pointer + */ + void* 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 { + void* phys; /* physical address */ + u32 size; /* size in bytes */ + struct cgeb_far_ptr virt; +}; + +struct cgeb_map_mem_list { + u32 count; /* number of memory map entries */ + struct cgeb_map_mem entries[]; +}; + +struct cgeb_boardinfo { + u32 size; + u32 flags; + u32 classes; + u32 primary_class; + char board[CGOS_BOARD_MAX_SIZE_ID_STRING]; + /* optional */ + char vendor[CGOS_BOARD_MAX_SIZE_ID_STRING]; +}; + +struct cgeb_i2c_info { + u32 size; + u32 type; + u32 frequency; + u32 maxFrequency; +}; + +#pragma pack(pop) + +/* 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; + u16 ds; + struct cgeb_map_mem_list *map_mem; + struct platform_device **devices; + int num_devices; + + #ifdef CONFIG_X86_64 + void (*entry)(void*, struct cgeb_fps *, struct cgeb_fps *, void*); + #else + /* + * 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); + #endif +}; + +static unsigned short get_data_segment(void) +{ + unsigned short ret; + +#ifdef CONFIG_X86_64 + ret = 0; +#else + asm volatile("mov %%ds, %0\n" + : "=r"(ret) + : + : "memory" + ); +#endif + + return ret; +} + +/* + * cgeb_call - 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 = 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:%p, status %08x\n", + fps.size, fps.fct, fps.data.seg, fps.data.off, + fps.status); + +#ifdef CONFIG_X86_64 + board->entry(NULL, &fps, &fps, NULL); +#else + board->entry(0, &fps, fps.data.seg); +#endif + + pr_debug("CGEB: CGEB: <- size %02x, fct %02x, data %04x:%p, status %08x\n", + fps.size, fps.fct, fps.data.seg, fps.data.off, + fps.status); + + switch (fps.status) { + case CGEB_SUCCESS: + goto out; + case CGEB_NEXT: + break; /* simply call again */ + case CGEB_NOIRQS: + __set_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, + cgeb_function_t fct, u32 unit, + void **optr, u32 *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 = 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) +{ + u32 magic0 = ((u32 *)magic)[0]; + u32 magic1 = ((u32 *)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 %d entries\n", pmm->count); + + for (i = 0; i < pmm->count; i++, pmme++) { + pr_debug("CGEB: Memory map entry phys=%px, size=%08x\n", + pmme->phys, pmme->size); + if (pmme->phys && pmme->size) { + /* We only want to look at the lower 32 bits */ + pmme->virt.off = + ioremap_cache((u32)(resource_size_t)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 %p, size %08x, virt %04x:%p\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(resource_size_t base, u32 len) +{ + u32 dw; + struct cgeb_boardinfo *pbi; + struct cgeb_low_desc *low_desc; + struct cgeb_high_desc *high_desc = NULL; + u32 high_desc_phys; + u32 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 int) -(int)high_desc_phys; + + pr_debug("CGEB: Looking for CGEB hi desc between phys 0x%x 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_node_range(high_desc->code_size, 1, + VMALLOC_START, VMALLOC_END, + GFP_KERNEL, PAGE_KERNEL_EXEC, + (VM_WRITE | VM_EXEC), NUMA_NO_NODE, + __builtin_return_address(0)); + if (!board->code) + goto err_iounmap; + + memcpy(board->code, high_desc, high_desc->code_size); + + high_desc = board->code; + + board->entry = (void*)((char*)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: %d.%d\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, (void*)&pbi, NULL); + if (ret) + goto err_free_map; + + 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; + resource_size_t base; + int i, ret; + struct cgeb_pdata pdata; + u32 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 __exit 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 "); +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 000000000000..26f5452286af --- /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, u32, + void **, u32 *); + +/* + * Platform data for child devices + */ +struct cgeb_pdata { + struct cgeb_board_data *board; + int unit; +}; + +#endif /* __CONGATEC_CGEB_H */ From patchwork Thu Jul 18 01:15:04 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mary Strodl X-Patchwork-Id: 813485 Received: from greygoose-centos7.csh.rit.edu (greygoose-centos7.csh.rit.edu [129.21.49.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D0CEA110A; Thu, 18 Jul 2024 01:25:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=129.21.49.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1721265923; cv=none; b=KVCtlkICD4Y3EAb+k6nIZ7FfUZzuhwxHkExifAiNErUg7BXyi+2/EJ+Pkc/jP3iD3g7BD1cZb/4J10mHU3XIAAdZJyTkcby9r6k56aj/l4zRagYVQvKYz4EelSnJLlCbjeud8aPm1AboSXjw3Bd4JFd8lshIt3JXNf4gtzNCXck= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1721265923; c=relaxed/simple; bh=Iw5zX93m3E1bz0YWEvwpblm2zVBgrHtJbGtokDUqxh4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Dvy7FpYGn340os6fISbynqH8E/CUlhV48ID3y3jd8Ayi7W9oLO2sSeXsVAxEBsihfaSyUpaG+8fORBueBV8wCu+lBjdcxM9yXwxkATmtRP7Q/b3Jx/cytm8CSJq0BqYgeWhIDMTDYNEUEDARppdvH7D3i8d77iewB5bmC7g/6hA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=csh.rit.edu; spf=pass smtp.mailfrom=csh.rit.edu; dkim=pass (1024-bit key) header.d=csh.rit.edu header.i=@csh.rit.edu header.b=CpR5+1zi; arc=none smtp.client-ip=129.21.49.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=csh.rit.edu Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=csh.rit.edu Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=csh.rit.edu header.i=@csh.rit.edu header.b="CpR5+1zi" Received: from localhost (localhost [127.0.0.1]) by greygoose-centos7.csh.rit.edu (Postfix) with ESMTP id 8C948456D414; Wed, 17 Jul 2024 21:16:21 -0400 (EDT) Authentication-Results: mail.csh.rit.edu (amavisd-new); dkim=pass (1024-bit key) reason="pass (just generated, assumed good)" header.d=csh.rit.edu DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=csh.rit.edu; h= content-transfer-encoding:mime-version:references:in-reply-to :x-mailer:message-id:date:date:subject:subject:from:from :received:received; s=mail; t=1721265380; x=1723079781; bh=Iw5zX 93m3E1bz0YWEvwpblm2zVBgrHtJbGtokDUqxh4=; b=CpR5+1ziyEXREg8T3e/xV 6e7nLA34GlEQNt6sZ8n0pYqD5PkbD6mwldmGstauY1SVlVfgmw1n9loTg48Ythqt mFPtQqzbENrlj4GVntEMXe+ZMQMC5qkMO3yGSCKzQNEcXh2iazxy+bTjDA0zGVMz 48xytj4ueYZqdcfcsdncJg= X-Virus-Scanned: amavisd-new at csh.rit.edu Received: from greygoose-centos7.csh.rit.edu ([127.0.0.1]) by localhost (mail.csh.rit.edu [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id 5_gEh8dL0wtJ; Wed, 17 Jul 2024 21:16:20 -0400 (EDT) Received: from freedom.csh.rit.edu (freedom.csh.rit.edu [129.21.49.182]) by greygoose-centos7.csh.rit.edu (Postfix) with ESMTP id DC35B456D91E; Wed, 17 Jul 2024 21:16:20 -0400 (EDT) From: Mary Strodl To: linux-kernel@vger.kernel.org Cc: akpm@linux-foundation.org, urezki@gmail.com, hch@infradead.org, linux-mm@kvack.org, lee@kernel.org, andi.shyti@kernel.org, linux-i2c@vger.kernel.org, s.hauer@pengutronix.de, christian.gmeiner@gmail.com, Mary Strodl Subject: [PATCH 3/3] i2c: Add Congatec CGEB I2C driver Date: Wed, 17 Jul 2024 21:15:04 -0400 Message-ID: <20240718011504.4106163-4-mstrodl@csh.rit.edu> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20240718011504.4106163-1-mstrodl@csh.rit.edu> References: <20240718011504.4106163-1-mstrodl@csh.rit.edu> Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Sascha Hauer This driver provides a I2C bus driver for the CGEB interface found on some Congatec x86 modules. No devices are registered on the bus, the user has to do this via the i2c device /sys interface. Signed-off-by: Sascha Hauer Signed-off-by: Christian Gmeiner Signed-off-by: Mary Strodl --- drivers/i2c/busses/Kconfig | 7 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-congatec-cgeb.c | 187 +++++++++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 drivers/i2c/busses/i2c-congatec-cgeb.c diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index fe6e8a1bb607..504a0be54f04 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -1261,6 +1261,13 @@ config I2C_RCAR This driver can also be built as a module. If so, the module will be called i2c-rcar. +config I2C_CONGATEC_CGEB + tristate "Congatec CGEB I2C driver" + depends on MFD_CONGATEC_CGEB + help + This driver provides support for the I2C busses accssable via + the Congatec CGEB interface found on Congatec boards. + comment "External I2C/SMBus adapter drivers" config I2C_DIOLAN_U2C diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 78d0561339e5..f4e9fa7542be 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -128,6 +128,7 @@ obj-$(CONFIG_I2C_XILINX) += i2c-xiic.o obj-$(CONFIG_I2C_XLP9XX) += i2c-xlp9xx.o obj-$(CONFIG_I2C_RCAR) += i2c-rcar.o obj-$(CONFIG_I2C_GXP) += i2c-gxp.o +obj-$(CONFIG_I2C_CONGATEC_CGEB) += i2c-congatec-cgeb.o # External I2C/SMBus adapter drivers obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o diff --git a/drivers/i2c/busses/i2c-congatec-cgeb.c b/drivers/i2c/busses/i2c-congatec-cgeb.c new file mode 100644 index 000000000000..decaef30ca0a --- /dev/null +++ b/drivers/i2c/busses/i2c-congatec-cgeb.c @@ -0,0 +1,187 @@ +/* + * CGEB i2c driver + * + * (c) 2011 Sascha Hauer, Pengutronix + * + * 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 +#include +#include +#include +#include + +#define CG_I2C_FLAG_START 0x00080 /* send START condition */ +#define CG_I2C_FLAG_STOP 0x00040 /* send STOP condition */ +#define CG_I2C_FLAG_ALL_ACK 0x08000 /* send ACK on all read bytes */ +#define CG_I2C_FLAG_ALL_NAK 0x04000 /* send NAK on all read bytes */ + +struct cgeb_i2c_priv { + struct cgeb_board_data *board; + struct i2c_adapter adapter; + int unit; +}; + +static u32 cgeb_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static int cgeb_i2c_set_speed(struct cgeb_i2c_priv *priv, int speed) +{ + struct cgeb_function_parameters fps; + + memset(&fps, 0, sizeof(fps)); + + fps.unit = priv->unit; + fps.pars[0] = speed; + + return cgeb_call(priv->board, &fps, CgebI2CSetFrequency); +} + +static int cgeb_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct cgeb_function_parameters fps; + int i, ret; + unsigned long flags = CG_I2C_FLAG_START; + struct cgeb_i2c_priv *priv = i2c_get_adapdata(adapter); + unsigned long rdlen, wrlen; + unsigned char *rdbuf, *wrbuf, *raw_wrbuf; + unsigned short lmax = 0; + + /* + * With cgeb the I2C address is part of the write data + * buffer, so allocate a buffer with the length of the + * longest write buffer + 1 + */ + for (i = 0; i < num; i++) + if (!(msgs[i].flags & I2C_M_RD)) + lmax = max(lmax, msgs[i].len); + + raw_wrbuf = kmalloc(lmax + 1, GFP_KERNEL); + if (!raw_wrbuf) + return -ENOMEM; + + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) { + rdbuf = msgs[i].buf; + rdlen = msgs[i].len; + wrbuf = NULL; + wrlen = 0; + } else { + rdbuf = NULL; + rdlen = 0; + wrbuf = msgs[i].buf; + wrlen = msgs[i].len; + } + + raw_wrbuf[0] = msgs[i].addr << 1; + if (wrlen) + memcpy(&raw_wrbuf[1], wrbuf, wrlen); + + if (msgs[i].flags & I2C_M_RD) + raw_wrbuf[0] |= 1; + + if (i == num - 1) + flags |= CG_I2C_FLAG_STOP; + + dev_dbg(&adapter->dev, + "%s: rd: %p/%ld wr: %p/%ld flags: 0x%08lx %s\n", + __func__, rdbuf, rdlen, raw_wrbuf, wrlen + 1, + flags, + msgs[i].flags & I2C_M_RD ? "READ" : "WRITE"); + + memset(&fps, 0, sizeof(fps)); + + fps.unit = priv->unit; + fps.pars[0] = wrlen + 1; + fps.pars[1] = rdlen; + fps.pars[2] = flags; + fps.iptr = raw_wrbuf; + fps.optr = rdbuf; + + ret = cgeb_call(priv->board, &fps, CgebI2CTransfer); + if (ret) { + ret = -EREMOTEIO; + goto out; + } + } + + ret = num; + +out: + kfree(raw_wrbuf); + + return ret; +} + +static struct i2c_algorithm cgeb_i2c_algo = { + .master_xfer = cgeb_i2c_xfer, + .functionality = cgeb_i2c_func, +}; + +static int cgeb_i2c_probe(struct platform_device *pdev) +{ + struct cgeb_i2c_priv *priv; + struct cgeb_pdata *pdata = pdev->dev.platform_data; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + strcpy(priv->adapter.name, pdev->name); + priv->adapter.owner = THIS_MODULE; + priv->adapter.algo = &cgeb_i2c_algo; + priv->adapter.dev.parent = &pdev->dev; + priv->unit = pdata->unit; + priv->board = pdata->board; + i2c_set_adapdata(&priv->adapter, priv); + + platform_set_drvdata(pdev, priv); + + ret = cgeb_i2c_set_speed(priv, 400000); + if (ret) + /* not a critical error, we can continue with the default speed. */ + dev_warn(&pdev->dev, "Could not set speed to 400KHz\n"); + + ret = i2c_add_adapter(&priv->adapter); + if (ret < 0) { + dev_err(&pdev->dev, "registration failed\n"); + return ret; + } + + dev_info(&pdev->dev, "registered\n"); + + return 0; +}; + +static int cgeb_i2c_remove(struct platform_device *pdev) +{ + struct cgeb_i2c_priv *priv = platform_get_drvdata(pdev); + + i2c_del_adapter(&priv->adapter); + + return 0; +} + +static struct platform_driver cgeb_i2c_driver = { + .probe = cgeb_i2c_probe, + .remove = cgeb_i2c_remove, + .driver = { + .name = "cgeb-i2c", + }, +}; +module_platform_driver(cgeb_i2c_driver); + +MODULE_AUTHOR("Sascha Hauer "); +MODULE_DESCRIPTION("cgeb i2c driver"); +MODULE_LICENSE("GPL");