All of lore.kernel.org
 help / color / mirror / Atom feed
From: Heikki Krogerus <heikki.krogerus@linux.intel.com>
To: Hans de Goede <hdegoede@redhat.com>, Guenter Roeck <linux@roeck-us.net>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>,
	MyungJoo Ham <myungjoo.ham@samsung.com>,
	Chanwoo Choi <cw00.choi@samsung.com>,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>
Subject: Re: [PATCH 2/5] extcon: Add FUSB302 USB TYPE-C controller support
Date: Mon, 24 Apr 2017 14:02:04 +0300	[thread overview]
Message-ID: <20170424110204.GA14268@kuha.fi.intel.com> (raw)
In-Reply-To: <CAHp75VciTKO_PXGQKs_a=S2qwK7eUG38yGQEidRtAcoOi1YPKA@mail.gmail.com>

+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@redhat.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@redhat.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
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?


[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@redhat.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@redhat.com>");
> > +MODULE_DESCRIPTION("FUSB302 USB TYPE-C controller Driver");
> > --
> > 2.9.3
> >

Br,

-- 
heikki

  reply	other threads:[~2017-04-24 11:02 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <CGME20170421130123epcas4p12e891d305aa2b4126089e691d15c6659@epcas4p1.samsung.com>
2017-04-21 13:01 ` [PATCH 1/5] extcon: Allow extcon drivers to specify the extcon name Hans de Goede
2017-04-21 13:01   ` [PATCH 2/5] extcon: Add FUSB302 USB TYPE-C controller support Hans de Goede
2017-04-21 18:51     ` Andy Shevchenko
2017-04-24 11:02       ` Heikki Krogerus [this message]
2017-04-24 13:12         ` Guenter Roeck
2017-05-12 21:22           ` Hans de Goede
2017-05-16 12:07             ` Heikki Krogerus
2017-05-16 22:24               ` Hans de Goede
2017-05-17 11:45                 ` Heikki Krogerus
2017-05-17 14:47                   ` USB TYPE-C support and the power-supply subsys (was Re: [PATCH 2/5] extcon: Add FUSB302 USB TYPE-C controller support) Hans de Goede
2017-05-18  8:37                     ` Heikki Krogerus
2017-05-18 11:50                       ` Heikki Krogerus
2017-05-19 20:12                       ` Hans de Goede
2017-05-19 20:15                         ` Hans de Goede
2017-05-22 14:56                         ` Heikki Krogerus
2017-05-16 22:52             ` [PATCH 2/5] extcon: Add FUSB302 USB TYPE-C controller support Guenter Roeck
2017-04-21 23:23     ` kbuild test robot
2017-05-23  9:27     ` Chanwoo Choi
2017-05-24 12:23       ` Andy Shevchenko
2017-04-21 13:01   ` [PATCH 3/5] extcon: intel-cht-wc: Default to SDP on regmap io errors Hans de Goede
2017-04-21 13:01   ` [PATCH 4/5] extcon: intel-cht-wc: Add new cht_wc_extcon_set_state helper Hans de Goede
2017-04-21 18:54     ` Andy Shevchenko
2017-04-21 13:01   ` [PATCH 5/5] extcon: intel-cht-wc: Add support for monitoring external USB Type-C controller Hans de Goede
2017-04-21 18:55     ` Andy Shevchenko
2017-04-24 14:25     ` Heikki Krogerus
2017-05-23  9:26   ` [PATCH 1/5] extcon: Allow extcon drivers to specify the extcon name Chanwoo Choi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170424110204.GA14268@kuha.fi.intel.com \
    --to=heikki.krogerus@linux.intel.com \
    --cc=andriy.shevchenko@linux.intel.com \
    --cc=cw00.choi@samsung.com \
    --cc=hdegoede@redhat.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=myungjoo.ham@samsung.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.