From patchwork Mon Mar 29 21:33:57 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cristian Birsan X-Patchwork-Id: 12171341 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-17.0 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8AFF8C433C1 for ; Tue, 30 Mar 2021 01:02:15 +0000 (UTC) Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id EF79361601 for ; Tue, 30 Mar 2021 01:02:14 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org EF79361601 Authentication-Results: mail.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=microchip.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding :Content-Type:List-Subscribe:List-Help:List-Post:List-Archive: List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To:Message-ID:Date: Subject:CC:To:From:Reply-To:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=xULm05Z4LlM5ZGg4Yb2N8InueXRvu0zZctnyxUjpu3o=; b=mJ8ehNAgGotR5GW5UQvkFhw32 xJyBbHW4PF+hM3CowhFG4Nv+/WbCwIg6P27uWVl5fCQtJiJzuBLyB3ENHlN3pz9HMOdvA+R2hwk23 OgAqoqNzwr17lVgdvogzUPLa6EsZXfVd2YXk5/ThEF0syK3DtAW9SLtdlpq9tKXd4a5m5TBcCk6C0 JcOUP3PbQoWOLNSvOmbvcbEAm44Wc51aX3HbsX6bOa7i/Uc+l8QvIZQ23vOgkm2HedfYVK4CAB0hR WtRJV4dzGDAEDbKB8+EnN4MozBqQFtG7NxUP44wKZXuaLgGn/YKj37EgHXwsD5tyT5lqUC8/TWDI5 eF/g2MB5A==; Received: from localhost ([::1] helo=desiato.infradead.org) by desiato.infradead.org with esmtp (Exim 4.94 #2 (Red Hat Linux)) id 1lR2jd-002Hqz-SS; Tue, 30 Mar 2021 00:59:58 +0000 Received: from esa.microchip.iphmx.com ([68.232.153.233]) by desiato.infradead.org with esmtps (Exim 4.94 #2 (Red Hat Linux)) id 1lQzWn-001Txa-AC for linux-arm-kernel@lists.infradead.org; Mon, 29 Mar 2021 21:34:40 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=microchip.com; i=@microchip.com; q=dns/txt; s=mchp; t=1617053669; x=1648589669; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=ouuKmK1tT5f/XSKQEwLkolpYHk5kYVEvIHKQrcf4+0A=; b=01W0a+7yd7zPv6oKhIBK8oLpTpnPq/igRzhVHuIAVZTNEREsvEKZ2JhF jNVfK/11oA3kFvC5twIaYCsMCreWhHGMwK/q7SNgpQYTCLfvDUyEGwk5F rusJvccbAKKvnWt5ZX1AXAQ5V2Brl/9rQxSCVAGhMMtj4s4XyNQlgGbMY MuQUp/XI4GW6kLRlS8cujg/GbxW21Uwnh1sAcMzuL3+VxqG4ahe0crG8x HhlK35HKD1dsRHwv7teV0dyDE9FeXZ8oVaY8GFzGTYkpg6YXwE0oII/xw wWGCtb15aJJAVr77V7cEiEBVqj57L0J6tOarxArAO/G/GPx+bEb64h0Ls Q==; IronPort-SDR: Ky5ev9pBVpZ7doQ26qCNJpFy4+cwfihrRABWK8vltLeI9PtbD6rbo8YTHskiYAn8AgMsYzuoTy vvzEkBWZ13VgaHc1pCpErqJ0iLCGjk0BzC/nnfX8GfUvHA3Y25JBFOePfqu8tu8pMlW8znX9At Z5XYJ2AUZ7wOs92sAps0UT++AXrVyz9yM7xF3r+yFGNjcbwVTMcsQiw/ecqov8gfpCtxyptGsJ 1gcS8qDGrRbMAZHCGE/nZZmIUXdU0BHQdWYVhpzEaiod7h/wrmxElxqXTe2wPbbroWR0pWtYJu wTs= X-IronPort-AV: E=Sophos;i="5.81,288,1610434800"; d="scan'208";a="120956204" Received: from smtpout.microchip.com (HELO email.microchip.com) ([198.175.253.82]) by esa1.microchip.iphmx.com with ESMTP/TLS/AES256-SHA256; 29 Mar 2021 14:34:27 -0700 Received: from chn-vm-ex03.mchp-main.com (10.10.85.151) by chn-vm-ex02.mchp-main.com (10.10.85.144) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2176.2; Mon, 29 Mar 2021 14:34:26 -0700 Received: from cristi-P53.amer.actel.com (10.10.115.15) by chn-vm-ex03.mchp-main.com (10.10.85.151) with Microsoft SMTP Server id 15.1.2176.2 via Frontend Transport; Mon, 29 Mar 2021 14:34:24 -0700 From: To: , , , CC: , , , , Cristian Birsan Subject: [RFC PATCH 2/2] usb: typec: sama7g5_tcpc: add driver for Microchip sama7g5 tcpc Date: Tue, 30 Mar 2021 00:33:57 +0300 Message-ID: <20210329213357.431083-3-cristian.birsan@microchip.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210329213357.431083-1-cristian.birsan@microchip.com> References: <20210329213357.431083-1-cristian.birsan@microchip.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20210329_223437_542040_5D8100E5 X-CRM114-Status: GOOD ( 22.64 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org From: Cristian Birsan This patch adds initial driver support for the new Microchip USB Type-C Port Controller (TCPC) embedded in sama7g5 SoC. Signed-off-by: Cristian Birsan --- drivers/usb/typec/tcpm/Kconfig | 8 + drivers/usb/typec/tcpm/Makefile | 1 + drivers/usb/typec/tcpm/sama7g5_tcpc.c | 602 ++++++++++++++++++++++++++ 3 files changed, 611 insertions(+) create mode 100644 drivers/usb/typec/tcpm/sama7g5_tcpc.c diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig index 557f392fe24d..8ba0fd85741f 100644 --- a/drivers/usb/typec/tcpm/Kconfig +++ b/drivers/usb/typec/tcpm/Kconfig @@ -52,6 +52,14 @@ config TYPEC_FUSB302 Type-C Port Controller Manager to provide USB PD and USB Type-C functionalities. +config TYPEC_SAMA7G5 + tristate "Microchip SAMA7G5 Type-C Port Controller driver" + select REGMAP_MMIO + help + Say Y or M here if your system has SAMA7G5 TCPC controller. + It works with Type-C Port Controller Manager to provide USB + Type-C functionalities. + config TYPEC_WCOVE tristate "Intel WhiskeyCove PMIC USB Type-C PHY driver" depends on ACPI diff --git a/drivers/usb/typec/tcpm/Makefile b/drivers/usb/typec/tcpm/Makefile index 7d499f3569fd..9abe8a7ae1cc 100644 --- a/drivers/usb/typec/tcpm/Makefile +++ b/drivers/usb/typec/tcpm/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC_TCPM) += tcpm.o obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o +obj-$(CONFIG_TYPEC_SAMA7G5) += sama7g5_tcpc.o obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o typec_wcove-y := wcove.o obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o diff --git a/drivers/usb/typec/tcpm/sama7g5_tcpc.c b/drivers/usb/typec/tcpm/sama7g5_tcpc.c new file mode 100644 index 000000000000..d1a912976418 --- /dev/null +++ b/drivers/usb/typec/tcpm/sama7g5_tcpc.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Microchip SAMA7G5 Type-C Port Controller Driver + * + * Copyright (C) 2021 Microchip Technology, Inc. and its subsidiaries + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SAMA7G5_TCPC_GCLK 32000 + +/* TCPC registers offsets */ +#define TCPC_CR 0x80 /* TCPC Control Register */ +#define TCPC_UPC 0xA0 /* TCPC PHY Control Register */ +#define TCPC_UPS 0xA4 /* TCPC PHY Status Register */ + +#define TCPC_CR_RESET 0x54434301 /* Magic value */ + +/* TCPC PHY Control Register */ +#define TCPC_UPC_BCDETE BIT(29) +#define TCPC_UPC_BCVSRCE BIT(28) +#define TCPC_UPC_BCDETSEL BIT(27) +#define TCPC_UPC_BCIDPSRCE BIT(26) +#define TCPC_UPC_DMPDFE BIT(25) +#define TCPC_UPC_DMPDFD BIT(24) +#define TCPC_UPC_IP_OFF (0 << 12) +#define TCPC_UPC_IP_0P5 (1 << 12) +#define TCPC_UPC_IP_1P5 (2 << 12) +#define TCPC_UPC_IP_3P0 (3 << 12) +#define TCPC_UPC_THRESHOLD0 (0 << 8) +#define TCPC_UPC_THRESHOLD2 (2 << 8) +#define TCPC_UPC_THRESHOLD4 (4 << 8) +#define TCPC_UPC_THRESHOLD6 (6 << 8) + +/* TCPC PHY Status Register */ +#define TCPC_UPS_CC2RDT BIT(4) +#define TCPC_UPS_CC1ID BIT(3) +#define TCPC_UPS_CC_MASK GENMASK(4, 3) +#define TCPC_UPS_CHGDCP BIT(2) +#define TCPC_UPS_DM BIT(1) +#define TCPC_UPS_DP BIT(0) + +#define TCPC_VERSION 0xFC + +/* USB Type-C measurement timings */ +#define T_CC_MEASURE 100 /* 100 ms */ + +#define SAMA7G5_TCPC_VBUS_IRQFLAGS (IRQF_ONESHOT \ + | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING) + +struct sama7g5_tcpc { + struct device *dev; + + struct workqueue_struct *wq; + struct delayed_work measure_work; + + struct regmap *regmap; + void __iomem *base; + + struct clk *pclk; + struct clk *gclk; + + struct gpio_desc *vbus_pin; + struct regulator *vbus; + + /* lock for sharing states */ + struct mutex lock; + + /* port status */ + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc1_status; + enum typec_cc_status cc2_status; + enum typec_cc_status cc1_status_prev; + enum typec_cc_status cc2_status_prev; + + /* mutex used for VBUS detection */ + struct mutex vbus_mutex; + int vbus_present; + int vbus_present_prev; + + unsigned int phy_status; + unsigned int phy_status_old; + + struct tcpc_dev tcpc; + struct tcpm_port *tcpm; +}; + +#define tcpc_to_sama7g5_tcpc(_tcpc_) \ + container_of(_tcpc_, struct sama7g5_tcpc, tcpc) + +static bool sama7g5_tcpc_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TCPC_CR: + case TCPC_UPC: + case TCPC_UPS: + return true; + default: + return false; + } +} + +static bool sama7g5_tcpc_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TCPC_CR: + case TCPC_UPC: + case TCPC_UPS: + return true; + default: + return false; + } +} + +static const struct regmap_config sama7g5_tcpc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TCPC_VERSION, + .readable_reg = sama7g5_tcpc_readable_reg, + .writeable_reg = sama7g5_tcpc_writeable_reg, +}; + +static int sama7g5_tcpc_get_vbus(struct tcpc_dev *tcpc) +{ + struct sama7g5_tcpc *sama7g5_tcpc = tcpc_to_sama7g5_tcpc(tcpc); + int ret; + + mutex_lock(&sama7g5_tcpc->vbus_mutex); + ret = sama7g5_tcpc->vbus_present ? 1 : 0; + mutex_unlock(&sama7g5_tcpc->vbus_mutex); + + return ret; +} + +static int sama7g5_tcpc_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) +{ + struct sama7g5_tcpc *sama7g5_tcpc = tcpc_to_sama7g5_tcpc(tcpc); + int ret; + + mutex_lock(&sama7g5_tcpc->vbus_mutex); + if (on) + ret = regulator_enable(sama7g5_tcpc->vbus); + else + ret = regulator_disable(sama7g5_tcpc->vbus); + mutex_unlock(&sama7g5_tcpc->vbus_mutex); + + return ret; +} + +static int sama7g5_tcpc_set_vconn(struct tcpc_dev *tcpc, bool on) +{ + /* VCONN is not supported */ + return -EPERM; +} + +static int sama7g5_tcpc_get_cc(struct tcpc_dev *tcpc, enum typec_cc_status *cc1, + enum typec_cc_status *cc2) +{ + struct sama7g5_tcpc *sama7g5_tcpc = tcpc_to_sama7g5_tcpc(tcpc); + + mutex_lock(&sama7g5_tcpc->lock); + *cc1 = sama7g5_tcpc->cc1_status; + *cc2 = sama7g5_tcpc->cc2_status; + mutex_unlock(&sama7g5_tcpc->lock); + + return 0; +} + +static int sama7g5_tcpc_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) +{ + struct sama7g5_tcpc *sama7g5_tcpc = tcpc_to_sama7g5_tcpc(tcpc); + unsigned int ctrl; + int ret = 0; + + mutex_lock(&sama7g5_tcpc->lock); + switch (cc) { + case TYPEC_CC_RD: + ctrl = TCPC_UPC_IP_OFF; + break; + case TYPEC_CC_RP_DEF: + ctrl = TCPC_UPC_IP_0P5; + break; + default: + ret = -EINVAL; + goto done; + } + ret = regmap_write(sama7g5_tcpc->regmap, TCPC_UPC, ctrl); +done: + mutex_unlock(&sama7g5_tcpc->lock); + return ret; +} + +static int sama7g5_tcpc_set_polarity(struct tcpc_dev *tcpc, + enum typec_cc_polarity pol) +{ + return 0; +} + +static int sama7g5_tcpc_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role role, enum typec_data_role data) +{ + return 0; +} + +static int sama7g5_tcpc_set_pd_rx(struct tcpc_dev *tcpc, bool on) +{ + return -EPERM; +} + +static int sama7g5_tcpc_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg) +{ + return -EPERM; +} + +static int sama7g5_tcpc_start_toggling(struct tcpc_dev *tcpc, + enum typec_port_type port_type, + enum typec_cc_status cc) +{ + return -EOPNOTSUPP; +} + +static void _sama7g5_tcpc_measure_snk(struct sama7g5_tcpc *sama7g5_tcpc) +{ + int ret; + + /* Save previous CC1/CC2 state */ + sama7g5_tcpc->cc1_status_prev = sama7g5_tcpc->cc1_status; + sama7g5_tcpc->cc2_status_prev = sama7g5_tcpc->cc2_status; + + /* Comparator Threshold 2 */ + ret = regmap_write(sama7g5_tcpc->regmap, TCPC_UPC, TCPC_UPC_IP_OFF | + TCPC_UPC_THRESHOLD2); + if (ret) + dev_err(sama7g5_tcpc->dev, "failed to wite register: %d\n", + ret); + + usleep_range(560, 1000); + + ret = regmap_read(sama7g5_tcpc->regmap, TCPC_UPS, + &sama7g5_tcpc->phy_status); + if (ret) + dev_err(sama7g5_tcpc->dev, "failed to read register: %d\n", + ret); + + if (!(sama7g5_tcpc->phy_status & TCPC_UPS_CC_MASK)) { + /* VRa*/ + sama7g5_tcpc->cc1_status = TYPEC_CC_OPEN; + sama7g5_tcpc->cc2_status = TYPEC_CC_OPEN; + return; + } + + /* CC1 or CC2 is connected wait for PD messages to end ~ 30ms */ + usleep_range(30000, 35000); + + /* Comparator Threshold 4 */ + sama7g5_tcpc->phy_status_old = sama7g5_tcpc->phy_status; + + ret = regmap_write(sama7g5_tcpc->regmap, TCPC_UPC, TCPC_UPC_IP_OFF | + TCPC_UPC_THRESHOLD4); + if (ret) + dev_err(sama7g5_tcpc->dev, "failed to wite register: %d\n", + ret); + + usleep_range(560, 1000); + ret = regmap_read(sama7g5_tcpc->regmap, TCPC_UPS, + &sama7g5_tcpc->phy_status); + if (ret) + dev_err(sama7g5_tcpc->dev, "failed to read register: %d\n", + ret); + + if ((sama7g5_tcpc->phy_status_old & TCPC_UPS_CC1ID) && + (!(sama7g5_tcpc->phy_status & TCPC_UPS_CC1ID))) { + sama7g5_tcpc->cc1_status = TYPEC_CC_RP_DEF; + sama7g5_tcpc->cc2_status = TYPEC_CC_OPEN; + return; + } + + if ((sama7g5_tcpc->phy_status_old & TCPC_UPS_CC2RDT) && + (!(sama7g5_tcpc->phy_status & TCPC_UPS_CC2RDT))) { + sama7g5_tcpc->cc1_status = TYPEC_CC_OPEN; + sama7g5_tcpc->cc2_status = TYPEC_CC_RP_DEF; + return; + } + + /* Comparator Threshold 6 */ + sama7g5_tcpc->phy_status_old = sama7g5_tcpc->phy_status; + + ret = regmap_write(sama7g5_tcpc->regmap, TCPC_UPC, TCPC_UPC_IP_OFF | + TCPC_UPC_THRESHOLD6); + if (ret) + dev_err(sama7g5_tcpc->dev, "failed to wite register: %d\n", + ret); + + usleep_range(560, 1000); + ret = regmap_read(sama7g5_tcpc->regmap, TCPC_UPS, + &sama7g5_tcpc->phy_status); + if (ret) + dev_err(sama7g5_tcpc->dev, "failed to read register: %d\n", + ret); + + if ((sama7g5_tcpc->phy_status_old & TCPC_UPS_CC1ID) && + (!(sama7g5_tcpc->phy_status & TCPC_UPS_CC1ID))) { + sama7g5_tcpc->cc1_status = TYPEC_CC_RP_1_5; + sama7g5_tcpc->cc2_status = TYPEC_CC_OPEN; + return; + } + + if ((sama7g5_tcpc->phy_status_old & TCPC_UPS_CC1ID) && + ((sama7g5_tcpc->phy_status & TCPC_UPS_CC1ID))) { + sama7g5_tcpc->cc1_status = TYPEC_CC_RP_3_0; + sama7g5_tcpc->cc2_status = TYPEC_CC_OPEN; + return; + } + + if ((sama7g5_tcpc->phy_status_old & TCPC_UPS_CC2RDT) && + (!(sama7g5_tcpc->phy_status & TCPC_UPS_CC2RDT))) { + sama7g5_tcpc->cc1_status = TYPEC_CC_OPEN; + sama7g5_tcpc->cc2_status = TYPEC_CC_RP_1_5; + return; + } + + if ((sama7g5_tcpc->phy_status_old & TCPC_UPS_CC2RDT) && + ((sama7g5_tcpc->phy_status & TCPC_UPS_CC2RDT))) { + sama7g5_tcpc->cc1_status = TYPEC_CC_OPEN; + sama7g5_tcpc->cc2_status = TYPEC_CC_RP_3_0; + return; + } +} + +static void sama7g5_tcpc_measure_work(struct work_struct *work) +{ + struct sama7g5_tcpc *port = container_of(work, struct sama7g5_tcpc, + measure_work.work); + + mutex_lock(&port->lock); + + _sama7g5_tcpc_measure_snk(port); + + /* Check if the state has changed and notify TCPM */ + if (port->cc1_status != port->cc1_status_prev || + port->cc2_status != port->cc2_status_prev) + tcpm_cc_change(port->tcpm); + + mod_delayed_work(port->wq, &port->measure_work, + msecs_to_jiffies(T_CC_MEASURE)); + + mutex_unlock(&port->lock); +} + +static int sama7g5_tcpc_init(struct tcpc_dev *tcpc) +{ + struct sama7g5_tcpc *sama7g5_tcpc = tcpc_to_sama7g5_tcpc(tcpc); + int ret; + + ret = regmap_write(sama7g5_tcpc->regmap, TCPC_CR, TCPC_CR_RESET); + if (ret) + return ret; + + sama7g5_tcpc->wq = + create_singlethread_workqueue(dev_name(sama7g5_tcpc->dev)); + if (!sama7g5_tcpc->wq) + return -ENOMEM; + + INIT_DELAYED_WORK(&sama7g5_tcpc->measure_work, + sama7g5_tcpc_measure_work); + + sama7g5_tcpc->cc1_status = TYPEC_CC_OPEN; + sama7g5_tcpc->cc2_status = TYPEC_CC_OPEN; + sama7g5_tcpc->cc1_status_prev = TYPEC_CC_OPEN; + sama7g5_tcpc->cc2_status_prev = TYPEC_CC_OPEN; + sama7g5_tcpc->cc_polarity = TYPEC_POLARITY_CC1; + + /* We do not have an interrupt so polling only */ + mod_delayed_work(sama7g5_tcpc->wq, &sama7g5_tcpc->measure_work, + msecs_to_jiffies(T_CC_MEASURE)); + + /* Enable VBUS detection */ + if (sama7g5_tcpc->vbus_pin) + enable_irq(gpiod_to_irq(sama7g5_tcpc->vbus_pin)); + + return 0; +} + +static int vbus_is_present(struct sama7g5_tcpc *sama7g5_tcpc) +{ + if (sama7g5_tcpc->vbus_pin) + return gpiod_get_value(sama7g5_tcpc->vbus_pin); + + /* No Vbus detection: Assume always present */ + return 1; +} + +static irqreturn_t sama7g5_vbus_irq_thread(int irq, void *devid) +{ + struct sama7g5_tcpc *sama7g5_tcpc = devid; + + /* debounce */ + udelay(10); + + mutex_lock(&sama7g5_tcpc->vbus_mutex); + + sama7g5_tcpc->vbus_present = vbus_is_present(sama7g5_tcpc); + if (sama7g5_tcpc->vbus_present != sama7g5_tcpc->vbus_present_prev) { + /* VBUS changed, notify TCPM */ + tcpm_vbus_change(sama7g5_tcpc->tcpm); + sama7g5_tcpc->vbus_present_prev = sama7g5_tcpc->vbus_present; + } + + mutex_unlock(&sama7g5_tcpc->vbus_mutex); + return IRQ_HANDLED; +} + +static int sama7g5_tcpc_probe(struct platform_device *pdev) +{ + int ret; + struct sama7g5_tcpc *sama7g5_tcpc; + + struct resource *mem; + void __iomem *base; + + sama7g5_tcpc = devm_kzalloc(&pdev->dev, sizeof(*sama7g5_tcpc), + GFP_KERNEL); + if (!sama7g5_tcpc) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + sama7g5_tcpc->base = base; + + sama7g5_tcpc->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sama7g5_tcpc_regmap_config); + if (IS_ERR(sama7g5_tcpc->regmap)) { + dev_err(&pdev->dev, "Regmap init failed\n"); + return PTR_ERR(sama7g5_tcpc->regmap); + } + + /* Get the peripheral clock */ + sama7g5_tcpc->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(sama7g5_tcpc->pclk)) { + ret = PTR_ERR(sama7g5_tcpc->pclk); + dev_err(&pdev->dev, + "failed to get the peripheral clock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(sama7g5_tcpc->pclk); + if (ret) { + dev_err(&pdev->dev, + "failed to enable the peripheral clock: %d\n", ret); + return ret; + } + + /* Get the generic clock */ + sama7g5_tcpc->gclk = devm_clk_get(&pdev->dev, "gclk"); + if (IS_ERR(sama7g5_tcpc->gclk)) { + ret = PTR_ERR(sama7g5_tcpc->gclk); + dev_err(&pdev->dev, + "failed to get the PMC generic clock: %d\n", ret); + return ret; + } + + ret = clk_set_rate(sama7g5_tcpc->gclk, SAMA7G5_TCPC_GCLK); + if (ret) { + dev_err(&pdev->dev, + "unable to change gclk rate to: %u\n", + SAMA7G5_TCPC_GCLK); + return ret; + } + + ret = clk_prepare_enable(sama7g5_tcpc->gclk); + if (ret) { + dev_err(&pdev->dev, + "failed to enable the generic clock: %d\n", ret); + return ret; + } + + mutex_init(&sama7g5_tcpc->lock); + mutex_init(&sama7g5_tcpc->vbus_mutex); + + sama7g5_tcpc->vbus_pin = devm_gpiod_get_optional(&pdev->dev, + "microchip,vbus", GPIOD_IN); + + if (IS_ERR(sama7g5_tcpc->vbus_pin)) { + ret = PTR_ERR(sama7g5_tcpc->vbus_pin); + dev_err(&pdev->dev, "unable to claim vbus-gpio: %d\n", ret); + } + + sama7g5_tcpc->vbus = devm_regulator_get_optional(&pdev->dev, "vbus"); + + if (IS_ERR(sama7g5_tcpc->vbus)) { + ret = PTR_ERR(sama7g5_tcpc->vbus); + dev_err(&pdev->dev, "unable to claim vbus-supply: %d\n", ret); + } + + if (sama7g5_tcpc->vbus_pin) { + irq_set_status_flags(gpiod_to_irq(sama7g5_tcpc->vbus_pin), + IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&pdev->dev, + gpiod_to_irq(sama7g5_tcpc->vbus_pin), NULL, + sama7g5_vbus_irq_thread, + SAMA7G5_TCPC_VBUS_IRQFLAGS, + "sama7g5_tcpc", sama7g5_tcpc); + if (ret) { + sama7g5_tcpc->vbus_pin = NULL; + dev_warn(&pdev->dev, + "failed to request vbus irq; " + "assuming always on\n"); + } + } + + sama7g5_tcpc->dev = &pdev->dev; + platform_set_drvdata(pdev, sama7g5_tcpc); + + sama7g5_tcpc->tcpc.init = sama7g5_tcpc_init; + sama7g5_tcpc->tcpc.get_vbus = sama7g5_tcpc_get_vbus; + sama7g5_tcpc->tcpc.set_vbus = sama7g5_tcpc_set_vbus; + sama7g5_tcpc->tcpc.set_cc = sama7g5_tcpc_set_cc; + sama7g5_tcpc->tcpc.get_cc = sama7g5_tcpc_get_cc; + sama7g5_tcpc->tcpc.set_polarity = sama7g5_tcpc_set_polarity; + sama7g5_tcpc->tcpc.set_vconn = sama7g5_tcpc_set_vconn; + sama7g5_tcpc->tcpc.start_toggling = sama7g5_tcpc_start_toggling; + sama7g5_tcpc->tcpc.set_pd_rx = sama7g5_tcpc_set_pd_rx; + sama7g5_tcpc->tcpc.set_roles = sama7g5_tcpc_set_roles; + sama7g5_tcpc->tcpc.pd_transmit = sama7g5_tcpc_pd_transmit; + + sama7g5_tcpc->tcpc.fwnode = device_get_named_child_node(&pdev->dev, + "connector"); + if (!sama7g5_tcpc->tcpc.fwnode) { + dev_err(&pdev->dev, "Can't find connector node.\n"); + return -EINVAL; + } + + sama7g5_tcpc->tcpm = tcpm_register_port(sama7g5_tcpc->dev, + &sama7g5_tcpc->tcpc); + if (IS_ERR(sama7g5_tcpc->tcpm)) { + fwnode_remove_software_node(sama7g5_tcpc->tcpc.fwnode); + return PTR_ERR(sama7g5_tcpc->tcpm); + } + + return 0; +} + +static int sama7g5_tcpc_remove(struct platform_device *pdev) +{ + struct sama7g5_tcpc *sama7g5_tcpc; + + sama7g5_tcpc = platform_get_drvdata(pdev); + + /* Mask everything */ + if (sama7g5_tcpc->vbus_pin) + disable_irq(gpiod_to_irq(sama7g5_tcpc->vbus_pin)); + + + if (!IS_ERR_OR_NULL(sama7g5_tcpc->tcpm)) + tcpm_unregister_port(sama7g5_tcpc->tcpm); + + destroy_workqueue(sama7g5_tcpc->wq); + + clk_disable_unprepare(sama7g5_tcpc->gclk); + clk_disable_unprepare(sama7g5_tcpc->pclk); + + return 0; +} + +static const struct of_device_id sama7g5_tcpc_dt_ids[] = { + { + .compatible = "microchip,sama7g5-tcpc", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sama7g5_tcpc_dt_ids); + +static struct platform_driver sama7g5_tcpc_driver = { + .probe = sama7g5_tcpc_probe, + .remove = sama7g5_tcpc_remove, + .driver = { + .name = "microchip,sama7g5-tcpc", + .of_match_table = sama7g5_tcpc_dt_ids, + }, +}; +module_platform_driver(sama7g5_tcpc_driver); + +MODULE_AUTHOR("Cristian Birsan "); +MODULE_DESCRIPTION("Microchip SAMA7G5 Type-C Port Controller Driver"); +MODULE_LICENSE("GPL");