lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Mon, 24 Apr 2017 06:12:03 -0700
From:   Guenter Roeck <linux@...ck-us.net>
To:     Heikki Krogerus <heikki.krogerus@...ux.intel.com>,
        Hans de Goede <hdegoede@...hat.com>
Cc:     Andy Shevchenko <andriy.shevchenko@...ux.intel.com>,
        MyungJoo Ham <myungjoo.ham@...sung.com>,
        Chanwoo Choi <cw00.choi@...sung.com>,
        "linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
        "Yueyao (Nathan) Zhu (yueyao"@google.com
Subject: Re: [PATCH 2/5] extcon: Add FUSB302 USB TYPE-C controller support

On 04/24/2017 04:02 AM, Heikki Krogerus wrote:
> +Guenter
>
> On Fri, Apr 21, 2017 at 09:51:50PM +0300, Andy Shevchenko wrote:
>> +Cc: Heikki.
>>
>> He might comment on this.
>
> Thanks Andy.
>
>> On Fri, Apr 21, 2017 at 4:01 PM, Hans de Goede <hdegoede@...hat.com> wrote:
>>> Add support for USB TYPE-C cable detection on systems using a
>>> FUSB302 USB TYPE-C controller.
>>>
>>> Signed-off-by: Hans de Goede <hdegoede@...hat.com>
>>> ---
>>>  drivers/extcon/Kconfig          |   8 +
>>>  drivers/extcon/Makefile         |   1 +
>>>  drivers/extcon/extcon-fusb302.c | 782 ++++++++++++++++++++++++++++++++++++++++
>>>  3 files changed, 791 insertions(+)
>>>  create mode 100644 drivers/extcon/extcon-fusb302.c
>
> There is now the typec class in linux-next that really needs to be
> used with all USB Type-C PHYs like fusb302.
>
> Unless I'm mistaken, Guenter has also written a driver for fusb302. I

Not me; it was Nathan.

> have not seen it, but if I understood correctly, that driver will
> register itself with the upcoming tcpm (USB Type-C Port Manager) [1],
> which in practice would mean we can take properly advantage of the USB
> PD transceiver on fusb302.
>
> Guenter! Can you publish the fusb302 driver?
>

Working on it.

Guenter

