From patchwork Fri Jan 8 22:29:24 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: richard.dorsch@gmail.com X-Patchwork-Id: 565088 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42A7B140B92 for ; Sat, 9 Jan 2016 09:31:35 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=woDtOrzL; dkim-atps=neutral Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756792AbcAHWaw (ORCPT ); Fri, 8 Jan 2016 17:30:52 -0500 Received: from mail-pf0-f193.google.com ([209.85.192.193]:34736 "EHLO mail-pf0-f193.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756474AbcAHW3k (ORCPT ); Fri, 8 Jan 2016 17:29:40 -0500 Received: by mail-pf0-f193.google.com with SMTP id 65so1192042pfd.1; Fri, 08 Jan 2016 14:29:39 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=b65Zl4WMjeuGaHCeXnAG8neUhuxYECkKPVdWsv+Lbsg=; b=woDtOrzLU9ZeVrnSMm/3bpi9r2IBHayCoIQEMfWnYCi/YqejPRa56xNkAcc4e51MYt p9ks+coHzlhPe29riHEW75U45+W1L5FiPS7O0F9u7Uzn3oAO87lTWoDlYbT0keCg42aO KTXLr8gQg67bB2K4f6+rCbu1AZI7QmivhQJq46TNcTbV9e4urForgmhTdO+BwLwZzLFk HFMDjujBwvbay+GsCzksSXIl0Z5mcos/7SWkThxJKI1H2lyVlxA411XYeXS/uP38Ir4f ICIqKy4K+SeNh3ktvVDYpPQqao6YZmZiKayreVRJtf7kluOBOMZSiFlUrFUqW/3IsCN9 boQw== X-Received: by 10.98.16.72 with SMTP id y69mr7807841pfi.95.1452292179389; Fri, 08 Jan 2016 14:29:39 -0800 (PST) Received: from localhost.localdomain.localdomain (cpe-172-89-102-93.socal.res.rr.com. [172.89.102.93]) by smtp.gmail.com with ESMTPSA id cq4sm53594462pad.28.2016.01.08.14.29.38 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 08 Jan 2016 14:29:39 -0800 (PST) From: richard.dorsch@gmail.com To: linux-kernel@vger.kernel.org Cc: lm-sensors@lm-sensors.org, linux-i2c@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-gpio@vger.kernel.org, lee.jones@linaro.org, jdelvare@suse.com, linux@roeck-us.net, wim@iguana.be, jo.sunga@advantech.com, Richard Vidal-Dorsch Subject: [PATCH 4/6] Add Advantech iManager I2C driver Date: Fri, 8 Jan 2016 14:29:24 -0800 Message-Id: <1452292166-20118-5-git-send-email-richard.dorsch@gmail.com> X-Mailer: git-send-email 2.6.4 In-Reply-To: <1452292166-20118-1-git-send-email-richard.dorsch@gmail.com> References: <1452292166-20118-1-git-send-email-richard.dorsch@gmail.com> Sender: linux-i2c-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-i2c@vger.kernel.org From: Richard Vidal-Dorsch --- Documentation/i2c/busses/i2c-imanager | 48 ++++ drivers/i2c/busses/Kconfig | 11 + drivers/i2c/busses/Makefile | 2 + drivers/i2c/busses/imanager-ec-i2c.c | 466 ++++++++++++++++++++++++++++++++++ drivers/i2c/busses/imanager-i2c.c | 240 +++++++++++++++++ include/linux/mfd/imanager/i2c.h | 55 ++++ 6 files changed, 822 insertions(+) create mode 100644 Documentation/i2c/busses/i2c-imanager create mode 100644 drivers/i2c/busses/imanager-ec-i2c.c create mode 100644 drivers/i2c/busses/imanager-i2c.c create mode 100644 include/linux/mfd/imanager/i2c.h diff --git a/Documentation/i2c/busses/i2c-imanager b/Documentation/i2c/busses/i2c-imanager new file mode 100644 index 0000000..d149fbf --- /dev/null +++ b/Documentation/i2c/busses/i2c-imanager @@ -0,0 +1,48 @@ +Kernel driver imanager_i2c +========================== + +This platform driver provides support for iManager I2C/SMBus. + +This driver depends on imanager (mfd). + +Module Parameters +----------------- + +* bus_frequency (unsigned short) +Set desired bus frequency. Valid values (kHz) are: + 50 Slow + 100 Standard (default) + 400 Fast + + +Description +----------- + +The Advantech iManager provides up to four SMBus controllers. One of them +is configured for I2C compatibility. + + +Process Call Support +-------------------- + +Not supported. + + +I2C Block Read Support +---------------------- + +I2C block read is supported. + + +SMBus 2.0 Support +----------------- + +Several SMBus 2.0 features are supported. +No PEC support. + + +Interrupt Support +----------------- + +No interrupt support available + diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 7b0aa82..4c401a4 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -42,6 +42,17 @@ config I2C_ALI15X3 This driver can also be built as a module. If so, the module will be called i2c-ali15x3. +config I2C_IMANAGER + tristate "Advantech iManager I2C Interface" + depends on MFD_IMANAGER + help + This enables support for Advantech iManager I2C of some + Advantech SOM, MIO, AIMB, and PCM modules/boards. + Requires mfd-core and imanager-core to function properly. + + This driver can also be built as a module. If so, the module + will be called i2c-imanager. + config I2C_AMD756 tristate "AMD 756/766/768/8111 and nVidia nForce" depends on PCI diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 37f2819..da76404 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -15,6 +15,8 @@ obj-$(CONFIG_I2C_AMD8111) += i2c-amd8111.o obj-$(CONFIG_I2C_I801) += i2c-i801.o obj-$(CONFIG_I2C_ISCH) += i2c-isch.o obj-$(CONFIG_I2C_ISMT) += i2c-ismt.o +i2c-imanager-objs := imanager-i2c.o imanager-ec-i2c.o +obj-$(CONFIG_I2C_IMANAGER) += i2c-imanager.o obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o obj-$(CONFIG_I2C_NFORCE2_S4985) += i2c-nforce2-s4985.o obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o diff --git a/drivers/i2c/busses/imanager-ec-i2c.c b/drivers/i2c/busses/imanager-ec-i2c.c new file mode 100644 index 0000000..e94964f --- /dev/null +++ b/drivers/i2c/busses/imanager-ec-i2c.c @@ -0,0 +1,466 @@ +/* + * Advantech iManager I2C bus core + * + * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA + * Author: Richard Vidal-Dorsch + * + * 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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define I2C_SMBUS_BLOCK_SIZE 32 + +#define EVAL_WR_SIZE(x) \ + (x < I2C_SMBUS_BLOCK_SIZE ? x : I2C_SMBUS_BLOCK_SIZE - 1) +#define EVAL_RD_SIZE(x) \ + (x && (x <= I2C_SMBUS_BLOCK_SIZE) ? x : I2C_SMBUS_BLOCK_SIZE) + +#define EC_HWRAM_OFFSET_STATUS 0UL + +#define I2C_ERR_PROTO 0x19UL +#define I2C_ERR_TIMEOUT 0x18UL +#define I2C_ERR_ACCESS 0x17UL +#define I2C_ERR_UNKNOWN 0x13UL +#define I2C_ERR_ADDR_NACK 0x10UL + +#define EC_SMB_DID(N) (0x28 + N) + +struct ec_i2c_status { + u32 error : 7; + u32 complete : 1; +}; + +static const struct imanager_i2c_device *i2c; + +static int i2c_core_eval_status(u8 _status) +{ + struct ec_i2c_status *status = (struct ec_i2c_status *)&_status; + int err = 0; + + switch (status->error) { + case 0: + break; + case I2C_ERR_ADDR_NACK: + err = -ENODEV; + break; + case I2C_ERR_ACCESS: + case I2C_ERR_UNKNOWN: + err = -EAGAIN; + break; + case I2C_ERR_TIMEOUT: + err = -ETIME; + break; + case I2C_ERR_PROTO: + err = -EPROTO; + break; + default: + pr_err("Undefined status code 0x%02X\n", status->error); + err = -EIO; + break; + } + + return err; +} + +static inline int i2c_send_message(u8 cmd, u8 param, struct ec_message *msg) +{ + int err; + + err = imanager_msg_write(cmd, param, msg); + if (err) + return i2c_core_eval_status(err); + + return 0; +} + +static int i2c_core_blk_wr_rw_combined(u8 proto, struct ec_message *msg) +{ + int err; + + if (WARN_ON(!msg)) + return -EINVAL; + + err = imanager_wait_proc_complete(EC_HWRAM_OFFSET_STATUS, 0); + if (err) + return err; + + err = i2c_send_message(proto, i2c->i2coem->did, msg); + if (err) + return err; + + if (msg->rlen) { + if (msg->rlen == 1) + return msg->u.data[0]; + else if (msg->rlen == 2) + return (msg->u.data[1] << 8) | msg->u.data[0]; + else + return msg->rlen; + } + + return 0; +} + +/* Write-Read and Read-Write wrappers */ +static inline int i2c_core_wr_combined(struct ec_message *msg) +{ + return i2c_core_blk_wr_rw_combined(EC_CMD_I2C_WR, msg); +} + +static inline int i2c_core_rw_combined(struct ec_message *msg) +{ + return i2c_core_blk_wr_rw_combined(EC_CMD_I2C_RW, msg); +} + +/* + * iManager I2C core API + */ +int i2c_core_write_quick(u16 addr) +{ + struct ec_message msg = { + .rlen = 0, + .wlen = sizeof(struct ec_message_header), + .u = { + .smb.hdr = { + .addr_low = LOADDR16(addr), + .addr_high = HIADDR16(addr), + .rlen = 0, + .wlen = 1, + }, + }, + }; + + return i2c_core_wr_combined(&msg); +} + +int i2c_core_read_byte(u16 addr) +{ + struct ec_message msg = { + .rlen = 1, + .wlen = sizeof(struct ec_message_header), + .u = { + .smb.hdr = { + .addr_low = LOADDR16(addr), + .addr_high = HIADDR16(addr), + .rlen = 1, + .wlen = 0, + }, + }, + }; + + return i2c_core_rw_combined(&msg); +} + +int i2c_core_write_byte(u16 addr, u8 cmd) +{ + struct ec_message msg = { + .rlen = 1, + .wlen = sizeof(struct ec_message_header), + .u = { + .smb.hdr = { + .addr_low = LOADDR16(addr), + .addr_high = HIADDR16(addr), + .rlen = 1, + .wlen = 1, + .cmd = cmd, + }, + }, + }; + + return i2c_core_wr_combined(&msg); +} + +int i2c_core_read_byte_data(u16 addr, u8 cmd) +{ + struct ec_message msg = { + .rlen = 1, + .wlen = sizeof(struct ec_message_header), + .u = { + .smb.hdr = { + .addr_low = LOADDR16(addr), + .addr_high = HIADDR16(addr), + .rlen = 1, + .wlen = 1, + .cmd = cmd, + }, + }, + }; + + return i2c_core_wr_combined(&msg); +} + +int i2c_core_write_byte_data(u16 addr, u8 cmd, u8 value) +{ + struct ec_message msg = { + .rlen = 1, + .wlen = sizeof(struct ec_message_header) + 1, + .u = { + .smb.hdr = { + .addr_low = LOADDR16(addr), + .addr_high = HIADDR16(addr), + .rlen = 0, + .wlen = 2, + .cmd = cmd, + }, + .smb.data[0] = value, + }, + }; + + return i2c_core_wr_combined(&msg); +} + +int i2c_core_read_word_data(u16 addr, u8 cmd) +{ + struct ec_message msg = { + .rlen = 2, + .wlen = sizeof(struct ec_message_header), + .u = { + .smb.hdr = { + .addr_low = LOADDR16(addr), + .addr_high = HIADDR16(addr), + .rlen = 2, + .wlen = 1, + .cmd = cmd, + }, + }, + }; + + return i2c_core_wr_combined(&msg); +} + +int i2c_core_write_word_data(u16 addr, u8 cmd, u16 value) +{ + struct ec_message msg = { + .rlen = 1, + .wlen = sizeof(struct ec_message_header) + 2, + .u = { + .smb.hdr = { + .addr_low = LOADDR16(addr), + .addr_high = HIADDR16(addr), + .rlen = 0, + .wlen = 3, + .cmd = cmd, + }, + .smb.data[0] = LOBYTE16(value), + .smb.data[1] = HIBYTE16(value), + }, + }; + + return i2c_core_wr_combined(&msg); +} + +int i2c_core_write_block_data(u16 addr, u8 cmd, u8 *buf) +{ + int i; + struct ec_message msg = { + .rlen = 1, + .wlen = sizeof(struct ec_message_header) + + EVAL_WR_SIZE(buf[0]), + .u = { + .smb.hdr = { + .addr_low = LOADDR16(addr), + .addr_high = HIADDR16(addr), + .rlen = 0, + .wlen = 1 + EVAL_WR_SIZE(buf[0]), + .cmd = cmd, + }, + }, + }; + + if ((buf[0] == 0) || (buf[0] >= I2C_MAX_WRITE_BYTES)) { + pr_err("Invalid I2C write length %d\n", buf[0]); + return -EINVAL; + } + + for (i = 0; i < EVAL_WR_SIZE(buf[0]); i++) + msg.u.data[i + sizeof(struct ec_message_header)] = buf[i + 1]; + + return i2c_core_wr_combined(&msg); +} + +int i2c_core_read_block_data(u16 addr, u8 cmd, u8 *buf) +{ + int i; + int ret; + struct ec_message msg = { + .rlen = EVAL_RD_SIZE(buf[0]), + .wlen = sizeof(struct ec_message_header), + .u = { + .smb.hdr = { + .addr_low = LOADDR16(addr), + .addr_high = HIADDR16(addr), + .rlen = EVAL_RD_SIZE(buf[0]), + .wlen = 1, + .cmd = cmd, + }, + }, + }; + + /* + * If buf[0] == 0 EC will read I2C_MAX_READ_BYTES + */ + ret = i2c_core_wr_combined(&msg); + if (ret < 0) { + pr_err("I2C transaction failed\n"); + return ret; + } + + buf[0] = ret; + for (i = 0; i < ret; i++) + buf[i + 1] = msg.u.data[i]; + + return 0; +} + +int i2c_core_read_i2c_block_data(u16 addr, u8 cmd, u8 *buf) +{ + int i; + int ret; + struct ec_message msg = { + .rlen = EVAL_RD_SIZE(buf[0]), + .wlen = sizeof(struct ec_message_header), + .u = { + .smb.hdr = { + .addr_low = LOADDR16(addr), + .addr_high = HIADDR16(addr), + .rlen = EVAL_RD_SIZE(buf[0]), + .wlen = 1, + .cmd = cmd, + }, + }, + }; + + if ((buf[0] == 0) || (buf[0] > I2C_MAX_READ_BYTES)) { + pr_err("Invalid I2C read length\n"); + return -EINVAL; + } + + ret = i2c_core_wr_combined(&msg); + if (ret < 0) { + pr_err("I2C transaction failed\n"); + return ret; + } + + buf[0] = ret; + for (i = 0; i < ret; i++) + buf[i + 1] = msg.u.data[i]; + + return 0; +} + +int i2c_core_write_i2c_block_data(u16 addr, u8 cmd, u8 *buf) +{ + if (WARN_ON(!buf)) + return -EINVAL; + + return i2c_core_write_block_data(addr, cmd, buf); +} + +int i2c_core_smb_get_freq(u32 bus_id) +{ + int ret = 0, f; + int freq_id, freq; + + if (WARN_ON(bus_id > I2C_OEM_1)) + return -EINVAL; + + switch (i2c->ecdev->id) { + case IT8518: + case IT8528: + ret = imanager_read_word(EC_CMD_SMB_FREQ_RD, + EC_SMB_DID(bus_id)); + if (ret < 0) { + pr_err("Failed to get bus frequency\n"); + return ret; + } + + freq_id = HIBYTE16(ret); + f = LOBYTE16(ret); + switch (freq_id) { + case 0: + freq = f; + break; + case 1: + freq = 50; + break; + case 2: + freq = 100; + break; + case 3: + freq = 400; + break; + default: + return -EINVAL; + } + break; + default: + pr_err("EC version not supported!\n"); + return -ENODEV; + } + + return freq; +} + +int i2c_core_smb_set_freq(u32 bus_id, u32 freq) +{ + int err; + u16 val; + + if (WARN_ON(bus_id > I2C_OEM_1)) + return -EINVAL; + + switch (i2c->ecdev->id) { + case IT8518: + case IT8528: + switch (freq) { + case 50: + val = 0x0100; + break; + case 100: + val = 0x0200; + break; + case 400: + val = 0x0300; + break; + default: + if (freq < 50) + val = freq; + else + return -EINVAL; + } + + err = imanager_write_word(EC_CMD_SMB_FREQ_WR, + EC_SMB_DID(bus_id), val); + if (err) { + pr_err("Failed to set I2C bus frequency\n"); + return err; + } + break; + default: + pr_err("EC version not supported!\n"); + return -ENODEV; + } + + return 0; +} + +int i2c_core_init(void) +{ + i2c = imanager_get_i2c_device(); + if (!i2c) + return -ENODEV; + + return 0; +} + diff --git a/drivers/i2c/busses/imanager-i2c.c b/drivers/i2c/busses/imanager-i2c.c new file mode 100644 index 0000000..fb7236d --- /dev/null +++ b/drivers/i2c/busses/imanager-i2c.c @@ -0,0 +1,240 @@ +/* + * Advantech iManager I2C bus driver + * + * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA + * Author: Richard Vidal-Dorsch + * + * 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; either version 2 of the License, or (at your + * option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +static uint bus_frequency = 100; +module_param(bus_frequency, uint, 0); +MODULE_PARM_DESC(bus_frequency, + "I2C bus frequency [50, 100, 400]kHz (defaults to 100kHz)"); + +struct imanager_i2c_data { + struct imanager_device_data *idev; + struct i2c_adapter adapter; +}; + +static int imanager_smb_access(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data *smb_data) +{ + struct imanager_i2c_data *data = i2c_get_adapdata(adap); + struct device *dev = data->adapter.dev.parent; + int ret = 0; + int val = 0; + + if (!data) + return -ENODEV; + + addr <<= 1; + + mutex_lock(&data->idev->lock); + + switch (size) { + case I2C_SMBUS_QUICK: + ret = i2c_core_write_quick(addr); + break; + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) /* NOT tested */ + val = i2c_core_write_byte(addr, command); + else + val = i2c_core_read_byte(addr); + + if (val < 0) + ret = val; + else + smb_data->byte = val; + break; + case I2C_SMBUS_BYTE_DATA: + if (read_write == I2C_SMBUS_WRITE) + val = i2c_core_write_byte_data(addr, command, + smb_data->byte); + else + val = i2c_core_read_byte_data(addr, command); + + if (val < 0) + ret = val; + else + smb_data->byte = val; + break; + case I2C_SMBUS_WORD_DATA: + if (read_write == I2C_SMBUS_WRITE) + val = i2c_core_write_word_data(addr, command, + smb_data->word); + else + val = i2c_core_read_word_data(addr, command); + + if (val < 0) + ret = val; + else + smb_data->word = val; + break; + case I2C_SMBUS_BLOCK_DATA: + if (read_write == I2C_SMBUS_WRITE) + ret = i2c_core_write_block_data(addr, command, + smb_data->block); + else + ret = i2c_core_read_block_data(addr, command, + smb_data->block); + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + if (read_write == I2C_SMBUS_WRITE) + ret = i2c_core_write_i2c_block_data(addr, command, + smb_data->block); + else + ret = i2c_core_read_i2c_block_data(addr, command, + smb_data->block); + break; + default: + dev_err(dev, "Unsupported SMB transaction %d\n", size); + ret = -EOPNOTSUPP; + } + + mutex_unlock(&data->idev->lock); + + return ret; +} + +static int imanager_i2c_access(struct i2c_adapter *adap, struct i2c_msg *msg, + int num) +{ + struct imanager_i2c_data *data = i2c_get_adapdata(adap); + struct device *dev = data->adapter.dev.parent; + + /* + * To be implemented + */ + + dev_info(dev, "i2c_access() is not yet implemented. msg=%p, num=%d\n", + msg, num); + + return 0; +} + +static u32 imanager_smb_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_I2C; +} + +static const struct i2c_algorithm imanager_algorithm = { + .smbus_xfer = imanager_smb_access, + .master_xfer = imanager_i2c_access, + .functionality = imanager_smb_i2c_func, +}; + +static int imanager_i2c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imanager_device_data *idev = dev_get_drvdata(dev->parent); + struct imanager_i2c_data *i2c; + int ret; + + if (!idev) { + dev_err(dev, "Invalid platform data\n"); + return -EINVAL; + } + + ret = i2c_core_init(); + if (ret) { + dev_err(dev, "Failed to initialize I2C core\n"); + return -EIO; + } + + if (bus_frequency > 100) + bus_frequency = 400; + else if (bus_frequency < 50) + bus_frequency = 50; + else + bus_frequency = 100; + + ret = i2c_core_smb_set_freq(I2C_OEM_1, bus_frequency); + if (ret < 0) { + dev_err(dev, "Failed to set I2C bus frequency to %d kHz\n", + bus_frequency); + return ret; + } + + ret = i2c_core_smb_get_freq(I2C_OEM_1); + if (ret < 0) { + dev_err(dev, "Failed to get I2C bus frequency\n"); + return ret; + } + bus_frequency = ret; + dev_info(dev, "Bus frequency: %d kHz\n", bus_frequency); + + i2c = devm_kzalloc(dev, sizeof(struct imanager_i2c_data), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + platform_set_drvdata(pdev, i2c); + i2c_set_adapdata(&i2c->adapter, i2c); + + i2c->idev = idev; + + i2c->adapter.owner = THIS_MODULE; + i2c->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + i2c->adapter.algo = &imanager_algorithm; + + /* set up the sysfs linkage to our parent device */ + i2c->adapter.dev.parent = dev; + + /* Retry up to 3 times on lost arbitration */ + i2c->adapter.retries = 3; + + snprintf(i2c->adapter.name, sizeof(i2c->adapter.name), + "iManager I2C driver"); + + ret = i2c_add_adapter(&i2c->adapter); + if (ret) { + dev_err(dev, "Failed to add SMBus adapter\n"); + return ret; + } + + return 0; +} + +static int imanager_i2c_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imanager_i2c_data *i2c = dev_get_drvdata(dev); + + i2c_del_adapter(&i2c->adapter); + i2c_set_adapdata(&i2c->adapter, NULL); + + return 0; +} + +static struct platform_driver imanager_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "imanager_i2c", + }, + .probe = imanager_i2c_probe, + .remove = imanager_i2c_remove, +}; + +module_platform_driver(imanager_i2c_driver); + +MODULE_DESCRIPTION("Advantech iManager I2C Driver"); +MODULE_AUTHOR("Richard Vidal-Dorsch "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imanager_i2c"); diff --git a/include/linux/mfd/imanager/i2c.h b/include/linux/mfd/imanager/i2c.h new file mode 100644 index 0000000..455f89f --- /dev/null +++ b/include/linux/mfd/imanager/i2c.h @@ -0,0 +1,55 @@ +/* + * Advantech iManager I2C bus core + * + * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA + * Author: Richard Vidal-Dorsch + * + * 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; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __I2C_H__ +#define __I2C_H__ + +#include + +#define I2C_MAX_READ_BYTES 32 +#define I2C_MAX_WRITE_BYTES 32 + +/* Only for setting SMBus frequency */ +enum smb_bus_id { + SMB_OEM_0, + SMB_OEM_1, + SMB_OEM_2, + SMB_EEPROM, + SMB_TH_0, + SMB_TH_1, + SMB_SECURITY_EEPROM, + I2C_OEM_1, +}; + +int i2c_core_init(void); + +int i2c_core_write_quick(u16 addr); + +int i2c_core_read_byte(u16 addr); +int i2c_core_write_byte(u16 addr, u8 cmd); + +int i2c_core_write_byte_data(u16 addr, u8 cmd, u8 value); +int i2c_core_read_byte_data(u16 addr, u8 cmd); + +int i2c_core_write_word_data(u16 addr, u8 cmd, u16 value); +int i2c_core_read_word_data(u16 addr, u8 cmd); + +int i2c_core_write_block_data(u16 addr, u8 cmd, u8 *buf); +int i2c_core_read_block_data(u16 addr, u8 cmd, u8 *buf); + +int i2c_core_write_i2c_block_data(u16 addr, u8 cmd, u8 *buf); +int i2c_core_read_i2c_block_data(u16 addr, u8 cmd, u8 *buf); + +int i2c_core_smb_get_freq(u32 bus_id); +int i2c_core_smb_set_freq(u32 bus_id, u32 freq); + +#endif