From b59320cc5a5e6ceaa17f0895ffbe0711ebad7adf Mon Sep 17 00:00:00 2001 From: Daniel Jeong Date: Mon, 17 Dec 2012 10:24:06 +0900 Subject: regulator: lp8755: new driver for LP8755 This patch is for new lp8755 regulator dirver and several unsed variables were deleted and then test was done. LP8755 : The LP8755 is a high performance power management unit.It contains six step-down DC-DC converters which can can be filexibly bundled together in multiphase converters as required by application. www.ti.com Signed-off-by: Daniel Jeong Signed-off-by: Mark Brown --- include/linux/platform_data/lp8755.h | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 include/linux/platform_data/lp8755.h (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/lp8755.h b/include/linux/platform_data/lp8755.h new file mode 100644 index 000000000000..a7fd0776c9bf --- /dev/null +++ b/include/linux/platform_data/lp8755.h @@ -0,0 +1,71 @@ +/* + * LP8755 High Performance Power Management Unit Driver:System Interface Driver + * + * Copyright (C) 2012 Texas Instruments + * + * Author: Daniel(Geon Si) Jeong + * G.Shark Jeong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _LP8755_H +#define _LP8755_H + +#include + +#define LP8755_NAME "lp8755-regulator" +/* + *PWR FAULT : power fault detected + *OCP : over current protect activated + *OVP : over voltage protect activated + *TEMP_WARN : thermal warning + *TEMP_SHDN : thermal shutdonw detected + *I_LOAD : current measured + */ +#define LP8755_EVENT_PWR_FAULT REGULATOR_EVENT_FAIL +#define LP8755_EVENT_OCP REGULATOR_EVENT_OVER_CURRENT +#define LP8755_EVENT_OVP 0x10000 +#define LP8755_EVENT_TEMP_WARN 0x2000 +#define LP8755_EVENT_TEMP_SHDN REGULATOR_EVENT_OVER_TEMP +#define LP8755_EVENT_I_LOAD 0x40000 + +enum lp8755_bucks { + LP8755_BUCK0 = 0, + LP8755_BUCK1, + LP8755_BUCK2, + LP8755_BUCK3, + LP8755_BUCK4, + LP8755_BUCK5, + LP8755_BUCK_MAX, +}; + +/** + * multiphase configuration options + */ +enum lp8755_mphase_config { + MPHASE_CONF0, + MPHASE_CONF1, + MPHASE_CONF2, + MPHASE_CONF3, + MPHASE_CONF4, + MPHASE_CONF5, + MPHASE_CONF6, + MPHASE_CONF7, + MPHASE_CONF8, + MPHASE_CONF_MAX +}; + +/** + * struct lp8755_platform_data + * @mphase_type : Multiphase Switcher Configurations. + * @buck_data : buck0~6 init voltage in uV + */ +struct lp8755_platform_data { + int mphase; + struct regulator_init_data *buck_data[LP8755_BUCK_MAX]; +}; +#endif -- cgit From 5cbc7ca987fb3f293203dc14a6c53b91b7c978a5 Mon Sep 17 00:00:00 2001 From: Matthias Brugger Date: Thu, 24 Jan 2013 13:40:41 +0100 Subject: spi: spi-omap2-mcspi.c: Toggle CS after each word This patch allows the board code to define SPI devices which needs to toggle the chip select after every word send. This is needed to get a better resolution reading e.g. an ADC data stream. Apart from that, as in the normal code CS is controlled by software, a transfer is done much faster. Signed-off-by: Matthias Brugger Signed-off-by: Mark Brown --- drivers/spi/spi-omap2-mcspi.c | 18 ++++++++++++++++++ include/linux/platform_data/spi-omap2-mcspi.h | 3 +++ 2 files changed, 21 insertions(+) (limited to 'include/linux/platform_data') diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index 31f6e842ea86..12789fcfa980 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -927,6 +927,7 @@ static void omap2_mcspi_work(struct omap2_mcspi *mcspi, struct spi_message *m) struct spi_device *spi; struct spi_transfer *t = NULL; + struct spi_master *master; int cs_active = 0; struct omap2_mcspi_cs *cs; struct omap2_mcspi_device_config *cd; @@ -935,6 +936,7 @@ static void omap2_mcspi_work(struct omap2_mcspi *mcspi, struct spi_message *m) u32 chconf; spi = m->spi; + master = spi->master; cs = spi->controller_state; cd = spi->controller_data; @@ -952,6 +954,14 @@ static void omap2_mcspi_work(struct omap2_mcspi *mcspi, struct spi_message *m) if (!t->speed_hz && !t->bits_per_word) par_override = 0; } + if (cd && cd->cs_per_word) { + chconf = mcspi->ctx.modulctrl; + chconf &= ~OMAP2_MCSPI_MODULCTRL_SINGLE; + mcspi_write_reg(master, OMAP2_MCSPI_MODULCTRL, chconf); + mcspi->ctx.modulctrl = + mcspi_read_cs_reg(spi, OMAP2_MCSPI_MODULCTRL); + } + if (!cs_active) { omap2_mcspi_force_cs(spi, 1); @@ -1013,6 +1023,14 @@ static void omap2_mcspi_work(struct omap2_mcspi *mcspi, struct spi_message *m) if (cs_active) omap2_mcspi_force_cs(spi, 0); + if (cd && cd->cs_per_word) { + chconf = mcspi->ctx.modulctrl; + chconf |= OMAP2_MCSPI_MODULCTRL_SINGLE; + mcspi_write_reg(master, OMAP2_MCSPI_MODULCTRL, chconf); + mcspi->ctx.modulctrl = + mcspi_read_cs_reg(spi, OMAP2_MCSPI_MODULCTRL); + } + omap2_mcspi_set_enable(spi, 0); m->status = status; diff --git a/include/linux/platform_data/spi-omap2-mcspi.h b/include/linux/platform_data/spi-omap2-mcspi.h index a65572d53211..c100456eab17 100644 --- a/include/linux/platform_data/spi-omap2-mcspi.h +++ b/include/linux/platform_data/spi-omap2-mcspi.h @@ -22,6 +22,9 @@ struct omap2_mcspi_dev_attr { struct omap2_mcspi_device_config { unsigned turbo_mode:1; + + /* toggle chip select after every word */ + unsigned cs_per_word:1; }; #endif -- cgit From cfad1ba87150e198be9ea32367a24e500e59de2c Mon Sep 17 00:00:00 2001 From: Eric Lapuyade Date: Tue, 18 Dec 2012 14:53:53 +0100 Subject: NFC: Initial support for Inside Secure microread Inside Secure microread is an HCI based NFC chipset. This initial support includes reader and p2p (Target and initiator) modes. Signed-off-by: Eric Lapuyade Signed-off-by: Samuel Ortiz --- drivers/nfc/Kconfig | 1 + drivers/nfc/Makefile | 1 + drivers/nfc/microread/Kconfig | 13 + drivers/nfc/microread/Makefile | 5 + drivers/nfc/microread/microread.c | 728 ++++++++++++++++++++++++++++++++ drivers/nfc/microread/microread.h | 33 ++ include/linux/platform_data/microread.h | 35 ++ 7 files changed, 816 insertions(+) create mode 100644 drivers/nfc/microread/Kconfig create mode 100644 drivers/nfc/microread/Makefile create mode 100644 drivers/nfc/microread/microread.c create mode 100644 drivers/nfc/microread/microread.h create mode 100644 include/linux/platform_data/microread.h (limited to 'include/linux/platform_data') diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig index 80c728b28828..e57034971ccc 100644 --- a/drivers/nfc/Kconfig +++ b/drivers/nfc/Kconfig @@ -27,5 +27,6 @@ config NFC_WILINK into the kernel or say M to compile it as module. source "drivers/nfc/pn544/Kconfig" +source "drivers/nfc/microread/Kconfig" endmenu diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile index 574bbc04d97a..a189ada0926a 100644 --- a/drivers/nfc/Makefile +++ b/drivers/nfc/Makefile @@ -3,6 +3,7 @@ # obj-$(CONFIG_NFC_PN544) += pn544/ +obj-$(CONFIG_NFC_MICROREAD) += microread/ obj-$(CONFIG_NFC_PN533) += pn533.o obj-$(CONFIG_NFC_WILINK) += nfcwilink.o diff --git a/drivers/nfc/microread/Kconfig b/drivers/nfc/microread/Kconfig new file mode 100644 index 000000000000..5b89d011d098 --- /dev/null +++ b/drivers/nfc/microread/Kconfig @@ -0,0 +1,13 @@ +config NFC_MICROREAD + tristate "Inside Secure microread NFC driver" + depends on NFC_HCI + select CRC_CCITT + default n + ---help--- + This module contains the main code for Inside Secure microread + NFC chipsets. It implements the chipset HCI logic and hooks into + the NFC kernel APIs. Physical layers will register against it. + + To compile this driver as a module, choose m here. The module will + be called microread. + Say N if unsure. diff --git a/drivers/nfc/microread/Makefile b/drivers/nfc/microread/Makefile new file mode 100644 index 000000000000..9ce2c53f49a7 --- /dev/null +++ b/drivers/nfc/microread/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for Microread HCI based NFC driver +# + +obj-$(CONFIG_NFC_MICROREAD) += microread.o diff --git a/drivers/nfc/microread/microread.c b/drivers/nfc/microread/microread.c new file mode 100644 index 000000000000..3420d833db17 --- /dev/null +++ b/drivers/nfc/microread/microread.c @@ -0,0 +1,728 @@ +/* + * HCI based Driver for Inside Secure microread NFC Chip + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "microread.h" + +/* Proprietary gates, events, commands and registers */ +/* Admin */ +#define MICROREAD_GATE_ID_ADM NFC_HCI_ADMIN_GATE +#define MICROREAD_GATE_ID_MGT 0x01 +#define MICROREAD_GATE_ID_OS 0x02 +#define MICROREAD_GATE_ID_TESTRF 0x03 +#define MICROREAD_GATE_ID_LOOPBACK NFC_HCI_LOOPBACK_GATE +#define MICROREAD_GATE_ID_IDT NFC_HCI_ID_MGMT_GATE +#define MICROREAD_GATE_ID_LMS NFC_HCI_LINK_MGMT_GATE + +/* Reader */ +#define MICROREAD_GATE_ID_MREAD_GEN 0x10 +#define MICROREAD_GATE_ID_MREAD_ISO_B NFC_HCI_RF_READER_B_GATE +#define MICROREAD_GATE_ID_MREAD_NFC_T1 0x12 +#define MICROREAD_GATE_ID_MREAD_ISO_A NFC_HCI_RF_READER_A_GATE +#define MICROREAD_GATE_ID_MREAD_NFC_T3 0x14 +#define MICROREAD_GATE_ID_MREAD_ISO_15_3 0x15 +#define MICROREAD_GATE_ID_MREAD_ISO_15_2 0x16 +#define MICROREAD_GATE_ID_MREAD_ISO_B_3 0x17 +#define MICROREAD_GATE_ID_MREAD_BPRIME 0x18 +#define MICROREAD_GATE_ID_MREAD_ISO_A_3 0x19 + +/* Card */ +#define MICROREAD_GATE_ID_MCARD_GEN 0x20 +#define MICROREAD_GATE_ID_MCARD_ISO_B 0x21 +#define MICROREAD_GATE_ID_MCARD_BPRIME 0x22 +#define MICROREAD_GATE_ID_MCARD_ISO_A 0x23 +#define MICROREAD_GATE_ID_MCARD_NFC_T3 0x24 +#define MICROREAD_GATE_ID_MCARD_ISO_15_3 0x25 +#define MICROREAD_GATE_ID_MCARD_ISO_15_2 0x26 +#define MICROREAD_GATE_ID_MCARD_ISO_B_2 0x27 +#define MICROREAD_GATE_ID_MCARD_ISO_CUSTOM 0x28 +#define MICROREAD_GATE_ID_SECURE_ELEMENT 0x2F + +/* P2P */ +#define MICROREAD_GATE_ID_P2P_GEN 0x30 +#define MICROREAD_GATE_ID_P2P_TARGET 0x31 +#define MICROREAD_PAR_P2P_TARGET_MODE 0x01 +#define MICROREAD_PAR_P2P_TARGET_GT 0x04 +#define MICROREAD_GATE_ID_P2P_INITIATOR 0x32 +#define MICROREAD_PAR_P2P_INITIATOR_GI 0x01 +#define MICROREAD_PAR_P2P_INITIATOR_GT 0x03 + +/* Those pipes are created/opened by default in the chip */ +#define MICROREAD_PIPE_ID_LMS 0x00 +#define MICROREAD_PIPE_ID_ADMIN 0x01 +#define MICROREAD_PIPE_ID_MGT 0x02 +#define MICROREAD_PIPE_ID_OS 0x03 +#define MICROREAD_PIPE_ID_HDS_LOOPBACK 0x04 +#define MICROREAD_PIPE_ID_HDS_IDT 0x05 +#define MICROREAD_PIPE_ID_HDS_MCARD_ISO_B 0x08 +#define MICROREAD_PIPE_ID_HDS_MCARD_ISO_BPRIME 0x09 +#define MICROREAD_PIPE_ID_HDS_MCARD_ISO_A 0x0A +#define MICROREAD_PIPE_ID_HDS_MCARD_ISO_15_3 0x0B +#define MICROREAD_PIPE_ID_HDS_MCARD_ISO_15_2 0x0C +#define MICROREAD_PIPE_ID_HDS_MCARD_NFC_T3 0x0D +#define MICROREAD_PIPE_ID_HDS_MCARD_ISO_B_2 0x0E +#define MICROREAD_PIPE_ID_HDS_MCARD_CUSTOM 0x0F +#define MICROREAD_PIPE_ID_HDS_MREAD_ISO_B 0x10 +#define MICROREAD_PIPE_ID_HDS_MREAD_NFC_T1 0x11 +#define MICROREAD_PIPE_ID_HDS_MREAD_ISO_A 0x12 +#define MICROREAD_PIPE_ID_HDS_MREAD_ISO_15_3 0x13 +#define MICROREAD_PIPE_ID_HDS_MREAD_ISO_15_2 0x14 +#define MICROREAD_PIPE_ID_HDS_MREAD_NFC_T3 0x15 +#define MICROREAD_PIPE_ID_HDS_MREAD_ISO_B_3 0x16 +#define MICROREAD_PIPE_ID_HDS_MREAD_BPRIME 0x17 +#define MICROREAD_PIPE_ID_HDS_MREAD_ISO_A_3 0x18 +#define MICROREAD_PIPE_ID_HDS_MREAD_GEN 0x1B +#define MICROREAD_PIPE_ID_HDS_STACKED_ELEMENT 0x1C +#define MICROREAD_PIPE_ID_HDS_INSTANCES 0x1D +#define MICROREAD_PIPE_ID_HDS_TESTRF 0x1E +#define MICROREAD_PIPE_ID_HDS_P2P_TARGET 0x1F +#define MICROREAD_PIPE_ID_HDS_P2P_INITIATOR 0x20 + +/* Events */ +#define MICROREAD_EVT_MREAD_DISCOVERY_OCCURED NFC_HCI_EVT_TARGET_DISCOVERED +#define MICROREAD_EVT_MREAD_CARD_FOUND 0x3D +#define MICROREAD_EMCF_A_ATQA 0 +#define MICROREAD_EMCF_A_SAK 2 +#define MICROREAD_EMCF_A_LEN 3 +#define MICROREAD_EMCF_A_UID 4 +#define MICROREAD_EMCF_A3_ATQA 0 +#define MICROREAD_EMCF_A3_SAK 2 +#define MICROREAD_EMCF_A3_LEN 3 +#define MICROREAD_EMCF_A3_UID 4 +#define MICROREAD_EMCF_B_UID 0 +#define MICROREAD_EMCF_T1_ATQA 0 +#define MICROREAD_EMCF_T1_UID 4 +#define MICROREAD_EMCF_T3_UID 0 +#define MICROREAD_EVT_MREAD_DISCOVERY_START NFC_HCI_EVT_READER_REQUESTED +#define MICROREAD_EVT_MREAD_DISCOVERY_START_SOME 0x3E +#define MICROREAD_EVT_MREAD_DISCOVERY_STOP NFC_HCI_EVT_END_OPERATION +#define MICROREAD_EVT_MREAD_SIM_REQUESTS 0x3F +#define MICROREAD_EVT_MCARD_EXCHANGE NFC_HCI_EVT_TARGET_DISCOVERED +#define MICROREAD_EVT_P2P_INITIATOR_EXCHANGE_TO_RF 0x20 +#define MICROREAD_EVT_P2P_INITIATOR_EXCHANGE_FROM_RF 0x21 +#define MICROREAD_EVT_MCARD_FIELD_ON 0x11 +#define MICROREAD_EVT_P2P_TARGET_ACTIVATED 0x13 +#define MICROREAD_EVT_P2P_TARGET_DEACTIVATED 0x12 +#define MICROREAD_EVT_MCARD_FIELD_OFF 0x14 + +/* Commands */ +#define MICROREAD_CMD_MREAD_EXCHANGE 0x10 +#define MICROREAD_CMD_MREAD_SUBSCRIBE 0x3F + +/* Hosts IDs */ +#define MICROREAD_ELT_ID_HDS NFC_HCI_TERMINAL_HOST_ID +#define MICROREAD_ELT_ID_SIM NFC_HCI_UICC_HOST_ID +#define MICROREAD_ELT_ID_SE1 0x03 +#define MICROREAD_ELT_ID_SE2 0x04 +#define MICROREAD_ELT_ID_SE3 0x05 + +static struct nfc_hci_gate microread_gates[] = { + {MICROREAD_GATE_ID_ADM, MICROREAD_PIPE_ID_ADMIN}, + {MICROREAD_GATE_ID_LOOPBACK, MICROREAD_PIPE_ID_HDS_LOOPBACK}, + {MICROREAD_GATE_ID_IDT, MICROREAD_PIPE_ID_HDS_IDT}, + {MICROREAD_GATE_ID_LMS, MICROREAD_PIPE_ID_LMS}, + {MICROREAD_GATE_ID_MREAD_ISO_B, MICROREAD_PIPE_ID_HDS_MREAD_ISO_B}, + {MICROREAD_GATE_ID_MREAD_ISO_A, MICROREAD_PIPE_ID_HDS_MREAD_ISO_A}, + {MICROREAD_GATE_ID_MREAD_ISO_A_3, MICROREAD_PIPE_ID_HDS_MREAD_ISO_A_3}, + {MICROREAD_GATE_ID_MGT, MICROREAD_PIPE_ID_MGT}, + {MICROREAD_GATE_ID_OS, MICROREAD_PIPE_ID_OS}, + {MICROREAD_GATE_ID_MREAD_NFC_T1, MICROREAD_PIPE_ID_HDS_MREAD_NFC_T1}, + {MICROREAD_GATE_ID_MREAD_NFC_T3, MICROREAD_PIPE_ID_HDS_MREAD_NFC_T3}, + {MICROREAD_GATE_ID_P2P_TARGET, MICROREAD_PIPE_ID_HDS_P2P_TARGET}, + {MICROREAD_GATE_ID_P2P_INITIATOR, MICROREAD_PIPE_ID_HDS_P2P_INITIATOR} +}; + +/* Largest headroom needed for outgoing custom commands */ +#define MICROREAD_CMDS_HEADROOM 2 +#define MICROREAD_CMD_TAILROOM 2 + +struct microread_info { + struct nfc_phy_ops *phy_ops; + void *phy_id; + + struct nfc_hci_dev *hdev; + + int async_cb_type; + data_exchange_cb_t async_cb; + void *async_cb_context; +}; + +static int microread_open(struct nfc_hci_dev *hdev) +{ + struct microread_info *info = nfc_hci_get_clientdata(hdev); + + return info->phy_ops->enable(info->phy_id); +} + +static void microread_close(struct nfc_hci_dev *hdev) +{ + struct microread_info *info = nfc_hci_get_clientdata(hdev); + + info->phy_ops->disable(info->phy_id); +} + +static int microread_hci_ready(struct nfc_hci_dev *hdev) +{ + int r; + u8 param[4]; + + param[0] = 0x03; + r = nfc_hci_send_cmd(hdev, MICROREAD_GATE_ID_MREAD_ISO_A, + MICROREAD_CMD_MREAD_SUBSCRIBE, param, 1, NULL); + if (r) + return r; + + r = nfc_hci_send_cmd(hdev, MICROREAD_GATE_ID_MREAD_ISO_A_3, + MICROREAD_CMD_MREAD_SUBSCRIBE, NULL, 0, NULL); + if (r) + return r; + + param[0] = 0x00; + param[1] = 0x03; + param[2] = 0x00; + r = nfc_hci_send_cmd(hdev, MICROREAD_GATE_ID_MREAD_ISO_B, + MICROREAD_CMD_MREAD_SUBSCRIBE, param, 3, NULL); + if (r) + return r; + + r = nfc_hci_send_cmd(hdev, MICROREAD_GATE_ID_MREAD_NFC_T1, + MICROREAD_CMD_MREAD_SUBSCRIBE, NULL, 0, NULL); + if (r) + return r; + + param[0] = 0xFF; + param[1] = 0xFF; + param[2] = 0x00; + param[3] = 0x00; + r = nfc_hci_send_cmd(hdev, MICROREAD_GATE_ID_MREAD_NFC_T3, + MICROREAD_CMD_MREAD_SUBSCRIBE, param, 4, NULL); + + return r; +} + +static int microread_xmit(struct nfc_hci_dev *hdev, struct sk_buff *skb) +{ + struct microread_info *info = nfc_hci_get_clientdata(hdev); + + return info->phy_ops->write(info->phy_id, skb); +} + +static int microread_start_poll(struct nfc_hci_dev *hdev, + u32 im_protocols, u32 tm_protocols) +{ + int r; + + u8 param[2]; + u8 mode; + + param[0] = 0x00; + param[1] = 0x00; + + if (im_protocols & NFC_PROTO_ISO14443_MASK) + param[0] |= (1 << 2); + + if (im_protocols & NFC_PROTO_ISO14443_B_MASK) + param[0] |= 1; + + if (im_protocols & NFC_PROTO_MIFARE_MASK) + param[1] |= 1; + + if (im_protocols & NFC_PROTO_JEWEL_MASK) + param[0] |= (1 << 1); + + if (im_protocols & NFC_PROTO_FELICA_MASK) + param[0] |= (1 << 5); + + if (im_protocols & NFC_PROTO_NFC_DEP_MASK) + param[1] |= (1 << 1); + + if ((im_protocols | tm_protocols) & NFC_PROTO_NFC_DEP_MASK) { + hdev->gb = nfc_get_local_general_bytes(hdev->ndev, + &hdev->gb_len); + if (hdev->gb == NULL || hdev->gb_len == 0) { + im_protocols &= ~NFC_PROTO_NFC_DEP_MASK; + tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK; + } + } + + r = nfc_hci_send_event(hdev, MICROREAD_GATE_ID_MREAD_ISO_A, + MICROREAD_EVT_MREAD_DISCOVERY_STOP, NULL, 0); + if (r) + return r; + + mode = 0xff; + r = nfc_hci_set_param(hdev, MICROREAD_GATE_ID_P2P_TARGET, + MICROREAD_PAR_P2P_TARGET_MODE, &mode, 1); + if (r) + return r; + + if (im_protocols & NFC_PROTO_NFC_DEP_MASK) { + r = nfc_hci_set_param(hdev, MICROREAD_GATE_ID_P2P_INITIATOR, + MICROREAD_PAR_P2P_INITIATOR_GI, + hdev->gb, hdev->gb_len); + if (r) + return r; + } + + if (tm_protocols & NFC_PROTO_NFC_DEP_MASK) { + r = nfc_hci_set_param(hdev, MICROREAD_GATE_ID_P2P_TARGET, + MICROREAD_PAR_P2P_TARGET_GT, + hdev->gb, hdev->gb_len); + if (r) + return r; + + mode = 0x02; + r = nfc_hci_set_param(hdev, MICROREAD_GATE_ID_P2P_TARGET, + MICROREAD_PAR_P2P_TARGET_MODE, &mode, 1); + if (r) + return r; + } + + return nfc_hci_send_event(hdev, MICROREAD_GATE_ID_MREAD_ISO_A, + MICROREAD_EVT_MREAD_DISCOVERY_START_SOME, + param, 2); +} + +static int microread_dep_link_up(struct nfc_hci_dev *hdev, + struct nfc_target *target, u8 comm_mode, + u8 *gb, size_t gb_len) +{ + struct sk_buff *rgb_skb = NULL; + int r; + + r = nfc_hci_get_param(hdev, target->hci_reader_gate, + MICROREAD_PAR_P2P_INITIATOR_GT, &rgb_skb); + if (r < 0) + return r; + + if (rgb_skb->len == 0 || rgb_skb->len > NFC_GB_MAXSIZE) { + r = -EPROTO; + goto exit; + } + + r = nfc_set_remote_general_bytes(hdev->ndev, rgb_skb->data, + rgb_skb->len); + if (r == 0) + r = nfc_dep_link_is_up(hdev->ndev, target->idx, comm_mode, + NFC_RF_INITIATOR); +exit: + kfree_skb(rgb_skb); + + return r; +} + +static int microread_dep_link_down(struct nfc_hci_dev *hdev) +{ + return nfc_hci_send_event(hdev, MICROREAD_GATE_ID_P2P_INITIATOR, + MICROREAD_EVT_MREAD_DISCOVERY_STOP, NULL, 0); +} + +static int microread_target_from_gate(struct nfc_hci_dev *hdev, u8 gate, + struct nfc_target *target) +{ + switch (gate) { + case MICROREAD_GATE_ID_P2P_INITIATOR: + target->supported_protocols = NFC_PROTO_NFC_DEP_MASK; + break; + default: + return -EPROTO; + } + + return 0; +} + +static int microread_complete_target_discovered(struct nfc_hci_dev *hdev, + u8 gate, + struct nfc_target *target) +{ + return 0; +} + +#define MICROREAD_CB_TYPE_READER_ALL 1 + +static void microread_im_transceive_cb(void *context, struct sk_buff *skb, + int err) +{ + struct microread_info *info = context; + + switch (info->async_cb_type) { + case MICROREAD_CB_TYPE_READER_ALL: + if (err == 0) { + if (skb->len == 0) { + err = -EPROTO; + kfree_skb(skb); + info->async_cb(info->async_cb_context, NULL, + -EPROTO); + return; + } + + if (skb->data[skb->len - 1] != 0) { + err = nfc_hci_result_to_errno( + skb->data[skb->len - 1]); + kfree_skb(skb); + info->async_cb(info->async_cb_context, NULL, + err); + return; + } + + skb_trim(skb, skb->len - 1); /* RF Error ind. */ + } + info->async_cb(info->async_cb_context, skb, err); + break; + default: + if (err == 0) + kfree_skb(skb); + break; + } +} + +/* + * Returns: + * <= 0: driver handled the data exchange + * 1: driver doesn't especially handle, please do standard processing + */ +static int microread_im_transceive(struct nfc_hci_dev *hdev, + struct nfc_target *target, + struct sk_buff *skb, data_exchange_cb_t cb, + void *cb_context) +{ + struct microread_info *info = nfc_hci_get_clientdata(hdev); + u8 control_bits; + u16 crc; + + pr_info("data exchange to gate 0x%x\n", target->hci_reader_gate); + + if (target->hci_reader_gate == MICROREAD_GATE_ID_P2P_INITIATOR) { + *skb_push(skb, 1) = 0; + + return nfc_hci_send_event(hdev, target->hci_reader_gate, + MICROREAD_EVT_P2P_INITIATOR_EXCHANGE_TO_RF, + skb->data, skb->len); + } + + switch (target->hci_reader_gate) { + case MICROREAD_GATE_ID_MREAD_ISO_A: + control_bits = 0xCB; + break; + case MICROREAD_GATE_ID_MREAD_ISO_A_3: + control_bits = 0xCB; + break; + case MICROREAD_GATE_ID_MREAD_ISO_B: + control_bits = 0xCB; + break; + case MICROREAD_GATE_ID_MREAD_NFC_T1: + control_bits = 0x1B; + + crc = crc_ccitt(0xffff, skb->data, skb->len); + crc = ~crc; + *skb_put(skb, 1) = crc & 0xff; + *skb_put(skb, 1) = crc >> 8; + break; + case MICROREAD_GATE_ID_MREAD_NFC_T3: + control_bits = 0xDB; + break; + default: + pr_info("Abort im_transceive to invalid gate 0x%x\n", + target->hci_reader_gate); + return 1; + } + + *skb_push(skb, 1) = control_bits; + + info->async_cb_type = MICROREAD_CB_TYPE_READER_ALL; + info->async_cb = cb; + info->async_cb_context = cb_context; + + return nfc_hci_send_cmd_async(hdev, target->hci_reader_gate, + MICROREAD_CMD_MREAD_EXCHANGE, + skb->data, skb->len, + microread_im_transceive_cb, info); +} + +static int microread_tm_send(struct nfc_hci_dev *hdev, struct sk_buff *skb) +{ + int r; + + r = nfc_hci_send_event(hdev, MICROREAD_GATE_ID_P2P_TARGET, + MICROREAD_EVT_MCARD_EXCHANGE, + skb->data, skb->len); + + kfree_skb(skb); + + return r; +} + +static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, + struct sk_buff *skb) +{ + struct nfc_target *targets; + int r = 0; + + pr_info("target discovered to gate 0x%x\n", gate); + + targets = kzalloc(sizeof(struct nfc_target), GFP_KERNEL); + if (targets == NULL) { + r = -ENOMEM; + goto exit; + } + + targets->hci_reader_gate = gate; + + switch (gate) { + case MICROREAD_GATE_ID_MREAD_ISO_A: + targets->supported_protocols = + nfc_hci_sak_to_protocol(skb->data[MICROREAD_EMCF_A_SAK]); + targets->sens_res = + be16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_A_ATQA]); + targets->sel_res = skb->data[MICROREAD_EMCF_A_SAK]; + memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_A_UID], + skb->data[MICROREAD_EMCF_A_LEN]); + targets->nfcid1_len = skb->data[MICROREAD_EMCF_A_LEN]; + break; + case MICROREAD_GATE_ID_MREAD_ISO_A_3: + targets->supported_protocols = + nfc_hci_sak_to_protocol(skb->data[MICROREAD_EMCF_A3_SAK]); + targets->sens_res = + be16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_A3_ATQA]); + targets->sel_res = skb->data[MICROREAD_EMCF_A3_SAK]; + memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_A3_UID], + skb->data[MICROREAD_EMCF_A3_LEN]); + targets->nfcid1_len = skb->data[MICROREAD_EMCF_A3_LEN]; + break; + case MICROREAD_GATE_ID_MREAD_ISO_B: + targets->supported_protocols = NFC_PROTO_ISO14443_B_MASK; + memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_B_UID], 4); + targets->nfcid1_len = 4; + break; + case MICROREAD_GATE_ID_MREAD_NFC_T1: + targets->supported_protocols = NFC_PROTO_JEWEL_MASK; + targets->sens_res = + le16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_T1_ATQA]); + memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_T1_UID], 4); + targets->nfcid1_len = 4; + break; + case MICROREAD_GATE_ID_MREAD_NFC_T3: + targets->supported_protocols = NFC_PROTO_FELICA_MASK; + memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_T3_UID], 8); + targets->nfcid1_len = 8; + break; + default: + pr_info("discard target discovered to gate 0x%x\n", gate); + goto exit_free; + } + + r = nfc_targets_found(hdev->ndev, targets, 1); + +exit_free: + kfree(targets); + +exit: + kfree_skb(skb); + + if (r) + pr_err("Failed to handle discovered target err=%d", r); +} + +static int microread_event_received(struct nfc_hci_dev *hdev, u8 gate, + u8 event, struct sk_buff *skb) +{ + int r; + u8 mode; + + pr_info("Microread received event 0x%x to gate 0x%x\n", event, gate); + + switch (event) { + case MICROREAD_EVT_MREAD_CARD_FOUND: + microread_target_discovered(hdev, gate, skb); + return 0; + + case MICROREAD_EVT_P2P_INITIATOR_EXCHANGE_FROM_RF: + if (skb->len < 1) { + kfree_skb(skb); + return -EPROTO; + } + + if (skb->data[skb->len - 1]) { + kfree_skb(skb); + return -EIO; + } + + skb_trim(skb, skb->len - 1); + + r = nfc_tm_data_received(hdev->ndev, skb); + break; + + case MICROREAD_EVT_MCARD_FIELD_ON: + case MICROREAD_EVT_MCARD_FIELD_OFF: + kfree_skb(skb); + return 0; + + case MICROREAD_EVT_P2P_TARGET_ACTIVATED: + r = nfc_tm_activated(hdev->ndev, NFC_PROTO_NFC_DEP_MASK, + NFC_COMM_PASSIVE, skb->data, + skb->len); + + kfree_skb(skb); + break; + + case MICROREAD_EVT_MCARD_EXCHANGE: + if (skb->len < 1) { + kfree_skb(skb); + return -EPROTO; + } + + if (skb->data[skb->len-1]) { + kfree_skb(skb); + return -EIO; + } + + skb_trim(skb, skb->len - 1); + + r = nfc_tm_data_received(hdev->ndev, skb); + break; + + case MICROREAD_EVT_P2P_TARGET_DEACTIVATED: + kfree_skb(skb); + + mode = 0xff; + r = nfc_hci_set_param(hdev, MICROREAD_GATE_ID_P2P_TARGET, + MICROREAD_PAR_P2P_TARGET_MODE, &mode, 1); + if (r) + break; + + r = nfc_hci_send_event(hdev, gate, + MICROREAD_EVT_MREAD_DISCOVERY_STOP, NULL, + 0); + break; + + default: + return 1; + } + + return r; +} + +static struct nfc_hci_ops microread_hci_ops = { + .open = microread_open, + .close = microread_close, + .hci_ready = microread_hci_ready, + .xmit = microread_xmit, + .start_poll = microread_start_poll, + .dep_link_up = microread_dep_link_up, + .dep_link_down = microread_dep_link_down, + .target_from_gate = microread_target_from_gate, + .complete_target_discovered = microread_complete_target_discovered, + .im_transceive = microread_im_transceive, + .tm_send = microread_tm_send, + .check_presence = NULL, + .event_received = microread_event_received, +}; + +int microread_probe(void *phy_id, struct nfc_phy_ops *phy_ops, char *llc_name, + int phy_headroom, int phy_tailroom, int phy_payload, + struct nfc_hci_dev **hdev) +{ + struct microread_info *info; + unsigned long quirks = 0; + u32 protocols, se; + struct nfc_hci_init_data init_data; + int r; + + info = kzalloc(sizeof(struct microread_info), GFP_KERNEL); + if (!info) { + pr_err("Cannot allocate memory for microread_info.\n"); + r = -ENOMEM; + goto err_info_alloc; + } + + info->phy_ops = phy_ops; + info->phy_id = phy_id; + + init_data.gate_count = ARRAY_SIZE(microread_gates); + memcpy(init_data.gates, microread_gates, sizeof(microread_gates)); + + strcpy(init_data.session_id, "MICROREA"); + + set_bit(NFC_HCI_QUIRK_SHORT_CLEAR, &quirks); + + protocols = NFC_PROTO_JEWEL_MASK | + NFC_PROTO_MIFARE_MASK | + NFC_PROTO_FELICA_MASK | + NFC_PROTO_ISO14443_MASK | + NFC_PROTO_ISO14443_B_MASK | + NFC_PROTO_NFC_DEP_MASK; + + se = NFC_SE_UICC | NFC_SE_EMBEDDED; + + info->hdev = nfc_hci_allocate_device(µread_hci_ops, &init_data, + quirks, protocols, se, llc_name, + phy_headroom + + MICROREAD_CMDS_HEADROOM, + phy_tailroom + + MICROREAD_CMD_TAILROOM, + phy_payload); + if (!info->hdev) { + pr_err("Cannot allocate nfc hdev.\n"); + r = -ENOMEM; + goto err_alloc_hdev; + } + + nfc_hci_set_clientdata(info->hdev, info); + + r = nfc_hci_register_device(info->hdev); + if (r) + goto err_regdev; + + *hdev = info->hdev; + + return 0; + +err_regdev: + nfc_hci_free_device(info->hdev); + +err_alloc_hdev: + kfree(info); + +err_info_alloc: + return r; +} +EXPORT_SYMBOL(microread_probe); + +void microread_remove(struct nfc_hci_dev *hdev) +{ + struct microread_info *info = nfc_hci_get_clientdata(hdev); + + nfc_hci_unregister_device(hdev); + nfc_hci_free_device(hdev); + kfree(info); +} +EXPORT_SYMBOL(microread_remove); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/nfc/microread/microread.h b/drivers/nfc/microread/microread.h new file mode 100644 index 000000000000..64b447a1c5bf --- /dev/null +++ b/drivers/nfc/microread/microread.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011 - 2012 Intel Corporation. All rights reserved. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __LOCAL_MICROREAD_H_ +#define __LOCAL_MICROREAD_H_ + +#include + +#define DRIVER_DESC "NFC driver for microread" + +int microread_probe(void *phy_id, struct nfc_phy_ops *phy_ops, char *llc_name, + int phy_headroom, int phy_tailroom, int phy_payload, + struct nfc_hci_dev **hdev); + +void microread_remove(struct nfc_hci_dev *hdev); + +#endif /* __LOCAL_MICROREAD_H_ */ diff --git a/include/linux/platform_data/microread.h b/include/linux/platform_data/microread.h new file mode 100644 index 000000000000..cfda59b226ee --- /dev/null +++ b/include/linux/platform_data/microread.h @@ -0,0 +1,35 @@ +/* + * Driver include for the PN544 NFC chip. + * + * Copyright (C) 2011 Tieto Poland + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MICROREAD_H +#define _MICROREAD_H + +#include + +#define MICROREAD_DRIVER_NAME "microread" + +/* board config platform data for microread */ +struct microread_nfc_platform_data { + unsigned int rst_gpio; + unsigned int irq_gpio; + unsigned int ioh_gpio; +}; + +#endif /* _MICROREAD_H */ -- cgit From 5372d2d71c46e5649e5d2edd4514adcd6fe7a085 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 12 Oct 2012 23:01:58 -0700 Subject: hwmon: Driver for Maxim MAX6697 and compatibles Add support for MAX6581, MAX6602, MAX6622, MAX6636, MAX6689, MAX6693, MAX6694, MAX6697, MAX6698, and MAX6699 temperature sensors Signed-off-by: Guenter Roeck Reviewed-by: Jean Delvare --- Documentation/devicetree/bindings/i2c/max6697.txt | 64 ++ Documentation/hwmon/max6697 | 58 ++ drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/max6697.c | 726 ++++++++++++++++++++++ include/linux/platform_data/max6697.h | 36 ++ 6 files changed, 896 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/max6697.txt create mode 100644 Documentation/hwmon/max6697 create mode 100644 drivers/hwmon/max6697.c create mode 100644 include/linux/platform_data/max6697.h (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/i2c/max6697.txt b/Documentation/devicetree/bindings/i2c/max6697.txt new file mode 100644 index 000000000000..5f793998e4a4 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/max6697.txt @@ -0,0 +1,64 @@ +max6697 properties + +Required properties: +- compatible: + Should be one of + maxim,max6581 + maxim,max6602 + maxim,max6622 + maxim,max6636 + maxim,max6689 + maxim,max6693 + maxim,max6694 + maxim,max6697 + maxim,max6698 + maxim,max6699 +- reg: I2C address + +Optional properties: + +- smbus-timeout-disable + Set to disable SMBus timeout. If not specified, SMBus timeout will be + enabled. +- extended-range-enable + Only valid for MAX6581. Set to enable extended temperature range. + Extended temperature will be disabled if not specified. +- beta-compensation-enable + Only valid for MAX6693 and MX6694. Set to enable beta compensation on + remote temperature channel 1. + Beta compensation will be disabled if not specified. +- alert-mask + Alert bit mask. Alert disabled for bits set. + Select bit 0 for local temperature, bit 1..7 for remote temperatures. + If not specified, alert will be enabled for all channels. +- over-temperature-mask + Over-temperature bit mask. Over-temperature reporting disabled for + bits set. + Select bit 0 for local temperature, bit 1..7 for remote temperatures. + If not specified, over-temperature reporting will be enabled for all + channels. +- resistance-cancellation + Boolean for all chips other than MAX6581. Set to enable resistance + cancellation on remote temperature channel 1. + For MAX6581, resistance cancellation enabled for all channels if + specified as boolean, otherwise as per bit mask specified. + Only supported for remote temperatures (bit 1..7). + If not specified, resistance cancellation will be disabled for all + channels. +- transistor-ideality + For MAX6581 only. Two values; first is bit mask, second is ideality + select value as per MAX6581 data sheet. Select bit 1..7 for remote + channels. + Transistor ideality will be initialized to default (1.008) if not + specified. + +Example: + +temp-sensor@1a { + compatible = "maxim,max6697"; + reg = <0x1a>; + smbus-timeout-disable; + resistance-cancellation; + alert-mask = <0x72>; + over-temperature-mask = <0x7f>; +}; diff --git a/Documentation/hwmon/max6697 b/Documentation/hwmon/max6697 new file mode 100644 index 000000000000..6594177ededa --- /dev/null +++ b/Documentation/hwmon/max6697 @@ -0,0 +1,58 @@ +Kernel driver max6697 +===================== + +Supported chips: + * Maxim MAX6581 + Prefix: 'max6581' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6581.pdf + * Maxim MAX6602 + Prefix: 'max6602' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6602.pdf + * Maxim MAX6622 + Prefix: 'max6622' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6622.pdf + * Maxim MAX6636 + Prefix: 'max6636' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6636.pdf + * Maxim MAX6689 + Prefix: 'max6689' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6689.pdf + * Maxim MAX6693 + Prefix: 'max6693' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6693.pdf + * Maxim MAX6694 + Prefix: 'max6694' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6694.pdf + * Maxim MAX6697 + Prefix: 'max6697' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6697.pdf + * Maxim MAX6698 + Prefix: 'max6698' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6698.pdf + * Maxim MAX6699 + Prefix: 'max6699' + Datasheet: http://datasheets.maximintegrated.com/en/ds/MAX6699.pdf + +Author: + Guenter Roeck + +Description +----------- + +This driver implements support for several MAX6697 compatible temperature sensor +chips. The chips support one local temperature sensor plus four, six, or seven +remote temperature sensors. Remote temperature sensors are diode-connected +thermal transitors, except for MAX6698 which supports three diode-connected +thermal transistors plus three thermistors in addition to the local temperature +sensor. + +The driver provides the following sysfs attributes. temp1 is the local (chip) +temperature, temp[2..n] are remote temperatures. The actually supported +per-channel attributes are chip type and channel dependent. + +tempX_input RO temperature +tempX_max RW temperature maximum threshold +tempX_max_alarm RO temperature maximum threshold alarm +tempX_crit RW temperature critical threshold +tempX_crit_alarm RO temperature critical threshold alarm +tempX_fault RO temperature diode fault (remote sensors only) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 32f238f3caea..ef5757265f78 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -854,6 +854,17 @@ config SENSORS_MAX6650 This driver can also be built as a module. If so, the module will be called max6650. +config SENSORS_MAX6697 + tristate "Maxim MAX6697 and compatibles" + depends on I2C + help + If you say yes here you get support for MAX6581, MAX6602, MAX6622, + MAX6636, MAX6689, MAX6693, MAX6694, MAX6697, MAX6698, and MAX6699 + temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called max6697. + config SENSORS_MCP3021 tristate "Microchip MCP3021 and compatibles" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5da287443f6c..a37a82c64da2 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_SENSORS_MAX197) += max197.o obj-$(CONFIG_SENSORS_MAX6639) += max6639.o obj-$(CONFIG_SENSORS_MAX6642) += max6642.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o +obj-$(CONFIG_SENSORS_MAX6697) += max6697.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c new file mode 100644 index 000000000000..bf4aa3777fc1 --- /dev/null +++ b/drivers/hwmon/max6697.c @@ -0,0 +1,726 @@ +/* + * Copyright (c) 2012 Guenter Roeck + * + * based on max1668.c + * Copyright (c) 2011 David George + * + * 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. + * + * 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 + +enum chips { max6581, max6602, max6622, max6636, max6689, max6693, max6694, + max6697, max6698, max6699 }; + +/* Report local sensor as temp1 */ + +static const u8 MAX6697_REG_TEMP[] = { + 0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08 }; +static const u8 MAX6697_REG_TEMP_EXT[] = { + 0x57, 0x09, 0x52, 0x53, 0x54, 0x55, 0x56, 0 }; +static const u8 MAX6697_REG_MAX[] = { + 0x17, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18 }; +static const u8 MAX6697_REG_CRIT[] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 }; + +/* + * Map device tree / platform data register bit map to chip bit map. + * Applies to alert register and over-temperature register. + */ +#define MAX6697_MAP_BITS(reg) ((((reg) & 0x7e) >> 1) | \ + (((reg) & 0x01) << 6) | ((reg) & 0x80)) + +#define MAX6697_REG_STAT(n) (0x44 + (n)) + +#define MAX6697_REG_CONFIG 0x41 +#define MAX6581_CONF_EXTENDED (1 << 1) +#define MAX6693_CONF_BETA (1 << 2) +#define MAX6697_CONF_RESISTANCE (1 << 3) +#define MAX6697_CONF_TIMEOUT (1 << 5) +#define MAX6697_REG_ALERT_MASK 0x42 +#define MAX6697_REG_OVERT_MASK 0x43 + +#define MAX6581_REG_RESISTANCE 0x4a +#define MAX6581_REG_IDEALITY 0x4b +#define MAX6581_REG_IDEALITY_SELECT 0x4c +#define MAX6581_REG_OFFSET 0x4d +#define MAX6581_REG_OFFSET_SELECT 0x4e + +#define MAX6697_CONV_TIME 156 /* ms per channel, worst case */ + +struct max6697_chip_data { + int channels; + u32 have_ext; + u32 have_crit; + u32 have_fault; + u8 valid_conf; + const u8 *alarm_map; +}; + +struct max6697_data { + struct device *hwmon_dev; + + enum chips type; + const struct max6697_chip_data *chip; + + int update_interval; /* in milli-seconds */ + int temp_offset; /* in degrees C */ + + struct mutex update_lock; + unsigned long last_updated; /* In jiffies */ + bool valid; /* true if following fields are valid */ + + /* 1x local and up to 7x remote */ + u8 temp[8][4]; /* [nr][0]=temp [1]=ext [2]=max [3]=crit */ +#define MAX6697_TEMP_INPUT 0 +#define MAX6697_TEMP_EXT 1 +#define MAX6697_TEMP_MAX 2 +#define MAX6697_TEMP_CRIT 3 + u32 alarms; +}; + +/* Diode fault status bits on MAX6581 are right shifted by one bit */ +static const u8 max6581_alarm_map[] = { + 0, 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23 }; + +static const struct max6697_chip_data max6697_chip_data[] = { + [max6581] = { + .channels = 8, + .have_crit = 0xff, + .have_ext = 0x7f, + .have_fault = 0xfe, + .valid_conf = MAX6581_CONF_EXTENDED | MAX6697_CONF_TIMEOUT, + .alarm_map = max6581_alarm_map, + }, + [max6602] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6622] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6636] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6689] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6693] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6693_CONF_BETA | + MAX6697_CONF_TIMEOUT, + }, + [max6694] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6693_CONF_BETA | + MAX6697_CONF_TIMEOUT, + }, + [max6697] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6698] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x0e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6699] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, +}; + +static struct max6697_data *max6697_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6697_data *data = i2c_get_clientdata(client); + struct max6697_data *ret = data; + int val; + int i; + u32 alarms; + + mutex_lock(&data->update_lock); + + if (data->valid && + !time_after(jiffies, data->last_updated + + msecs_to_jiffies(data->update_interval))) + goto abort; + + for (i = 0; i < data->chip->channels; i++) { + if (data->chip->have_ext & (1 << i)) { + val = i2c_smbus_read_byte_data(client, + MAX6697_REG_TEMP_EXT[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_EXT] = val; + } + + val = i2c_smbus_read_byte_data(client, MAX6697_REG_TEMP[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_INPUT] = val; + + val = i2c_smbus_read_byte_data(client, MAX6697_REG_MAX[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_MAX] = val; + + if (data->chip->have_crit & (1 << i)) { + val = i2c_smbus_read_byte_data(client, + MAX6697_REG_CRIT[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_CRIT] = val; + } + } + + alarms = 0; + for (i = 0; i < 3; i++) { + val = i2c_smbus_read_byte_data(client, MAX6697_REG_STAT(i)); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + alarms = (alarms << 8) | val; + } + data->alarms = alarms; + data->last_updated = jiffies; + data->valid = true; +abort: + mutex_unlock(&data->update_lock); + + return ret; +} + +static ssize_t show_temp_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct max6697_data *data = max6697_update_device(dev); + int temp; + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = (data->temp[index][MAX6697_TEMP_INPUT] - data->temp_offset) << 3; + temp |= data->temp[index][MAX6697_TEMP_EXT] >> 5; + + return sprintf(buf, "%d\n", temp * 125); +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr_2(devattr)->nr; + int index = to_sensor_dev_attr_2(devattr)->index; + struct max6697_data *data = max6697_update_device(dev); + int temp; + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = data->temp[nr][index]; + temp -= data->temp_offset; + + return sprintf(buf, "%d\n", temp * 1000); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct max6697_data *data = max6697_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->chip->alarm_map) + index = data->chip->alarm_map[index]; + + return sprintf(buf, "%u\n", (data->alarms >> index) & 0x1); +} + +static ssize_t set_temp(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr_2(devattr)->nr; + int index = to_sensor_dev_attr_2(devattr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct max6697_data *data = i2c_get_clientdata(client); + long temp; + int ret; + + ret = kstrtol(buf, 10, &temp); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + temp = DIV_ROUND_CLOSEST(temp, 1000) + data->temp_offset; + temp = clamp_val(temp, 0, data->type == max6581 ? 255 : 127); + data->temp[nr][index] = temp; + ret = i2c_smbus_write_byte_data(client, + index == 2 ? MAX6697_REG_MAX[nr] + : MAX6697_REG_CRIT[nr], + temp); + mutex_unlock(&data->update_lock); + + return ret < 0 ? ret : count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 0, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 0, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 1, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 1, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_input, NULL, 2); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 2, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 2, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_input, NULL, 3); +static SENSOR_DEVICE_ATTR_2(temp4_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 3, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp4_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 3, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp_input, NULL, 4); +static SENSOR_DEVICE_ATTR_2(temp5_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 4, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp5_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 4, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp_input, NULL, 5); +static SENSOR_DEVICE_ATTR_2(temp6_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 5, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp6_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 5, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp_input, NULL, 6); +static SENSOR_DEVICE_ATTR_2(temp7_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 6, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp7_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 6, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp_input, NULL, 7); +static SENSOR_DEVICE_ATTR_2(temp8_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 7, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp8_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 7, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 22); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 16); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, 17); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, 18); +static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, show_alarm, NULL, 19); +static SENSOR_DEVICE_ATTR(temp6_max_alarm, S_IRUGO, show_alarm, NULL, 20); +static SENSOR_DEVICE_ATTR(temp7_max_alarm, S_IRUGO, show_alarm, NULL, 21); +static SENSOR_DEVICE_ATTR(temp8_max_alarm, S_IRUGO, show_alarm, NULL, 23); + +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 14); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp6_crit_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(temp7_crit_alarm, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(temp8_crit_alarm, S_IRUGO, show_alarm, NULL, 15); + +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_fault, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_fault, S_IRUGO, show_alarm, NULL, 7); + +static struct attribute *max6697_attributes[8][7] = { + { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_crit.dev_attr.attr, + &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + &sensor_dev_attr_temp6_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_crit.dev_attr.attr, + &sensor_dev_attr_temp6_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp7_input.dev_attr.attr, + &sensor_dev_attr_temp7_max.dev_attr.attr, + &sensor_dev_attr_temp7_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp7_crit.dev_attr.attr, + &sensor_dev_attr_temp7_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp7_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp8_max.dev_attr.attr, + &sensor_dev_attr_temp8_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp8_crit.dev_attr.attr, + &sensor_dev_attr_temp8_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp8_fault.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group max6697_group[8] = { + { .attrs = max6697_attributes[0] }, + { .attrs = max6697_attributes[1] }, + { .attrs = max6697_attributes[2] }, + { .attrs = max6697_attributes[3] }, + { .attrs = max6697_attributes[4] }, + { .attrs = max6697_attributes[5] }, + { .attrs = max6697_attributes[6] }, + { .attrs = max6697_attributes[7] }, +}; + +static void max6697_get_config_of(struct device_node *node, + struct max6697_platform_data *pdata) +{ + int len; + const __be32 *prop; + + prop = of_get_property(node, "smbus-timeout-disable", &len); + if (prop) + pdata->smbus_timeout_disable = true; + prop = of_get_property(node, "extended-range-enable", &len); + if (prop) + pdata->extended_range_enable = true; + prop = of_get_property(node, "beta-compensation-enable", &len); + if (prop) + pdata->beta_compensation = true; + prop = of_get_property(node, "alert-mask", &len); + if (prop && len == sizeof(u32)) + pdata->alert_mask = be32_to_cpu(prop[0]); + prop = of_get_property(node, "over-temperature-mask", &len); + if (prop && len == sizeof(u32)) + pdata->over_temperature_mask = be32_to_cpu(prop[0]); + prop = of_get_property(node, "resistance-cancellation", &len); + if (prop) { + if (len == sizeof(u32)) + pdata->resistance_cancellation = be32_to_cpu(prop[0]); + else + pdata->resistance_cancellation = 0xfe; + } + prop = of_get_property(node, "transistor-ideality", &len); + if (prop && len == 2 * sizeof(u32)) { + pdata->ideality_mask = be32_to_cpu(prop[0]); + pdata->ideality_value = be32_to_cpu(prop[1]); + } +} + +static int max6697_init_chip(struct i2c_client *client) +{ + struct max6697_data *data = i2c_get_clientdata(client); + struct max6697_platform_data *pdata = dev_get_platdata(&client->dev); + struct max6697_platform_data p; + const struct max6697_chip_data *chip = data->chip; + int factor = chip->channels; + int ret, reg; + + /* + * Don't touch configuration if neither platform data nor OF + * configuration was specified. If that is the case, use the + * current chip configuration. + */ + if (!pdata && !client->dev.of_node) { + reg = i2c_smbus_read_byte_data(client, MAX6697_REG_CONFIG); + if (reg < 0) + return reg; + if (data->type == max6581) { + if (reg & MAX6581_CONF_EXTENDED) + data->temp_offset = 64; + reg = i2c_smbus_read_byte_data(client, + MAX6581_REG_RESISTANCE); + if (reg < 0) + return reg; + factor += hweight8(reg); + } else { + if (reg & MAX6697_CONF_RESISTANCE) + factor++; + } + goto done; + } + + if (client->dev.of_node) { + memset(&p, 0, sizeof(p)); + max6697_get_config_of(client->dev.of_node, &p); + pdata = &p; + } + + reg = 0; + if (pdata->smbus_timeout_disable && + (chip->valid_conf & MAX6697_CONF_TIMEOUT)) { + reg |= MAX6697_CONF_TIMEOUT; + } + if (pdata->extended_range_enable && + (chip->valid_conf & MAX6581_CONF_EXTENDED)) { + reg |= MAX6581_CONF_EXTENDED; + data->temp_offset = 64; + } + if (pdata->resistance_cancellation && + (chip->valid_conf & MAX6697_CONF_RESISTANCE)) { + reg |= MAX6697_CONF_RESISTANCE; + factor++; + } + if (pdata->beta_compensation && + (chip->valid_conf & MAX6693_CONF_BETA)) { + reg |= MAX6693_CONF_BETA; + } + + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_CONFIG, reg); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_ALERT_MASK, + MAX6697_MAP_BITS(pdata->alert_mask)); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_OVERT_MASK, + MAX6697_MAP_BITS(pdata->over_temperature_mask)); + if (ret < 0) + return ret; + + if (data->type == max6581) { + factor += hweight8(pdata->resistance_cancellation >> 1); + ret = i2c_smbus_write_byte_data(client, MAX6581_REG_RESISTANCE, + pdata->resistance_cancellation >> 1); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(client, MAX6581_REG_IDEALITY, + pdata->ideality_mask >> 1); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(client, + MAX6581_REG_IDEALITY_SELECT, + pdata->ideality_value); + if (ret < 0) + return ret; + } +done: + data->update_interval = factor * MAX6697_CONV_TIME; + return 0; +} + +static void max6697_remove_files(struct i2c_client *client) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(max6697_group); i++) + sysfs_remove_group(&client->dev.kobj, &max6697_group[i]); +} + +static int max6697_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct device *dev = &client->dev; + struct max6697_data *data; + int i, err; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(struct max6697_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->type = id->driver_data; + data->chip = &max6697_chip_data[data->type]; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + err = max6697_init_chip(client); + if (err) + return err; + + for (i = 0; i < data->chip->channels; i++) { + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][0]); + if (err) + goto error; + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][1]); + if (err) + goto error; + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][2]); + if (err) + goto error; + + if (data->chip->have_crit & (1 << i)) { + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][3]); + if (err) + goto error; + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][4]); + if (err) + goto error; + } + if (data->chip->have_fault & (1 << i)) { + err = sysfs_create_file(&dev->kobj, + max6697_attributes[i][5]); + if (err) + goto error; + } + } + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto error; + } + + return 0; + +error: + max6697_remove_files(client); + return err; +} + +static int max6697_remove(struct i2c_client *client) +{ + struct max6697_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + max6697_remove_files(client); + + return 0; +} + +static const struct i2c_device_id max6697_id[] = { + { "max6581", max6581 }, + { "max6602", max6602 }, + { "max6622", max6622 }, + { "max6636", max6636 }, + { "max6689", max6689 }, + { "max6693", max6693 }, + { "max6694", max6694 }, + { "max6697", max6697 }, + { "max6698", max6698 }, + { "max6699", max6699 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max6697_id); + +static struct i2c_driver max6697_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max6697", + }, + .probe = max6697_probe, + .remove = max6697_remove, + .id_table = max6697_id, +}; + +module_i2c_driver(max6697_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("MAX6697 temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/max6697.h b/include/linux/platform_data/max6697.h new file mode 100644 index 000000000000..ed9d3b3daf02 --- /dev/null +++ b/include/linux/platform_data/max6697.h @@ -0,0 +1,36 @@ +/* + * max6697.h + * Copyright (c) 2012 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef MAX6697_H +#define MAX6697_H + +#include + +/* + * For all bit masks: + * bit 0: local temperature + * bit 1..7: remote temperatures + */ +struct max6697_platform_data { + bool smbus_timeout_disable; /* set to disable SMBus timeouts */ + bool extended_range_enable; /* set to enable extended temp range */ + bool beta_compensation; /* set to enable beta compensation */ + u8 alert_mask; /* set bit to 1 to disable alert */ + u8 over_temperature_mask; /* set bit to 1 to disable */ + u8 resistance_cancellation; /* set bit to 0 to disable + * bit mask for MAX6581, + * boolean for other chips + */ + u8 ideality_mask; /* set bit to 0 to disable */ + u8 ideality_value; /* transistor ideality as per + * MAX6581 datasheet + */ +}; + +#endif /* MAX6697_H */ -- cgit From 3b72c2fe0c6bbec42ed7f899931daef227b80322 Mon Sep 17 00:00:00 2001 From: Mugunthan V N Date: Tue, 5 Feb 2013 08:26:48 +0000 Subject: drivers: net:ethernet: cpsw: add support for VLAN adding support for VLAN interface for cpsw. CPSW VLAN Capability * Can filter VLAN packets in Hardware Signed-off-by: Mugunthan V N Signed-off-by: David S. Miller --- drivers/net/ethernet/ti/cpsw.c | 106 ++++++++++++++++++++++++++++++++++++- drivers/net/ethernet/ti/cpsw_ale.h | 4 ++ include/linux/platform_data/cpsw.h | 1 + 3 files changed, 109 insertions(+), 2 deletions(-) (limited to 'include/linux/platform_data') diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c index 534bf7bc34db..888708ceb13c 100644 --- a/drivers/net/ethernet/ti/cpsw.c +++ b/drivers/net/ethernet/ti/cpsw.c @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -118,6 +119,9 @@ do { \ #define TX_PRIORITY_MAPPING 0x33221100 #define CPDMA_TX_PRIORITY_MAP 0x76543210 +#define CPSW_VLAN_AWARE BIT(1) +#define CPSW_ALE_VLAN_AWARE 1 + #define cpsw_enable_irq(priv) \ do { \ u32 i; \ @@ -607,14 +611,40 @@ static void cpsw_slave_open(struct cpsw_slave *slave, struct cpsw_priv *priv) } } +static inline void cpsw_add_default_vlan(struct cpsw_priv *priv) +{ + const int vlan = priv->data.default_vlan; + const int port = priv->host_port; + u32 reg; + int i; + + reg = (priv->version == CPSW_VERSION_1) ? CPSW1_PORT_VLAN : + CPSW2_PORT_VLAN; + + writel(vlan, &priv->host_port_regs->port_vlan); + + for (i = 0; i < 2; i++) + slave_write(priv->slaves + i, vlan, reg); + + cpsw_ale_add_vlan(priv->ale, vlan, ALE_ALL_PORTS << port, + ALE_ALL_PORTS << port, ALE_ALL_PORTS << port, + (ALE_PORT_1 | ALE_PORT_2) << port); +} + static void cpsw_init_host_port(struct cpsw_priv *priv) { + u32 control_reg; + /* soft reset the controller and initialize ale */ soft_reset("cpsw", &priv->regs->soft_reset); cpsw_ale_start(priv->ale); /* switch to vlan unaware mode */ - cpsw_ale_control_set(priv->ale, 0, ALE_VLAN_AWARE, 0); + cpsw_ale_control_set(priv->ale, priv->host_port, ALE_VLAN_AWARE, + CPSW_ALE_VLAN_AWARE); + control_reg = readl(&priv->regs->control); + control_reg |= CPSW_VLAN_AWARE; + writel(control_reg, &priv->regs->control); /* setup host port priority mapping */ __raw_writel(CPDMA_TX_PRIORITY_MAP, @@ -650,6 +680,9 @@ static int cpsw_ndo_open(struct net_device *ndev) cpsw_init_host_port(priv); for_each_slave(priv, cpsw_slave_open, priv); + /* Add default VLAN */ + cpsw_add_default_vlan(priv); + /* setup tx dma to fixed prio and zero offset */ cpdma_control_set(priv->dma, CPDMA_TX_PRIO_FIXED, 1); cpdma_control_set(priv->dma, CPDMA_RX_BUFFER_OFFSET, 0); @@ -933,6 +966,73 @@ static void cpsw_ndo_poll_controller(struct net_device *ndev) } #endif +static inline int cpsw_add_vlan_ale_entry(struct cpsw_priv *priv, + unsigned short vid) +{ + int ret; + + ret = cpsw_ale_add_vlan(priv->ale, vid, + ALE_ALL_PORTS << priv->host_port, + 0, ALE_ALL_PORTS << priv->host_port, + (ALE_PORT_1 | ALE_PORT_2) << priv->host_port); + if (ret != 0) + return ret; + + ret = cpsw_ale_add_ucast(priv->ale, priv->mac_addr, + priv->host_port, ALE_VLAN, vid); + if (ret != 0) + goto clean_vid; + + ret = cpsw_ale_add_mcast(priv->ale, priv->ndev->broadcast, + ALE_ALL_PORTS << priv->host_port, + ALE_VLAN, vid, 0); + if (ret != 0) + goto clean_vlan_ucast; + return 0; + +clean_vlan_ucast: + cpsw_ale_del_ucast(priv->ale, priv->mac_addr, + priv->host_port, ALE_VLAN, vid); +clean_vid: + cpsw_ale_del_vlan(priv->ale, vid, 0); + return ret; +} + +static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev, + unsigned short vid) +{ + struct cpsw_priv *priv = netdev_priv(ndev); + + if (vid == priv->data.default_vlan) + return 0; + + dev_info(priv->dev, "Adding vlanid %d to vlan filter\n", vid); + return cpsw_add_vlan_ale_entry(priv, vid); +} + +static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev, + unsigned short vid) +{ + struct cpsw_priv *priv = netdev_priv(ndev); + int ret; + + if (vid == priv->data.default_vlan) + return 0; + + dev_info(priv->dev, "removing vlanid %d from vlan filter\n", vid); + ret = cpsw_ale_del_vlan(priv->ale, vid, 0); + if (ret != 0) + return ret; + + ret = cpsw_ale_del_ucast(priv->ale, priv->mac_addr, + priv->host_port, ALE_VLAN, vid); + if (ret != 0) + return ret; + + return cpsw_ale_del_mcast(priv->ale, priv->ndev->broadcast, + 0, ALE_VLAN, vid); +} + static const struct net_device_ops cpsw_netdev_ops = { .ndo_open = cpsw_ndo_open, .ndo_stop = cpsw_ndo_stop, @@ -947,6 +1047,8 @@ static const struct net_device_ops cpsw_netdev_ops = { #ifdef CONFIG_NET_POLL_CONTROLLER .ndo_poll_controller = cpsw_ndo_poll_controller, #endif + .ndo_vlan_rx_add_vid = cpsw_ndo_vlan_rx_add_vid, + .ndo_vlan_rx_kill_vid = cpsw_ndo_vlan_rx_kill_vid, }; static void cpsw_get_drvinfo(struct net_device *ndev, @@ -1354,7 +1456,7 @@ static int cpsw_probe(struct platform_device *pdev) k++; } - ndev->flags |= IFF_ALLMULTI; /* see cpsw_ndo_change_rx_flags() */ + ndev->features |= NETIF_F_HW_VLAN_FILTER; ndev->netdev_ops = &cpsw_netdev_ops; SET_ETHTOOL_OPS(ndev, &cpsw_ethtool_ops); diff --git a/drivers/net/ethernet/ti/cpsw_ale.h b/drivers/net/ethernet/ti/cpsw_ale.h index a002417f952b..30daa1265f0c 100644 --- a/drivers/net/ethernet/ti/cpsw_ale.h +++ b/drivers/net/ethernet/ti/cpsw_ale.h @@ -69,6 +69,10 @@ enum cpsw_ale_port_state { #define ALE_SUPER BIT(2) #define ALE_VLAN BIT(3) +#define ALE_PORT_HOST BIT(0) +#define ALE_PORT_1 BIT(1) +#define ALE_PORT_2 BIT(2) + #define ALE_MCAST_FWD 0 #define ALE_MCAST_BLOCK_LEARN_FWD 1 #define ALE_MCAST_FWD_LEARN 2 diff --git a/include/linux/platform_data/cpsw.h b/include/linux/platform_data/cpsw.h index 24368a2e8b87..e962cfd552e3 100644 --- a/include/linux/platform_data/cpsw.h +++ b/include/linux/platform_data/cpsw.h @@ -35,6 +35,7 @@ struct cpsw_platform_data { u32 bd_ram_size; /*buffer descriptor ram size */ u32 rx_descs; /* Number of Rx Descriptios */ u32 mac_control; /* Mac control register */ + u16 default_vlan; /* Def VLAN for ALE lookup in VLAN aware mode*/ }; #endif /* __CPSW_H__ */ -- cgit From d9ba8f9e6298af71ec1c1fd3d88c3ef68abd0ec3 Mon Sep 17 00:00:00 2001 From: Mugunthan V N Date: Mon, 11 Feb 2013 09:52:20 +0000 Subject: driver: net: ethernet: cpsw: dual emac interface implementation The CPSW switch can act as Dual EMAC by segregating the switch ports using VLAN and port VLAN as per the TRM description in 14.3.2.10.2 Dual Mac Mode Following CPSW components will be common for both the interfaces. * Interrupt source is common for both eth interfaces * Interrupt pacing is common for both interfaces * Hardware statistics is common for all the ports * CPDMA is common for both eth interface * CPTS is common for both the interface and it should not be enabled on both the interface as timestamping information doesn't contain port information. Constrains * Reserved VID of One port should not be used in other interface which will enable switching functionality * Same VID must not be used in both the interface which will enable switching functionality Signed-off-by: Mugunthan V N Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/cpsw.txt | 2 + drivers/net/ethernet/ti/cpsw.c | 335 +++++++++++++++++++++---- include/linux/platform_data/cpsw.h | 3 + 3 files changed, 288 insertions(+), 52 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/net/cpsw.txt b/Documentation/devicetree/bindings/net/cpsw.txt index 6ddd0286a9b7..ecfdf756d10f 100644 --- a/Documentation/devicetree/bindings/net/cpsw.txt +++ b/Documentation/devicetree/bindings/net/cpsw.txt @@ -24,6 +24,8 @@ Required properties: Optional properties: - ti,hwmods : Must be "cpgmac0" - no_bd_ram : Must be 0 or 1 +- dual_emac : Specifies Switch to act as Dual EMAC +- dual_emac_res_vlan : Specifies VID to be used to segregate the ports Note: "ti,hwmods" field is used to fetch the base address and irq resources from TI, omap hwmod data base during device registration. diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c index 4b964bb02d4c..4ceed6e0f1be 100644 --- a/drivers/net/ethernet/ti/cpsw.c +++ b/drivers/net/ethernet/ti/cpsw.c @@ -122,6 +122,10 @@ do { \ #define CPSW_VLAN_AWARE BIT(1) #define CPSW_ALE_VLAN_AWARE 1 +#define CPSW_FIFO_NORMAL_MODE (0 << 15) +#define CPSW_FIFO_DUAL_MAC_MODE (1 << 15) +#define CPSW_FIFO_RATE_LIMIT_MODE (2 << 15) + #define cpsw_enable_irq(priv) \ do { \ u32 i; \ @@ -254,7 +258,7 @@ struct cpsw_ss_regs { struct cpsw_host_regs { u32 max_blks; u32 blk_cnt; - u32 flow_thresh; + u32 tx_in_ctl; u32 port_vlan; u32 tx_pri_map; u32 cpdma_tx_pri_map; @@ -281,6 +285,9 @@ struct cpsw_slave { u32 mac_control; struct cpsw_slave_data *data; struct phy_device *phy; + struct net_device *ndev; + u32 port_vlan; + u32 open_stat; }; static inline u32 slave_read(struct cpsw_slave *slave, u32 offset) @@ -320,15 +327,63 @@ struct cpsw_priv { u32 irqs_table[4]; u32 num_irqs; struct cpts *cpts; + u32 emac_port; }; #define napi_to_priv(napi) container_of(napi, struct cpsw_priv, napi) -#define for_each_slave(priv, func, arg...) \ - do { \ - int idx; \ - for (idx = 0; idx < (priv)->data.slaves; idx++) \ - (func)((priv)->slaves + idx, ##arg); \ +#define for_each_slave(priv, func, arg...) \ + do { \ + int idx; \ + if (priv->data.dual_emac) \ + (func)((priv)->slaves + priv->emac_port, ##arg);\ + else \ + for (idx = 0; idx < (priv)->data.slaves; idx++) \ + (func)((priv)->slaves + idx, ##arg); \ + } while (0) +#define cpsw_get_slave_ndev(priv, __slave_no__) \ + (priv->slaves[__slave_no__].ndev) +#define cpsw_get_slave_priv(priv, __slave_no__) \ + ((priv->slaves[__slave_no__].ndev) ? \ + netdev_priv(priv->slaves[__slave_no__].ndev) : NULL) \ + +#define cpsw_dual_emac_src_port_detect(status, priv, ndev, skb) \ + do { \ + if (!priv->data.dual_emac) \ + break; \ + if (CPDMA_RX_SOURCE_PORT(status) == 1) { \ + ndev = cpsw_get_slave_ndev(priv, 0); \ + priv = netdev_priv(ndev); \ + skb->dev = ndev; \ + } else if (CPDMA_RX_SOURCE_PORT(status) == 2) { \ + ndev = cpsw_get_slave_ndev(priv, 1); \ + priv = netdev_priv(ndev); \ + skb->dev = ndev; \ + } \ } while (0) +#define cpsw_add_mcast(priv, addr) \ + do { \ + if (priv->data.dual_emac) { \ + struct cpsw_slave *slave = priv->slaves + \ + priv->emac_port; \ + int slave_port = cpsw_get_slave_port(priv, \ + slave->slave_num); \ + cpsw_ale_add_mcast(priv->ale, addr, \ + 1 << slave_port | 1 << priv->host_port, \ + ALE_VLAN, slave->port_vlan, 0); \ + } else { \ + cpsw_ale_add_mcast(priv->ale, addr, \ + ALE_ALL_PORTS << priv->host_port, \ + 0, 0, 0); \ + } \ + } while (0) + +static inline int cpsw_get_slave_port(struct cpsw_priv *priv, u32 slave_num) +{ + if (priv->host_port == 0) + return slave_num + 1; + else + return slave_num; +} static void cpsw_ndo_set_rx_mode(struct net_device *ndev) { @@ -348,8 +403,7 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev) /* program multicast address list into ALE register */ netdev_for_each_mc_addr(ha, ndev) { - cpsw_ale_add_mcast(priv->ale, (u8 *)ha->addr, - ALE_ALL_PORTS << priv->host_port, 0, 0, 0); + cpsw_add_mcast(priv, (u8 *)ha->addr); } } } @@ -396,6 +450,8 @@ void cpsw_rx_handler(void *token, int len, int status) struct cpsw_priv *priv = netdev_priv(ndev); int ret = 0; + cpsw_dual_emac_src_port_detect(status, priv, ndev, skb); + /* free and bail if we are shutting down */ if (unlikely(!netif_running(ndev)) || unlikely(!netif_carrier_ok(ndev))) { @@ -437,18 +493,17 @@ static irqreturn_t cpsw_interrupt(int irq, void *dev_id) cpsw_intr_disable(priv); cpsw_disable_irq(priv); napi_schedule(&priv->napi); + } else { + priv = cpsw_get_slave_priv(priv, 1); + if (likely(priv) && likely(netif_running(priv->ndev))) { + cpsw_intr_disable(priv); + cpsw_disable_irq(priv); + napi_schedule(&priv->napi); + } } return IRQ_HANDLED; } -static inline int cpsw_get_slave_port(struct cpsw_priv *priv, u32 slave_num) -{ - if (priv->host_port == 0) - return slave_num + 1; - else - return slave_num; -} - static int cpsw_poll(struct napi_struct *napi, int budget) { struct cpsw_priv *priv = napi_to_priv(napi); @@ -566,6 +621,54 @@ static inline int __show_stat(char *buf, int maxlen, const char *name, u32 val) leader + strlen(name), val); } +static int cpsw_common_res_usage_state(struct cpsw_priv *priv) +{ + u32 i; + u32 usage_count = 0; + + if (!priv->data.dual_emac) + return 0; + + for (i = 0; i < priv->data.slaves; i++) + if (priv->slaves[i].open_stat) + usage_count++; + + return usage_count; +} + +static inline int cpsw_tx_packet_submit(struct net_device *ndev, + struct cpsw_priv *priv, struct sk_buff *skb) +{ + if (!priv->data.dual_emac) + return cpdma_chan_submit(priv->txch, skb, skb->data, + skb->len, 0, GFP_KERNEL); + + if (ndev == cpsw_get_slave_ndev(priv, 0)) + return cpdma_chan_submit(priv->txch, skb, skb->data, + skb->len, 1, GFP_KERNEL); + else + return cpdma_chan_submit(priv->txch, skb, skb->data, + skb->len, 2, GFP_KERNEL); +} + +static inline void cpsw_add_dual_emac_def_ale_entries( + struct cpsw_priv *priv, struct cpsw_slave *slave, + u32 slave_port) +{ + u32 port_mask = 1 << slave_port | 1 << priv->host_port; + + if (priv->version == CPSW_VERSION_1) + slave_write(slave, slave->port_vlan, CPSW1_PORT_VLAN); + else + slave_write(slave, slave->port_vlan, CPSW2_PORT_VLAN); + cpsw_ale_add_vlan(priv->ale, slave->port_vlan, port_mask, + port_mask, port_mask, 0); + cpsw_ale_add_mcast(priv->ale, priv->ndev->broadcast, + port_mask, ALE_VLAN, slave->port_vlan, 0); + cpsw_ale_add_ucast(priv->ale, priv->mac_addr, + priv->host_port, ALE_VLAN, slave->port_vlan); +} + static void cpsw_slave_open(struct cpsw_slave *slave, struct cpsw_priv *priv) { char name[32]; @@ -595,8 +698,11 @@ static void cpsw_slave_open(struct cpsw_slave *slave, struct cpsw_priv *priv) slave_port = cpsw_get_slave_port(priv, slave->slave_num); - cpsw_ale_add_mcast(priv->ale, priv->ndev->broadcast, - 1 << slave_port, 0, 0, ALE_MCAST_FWD_2); + if (priv->data.dual_emac) + cpsw_add_dual_emac_def_ale_entries(priv, slave, slave_port); + else + cpsw_ale_add_mcast(priv->ale, priv->ndev->broadcast, + 1 << slave_port, 0, 0, ALE_MCAST_FWD_2); slave->phy = phy_connect(priv->ndev, slave->data->phy_id, &cpsw_adjust_link, slave->data->phy_if); @@ -634,6 +740,7 @@ static inline void cpsw_add_default_vlan(struct cpsw_priv *priv) static void cpsw_init_host_port(struct cpsw_priv *priv) { u32 control_reg; + u32 fifo_mode; /* soft reset the controller and initialize ale */ soft_reset("cpsw", &priv->regs->soft_reset); @@ -645,6 +752,9 @@ static void cpsw_init_host_port(struct cpsw_priv *priv) control_reg = readl(&priv->regs->control); control_reg |= CPSW_VLAN_AWARE; writel(control_reg, &priv->regs->control); + fifo_mode = (priv->data.dual_emac) ? CPSW_FIFO_DUAL_MAC_MODE : + CPSW_FIFO_NORMAL_MODE; + writel(fifo_mode, &priv->host_port_regs->tx_in_ctl); /* setup host port priority mapping */ __raw_writel(CPDMA_TX_PRIORITY_MAP, @@ -654,9 +764,12 @@ static void cpsw_init_host_port(struct cpsw_priv *priv) cpsw_ale_control_set(priv->ale, priv->host_port, ALE_PORT_STATE, ALE_PORT_STATE_FORWARD); - cpsw_ale_add_ucast(priv->ale, priv->mac_addr, priv->host_port, 0, 0); - cpsw_ale_add_mcast(priv->ale, priv->ndev->broadcast, - 1 << priv->host_port, 0, 0, ALE_MCAST_FWD_2); + if (!priv->data.dual_emac) { + cpsw_ale_add_ucast(priv->ale, priv->mac_addr, priv->host_port, + 0, 0); + cpsw_ale_add_mcast(priv->ale, priv->ndev->broadcast, + 1 << priv->host_port, 0, 0, ALE_MCAST_FWD_2); + } } static int cpsw_ndo_open(struct net_device *ndev) @@ -665,7 +778,8 @@ static int cpsw_ndo_open(struct net_device *ndev) int i, ret; u32 reg; - cpsw_intr_disable(priv); + if (!cpsw_common_res_usage_state(priv)) + cpsw_intr_disable(priv); netif_carrier_off(ndev); pm_runtime_get_sync(&priv->pdev->dev); @@ -677,46 +791,54 @@ static int cpsw_ndo_open(struct net_device *ndev) CPSW_RTL_VERSION(reg)); /* initialize host and slave ports */ - cpsw_init_host_port(priv); + if (!cpsw_common_res_usage_state(priv)) + cpsw_init_host_port(priv); for_each_slave(priv, cpsw_slave_open, priv); /* Add default VLAN */ - cpsw_add_default_vlan(priv); + if (!priv->data.dual_emac) + cpsw_add_default_vlan(priv); - /* setup tx dma to fixed prio and zero offset */ - cpdma_control_set(priv->dma, CPDMA_TX_PRIO_FIXED, 1); - cpdma_control_set(priv->dma, CPDMA_RX_BUFFER_OFFSET, 0); + if (!cpsw_common_res_usage_state(priv)) { + /* setup tx dma to fixed prio and zero offset */ + cpdma_control_set(priv->dma, CPDMA_TX_PRIO_FIXED, 1); + cpdma_control_set(priv->dma, CPDMA_RX_BUFFER_OFFSET, 0); - /* disable priority elevation and enable statistics on all ports */ - __raw_writel(0, &priv->regs->ptype); + /* disable priority elevation */ + __raw_writel(0, &priv->regs->ptype); - /* enable statistics collection only on the host port */ - __raw_writel(0x7, &priv->regs->stat_port_en); + /* enable statistics collection only on all ports */ + __raw_writel(0x7, &priv->regs->stat_port_en); - if (WARN_ON(!priv->data.rx_descs)) - priv->data.rx_descs = 128; + if (WARN_ON(!priv->data.rx_descs)) + priv->data.rx_descs = 128; - for (i = 0; i < priv->data.rx_descs; i++) { - struct sk_buff *skb; + for (i = 0; i < priv->data.rx_descs; i++) { + struct sk_buff *skb; - ret = -ENOMEM; - skb = netdev_alloc_skb_ip_align(priv->ndev, - priv->rx_packet_max); - if (!skb) - break; - ret = cpdma_chan_submit(priv->rxch, skb, skb->data, + ret = -ENOMEM; + skb = netdev_alloc_skb_ip_align(priv->ndev, + priv->rx_packet_max); + if (!skb) + break; + ret = cpdma_chan_submit(priv->rxch, skb, skb->data, skb_tailroom(skb), 0, GFP_KERNEL); - if (WARN_ON(ret < 0)) - break; + if (WARN_ON(ret < 0)) + break; + } + /* continue even if we didn't manage to submit all + * receive descs + */ + cpsw_info(priv, ifup, "submitted %d rx descriptors\n", i); } - /* continue even if we didn't manage to submit all receive descs */ - cpsw_info(priv, ifup, "submitted %d rx descriptors\n", i); cpdma_ctlr_start(priv->dma); cpsw_intr_enable(priv); napi_enable(&priv->napi); cpdma_ctlr_eoi(priv->dma); + if (priv->data.dual_emac) + priv->slaves[priv->emac_port].open_stat = true; return 0; } @@ -737,12 +859,17 @@ static int cpsw_ndo_stop(struct net_device *ndev) netif_stop_queue(priv->ndev); napi_disable(&priv->napi); netif_carrier_off(priv->ndev); - cpsw_intr_disable(priv); - cpdma_ctlr_int_ctrl(priv->dma, false); - cpdma_ctlr_stop(priv->dma); - cpsw_ale_stop(priv->ale); + + if (cpsw_common_res_usage_state(priv) <= 1) { + cpsw_intr_disable(priv); + cpdma_ctlr_int_ctrl(priv->dma, false); + cpdma_ctlr_stop(priv->dma); + cpsw_ale_stop(priv->ale); + } for_each_slave(priv, cpsw_slave_stop, priv); pm_runtime_put_sync(&priv->pdev->dev); + if (priv->data.dual_emac) + priv->slaves[priv->emac_port].open_stat = false; return 0; } @@ -766,8 +893,7 @@ static netdev_tx_t cpsw_ndo_start_xmit(struct sk_buff *skb, skb_tx_timestamp(skb); - ret = cpdma_chan_submit(priv->txch, skb, skb->data, - skb->len, 0, GFP_KERNEL); + ret = cpsw_tx_packet_submit(ndev, priv, skb); if (unlikely(ret != 0)) { cpsw_err(priv, tx_err, "desc submit failed\n"); goto fail; @@ -836,9 +962,14 @@ static void cpsw_hwtstamp_v1(struct cpsw_priv *priv) static void cpsw_hwtstamp_v2(struct cpsw_priv *priv) { - struct cpsw_slave *slave = &priv->slaves[priv->data.cpts_active_slave]; + struct cpsw_slave *slave; u32 ctrl, mtype; + if (priv->data.dual_emac) + slave = &priv->slaves[priv->emac_port]; + else + slave = &priv->slaves[priv->data.cpts_active_slave]; + ctrl = slave_read(slave, CPSW2_CONTROL); ctrl &= ~CTRL_ALL_TS_MASK; @@ -1124,6 +1255,7 @@ static void cpsw_slave_init(struct cpsw_slave *slave, struct cpsw_priv *priv, slave->data = data; slave->regs = regs + slave_reg_ofs; slave->sliver = regs + sliver_reg_ofs; + slave->port_vlan = data->dual_emac_res_vlan; } static int cpsw_probe_dt(struct cpsw_platform_data *data, @@ -1204,6 +1336,9 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data, } data->mac_control = prop; + if (!of_property_read_u32(node, "dual_emac", &prop)) + data->dual_emac = prop; + /* * Populate all the child nodes here... */ @@ -1237,6 +1372,18 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data, if (mac_addr) memcpy(slave_data->mac_addr, mac_addr, ETH_ALEN); + if (data->dual_emac) { + if (of_property_read_u32(node, "dual_emac_res_vlan", + &prop)) { + pr_err("Missing dual_emac_res_vlan in DT.\n"); + slave_data->dual_emac_res_vlan = i+1; + pr_err("Using %d as Reserved VLAN for %d slave\n", + slave_data->dual_emac_res_vlan, i); + } else { + slave_data->dual_emac_res_vlan = prop; + } + } + i++; } @@ -1247,6 +1394,79 @@ error_ret: return ret; } +static int cpsw_probe_dual_emac(struct platform_device *pdev, + struct cpsw_priv *priv) +{ + struct cpsw_platform_data *data = &priv->data; + struct net_device *ndev; + struct cpsw_priv *priv_sl2; + int ret = 0, i; + + ndev = alloc_etherdev(sizeof(struct cpsw_priv)); + if (!ndev) { + pr_err("cpsw: error allocating net_device\n"); + return -ENOMEM; + } + + priv_sl2 = netdev_priv(ndev); + spin_lock_init(&priv_sl2->lock); + priv_sl2->data = *data; + priv_sl2->pdev = pdev; + priv_sl2->ndev = ndev; + priv_sl2->dev = &ndev->dev; + priv_sl2->msg_enable = netif_msg_init(debug_level, CPSW_DEBUG); + priv_sl2->rx_packet_max = max(rx_packet_max, 128); + + if (is_valid_ether_addr(data->slave_data[1].mac_addr)) { + memcpy(priv_sl2->mac_addr, data->slave_data[1].mac_addr, + ETH_ALEN); + pr_info("cpsw: Detected MACID = %pM\n", priv_sl2->mac_addr); + } else { + random_ether_addr(priv_sl2->mac_addr); + pr_info("cpsw: Random MACID = %pM\n", priv_sl2->mac_addr); + } + memcpy(ndev->dev_addr, priv_sl2->mac_addr, ETH_ALEN); + + priv_sl2->slaves = priv->slaves; + priv_sl2->clk = priv->clk; + + priv_sl2->cpsw_res = priv->cpsw_res; + priv_sl2->regs = priv->regs; + priv_sl2->host_port = priv->host_port; + priv_sl2->host_port_regs = priv->host_port_regs; + priv_sl2->wr_regs = priv->wr_regs; + priv_sl2->dma = priv->dma; + priv_sl2->txch = priv->txch; + priv_sl2->rxch = priv->rxch; + priv_sl2->ale = priv->ale; + priv_sl2->emac_port = 1; + priv->slaves[1].ndev = ndev; + priv_sl2->cpts = priv->cpts; + priv_sl2->version = priv->version; + + for (i = 0; i < priv->num_irqs; i++) { + priv_sl2->irqs_table[i] = priv->irqs_table[i]; + priv_sl2->num_irqs = priv->num_irqs; + } + + ndev->features |= NETIF_F_HW_VLAN_FILTER; + + ndev->netdev_ops = &cpsw_netdev_ops; + SET_ETHTOOL_OPS(ndev, &cpsw_ethtool_ops); + netif_napi_add(ndev, &priv_sl2->napi, cpsw_poll, CPSW_POLL_WEIGHT); + + /* register the network device */ + SET_NETDEV_DEV(ndev, &pdev->dev); + ret = register_netdev(ndev); + if (ret) { + pr_err("cpsw: error registering net device\n"); + free_netdev(ndev); + ret = -ENODEV; + } + + return ret; +} + static int cpsw_probe(struct platform_device *pdev) { struct cpsw_platform_data *data = pdev->dev.platform_data; @@ -1310,6 +1530,9 @@ static int cpsw_probe(struct platform_device *pdev) for (i = 0; i < data->slaves; i++) priv->slaves[i].slave_num = i; + priv->slaves[0].ndev = ndev; + priv->emac_port = 0; + priv->clk = clk_get(&pdev->dev, "fck"); if (IS_ERR(priv->clk)) { dev_err(&pdev->dev, "fck is not found\n"); @@ -1484,6 +1707,14 @@ static int cpsw_probe(struct platform_device *pdev) cpsw_notice(priv, probe, "initialized device (regs %x, irq %d)\n", priv->cpsw_res->start, ndev->irq); + if (priv->data.dual_emac) { + ret = cpsw_probe_dual_emac(pdev, priv); + if (ret) { + cpsw_err(priv, probe, "error probe slave 2 emac interface\n"); + goto clean_irq_ret; + } + } + return 0; clean_irq_ret: diff --git a/include/linux/platform_data/cpsw.h b/include/linux/platform_data/cpsw.h index e962cfd552e3..798fb80b024b 100644 --- a/include/linux/platform_data/cpsw.h +++ b/include/linux/platform_data/cpsw.h @@ -21,6 +21,8 @@ struct cpsw_slave_data { char phy_id[MII_BUS_ID_SIZE]; int phy_if; u8 mac_addr[ETH_ALEN]; + u16 dual_emac_res_vlan; /* Reserved VLAN for DualEMAC */ + }; struct cpsw_platform_data { @@ -36,6 +38,7 @@ struct cpsw_platform_data { u32 rx_descs; /* Number of Rx Descriptios */ u32 mac_control; /* Mac control register */ u16 default_vlan; /* Def VLAN for ALE lookup in VLAN aware mode*/ + bool dual_emac; /* Enable Dual EMAC mode */ }; #endif /* __CPSW_H__ */ -- cgit