>
> [1] https://chromium-review.googlesource.com/c/389916/
>
>
>>> diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
>>> index 32f2dc8..562db5b 100644
>>> --- a/drivers/extcon/Kconfig
>>> +++ b/drivers/extcon/Kconfig
>>> @@ -35,6 +35,14 @@ config EXTCON_AXP288
>>>           Say Y here to enable support for USB peripheral detection
>>>           and USB MUX switching by X-Power AXP288 PMIC.
>>>
>>> +config EXTCON_FUSB302
>>> +       tristate "FUSB302 USB TYPE-C controller support"
>>> +       depends on I2C
>>> +       select REGMAP_I2C
>>> +       help
>>> +         Say Y here to enable support for USB TYPE-C cable detection using
>>> +         a FUSB302 USB TYPE-C controller.
>>> +
>>>  config EXTCON_GPIO
>>>         tristate "GPIO extcon support"
>>>         depends on GPIOLIB || COMPILE_TEST
>>> diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
>>> index ecfa958..e5eb493 100644
>>> --- a/drivers/extcon/Makefile
>>> +++ b/drivers/extcon/Makefile
>>> @@ -7,6 +7,7 @@ extcon-core-objs                += extcon.o devres.o
>>>  obj-$(CONFIG_EXTCON_ADC_JACK)  += extcon-adc-jack.o
>>>  obj-$(CONFIG_EXTCON_ARIZONA)   += extcon-arizona.o
>>>  obj-$(CONFIG_EXTCON_AXP288)    += extcon-axp288.o
>>> +obj-$(CONFIG_EXTCON_FUSB302)   += extcon-fusb302.o
>>>  obj-$(CONFIG_EXTCON_GPIO)      += extcon-gpio.o
>>>  obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
>>>  obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
>>> diff --git a/drivers/extcon/extcon-fusb302.c b/drivers/extcon/extcon-fusb302.c
>>> new file mode 100644
>>> index 0000000..577adb9
>>> --- /dev/null
>>> +++ b/drivers/extcon/extcon-fusb302.c
>>> @@ -0,0 +1,782 @@
>>> +/*
>>> + * Extcon USB-C cable detection driver for FUSB302 USB TYPE-C controller
>>> + *
>>> + * Copyright (C) 2017 Hans de Goede <hdegoede@...hat.com>
>>> + *
>>> + * 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.
>>> + */
>>> +
>>> +#include <linux/extcon.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/workqueue.h>
>>> +#include "extcon.h"
>>> +
>>> +#define REG_DEVICE_ID                          0x01
>>> +#define DEVICE_ID_VER_MASK                     GENMASK(7, 4)
>>> +#define DEVICE_ID_FUSB302_VER                  0x80
>>> +
>>> +#define REG_SWITCHES0                          0x02
>>> +#define SWITCHES0_PDWN1                                BIT(0)
>>> +#define SWITCHES0_PDWN2                                BIT(1)
>>> +#define SWITCHES0_PDWN                         GENMASK(1, 0)
>>> +#define SWITCHES0_MEAS_CC1                     BIT(2)
>>> +#define SWITCHES0_MEAS_CC2                     BIT(3)
>>> +#define SWITCHES0_VCONN_CC1                    BIT(4)
>>> +#define SWITCHES0_VCONN_CC2                    BIT(5)
>>> +#define SWITCHES0_PU_EN1                       BIT(6)
>>> +#define SWITCHES0_PU_EN2                       BIT(7)
>>> +
>>> +#define REG_MEASURE                            0x04
>>> +#define MEASURE_MDAC_MASK                      GENMASK(5, 0)
>>> +/* Datasheet says MDAC must be set to 0x34 / 2226mV for vRd-3.0 detection */
>>> +#define MEASURE_MDAC_SNK_VRD30                 0x34
>>> +/* MDAC must be set to 0x25 / 1600mV for disconnect det. with 80uA host-cur */
>>> +#define MEASURE_MDAC_SRC_80UA_HOST_CUR         0x25
>>> +
>>> +#define REG_CONTROL0                           0x06
>>> +#define CONTROL0_HOST_CUR_MASK                 GENMASK(3, 2)
>>> +#define CONTROL0_HOST_CUR_DISABLED             (0 << 2)
>>> +#define CONTROL0_HOST_CUR_80UA                 (1 << 2)
>>> +#define CONTROL0_HOST_CUR_180UA                        (2 << 2)
>>> +#define CONTROL0_HOST_CUR_330UA                        (3 << 2)
>>> +#define CONTROL0_INT_MASK                      BIT(5)
>>> +
>>> +#define REG_CONTROL2                           0x08
>>> +#define CONTROL2_TOGGLE                                BIT(0)
>>> +#define CONTROL2_MODE_MASK                     GENMASK(2, 1)
>>> +#define CONTROL2_MODE_DRP                      (1 << 1)
>>> +#define CONTROL2_MODE_SNK                      (2 << 1)
>>> +#define CONTROL2_MODE_SRC                      (3 << 1)
>>> +#define CONTROL2_SAVE_PWR_MASK                 GENMASK(7, 6)
>>> +#define CONTROL2_SAVE_PWR_DISABLED             (0 << 6)
>>> +#define CONTROL2_SAVE_PWR_40MS                 (1 << 6)
>>> +#define CONTROL2_SAVE_PWR_80MS                 (2 << 6)
>>> +#define CONTROL2_SAVE_PWR_160MS                        (3 << 6)
>>> +
>>> +#define REG_MASK1                              0x0a
>>> +/* REG_MASK1 value for low-power / disabled state from datasheet */
>>> +#define MASK1_DISABLED                         0xfe
>>> +#define MASK1_COMP_CHNG                                BIT(5)
>>> +#define MASK1_VBUSOK                           BIT(7)
>>> +
>>> +#define REG_POWER                              0x0b
>>> +/* REG_POWER values for disabled and normal state from datasheet */
>>> +#define POWER_DISABLED                         BIT(0)
>>> +#define POWER_NORMAL                           GENMASK(2, 0)
>>> +
>>> +#define REG_RESET                              0x0c
>>> +#define RESET_SW_RESET                         BIT(0)
>>> +
>>> +#define REG_OCP                                        0x0d
>>> +
>>> +#define REG_MASKA                              0x0e
>>> +/* REG_MASKA value for low-power / disabled state from datasheet */
>>> +#define MASKA_DISABLED                         0xbf
>>> +
>>> +#define REG_MASKB                              0x0f
>>> +/* REG_MASKB value for low-power / disabled state from datasheet */
>>> +#define MASKB_DISABLED                         0x01
>>> +
>>> +#define REG_STATUS1A                           0x3d
>>> +#define STATUS1A_TOGSS_MASK                    GENMASK(5, 3)
>>> +#define STATUS1A_TOGSS_SRC_CC1                 (1 << 3)
>>> +#define STATUS1A_TOGSS_SRC_CC2                 (2 << 3)
>>> +#define STATUS1A_TOGSS_SNK_CC1                 (5 << 3)
>>> +#define STATUS1A_TOGSS_SNK_CC2                 (6 << 3)
>>> +
>>> +#define REG_INTERRUPTA                         0x3e
>>> +#define INTERRUPTA_TOGDONE                     BIT(6)
>>> +
>>> +#define REG_STATUS0                            0x40
>>> +#define STATUS0_BC_LEVEL_MASK                  GENMASK(1, 0)
>>> +#define STATUS0_BC_LEVEL_VRA                   0
>>> +#define STATUS0_BC_LEVEL_VRDUSB                        1
>>> +#define STATUS0_BC_LEVEL_VRD15                 2
>>> +#define STATUS0_BC_LEVEL_VRD30                 3
>>> +#define STATUS0_COMP                           BIT(5)
>>> +#define STATUS0_VBUSOK                         BIT(7)
>>> +
>>> +#define REG_INTERRUPT                          0x42
>>> +#define INTERRUPT_BC_LVL                       BIT(0)
>>> +#define INTERRUPT_COMP_CHNG                    BIT(5)
>>> +#define INTERRUPT_VBUSOK                       BIT(7)
>>> +
>>> +/* Timeouts from the FUSB302 datasheet */
>>> +#define TTOGCYCLE                              msecs_to_jiffies(40 + 60 + 40)
>>> +
>>> +/* Timeouts from the USB-C specification */
>>> +#define TPDDEBOUNCE                            msecs_to_jiffies(20)
>>> +#define TDRPTRY_MS                             75
>>> +#define TDRPTRY                                        msecs_to_jiffies(TDRPTRY_MS)
>>> +#define TDRPTRYWAIT                            msecs_to_jiffies(400)
>>> +#define TVBUSON                                        msecs_to_jiffies(275)
>>> +
>>> +enum typec_port_type {
>>> +       TYPEC_PORT_DFP,
>>> +       TYPEC_PORT_UFP,
>>> +       TYPEC_PORT_DRP,
>>> +};
>>> +
>>> +enum typec_role {
>>> +       TYPEC_SINK,
>>> +       TYPEC_SOURCE,
>>> +};
>>> +
>>> +enum fusb302_state {
>>> +       DISABLED_SNK, /* UFP */
>>> +       DISABLED_SRC, /* DFP */
>>> +       DISABLED_DRP,
>>> +       UNATTACHED_SNK,
>>> +       ATTACHED_SNK,
>>> +       UNATTACHED_SRC,
>>> +       ATTACHED_SRC,
>>> +};
>>> +/* For debugging */
>>> +static __maybe_unused const char *fusb302_state_str[] = {
>>> +       "DISABLED_SNK",
>>> +       "DISABLED_SRC",
>>> +       "DISABLED_DRP",
>>> +       "UNATTACHED_SNK",
>>> +       "ATTACHED_SNK",
>>> +       "UNATTACHED_SRC",
>>> +       "ATTACHED_SRC",
>>> +};
>>> +
>>> +enum fusb302_event {
>>> +       TOGGLE_DONE,
>>> +       BC_LVL_CHANGE,
>>> +       VBUS_VALID,
>>> +       VBUS_INVALID,
>>> +       COMP_LOW,
>>> +       COMP_HIGH,
>>> +       TIMEOUT,
>>> +       FALLBACK_TIMEOUT,
>>> +};
>>> +/* For debugging */
>>> +static __maybe_unused const char *fusb302_event_str[] = {
>>> +       "TOGGLE_DONE",
>>> +       "BC_LVL_CHANGE",
>>> +       "VBUS_VALID",
>>> +       "VBUS_INVALID",
>>> +       "COMP_LOW",
>>> +       "COMP_HIGH",
>>> +       "TIMEOUT",
>>> +       "FALLBACK_TIMEOUT",
>>> +};
>>> +
>>> +static const unsigned int fusb302_extcon_cables[] = {
>>> +       EXTCON_USB,
>>> +       EXTCON_USB_HOST,
>>> +       EXTCON_CHG_USB_SDP,
>>> +       EXTCON_CHG_USB_CDP,
>>> +       EXTCON_CHG_USB_FAST,
>>> +       EXTCON_NONE,
>>> +};
>>> +
>>> +struct fusb302_data {
>>> +       struct device *dev;
>>> +       struct regmap *regmap;
>>> +       struct extcon_dev *edev;
>>> +       struct mutex lock;
>>> +       struct delayed_work timeout;
>>> +       struct delayed_work fallback_timeout;
>>> +       struct delayed_work bc_work;
>>> +       enum fusb302_state state;
>>> +       enum typec_port_type port_type;
>>> +       enum typec_role preferred_role;
>>> +       int status0;
>>> +       int status1a;
>>> +       unsigned int snk_cable_id;
>>> +};
>>> +
>>> +static void fusb302_write_reg(struct fusb302_data *data, int reg, int val)
>>> +{
>>> +       int ret;
>>> +
>>> +       ret = regmap_write(data->regmap, reg, val);
>>> +       if (ret)
>>> +               dev_err(data->dev, "Error writing reg %02x: %d\n", reg, ret);
>>> +}
>>> +
>>> +static int fusb302_read_reg(struct fusb302_data *data, int reg)
>>> +{
>>> +       int ret, val;
>>> +
>>> +       ret = regmap_read(data->regmap, reg, &val);
>>> +       if (ret) {
>>> +               dev_err(data->dev, "Error reading reg %02x: %d\n", reg, ret);
>>> +               return 0;
>>> +       }
>>> +
>>> +       return val;
>>> +}
>>> +
>>> +static void fusb302_update_bits(struct fusb302_data *data, int reg,
>>> +                               int mask, int val)
>>> +{
>>> +       int ret;
>>> +
>>> +       ret = regmap_update_bits(data->regmap, reg, mask, val);
>>> +       if (ret)
>>> +               dev_err(data->dev, "Error modifying reg %02x: %d\n", reg, ret);
>>> +}
>>> +
>>> +/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
>>> +static void fusb302_set_extcon_state(struct fusb302_data *data,
>>> +                                    unsigned int cable, bool state)
>>> +{
>>> +       extcon_set_state_sync(data->edev, cable, state);
>>> +       if (cable == EXTCON_CHG_USB_SDP)
>>> +               extcon_set_state_sync(data->edev, EXTCON_USB, state);
>>> +}
>>> +
>>> +/* Helper for the disabled states */
>>> +static void fusb302_disable(struct fusb302_data *data)
>>> +{
>>> +       fusb302_write_reg(data, REG_POWER, POWER_DISABLED);
>>> +       fusb302_write_reg(data, REG_MASK1, MASK1_DISABLED);
>>> +       fusb302_write_reg(data, REG_MASKA, MASKA_DISABLED);
>>> +       fusb302_write_reg(data, REG_MASKB, MASKB_DISABLED);
>>> +       fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
>>> +}
>>> +
>>> +/*
>>> + * fusb302_set_state() and fusb302_handle_event() implement the 3 state
>>> + * machines from the datasheet folded into 1 state-machine for code re-use.
>>> + */
>>> +static void fusb302_set_state(struct fusb302_data *data,
>>> +                             enum fusb302_state state)
>>> +{
>>> +       int status, switches0 = 0;
>>> +       enum fusb302_state old_state = data->state;
>>> +       union extcon_property_value prop;
>>> +
>>> +       /* Kill pending timeouts from previous state */
>>> +       cancel_delayed_work(&data->timeout);
>>> +       cancel_delayed_work(&data->fallback_timeout);
>>> +       cancel_delayed_work(&data->bc_work);
>>> +
>>> +       dev_dbg(data->dev, "New state %s\n", fusb302_state_str[state]);
>>> +
>>> +       switch (old_state) {
>>> +       case ATTACHED_SNK:
>>> +               fusb302_set_extcon_state(data, data->snk_cable_id, false);
>>> +               break;
>>> +       case ATTACHED_SRC:
>>> +               fusb302_set_extcon_state(data, EXTCON_USB_HOST, false);
>>> +               break;
>>> +       default:
>>> +               break; /* Do nothing */
>>> +       }
>>> +
>>> +       switch (state) {
>>> +       case DISABLED_SNK:
>>> +               fusb302_disable(data);
>>> +               fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
>>> +                                   CONTROL2_MODE_SNK);
>>> +               fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
>>> +                                   CONTROL2_TOGGLE);
>>> +               break;
>>> +
>>> +       case DISABLED_SRC:
>>> +               fusb302_disable(data);
>>> +               fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
>>> +                                   CONTROL2_MODE_SRC);
>>> +               fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
>>> +                                   CONTROL2_TOGGLE);
>>> +               break;
>>> +
>>> +       case DISABLED_DRP:
>>> +               fusb302_disable(data);
>>> +               fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
>>> +                                   CONTROL2_MODE_DRP);
>>> +               fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
>>> +                                   CONTROL2_TOGGLE);
>>> +               break;
>>> +
>>> +       case UNATTACHED_SNK:
>>> +               fusb302_write_reg(data, REG_POWER, POWER_NORMAL);
>>> +
>>> +               /* Enable pull-down on CC1 / CC2 based on orientation */
>>> +               switch (data->status1a & STATUS1A_TOGSS_MASK) {
>>> +               case STATUS1A_TOGSS_SNK_CC1:
>>> +                       switches0 = SWITCHES0_PDWN1 | SWITCHES0_MEAS_CC1;
>>> +                       prop.intval = USB_TYPEC_POLARITY_NORMAL;
>>> +                       break;
>>> +               case STATUS1A_TOGSS_SNK_CC2:
>>> +                       switches0 = SWITCHES0_PDWN2 | SWITCHES0_MEAS_CC2;
>>> +                       prop.intval = USB_TYPEC_POLARITY_FLIP;
>>> +                       break;
>>> +               }
>>> +               fusb302_write_reg(data, REG_SWITCHES0, switches0);
>>> +               fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
>>> +               extcon_set_property(data->edev, EXTCON_USB,
>>> +                                   EXTCON_PROP_USB_TYPEC_POLARITY, prop);
>>> +
>>> +               /* Enable VBUSOK and COMP irq at 2.24V for BC detection */
>>> +               fusb302_update_bits(data, REG_MASK1,
>>> +                                   MASK1_VBUSOK | MASK1_COMP_CHNG, 0);
>>> +               fusb302_update_bits(data, REG_MEASURE, MEASURE_MDAC_MASK,
>>> +                                   MEASURE_MDAC_SNK_VRD30);
>>> +
>>> +               status = fusb302_read_reg(data, REG_STATUS0);
>>> +               if (status & STATUS0_VBUSOK) {
>>> +                       /* Go straight to ATTACHED_SNK */
>>> +                       fusb302_set_state(data, ATTACHED_SNK);
>>> +                       return;
>>> +               }
>>> +
>>> +               mod_delayed_work(system_wq, &data->timeout, TVBUSON);
>>> +               break;
>>> +
>>> +       case ATTACHED_SNK:
>>> +               mod_delayed_work(system_wq, &data->bc_work, TPDDEBOUNCE);
>>> +               break;
>>> +
>>> +       case UNATTACHED_SRC:
>>> +               fusb302_write_reg(data, REG_POWER, POWER_NORMAL);
>>> +
>>> +               /* Enable pull-up / Vconn on CC1 / CC2 based on orientation */
>>> +               switch (data->status1a & STATUS1A_TOGSS_MASK) {
>>> +               case STATUS1A_TOGSS_SRC_CC1:
>>> +                       switches0 = SWITCHES0_PU_EN1 | SWITCHES0_VCONN_CC2 |
>>> +                                   SWITCHES0_MEAS_CC1;
>>> +                       prop.intval = USB_TYPEC_POLARITY_NORMAL;
>>> +                       break;
>>> +               case STATUS1A_TOGSS_SRC_CC2:
>>> +                       switches0 = SWITCHES0_PU_EN2 | SWITCHES0_VCONN_CC1 |
>>> +                                   SWITCHES0_MEAS_CC2;
>>> +                       prop.intval = USB_TYPEC_POLARITY_FLIP;
>>> +                       break;
>>> +               }
>>> +               fusb302_write_reg(data, REG_SWITCHES0, switches0);
>>> +               fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
>>> +               extcon_set_property(data->edev, EXTCON_USB,
>>> +                                   EXTCON_PROP_USB_TYPEC_POLARITY, prop);
>>> +
>>> +               /* Enable COMP irq at 1.6V for detach detection */
>>> +               fusb302_update_bits(data, REG_MASK1, MASK1_COMP_CHNG, 0);
>>> +               fusb302_update_bits(data, REG_MEASURE, MEASURE_MDAC_MASK,
>>> +                                   MEASURE_MDAC_SRC_80UA_HOST_CUR);
>>> +
>>> +               status = fusb302_read_reg(data, REG_STATUS0);
>>> +               if (!(status & STATUS0_COMP)) {
>>> +                       /* Go straight to ATTACHED_SRC */
>>> +                       fusb302_set_state(data, ATTACHED_SRC);
>>> +                       return;
>>> +               }
>>> +
>>> +               mod_delayed_work(system_wq, &data->timeout, TPDDEBOUNCE);
>>> +               break;
>>> +
>>> +       case ATTACHED_SRC:
>>> +               fusb302_set_extcon_state(data, EXTCON_USB_HOST, true);
>>> +               break;
>>> +       }
>>> +
>>> +       data->state = state;
>>> +}
>>> +
>>> +static void fusb302_set_state_disabled(struct fusb302_data *data)
>>> +{
>>> +
>>> +       switch (data->port_type) {
>>> +       case TYPEC_PORT_UFP:
>>> +               fusb302_set_state(data, DISABLED_SNK);
>>> +               break;
>>> +       case TYPEC_PORT_DFP:
>>> +               fusb302_set_state(data, DISABLED_SRC);
>>> +               break;
>>> +       case TYPEC_PORT_DRP:
>>> +               fusb302_set_state(data, DISABLED_DRP);
>>> +               break;
>>> +       }
>>> +}
>>> +
>>> +static void fusb302_handle_disabled_snk_event(struct fusb302_data *data,
>>> +                                             int event)
>>> +{
>>> +       switch (event) {
>>> +       case TOGGLE_DONE:
>>> +               switch (data->status1a & STATUS1A_TOGSS_MASK) {
>>> +               case STATUS1A_TOGSS_SNK_CC1:
>>> +               case STATUS1A_TOGSS_SNK_CC2:
>>> +                       fusb302_set_state(data, UNATTACHED_SNK);
>>> +                       break;
>>> +               }
>>> +               break;
>>> +
>>> +       case TIMEOUT:
>>> +               /* Cannot become snk fallback to src */
>>> +               fusb302_set_state(data, DISABLED_SRC);
>>> +               mod_delayed_work(system_wq, &data->fallback_timeout,
>>> +                                TDRPTRYWAIT);
>>> +               break;
>>> +
>>> +       case FALLBACK_TIMEOUT:
>>> +               /* Both states failed return to disabled drp state */
>>> +               fusb302_set_state(data, DISABLED_DRP);
>>> +               break;
>>> +       }
>>> +}
>>> +
>>> +static void fusb302_handle_disabled_src_event(struct fusb302_data *data,
>>> +                                             int event)
>>> +{
>>> +       switch (event) {
>>> +       case TOGGLE_DONE:
>>> +               switch (data->status1a & STATUS1A_TOGSS_MASK) {
>>> +               case STATUS1A_TOGSS_SRC_CC1:
>>> +               case STATUS1A_TOGSS_SRC_CC2:
>>> +                       fusb302_set_state(data, UNATTACHED_SRC);
>>> +                       break;
>>> +               }
>>> +               break;
>>> +
>>> +       case TIMEOUT:
>>> +               /* Cannot become src fallback to snk */
>>> +               fusb302_set_state(data, DISABLED_SNK);
>>> +               mod_delayed_work(system_wq, &data->fallback_timeout,
>>> +                                TDRPTRYWAIT);
>>> +               break;
>>> +
>>> +       case FALLBACK_TIMEOUT:
>>> +               /* Both states failed return to disabled drp state */
>>> +               fusb302_set_state(data, DISABLED_DRP);
>>> +               break;
>>> +       }
>>> +}
>>> +
>>> +static void fusb302_handle_disabled_drp_event(struct fusb302_data *data,
>>> +                                             int event)
>>> +{
>>> +       switch (event) {
>>> +       case TOGGLE_DONE:
>>> +               switch (data->status1a & STATUS1A_TOGSS_MASK) {
>>> +               case STATUS1A_TOGSS_SNK_CC1:
>>> +               case STATUS1A_TOGSS_SNK_CC2:
>>> +                       if (data->preferred_role == TYPEC_SINK) {
>>> +                               /* Jump directly to UNATTACHED_SNK */
>>> +                               fusb302_set_state(data, UNATTACHED_SNK);
>>> +                       } else {
>>> +                               /* Try to become src */
>>> +                               fusb302_set_state(data, DISABLED_SNK);
>>> +                               mod_delayed_work(system_wq, &data->timeout,
>>> +                                                TDRPTRY);
>>> +                       }
>>> +                       break;
>>> +               case STATUS1A_TOGSS_SRC_CC1:
>>> +               case STATUS1A_TOGSS_SRC_CC2:
>>> +                       if (data->preferred_role == TYPEC_SOURCE) {
>>> +                               /* Jump directly to UNATTACHED_SRC */
>>> +                               fusb302_set_state(data, UNATTACHED_SRC);
>>> +                       } else {
>>> +                               /*
>>> +                                * The USB-C spec says we must enable pull-downs
>>> +                                * and then wait tDRPTry before checking to
>>> +                                * avoid endless role-bouncing if both ends
>>> +                                * prefer the snk role.
>>> +                                */
>>> +                               fusb302_write_reg(data, REG_SWITCHES0,
>>> +                                                 SWITCHES0_PDWN);
>>> +                               fusb302_update_bits(data, REG_CONTROL2,
>>> +                                                   CONTROL2_TOGGLE, 0);
>>> +                               msleep(TDRPTRY_MS);
>>> +                               /*
>>> +                                * Use the toggle engine to do the src
>>> +                                * detection to keep things the same as for
>>> +                                * directly entering the src role.
>>> +                                */
>>> +                               fusb302_set_state(data, DISABLED_SNK);
>>> +                               mod_delayed_work(system_wq, &data->timeout,
>>> +                                                TTOGCYCLE);
>>> +                       }
>>> +                       break;
>>> +               }
>>> +               break;
>>> +       }
>>> +}
>>> +
>>> +static void fusb302_handle_unattached_snk_event(struct fusb302_data *data,
>>> +                                             int event)
>>> +{
>>> +       switch (event) {
>>> +       case VBUS_VALID: /* Cable attached */
>>> +               fusb302_set_state(data, ATTACHED_SNK);
>>> +               break;
>>> +       case TIMEOUT:
>>> +               fusb302_set_state_disabled(data);
>>> +               break;
>>> +       }
>>> +}
>>> +
>>> +static void fusb302_handle_attached_snk_event(struct fusb302_data *data,
>>> +                                             int event)
>>> +{
>>> +       switch (event) {
>>> +       case BC_LVL_CHANGE:
>>> +       case COMP_LOW:
>>> +       case COMP_HIGH:
>>> +               mod_delayed_work(system_wq, &data->bc_work, TPDDEBOUNCE);
>>> +               break;
>>> +       case VBUS_INVALID: /* Cable detached */
>>> +               fusb302_set_state_disabled(data);
>>> +               break;
>>> +       }
>>> +}
>>> +
>>> +static void fusb302_handle_unattached_src_event(struct fusb302_data *data,
>>> +                                             int event)
>>> +{
>>> +       switch (event) {
>>> +       case COMP_LOW: /* Cable attached */
>>> +               fusb302_set_state(data, ATTACHED_SRC);
>>> +               break;
>>> +       case TIMEOUT:
>>> +               fusb302_set_state_disabled(data);
>>> +               break;
>>> +       }
>>> +}
>>> +
>>> +static void fusb302_handle_attached_src_event(struct fusb302_data *data,
>>> +                                             int event)
>>> +{
>>> +       switch (event) {
>>> +       case COMP_HIGH: /* Cable detached */
>>> +               fusb302_set_state_disabled(data);
>>> +               break;
>>> +       }
>>> +}
>>> +
>>> +static void fusb302_handle_event(struct fusb302_data *data, int event)
>>> +{
>>> +
>>> +       mutex_lock(&data->lock);
>>> +
>>> +       dev_dbg(data->dev, "Handling state %s event %s status %02x %02x\n",
>>> +               fusb302_state_str[data->state], fusb302_event_str[event],
>>> +               fusb302_read_reg(data, REG_STATUS0),
>>> +               fusb302_read_reg(data, REG_STATUS1A));
>>> +
>>> +       switch (data->state) {
>>> +       case DISABLED_SNK:
>>> +               fusb302_handle_disabled_snk_event(data, event);
>>> +               break;
>>> +       case DISABLED_SRC:
>>> +               fusb302_handle_disabled_src_event(data, event);
>>> +               break;
>>> +       case DISABLED_DRP:
>>> +               fusb302_handle_disabled_drp_event(data, event);
>>> +               break;
>>> +       case UNATTACHED_SNK:
>>> +               fusb302_handle_unattached_snk_event(data, event);
>>> +               break;
>>> +       case ATTACHED_SNK:
>>> +               fusb302_handle_attached_snk_event(data, event);
>>> +               break;
>>> +       case UNATTACHED_SRC:
>>> +               fusb302_handle_unattached_src_event(data, event);
>>> +               break;
>>> +       case ATTACHED_SRC:
>>> +               fusb302_handle_attached_src_event(data, event);
>>> +               break;
>>> +       }
>>> +       mutex_unlock(&data->lock);
>>> +}
>>> +
>>> +static void fusb302_timeout(struct work_struct *work)
>>> +{
>>> +       struct fusb302_data *data =
>>> +               container_of(work, struct fusb302_data, timeout.work);
>>> +
>>> +       fusb302_handle_event(data, TIMEOUT);
>>> +}
>>> +
>>> +static void fusb302_fallback_timeout(struct work_struct *work)
>>> +{
>>> +       struct fusb302_data *data =
>>> +               container_of(work, struct fusb302_data, fallback_timeout.work);
>>> +
>>> +       fusb302_handle_event(data, FALLBACK_TIMEOUT);
>>> +}
>>> +
>>> +static void fusb302_report_bc_level(struct work_struct *work)
>>> +{
>>> +       struct fusb302_data *data =
>>> +               container_of(work, struct fusb302_data, bc_work.work);
>>> +
>>> +       /* Clear old charger cable id */
>>> +       fusb302_set_extcon_state(data, data->snk_cable_id, false);
>>> +
>>> +       if (data->status0 & STATUS0_COMP) {
>>> +               dev_warn(data->dev, "vRd over maximum, assuming 500mA source\n");
>>> +               data->snk_cable_id = EXTCON_CHG_USB_SDP;
>>> +       } else {
>>> +               switch (data->status0 & STATUS0_BC_LEVEL_MASK) {
>>> +               case STATUS0_BC_LEVEL_VRA:
>>> +               case STATUS0_BC_LEVEL_VRDUSB:
>>> +                       data->snk_cable_id = EXTCON_CHG_USB_SDP;
>>> +                       break;
>>> +               case STATUS0_BC_LEVEL_VRD15:
>>> +                       data->snk_cable_id = EXTCON_CHG_USB_CDP;
>>> +                       break;
>>> +               case STATUS0_BC_LEVEL_VRD30:
>>> +                       /* Use CHG_USB_FAST to indicate 3A capability */
>>> +                       data->snk_cable_id = EXTCON_CHG_USB_FAST;
>>> +                       break;
>>> +               }
>>> +       }
>>> +       fusb302_set_extcon_state(data, data->snk_cable_id, true);
>>> +}
>>> +
>>> +static irqreturn_t fusb302_irq_handler_thread(int irq, void *handler_data)
>>> +{
>>> +       struct fusb302_data *data = handler_data;
>>> +       int interrupt, interrupta;
>>> +
>>> +       interrupt = fusb302_read_reg(data, REG_INTERRUPT);
>>> +       interrupta = fusb302_read_reg(data, REG_INTERRUPTA);
>>> +
>>> +       if (interrupt)
>>> +               data->status0 = fusb302_read_reg(data, REG_STATUS0);
>>> +
>>> +       if (interrupta)
>>> +               data->status1a = fusb302_read_reg(data, REG_STATUS1A);
>>> +
>>> +       dev_dbg(data->dev, "Handling interrupt %02x %02x status %02x %02x\n",
>>> +               interrupt, interrupta, data->status0, data->status1a);
>>> +
>>> +       if (interrupt & INTERRUPT_BC_LVL)
>>> +               fusb302_handle_event(data, BC_LVL_CHANGE);
>>> +
>>> +       if (interrupt & INTERRUPT_COMP_CHNG) {
>>> +               if (data->status0 & STATUS0_COMP)
>>> +                       fusb302_handle_event(data, COMP_HIGH);
>>> +               else
>>> +                       fusb302_handle_event(data, COMP_LOW);
>>> +       }
>>> +
>>> +       if (interrupt & INTERRUPT_VBUSOK) {
>>> +               if (data->status0 & STATUS0_VBUSOK)
>>> +                       fusb302_handle_event(data, VBUS_VALID);
>>> +               else
>>> +                       fusb302_handle_event(data, VBUS_INVALID);
>>> +       }
>>> +
>>> +       if (interrupta & INTERRUPTA_TOGDONE)
>>> +               fusb302_handle_event(data, TOGGLE_DONE);
>>> +
>>> +       return IRQ_HANDLED;
>>> +}
>>> +
>>> +static const struct regmap_config fusb302_regmap_config = {
>>> +       .reg_bits = 8,
>>> +       .val_bits = 8,
>>> +       .val_format_endian = REGMAP_ENDIAN_NATIVE,
>>> +};
>>> +
>>> +static int fusb302_probe(struct i2c_client *client)
>>> +{
>>> +       struct device *dev = &client->dev;
>>> +       struct fusb302_data *data;
>>> +       int ret;
>>> +
>>> +       if (!client->irq) {
>>> +               dev_err(dev, "Error irq not specified\n");
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
>>> +       if (!data)
>>> +               return -ENOMEM;
>>> +
>>> +       data->dev = dev;
>>> +       /* TODO make these 2 configurable using device-properties */
>>> +       data->port_type = TYPEC_PORT_DRP;
>>> +       data->preferred_role = TYPEC_SINK;
>>> +
>>> +       data->regmap = devm_regmap_init_i2c(client, &fusb302_regmap_config);
>>> +       if (IS_ERR(data->regmap)) {
>>> +               ret = PTR_ERR(data->regmap);
>>> +               dev_err(dev, "Error to initializing regmap: %d\n", ret);
>>> +               return ret;
>>> +       }
>>> +
>>> +       mutex_init(&data->lock);
>>> +       INIT_DELAYED_WORK(&data->timeout, fusb302_timeout);
>>> +       INIT_DELAYED_WORK(&data->fallback_timeout, fusb302_fallback_timeout);
>>> +       INIT_DELAYED_WORK(&data->bc_work, fusb302_report_bc_level);
>>> +
>>> +       data->edev = devm_extcon_dev_allocate(dev, fusb302_extcon_cables);
>>> +       if (IS_ERR(data->edev))
>>> +               return PTR_ERR(data->edev);
>>> +
>>> +       data->edev->name = "fusb302";
>>> +
>>> +       ret = devm_extcon_dev_register(dev, data->edev);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       extcon_set_property_capability(data->edev, EXTCON_USB,
>>> +                                      EXTCON_PROP_USB_TYPEC_POLARITY);
>>> +
>>> +       fusb302_write_reg(data, REG_RESET, RESET_SW_RESET);
>>> +       usleep_range(10000, 20000);
>>> +
>>> +       /* Enable power-saving */
>>> +       fusb302_update_bits(data, REG_CONTROL2, CONTROL2_SAVE_PWR_MASK,
>>> +                           CONTROL2_SAVE_PWR_40MS);
>>> +
>>> +       fusb302_set_state_disabled(data);
>>> +
>>> +       ret = devm_request_threaded_irq(dev, client->irq, NULL,
>>> +                       fusb302_irq_handler_thread,
>>> +                       IRQF_TRIGGER_LOW | IRQF_ONESHOT, "fusb302", data);
>>> +       if (ret) {
>>> +               dev_err(dev, "Error requesting irq: %d\n", ret);
>>> +               return ret;
>>> +       }
>>> +
>>> +       fusb302_update_bits(data, REG_CONTROL0, CONTROL0_INT_MASK, 0);
>>> +
>>> +       i2c_set_clientdata(client, data);
>>> +       return 0;
>>> +}
>>> +
>>> +static int fusb302_remove(struct i2c_client *client)
>>> +{
>>> +       struct fusb302_data *data = i2c_get_clientdata(client);
>>> +
>>> +       devm_free_irq(data->dev, client->irq, data);
>>> +       cancel_delayed_work_sync(&data->timeout);
>>> +       cancel_delayed_work_sync(&data->fallback_timeout);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static const struct i2c_device_id fusb302_i2c_ids[] = {
>>> +       { "fusb302" },
>>> +       { },
>>> +};
>>> +MODULE_DEVICE_TABLE(i2c, fusb302_i2c_ids);
>>> +
>>> +static struct i2c_driver fusb302_driver = {
>>> +       .probe_new = fusb302_probe,
>>> +       .remove = fusb302_remove,
>>> +       .id_table = fusb302_i2c_ids,
>>> +       .driver = {
>>> +               .name = "fusb302",
>>> +       },
>>> +};
>>> +module_i2c_driver(fusb302_driver);
>>> +
>>> +MODULE_LICENSE("GPL");
>>> +MODULE_AUTHOR("Hans de Goede <hdegoede@...hat.com>");
>>> +MODULE_DESCRIPTION("FUSB302 USB TYPE-C controller Driver");
>>> --
>>> 2.9.3
>>>
>
> Br,
>

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