From 64aa77fbef2d61046b69c02e22759066f8cc7e0c Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Sun, 7 Feb 2021 09:40:26 -0800 Subject: [PATCH] rM2: misc: Copy rM2 otgcontrol driver Copy the rM2 zero-sugar branch (https://github.com/reMarkable/linux/tree/zero-sugar) to the new kernel. Signed-off-by: Alistair Francis --- drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 2 +- drivers/misc/rm-otgcontrol/Kconfig | 16 + drivers/misc/rm-otgcontrol/Makefile | 9 + drivers/misc/rm-otgcontrol/otgcontrol.h | 100 ++ .../rm-otgcontrol/otgcontrol_charging_ctrl.c | 167 ++ .../rm-otgcontrol/otgcontrol_charging_ctrl.h | 36 + .../misc/rm-otgcontrol/otgcontrol_dr_mode.c | 126 ++ .../misc/rm-otgcontrol/otgcontrol_dr_mode.h | 30 + drivers/misc/rm-otgcontrol/otgcontrol_fsm.c | 1464 +++++++++++++++++ drivers/misc/rm-otgcontrol/otgcontrol_fsm.h | 52 + drivers/misc/rm-otgcontrol/otgcontrol_main.c | 384 +++++ .../misc/rm-otgcontrol/otgcontrol_onewire.c | 513 ++++++ .../misc/rm-otgcontrol/otgcontrol_onewire.h | 53 + drivers/misc/rm-otgcontrol/otgcontrol_sysfs.c | 322 ++++ drivers/misc/rm-otgcontrol/otgcontrol_sysfs.h | 26 + include/linux/power_supply.h | 9 + 17 files changed, 3309 insertions(+), 1 deletion(-) create mode 100644 drivers/misc/rm-otgcontrol/Kconfig create mode 100644 drivers/misc/rm-otgcontrol/Makefile create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol.h create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_charging_ctrl.c create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_charging_ctrl.h create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_dr_mode.c create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_dr_mode.h create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_fsm.c create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_fsm.h create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_main.c create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_onewire.c create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_onewire.h create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_sysfs.c create mode 100644 drivers/misc/rm-otgcontrol/otgcontrol_sysfs.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index fba3965a8bf7..aa9bf37632ea 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -501,4 +501,5 @@ source "drivers/misc/cxl/Kconfig" source "drivers/misc/ocxl/Kconfig" source "drivers/misc/cardreader/Kconfig" source "drivers/misc/habanalabs/Kconfig" +source "drivers/misc/rm-otgcontrol/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index d0e772f8eeea..858ec6cb28ca 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 # # Makefile for misc devices that really don't fit anywhere else. -# +obj-${CONFIG_RM_OTGCONTROL} += rm-otgcontrol/ obj-$(CONFIG_IBM_ASM) += ibmasm/ obj-$(CONFIG_IBMVMC) += ibmvmc.o obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o diff --git a/drivers/misc/rm-otgcontrol/Kconfig b/drivers/misc/rm-otgcontrol/Kconfig new file mode 100644 index 000000000000..21a04090cc7d --- /dev/null +++ b/drivers/misc/rm-otgcontrol/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for reMarkable OTG Control +# + +config RM_OTGCONTROL + tristate "reMarkable OTG control" + help + Add reMarkable OTG control module, required to control access to/from external equipment + +config RM_OTGCONTROL_DEBUG + bool "reMarkable OTG control debug" + depends on RM_OTGCONTROL + help + "Enable extensive debug output (useful when debugging FSM type of errors to see what is going on)" + diff --git a/drivers/misc/rm-otgcontrol/Makefile b/drivers/misc/rm-otgcontrol/Makefile new file mode 100644 index 000000000000..5bf14b30171d --- /dev/null +++ b/drivers/misc/rm-otgcontrol/Makefile @@ -0,0 +1,9 @@ +obj-${CONFIG_RM_OTGCONTROL} += otgcontrol.o +otgcontrol-objs := otgcontrol_main.o +otgcontrol-objs += otgcontrol_sysfs.o +otgcontrol-objs += otgcontrol_fsm.o +otgcontrol-objs += otgcontrol_onewire.o +otgcontrol-objs += otgcontrol_charging_ctrl.o +otgcontrol-objs += otgcontrol_dr_mode.o + +ccflags-$(CONFIG_RM_OTGCONTROL_DEBUG) += -DDEBUG diff --git a/drivers/misc/rm-otgcontrol/otgcontrol.h b/drivers/misc/rm-otgcontrol/otgcontrol.h new file mode 100644 index 000000000000..2369b9350f5d --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol.h @@ -0,0 +1,100 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __RM_OTGCONTROL_H_ +#define __RM_OTGCONTROL_H_ + +#include +#include +#include +#include + +#define SYNC_SET_FLAG(flag, lock) ( \ + { \ + mutex_lock(lock); \ + flag = true; \ + mutex_unlock(lock); \ + } \ +) + +#define SYNC_CLEAR_FLAG(flag, lock) ( \ + { \ + mutex_lock(lock); \ + flag = false; \ + mutex_unlock(lock); \ + } \ +) + +#define SYNC_GET_FLAG(flag, lock) ( \ + { \ + bool state; \ + mutex_lock(lock); \ + state = flag; \ + mutex_unlock(lock); \ + state; \ + } \ +) + +struct rm_otgcontrol_platform_data { + /* Reference to charger driver for OTG power control */ + struct power_supply *vbus_supply; + + /* One-wire tty device and gpio config */ + const char *one_wire_tty_name; + struct gpio_desc *one_wire_gpio; + int one_wire_gpio_irq; +}; + +struct rm_otgcontrol_data { + struct device *dev; + struct rm_otgcontrol_platform_data *pdata; + + struct mutex lock; + + struct extcon_dev *extcon_dev; + + unsigned long one_wire_gpio_debounce_jiffies; + struct delayed_work one_wire_gpio_irq_work_queue; + bool one_wire_gpio_irq_is_active; + int one_wire_gpio_state; + bool one_wire_gpio_irq_is_handling; + + int otg_controlstate; + int mode_requested; + + struct pinctrl* one_wire_pinctrl; + struct pinctrl_state* one_wire_pinctrl_states[3]; + + struct kobject* kobject; + + struct kobj_attribute otg1_device_connected_attribute; + bool otg1_device_connected; + + struct kobj_attribute otg1_dr_mode_attribute; + int otg1_dr_mode; + + struct kobj_attribute otg1_chargermode_attribute; + int otg1_chargermode; + + struct kobj_attribute otg1_controllermode_attribute; + int otg1_controllermode; + + struct kobj_attribute otg1_pinctrlstate_attribute; + int otg1_pinctrlstate; +}; + +#endif /* __RM_OTGCONTROL_H */ diff --git a/drivers/misc/rm-otgcontrol/otgcontrol_charging_ctrl.c b/drivers/misc/rm-otgcontrol/otgcontrol_charging_ctrl.c new file mode 100644 index 000000000000..01c0b53ea29a --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol_charging_ctrl.c @@ -0,0 +1,167 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "otgcontrol_charging_ctrl.h" + +#include +#include + +/* Copy of the structure defined in + * ../drivers/power/supply/power_supply_sysfs.c + * + * As the power supply API does not define any + * methods to access the defined enum strings for + * the various properties, a local version is defined + * here, as this driver also defines corresponding enumerations locally + */ +static const char * const charger_mode_string_list[] = { + "Charger", "OTG Supply", "Off" +}; + +int otgcontrol_get_otg_charger_modes(struct rm_otgcontrol_data *otgc_data, + char *prop_buf) +{ + int i; + int cur_pos = 0; + + for(i = 0;i < ARRAY_SIZE(charger_mode_string_list);i++) { + sprintf(&prop_buf[cur_pos], + "%s%s", + (i > 0) ? " " : "", + charger_mode_string_list[i]); + + cur_pos = strlen(prop_buf); + } + sprintf(&prop_buf[cur_pos], "\n"); + return strlen(prop_buf); +} + +int otgcontrol_change_otg_charger_mode_int(struct rm_otgcontrol_data *otgc_data, + int mode) +{ + union power_supply_propval property_val; + int ret; + + switch(mode) + { + case OTG1_CHARGERMODE_CHARGE: + dev_dbg(otgc_data->dev, + "%s: Setting OTG1 chargermode (CHARGE)\n", + __func__); + + property_val.intval = POWER_SUPPLY_MODE_CHARGER; + ret = power_supply_set_property(otgc_data->pdata->vbus_supply, + POWER_SUPPLY_PROP_CHARGER_MODE, + &property_val); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to set charger mode\n", + __func__); + + return ret; + } + break; + + case OTG1_CHARGERMODE_OTG: + dev_dbg(otgc_data->dev, + "%s: Setting OTG1 chargermode (OTG)\n", + __func__); + property_val.intval = POWER_SUPPLY_MODE_OTG_SUPPLY; + ret = power_supply_set_property(otgc_data->pdata->vbus_supply, + POWER_SUPPLY_PROP_CHARGER_MODE, + &property_val); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to set charger mode\n", + __func__); + + return ret; + } + break; + + case OTG1_CHARGERMODE_OFF: + dev_dbg(otgc_data->dev, + "%s: Setting OTG1 chargermode (OFF)\n", + __func__); + property_val.intval = POWER_SUPPLY_MODE_ALL_OFF; + ret = power_supply_set_property(otgc_data->pdata->vbus_supply, + POWER_SUPPLY_PROP_CHARGER_MODE, + &property_val); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to set charger mode\n", + __func__); + + return ret; + } + break; + + default: + dev_err(otgc_data->dev, + "%s: Unable to set OTG1 chargermode (invalid mode %d)\n", + __func__, mode); + return -EINVAL; + } + + otgc_data->otg1_chargermode = mode; + return 0; +} + +int otgcontrol_change_otg_charger_mode_str(struct rm_otgcontrol_data *otgc_data, + const char *buf) +{ + int ret, mode, i, buf_len, mode_len; + + /* Remove trailing \n */ + if (buf[strlen(buf) - 1] == '\n') + buf_len = strlen(buf) - 1; + else + buf_len = strlen(buf); + + ret = kstrtoint(buf, 10, &mode); + if (ret < 0) { + mode = -1; + for (i = 0;i < ARRAY_SIZE(charger_mode_string_list);i++) { + mode_len = strlen(charger_mode_string_list[i]); + if (!strncmp(charger_mode_string_list[i], + buf, + max(buf_len, mode_len))) { + mode = i; + break; + } + } + if (mode < 0) { + dev_err(otgc_data->dev, + "%s: Unable to set OTG1 chargermode " + "(invalid mode %s)\n", + __func__, + buf); + + return -EINVAL; + } + + } + else if (mode >= ARRAY_SIZE(charger_mode_string_list)) { + dev_err(otgc_data->dev, + "%s: Unable to set OTG1 chargermode (invalid mode %d)\n", + __func__, mode); + + return -EINVAL; + } + + return otgcontrol_change_otg_charger_mode_int(otgc_data, mode); +} diff --git a/drivers/misc/rm-otgcontrol/otgcontrol_charging_ctrl.h b/drivers/misc/rm-otgcontrol/otgcontrol_charging_ctrl.h new file mode 100644 index 000000000000..151cd7855f71 --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol_charging_ctrl.h @@ -0,0 +1,36 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __OTGCONTROL_CHARGING_CTRL_H__ +#define __OTGCONTROL_CHARGING_CTRL_H__ + +#include "otgcontrol.h" + +#define OTG1_CHARGERMODE_CHARGE 0 +#define OTG1_CHARGERMODE_OTG 1 +#define OTG1_CHARGERMODE_OFF 2 + +int otgcontrol_get_otg_charger_modes(struct rm_otgcontrol_data *otgc_data, + char *prop_buf); + +int otgcontrol_change_otg_charger_mode_int(struct rm_otgcontrol_data *otgc_data, + int mode); + +int otgcontrol_change_otg_charger_mode_str(struct rm_otgcontrol_data *otgc_data, + const char *buf); + +#endif /* __OTGCONTROL_CHARGING_CTRL_H__ */ diff --git a/drivers/misc/rm-otgcontrol/otgcontrol_dr_mode.c b/drivers/misc/rm-otgcontrol/otgcontrol_dr_mode.c new file mode 100644 index 000000000000..884f1cdf02e2 --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol_dr_mode.c @@ -0,0 +1,126 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "otgcontrol_dr_mode.h" +#include +#include +#include + +static const unsigned int usb_extcon_cable[] = { + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +int otgcontrol_init_extcon(struct rm_otgcontrol_data *otgc_data) +{ + int ret; + + dev_dbg(otgc_data->dev, + "%s: Allocating extcon device\n", + __func__); + + otgc_data->extcon_dev = devm_extcon_dev_allocate(otgc_data->dev, + usb_extcon_cable); + if (IS_ERR(otgc_data->extcon_dev)) { + dev_err(otgc_data->dev, + "%s: failed to allocate extcon device\n", + __func__); + + return -ENOMEM; + } + + dev_dbg(otgc_data->dev, + "%s: Registering extcon device\n", + __func__); + + ret = devm_extcon_dev_register(otgc_data->dev, otgc_data->extcon_dev); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to register extcon device\n", + __func__); + + dev_dbg(otgc_data->dev, + "%s: De-allocating extcon device\n", + __func__); + + kfree(otgc_data->extcon_dev); + otgc_data->extcon_dev = NULL; + return ret; + } + + return 0; +} + +int otgcontrol_set_dr_mode(struct rm_otgcontrol_data *otgc_data, int mode) +{ + int ret; + + switch(mode) + { + case OTG1_DR_MODE__DEVICE: + dev_dbg(otgc_data->dev, + "%s: Switching OTG1 DR mode -> DEVICE\n", + __func__); + + ret = extcon_set_state_sync(otgc_data->extcon_dev, + EXTCON_USB_HOST, + false); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to set OTG1 DR mode\n", + __func__); + + return ret; + } + break; + + case OTG1_DR_MODE__HOST: + dev_dbg(otgc_data->dev, + "%s: Switching OTG1 DR mode -> HOST\n", + __func__); + + otgc_data->otg1_dr_mode = mode; + ret = extcon_set_state_sync(otgc_data->extcon_dev, + EXTCON_USB_HOST, + true); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to set OTG1 DR mode\n", + __func__); + + return ret; + } + break; + + default: + dev_err(otgc_data->dev, + "%s: unable to switch OTG1 DR mode (unknown mode %d)\n", + __func__, + mode); + + return -EINVAL; + } + + otgc_data->otg1_dr_mode = mode; + return ret; +} + +int otgcontrol_get_dr_mode(struct rm_otgcontrol_data *otgc_data) +{ + /* Just return the last stored value */ + return otgc_data->otg1_dr_mode; +} diff --git a/drivers/misc/rm-otgcontrol/otgcontrol_dr_mode.h b/drivers/misc/rm-otgcontrol/otgcontrol_dr_mode.h new file mode 100644 index 000000000000..32aa8333d9f9 --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol_dr_mode.h @@ -0,0 +1,30 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __OTGCONTROL_DR_MODE_H__ +#define __OTGCONTROL_DR_MODE_H__ + +#include "otgcontrol.h" + +#define OTG1_DR_MODE__DEVICE 0 +#define OTG1_DR_MODE__HOST 1 + +int otgcontrol_init_extcon(struct rm_otgcontrol_data *otgc_data); +int otgcontrol_set_dr_mode(struct rm_otgcontrol_data *otgc_dta, int mode); +int otgcontrol_get_dr_mode(struct rm_otgcontrol_data *otgc_data); + +#endif // __OTGCONTROL_DR_MODE_H__ diff --git a/drivers/misc/rm-otgcontrol/otgcontrol_fsm.c b/drivers/misc/rm-otgcontrol/otgcontrol_fsm.c new file mode 100644 index 000000000000..40c7f5e16def --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol_fsm.c @@ -0,0 +1,1464 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "otgcontrol.h" +#include "otgcontrol_fsm.h" +#include "otgcontrol_onewire.h" +#include "otgcontrol_charging_ctrl.h" +#include "otgcontrol_dr_mode.h" + +#include +#include +#include +#include + +/****************************************************************************** + * INTERNAL FUNCTIONS + *****************************************************************************/ +static int otgcontrol_reset_fsm(struct rm_otgcontrol_data *otgc_data); + +static int otgcontrol_handle_state_MANUAL_CONTROL( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param); + +static int otgcontrol_handle_state_ONEWIRE_AUTH_NOT_CONNECTED( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param); + +static int otgcontrol_handle_state_ONEWIRE_AUTH_WAIT_HANDSHAKE_RESPONS( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param); + +static int otgcontrol_handle_state_ONEWIRE_AUTH_DEVICE_CONNECTED( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param); + +static int otgcontrol_handle_state_USB_NO_AUTH_WAITING_CHALLENGE_RESPONSE( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param); + +static int otgcontrol_handle_state_USB_NO_AUTH_NOT_CONNECTED( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param); + +static int otgcontrol_handle_state_USB_NO_AUTH_DEVICE_CONNECTED( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param); + +static int otgcontrol_handle_state_HOST_CONNECTED( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param); + +static int otgcontrol_do_controller_mode_change_procedure( + struct rm_otgcontrol_data *otgc_data, + void *param); + +static int otgcontrol_do_authenticate_calling_application( + struct rm_otgcontrol_data *otgc_data, + int mode_requested); + +static int otgcontrol_do_verify_application_challenge_reply( + struct rm_otgcontrol_data *otgc_data, + void *param); + +static int otgcontrol_do_verify_device_challenge_reply( + struct rm_otgcontrol_data *otgc_data, + void *param); + +static int otgcontrol_do_set_controlmode( + struct rm_otgcontrol_data *otgc_data, + int mode); + +static int otgcontrol_do_device_connected_procedure( + struct rm_otgcontrol_data *otgc_data, + bool authentication_required); + +static int otgcontrol_do_device_disconnected_procedure( + struct rm_otgcontrol_data *otgc_data); + +static int otgcontrol_do_start_onewire_authentication( + struct rm_otgcontrol_data *otgc_data); + +static const char *otgcontrol_controllerstate_name(int state) +{ + switch(state) + { + case OTG1_STATE__MANUAL_CONTROL: + return "OTG1_STATE__MANUAL_CONTROL"; + break; + case OTG1_STATE__ONEWIRE_AUTH_NOT_CONNECTED: + return "OTG1_STATE__ONEWIRE_AUTH_NOT_CONNECTED"; + break; + case OTG1_STATE__ONEWIRE_AUTH_WAIT_HANDSHAKE_RESPONS: + return "OTG1_STATE__ONEWIRE_AUTH_WAIT_HANDSHAKE_RESPONS"; + break; + case OTG1_STATE__ONEWIRE_AUTH_DEVICE_CONNECTED: + return "OTG1_STATE__ONEWIRE_AUTH_DEVICE_CONNECTED"; + break; + case OTG1_STATE__USB_NO_AUTH_WAITING_CHALLENGE_RESPONSE: + return "OTG1_STATE__USB_NO_AUTH_WAITING_CHALLENGE_RESPONSE"; + break; + case OTG1_STATE__USB_NO_AUTH_NOT_CONNECTED: + return "OTG1_STATE__USB_NO_AUTH_NOT_CONNECTED"; + break; + case OTG1_STATE__USB_NO_AUTH_DEVICE_CONNECTED: + return "OTG1_STATE__USB_NO_AUTH_DEVICE_CONNECTED"; + break; + case OTG1_STATE__HOST_CONNECTED: + return "OTG1_STATE__HOST_CONNECTED"; + break; + default: + return "INVALID STATE"; + } +} + +static bool otgcontrol_controllerstate_is_valid(int state) +{ + switch(state) + { + case OTG1_STATE__MANUAL_CONTROL: + case OTG1_STATE__ONEWIRE_AUTH_NOT_CONNECTED: + case OTG1_STATE__ONEWIRE_AUTH_WAIT_HANDSHAKE_RESPONS: + case OTG1_STATE__ONEWIRE_AUTH_DEVICE_CONNECTED: + case OTG1_STATE__USB_NO_AUTH_WAITING_CHALLENGE_RESPONSE: + case OTG1_STATE__USB_NO_AUTH_NOT_CONNECTED: + case OTG1_STATE__USB_NO_AUTH_DEVICE_CONNECTED: + case OTG1_STATE__HOST_CONNECTED: + return true; + default: + return false; + } +} + + +int otgcontrol_init_fsm(struct rm_otgcontrol_data *otgc_data) +{ + /* For now, only a simple FSM reset is required + * init routine has been kept however, to possibly contain other + * initiation code when the solution evolves + */ + return otgcontrol_reset_fsm(otgc_data); +} + +static int otgcontrol_reset_fsm(struct rm_otgcontrol_data *otgc_data) +{ + int ret; + + /* Initially, set the DR mode to device, shut off the OTG power, and + * mux the onewire at GPIO + */ + ret = otgcontrol_change_otg_charger_mode_int(otgc_data, + OTG1_CHARGERMODE_CHARGE); + if (ret > 0) { + dev_err(otgc_data->dev, + "%s: Failed to reset FSM " + "(unable to change charger mode)\n", + __func__); + + return ret; + } + + ret = otgcontrol_set_dr_mode(otgc_data, OTG1_DR_MODE__DEVICE); + if (ret > 0) { + dev_err(otgc_data->dev, + "%s: Failed to reset FSM " + "(unable to set defaul USB OTG DR mode)\n", + __func__); + + return ret; + } + + ret = otgcontrol_switch_one_wire_mux_state(otgc_data, + OTG1_ONEWIRE_STATE__GPIO); + if (ret > 0) { + dev_err(otgc_data->dev, + "%s: Failed to reset FSM " + "(unable to set default one-wire pinmux state)\n", + __func__); + + return ret; + } + + dev_dbg(otgc_data->dev, + "%s: Activating one-wire GPIO IRQ\n", + __func__); + + /* otgcontrol_activate_gpio_irq(otgc_data);*/ + otgcontrol_deactivate_gpio_irq(otgc_data); + + dev_dbg(otgc_data->dev, + "%s: Checking if device is connected and initiating default " + "state\n", + __func__); + + otgc_data->otg1_controllermode = OTG_MODE__ONEWIRE_AUTH; + if(otgcontrol_get_current_gpio_state(otgc_data) == + OTG1_ONEWIRE_GPIO_STATE__DEVICE_CONNECTED) { + dev_dbg(otgc_data->dev, + "Device is connected, " + "doing default authenticated device connection procedure\n"); + + ret = otgcontrol_do_device_connected_procedure(otgc_data, true); + if (ret < 0) { + dev_err(otgc_data->dev, + "Unable to complete device connection procedure\n"); + + dev_dbg(otgc_data->dev, + "Waiting for device disconnect/connect\n"); + + otgc_data->otg_controlstate = + OTG1_STATE__ONEWIRE_AUTH_NOT_CONNECTED; + } + } + else { + dev_dbg(otgc_data->dev, + "%s: Device is not connected, so wait for device to connect\n", + __func__); + otgc_data->otg_controlstate = + OTG1_STATE__ONEWIRE_AUTH_NOT_CONNECTED; + } + + return 0; +} + +int otgcontrol_handleInput(struct rm_otgcontrol_data *otgc_data, + int signal, + void* param) +{ + switch(otgc_data->otg_controlstate) + { + case OTG1_STATE__MANUAL_CONTROL: + return otgcontrol_handle_state_MANUAL_CONTROL( + otgc_data, + signal, + param); + break; + case OTG1_STATE__ONEWIRE_AUTH_NOT_CONNECTED: + return otgcontrol_handle_state_ONEWIRE_AUTH_NOT_CONNECTED( + otgc_data, + signal, + param); + break; + case OTG1_STATE__ONEWIRE_AUTH_WAIT_HANDSHAKE_RESPONS: + return otgcontrol_handle_state_ONEWIRE_AUTH_WAIT_HANDSHAKE_RESPONS( + otgc_data, + signal, + param); + break; + case OTG1_STATE__ONEWIRE_AUTH_DEVICE_CONNECTED: + return otgcontrol_handle_state_ONEWIRE_AUTH_DEVICE_CONNECTED( + otgc_data, + signal, + param); + break; + case OTG1_STATE__USB_NO_AUTH_WAITING_CHALLENGE_RESPONSE: + return otgcontrol_handle_state_USB_NO_AUTH_WAITING_CHALLENGE_RESPONSE( + otgc_data, + signal, + param); + break; + case OTG1_STATE__USB_NO_AUTH_NOT_CONNECTED: + return otgcontrol_handle_state_USB_NO_AUTH_NOT_CONNECTED( + otgc_data, + signal, + param); + break; + case OTG1_STATE__USB_NO_AUTH_DEVICE_CONNECTED: + return otgcontrol_handle_state_USB_NO_AUTH_DEVICE_CONNECTED( + otgc_data, + signal, + param); + break; + case OTG1_STATE__HOST_CONNECTED: + return otgcontrol_handle_state_HOST_CONNECTED( + otgc_data, + signal, + param); + break; + default: + dev_dbg(otgc_data->dev, + "%s: Current control state is invalid, " + "resetting state machine to state " + "ONEWIRE_AUTH_NOT_CONNECTED\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } +} + +static int otgcontrol_handle_state_MANUAL_CONTROL( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param) +{ + switch(signal) + { + case OTG1_EVENT__CHARGER_CONNECTED: + case OTG1_EVENT__CHARGER_DISCONNECTED: + case OTG1_EVENT__DEVICE_CONNECTED: + case OTG1_EVENT__DEVICE_DISCONNECTED: + case OTG1_EVENT__CHALLENGE_REPLY_RECEIVED: + case OTG1_EVENT__MODE_CHANGE_CHALLENGE_REPLY: + case OTG1_EVENT__OTG_CHARGERMODE_CHANGE_REQUESTED: + case OTG1_EVENT__OTG_DR_MODE_CHANGE_REQUESTED: + case OTG1_EVENT__TIMEOUT: + dev_dbg(otgc_data->dev, + "%s: In manual control mode, \ + no automatic action taken\n", + __func__); + break; + + case OTG1_EVENT__MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Controller-mode change requested\n", + __func__); + + return otgcontrol_do_controller_mode_change_procedure( + otgc_data, + param); + break; + + default: + dev_dbg(otgc_data->dev, + "%s: Unknown signal/event (%d), \ + but in manual mode so ignoring\n", + __func__, signal); + } + return 0; +} + +static int otgcontrol_handle_state_ONEWIRE_AUTH_NOT_CONNECTED( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param) +{ + int ret; + + switch(signal) + { + case OTG1_EVENT__CHARGER_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is connected, disabling all OTG features " + "until host is disconnected\n", + __func__); + + otgc_data->otg_controlstate = OTG1_STATE__HOST_CONNECTED; + return 0; + + case OTG1_EVENT__CHARGER_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is disconnected (NOT EXPECTED), but just " + "ignore this and continue to wait for device " + "connection\n", + __func__); + return 0; + + case OTG1_EVENT__DEVICE_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device connected, doing authenticated connection " + "procedure\n", + __func__); + + ret = otgcontrol_do_device_connected_procedure(otgc_data, + false); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete device connection " + "procedure, resetting fsm as an attempt to " + "recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__DEVICE_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device disconnected, doing disconnection " + "procedure\n", + __func__); + + ret = otgcontrol_do_device_disconnected_procedure(otgc_data); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete disconnection " + "procedure, resetting fsm as an attempt to " + "recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__CHALLENGE_REPLY_RECEIVED: + dev_dbg(otgc_data->dev, + "%s: Unexpected message received from device, it might " + "be in an unknown state, doing fsm reset procedure to " + "power-cycle device\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + + case OTG1_EVENT__MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Controller-mode change requested, start caller " + "authentication\n", + __func__); + + return otgcontrol_do_controller_mode_change_procedure( + otgc_data, + param); + + case OTG1_EVENT__MODE_CHANGE_CHALLENGE_REPLY: + dev_dbg(otgc_data->dev, + "%s: Unexpected mode change challenge reply received, " + "application might be in an unknown state - ignoring\n", + __func__); + return 0; + + case OTG1_EVENT__OTG_CHARGERMODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Charger-mode change requested, not valid in this " + "state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_DR_MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: USB OTG DR-mode change requested, not valid in " + "this state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__TIMEOUT: + dev_dbg(otgc_data->dev, + "%s: TIMEOUT, but not waiting for anyting other than " + "device connection - ignoring\n", + __func__); + return 0; + + default: + dev_dbg(otgc_data->dev, + "%s: Unknown signal/event (%d)\n", + __func__, + signal); + return -EINVAL; + } +} + +static int otgcontrol_handle_state_ONEWIRE_AUTH_WAIT_HANDSHAKE_RESPONS( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param) +{ + int ret; + + switch(signal) + { + case OTG1_EVENT__CHARGER_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is connected, disabling all OTG features " + "until host is disconnected\n", + __func__); + + otgc_data->otg_controlstate = OTG1_STATE__HOST_CONNECTED; + return 0; + + case OTG1_EVENT__CHARGER_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is disconnected (NOT EXPECTED), but just " + "ignore this and continue to wait for one-wire " + "handshake response\n", + __func__); + return 0; + + case OTG1_EVENT__DEVICE_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device connected (UNEXPECTED), re-starting " + "authenticated connection procedure\n", + __func__); + + ret = otgcontrol_do_device_connected_procedure(otgc_data, false); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete device connection " + "procedure, resetting fsm as an attempt to " + "recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__DEVICE_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device disconnected, doing disconnection " + "procedure\n", + __func__); + + ret = otgcontrol_do_device_disconnected_procedure(otgc_data); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete disconnection " + "procedure, resetting fsm as an attempt to " + "recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__CHALLENGE_REPLY_RECEIVED: + dev_dbg(otgc_data->dev, + "%s: Challenge reply received from connected device\n", + __func__); + + ret = otgcontrol_do_verify_device_challenge_reply(otgc_data, + param); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to verify device challenge reply, " + "resetting fsm as an attempt to recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Controller-mode change requested, start caller " + "authentication\n", + __func__); + + return otgcontrol_do_controller_mode_change_procedure(otgc_data, + param); + + case OTG1_EVENT__MODE_CHANGE_CHALLENGE_REPLY: + dev_dbg(otgc_data->dev, + "%s: Unexpected mode change challenge reply received, " + "application might be in an unknown state - ignoring\n", + __func__); + return 0; + + case OTG1_EVENT__OTG_CHARGERMODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Charger-mode change requested, not valid in this " + "state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_DR_MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: USB OTG DR-mode change requested, not valid in " + "this state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__TIMEOUT: + dev_dbg(otgc_data->dev, + "%s: TIMEOUT, no response from connected device within " + "expected amount of time\n", + __func__); + + dev_dbg(otgc_data->dev, + "%s: Reset fsm, shutting off power and waiting for " + "disconnect/re-connect\n", + __func__); + + return otgcontrol_reset_fsm(otgc_data); + + default: + dev_dbg(otgc_data->dev, + "%s: Unknown signal/event (%d)\n", + __func__, + signal); + return -EINVAL; + } +} + +static int otgcontrol_handle_state_ONEWIRE_AUTH_DEVICE_CONNECTED( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param) +{ + int ret; + + switch(signal) + { + case OTG1_EVENT__CHARGER_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is connected, disabling all OTG features " + "until host is disconnected\n", + __func__); + + otgc_data->otg_controlstate = OTG1_STATE__HOST_CONNECTED; + return 0; + + case OTG1_EVENT__CHARGER_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is disconnected (NOT EXPECTED), but just " + "ignore this and continue being connected to " + "authenticated device\n", + __func__); + return 0; + + case OTG1_EVENT__DEVICE_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device connected (UNEXPECTED), re-starting " + "authenticated connection procedure\n", + __func__); + + ret = otgcontrol_do_device_connected_procedure(otgc_data, false); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete device connection " + "procedure, resetting fsm as an attempt to " + "recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__DEVICE_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device disconnected, doing disconnection " + "procedure\n", + __func__); + + ret = otgcontrol_do_device_disconnected_procedure(otgc_data); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete disconnection procedure, " + "resetting fsm as an attempt to recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__CHALLENGE_REPLY_RECEIVED: + dev_dbg(otgc_data->dev, + "%s: Unexpected message received from device, it might " + "be in an unknown state, doing fsm reset procedure to " + "power-cycle device\n", + __func__); + + return otgcontrol_reset_fsm(otgc_data); + + case OTG1_EVENT__MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Controller-mode change requested\n", + __func__); + + return otgcontrol_do_controller_mode_change_procedure(otgc_data, + param); + + case OTG1_EVENT__MODE_CHANGE_CHALLENGE_REPLY: + dev_dbg(otgc_data->dev, + "%s: Unexpected mode change challenge " + "reply received, application might be " + "in an unknown state - ignoring\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_CHARGERMODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Charger-mode change requested, not valid in this " + "state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_DR_MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: USB OTG DR-mode change requested, not valid in this " + "state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__TIMEOUT: + dev_dbg(otgc_data->dev, + "%s: TIMEOUT, but not waiting for anyting other than " + "device disconnection - ignoring\n", + __func__); + return 0; + + default: + dev_dbg(otgc_data->dev, + "%s: Unknown signal/event (%d)\n", + __func__, + signal); + return -EINVAL; + } +} + +static int otgcontrol_handle_state_USB_NO_AUTH_WAITING_CHALLENGE_RESPONSE( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param) +{ + int ret; + + switch(signal) + { + case OTG1_EVENT__CHARGER_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is connected, disabling all OTG features until " + "host is disconnected\n", + __func__); + + otgc_data->otg_controlstate = OTG1_STATE__HOST_CONNECTED; + return 0; + + case OTG1_EVENT__CHARGER_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is disconnected (NOT EXPECTED), but just " + "ignore this and continue to wait for challenge response " + "from application\n", + __func__); + return 0; + + case OTG1_EVENT__DEVICE_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device connected (UNEXPECTED), re-starting " + "authenticated connection procedure\n", + __func__); + + ret = otgcontrol_do_device_connected_procedure(otgc_data, + false); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete device connection " + "procedure, resetting fsm as an attempt to " + "recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__DEVICE_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device disconnected, doing disconnection " + "procedure\n", + __func__); + + ret = otgcontrol_do_device_disconnected_procedure(otgc_data); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete disconnection procedure, " + "resetting fsm as an attempt to recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__CHALLENGE_REPLY_RECEIVED: + dev_dbg(otgc_data->dev, + "%s: Unexpected message received from device, it might " + "be in an unknown state, doing fsm reset procedure to " + "power-cycle device\n", + __func__); + + return otgcontrol_reset_fsm(otgc_data); + + case OTG1_EVENT__MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Controller-mode change requested while already " + "waiting for challenge response, restart procedure\n", + __func__); + + return otgcontrol_do_controller_mode_change_procedure(otgc_data, + param); + + case OTG1_EVENT__MODE_CHANGE_CHALLENGE_REPLY: + dev_dbg(otgc_data->dev, + "%s: Received expected challenge response, changing to " + "unauthenticated USB OTG mode\n", + __func__); + + return otgcontrol_do_verify_application_challenge_reply(otgc_data, + param); + + case OTG1_EVENT__OTG_CHARGERMODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Charger-mode change requested, not valid in this " + "state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_DR_MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: USB OTG DR-mode change requested, not valid in " + "this state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__TIMEOUT: + dev_dbg(otgc_data->dev, + "%s: TIMEOUT, no response from application requesting " + "mode change within expected amount of time\n", + __func__); + + dev_dbg(otgc_data->dev, + "%s: Reset fsm, going back to authenticated mode, " + "requiring new mode change request\n", + __func__); + + return otgcontrol_reset_fsm(otgc_data); + + default: + dev_dbg(otgc_data->dev, + "%s: Unknown signal/event (%d)\n", + __func__, + signal); + return -EINVAL; + } +} + +static int otgcontrol_handle_state_USB_NO_AUTH_NOT_CONNECTED( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param) +{ + int ret; + + switch(signal) + { + case OTG1_EVENT__CHARGER_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is connected, disabling all OTG features " + "until host is disconnected\n", + __func__); + + otgc_data->otg_controlstate = OTG1_STATE__HOST_CONNECTED; + return 0; + + case OTG1_EVENT__CHARGER_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is disconnected (NOT EXPECTED), but just " + "ignore this and continue to wait for device connection\n", + __func__); + return 0; + + case OTG1_EVENT__DEVICE_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device connected, doing un-authenticated " + "connection procedure\n", + __func__); + + ret = otgcontrol_do_device_connected_procedure(otgc_data, + false); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete device connection " + "procedure, resetting fsm as an attempt to " + "recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__DEVICE_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device disconnected, doing disconnection procedure\n", + __func__); + + ret = otgcontrol_do_device_disconnected_procedure(otgc_data); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete disconnection procedure, " + "resetting fsm as an attempt to recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__CHALLENGE_REPLY_RECEIVED: + dev_dbg(otgc_data->dev, + "%s: Unexpected message received from device, it might " + "be in an unknown state, doing fsm reset procedure to " + "power-cycle device\n", + __func__); + + return otgcontrol_reset_fsm(otgc_data); + + case OTG1_EVENT__MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Controller-mode change requested, start caller " + "authentication\n", + __func__); + + return otgcontrol_do_controller_mode_change_procedure(otgc_data, + param); + + case OTG1_EVENT__MODE_CHANGE_CHALLENGE_REPLY: + dev_dbg(otgc_data->dev, + "%s: Unexpected mode change challenge reply received, " + "application might be in an unknown state - ignoring\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_CHARGERMODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Charger-mode change requested, not valid in this " + "state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_DR_MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: USB OTG DR-mode change requested, not valid in " + "this state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__TIMEOUT: + dev_dbg(otgc_data->dev, + "%s: TIMEOUT, but not waiting for anyting other than " + "device connection - ignoring\n", + __func__); + return 0; + + default: + dev_dbg(otgc_data->dev, + "%s: Unknown signal/event (%d)\n", + __func__, + signal); + return -EINVAL; + } +} + +static int otgcontrol_handle_state_USB_NO_AUTH_DEVICE_CONNECTED( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param) +{ + int ret; + + switch(signal) + { + case OTG1_EVENT__CHARGER_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is connected, disabling all OTG features " + "until host is disconnected\n", + __func__); + + otgc_data->otg_controlstate = OTG1_STATE__HOST_CONNECTED; + return 0; + + case OTG1_EVENT__CHARGER_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is disconnected (NOT EXPECTED), but just " + "ignore this and continue being connected to authenticated " + "device\n", + __func__); + return 0; + + case OTG1_EVENT__DEVICE_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device connected (UNEXPECTED), re-starting " + "un-authenticated connection procedure\n", + __func__); + + ret = otgcontrol_do_device_connected_procedure(otgc_data, + false); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete device " + "connection procedure, resetting " + "fsm as an attempt to recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__DEVICE_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device disconnected, doing disconnection procedure\n", + __func__); + + ret = otgcontrol_do_device_disconnected_procedure(otgc_data); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to complete disconnection procedure, " + "resetting fsm as an attempt to recover\n", + __func__); + return otgcontrol_reset_fsm(otgc_data); + } + return 0; + + case OTG1_EVENT__CHALLENGE_REPLY_RECEIVED: + dev_dbg(otgc_data->dev, + "%s: Unexpected message received from device, it might " + "be in an unknown state, doing fsm reset procedure to " + "power-cycle device\n", + __func__); + + return otgcontrol_reset_fsm(otgc_data); + + case OTG1_EVENT__MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Controller-mode change requested\n", + __func__); + + return otgcontrol_do_controller_mode_change_procedure( + otgc_data, + param); + + case OTG1_EVENT__MODE_CHANGE_CHALLENGE_REPLY: + dev_dbg(otgc_data->dev, + "%s: Unexpected mode change challenge reply received, " + "application might be in an unknown state - ignoring\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_CHARGERMODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Charger-mode change requested, not valid in this " + "state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_DR_MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: USB OTG DR-mode change requested, not valid in " + "this state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__TIMEOUT: + dev_dbg(otgc_data->dev, + "%s: TIMEOUT, but not waiting for anyting other than " + "device disconnection - ignoring\n", + __func__); + return 0; + + default: + dev_dbg(otgc_data->dev, + "%s: Unknown signal/event (%d)\n", + __func__, + signal); + return -EINVAL; + } +} + +static int otgcontrol_handle_state_HOST_CONNECTED( + struct rm_otgcontrol_data *otgc_data, + int signal, + void *param) +{ + switch(signal) + { + case OTG1_EVENT__CHARGER_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host connection detected in already connected " + "state, keeping all OTG features disabled until host " + "is disconnected\n", + __func__); + + otgc_data->otg_controlstate = OTG1_STATE__HOST_CONNECTED; + return 0; + + case OTG1_EVENT__CHARGER_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Host is disconnected, reset fsm into authenticated " + "mode\n", + __func__); + + return otgcontrol_reset_fsm(otgc_data); + + case OTG1_EVENT__DEVICE_CONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device connected (UNEXPECTED), reset fsm into " + "authenticated mode\n", + __func__); + + return otgcontrol_reset_fsm(otgc_data); + + case OTG1_EVENT__DEVICE_DISCONNECTED: + dev_dbg(otgc_data->dev, + "%s: Device disconnected (UNEXPECTED), reset fsm into " + "authenticated mode\n", + __func__); + + return otgcontrol_reset_fsm(otgc_data); + + case OTG1_EVENT__CHALLENGE_REPLY_RECEIVED: + dev_dbg(otgc_data->dev, + "%s: Unexpected message received from device, reset " + "fsm into authenticated mode\n", + __func__); + + return otgcontrol_reset_fsm(otgc_data); + + case OTG1_EVENT__MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Controller-mode change requested, not valid in " + "this state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__MODE_CHANGE_CHALLENGE_REPLY: + dev_dbg(otgc_data->dev, + "%s: Unexpected mode change challenge reply received, " + "application might be in an unknown state - ignoring\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_CHARGERMODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: Charger-mode change requested, not valid in this " + "state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__OTG_DR_MODE_CHANGE_REQUESTED: + dev_dbg(otgc_data->dev, + "%s: USB OTG DR-mode change requested, not valid in " + "this state\n", + __func__); + return -EINVAL; + + case OTG1_EVENT__TIMEOUT: + dev_dbg(otgc_data->dev, + "%s: TIMEOUT, but not waiting for anyting other than " + "host disconnection - ignoring\n", + __func__); + return 0; + + default: + dev_dbg(otgc_data->dev, + "%s: Unknown signal/event (%d)\n", + __func__, + signal); + return -EINVAL; + } +} + +static int otgcontrol_do_controller_mode_change_procedure( + struct rm_otgcontrol_data *otgc_data, + void *param) +{ + int mode_requested = *(int*)param; + + /* Depending on requested mode.. */ + + /* 1. + * Enable challenge/reply properties, and set challenge to be read by + * application */ + + /* 2. + * Wait for reply for a while */ + + /* But for now, just set mode */ + switch(mode_requested) + { + case OTG_MODE__MANUAL_CONTROL: + case OTG_MODE__USB_NO_AUTH: + dev_err(otgc_data->dev, + "%s: Change to manual or non-authenticated mode " + "requires authentication of calling application\n", + __func__); + + return otgcontrol_do_authenticate_calling_application( + otgc_data, + mode_requested); + break; + + default: + /* Invalid mode => authenticated mode */ + dev_err(otgc_data->dev, + "%s: Invalid controller mode or authenticated mode " + "requested (%d), setting one-wire authenticated mode\n", + __func__, + mode_requested); + + otgcontrol_do_set_controlmode(otgc_data, + OTG_MODE__ONEWIRE_AUTH); + return 0; + } +} + +static int otgcontrol_do_authenticate_calling_application( + struct rm_otgcontrol_data *otgc_data, + int mode_requested) +{ + switch(mode_requested) + { + case OTG_MODE__MANUAL_CONTROL: + case OTG_MODE__USB_NO_AUTH: + /* NORMALLY, A CHALLENGE WOULD BE SENT TO CALLING APPLICATION */ + /* IDEA: + * SHOW CHALLENGE PROPERTY + * WAIT FOR APPLICATION TO READ THIS + * SHOW REPLY PROPERTY, TO BE PRESENT WHEN APPLICATION + * HAS READ THE CHALLENGE + * GET REPLY AND VERIFY + * HIDE CHALLENGE/REPLY PROPERTIES + * + * FOR NOW, JUST CALL REPLY VERIFY ROUTINE, WHICH CURRENTLY + * HAS NO VERIFICATION ALGORITHM IMPLEMENTED, AND + * WILL DO A SIMPLE CONNECTION CHECK AND SET STATE ACCORDING + * TO THE REQUESTED MODE AND THE CURRENT CONNECTION STATE + */ + dev_dbg(otgc_data->dev, + "%s: Authentication of calling application not" + "implemented, skipping\n", + __func__); + + otgc_data->mode_requested = mode_requested; + otgcontrol_do_verify_application_challenge_reply(otgc_data, + NULL); + break; + + case OTG_MODE__ONEWIRE_AUTH: + default: + /* No authentication required to enable authenticated mode */ + otgcontrol_do_set_controlmode(otgc_data, + mode_requested); + } + return 0; +} + +static int otgcontrol_do_verify_application_challenge_reply( + struct rm_otgcontrol_data *otgc_data, + void *param) +{ + switch(otgc_data->mode_requested) + { + case OTG_MODE__MANUAL_CONTROL: + otgcontrol_do_set_controlmode(otgc_data, + otgc_data->mode_requested); + break; + + case OTG_MODE__USB_NO_AUTH: + otgcontrol_do_set_controlmode(otgc_data, + otgc_data->mode_requested); + break; + + case OTG_MODE__ONEWIRE_AUTH: + default: + otgcontrol_do_set_controlmode(otgc_data, + otgc_data->mode_requested); + break; + } + + return 0; +} + +static int otgcontrol_do_verify_device_challenge_reply( + struct rm_otgcontrol_data *otgc_data, + void *param) +{ + int ret; + dev_dbg(otgc_data->dev, + "%s: For now, just take the challenge to be good and activate " + "USB OTG Host mode", + __func__); + + ret = otgcontrol_set_dr_mode(otgc_data, OTG1_DR_MODE__HOST); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to set USB OTG host mode\n", + __func__); + return ret; + } + + otgc_data->otg_controlstate = OTG1_STATE__ONEWIRE_AUTH_DEVICE_CONNECTED; + return 0; +} + +static int otgcontrol_do_set_controlmode( + struct rm_otgcontrol_data *otgc_data, + int mode) +{ + switch(mode) + { + case OTG_MODE__MANUAL_CONTROL: + dev_dbg(otgc_data->dev, + "%s: setting MANUAL_CONTROL mode\n", + __func__); + + otgc_data->otg_controlstate = OTG1_STATE__MANUAL_CONTROL; + otgc_data->otg1_controllermode = OTG_MODE__MANUAL_CONTROL; + + otgcontrol_deactivate_gpio_irq(otgc_data); + break; + + case OTG_MODE__ONEWIRE_AUTH: + if (otgcontrol_get_current_gpio_state(otgc_data) == + OTG1_ONEWIRE_GPIO_STATE__DEVICE_CONNECTED) { + dev_dbg(otgc_data->dev, + "%s: Enabling ONE-WIRE AUTHENTICATED mode " + "(DEVICE CONNECTED)\n", + __func__); + + otgc_data->otg_controlstate = OTG1_STATE__ONEWIRE_AUTH_DEVICE_CONNECTED; + } + else { + dev_dbg(otgc_data->dev, + "%s: Enabling ONE-WIRE AUTHENTICATED mode " + "(DEVICE NOT CONNECTED)\n", + __func__); + + otgc_data->otg_controlstate = OTG1_STATE__ONEWIRE_AUTH_NOT_CONNECTED; + } + otgc_data->otg1_controllermode = OTG_MODE__ONEWIRE_AUTH; + + otgcontrol_deactivate_gpio_irq(otgc_data); + + /* Also do a disconnect procedure, setting the charger back + * to charger mode and the OTG USB device back to device mode + * until proper authenticated device connection has been + * implemented + */ + otgcontrol_do_device_disconnected_procedure(otgc_data); + break; + + case OTG_MODE__USB_NO_AUTH: + if (otgcontrol_get_current_gpio_state(otgc_data) == + OTG1_ONEWIRE_GPIO_STATE__DEVICE_CONNECTED) { + dev_dbg(otgc_data->dev, + "%s: Enabling ONE-WIRE NON-AUTHENTICATED mode " + "(DEVICE CONNECTED)\n", + __func__); + + /* Due to the fact that this mode is intended for the + * test-application requiring un-authenticated USB + * equipment to be connected, just enable OTG mode + * since the device is already connected when switching + * to this mode, to prevent need for unplug and replug + */ + otgcontrol_do_device_connected_procedure(otgc_data, false); + otgc_data->otg_controlstate = OTG1_STATE__USB_NO_AUTH_DEVICE_CONNECTED; + } + else { + dev_dbg(otgc_data->dev, + "%s: Enabling ONE-WIRE NON-AUTHENTICATED mode " + "(DEVICE NOT CONNECTED)\n", + __func__); + + /* Due to the fact that this device disconnection could + * not be detected in other modes, just disable OTG mode + * to make sure the charger mode is enabled since the + * device is not connected - just to be sure + */ + otgcontrol_do_device_disconnected_procedure(otgc_data); + otgc_data->otg_controlstate = OTG1_STATE__USB_NO_AUTH_NOT_CONNECTED; + } + otgc_data->otg1_controllermode = OTG_MODE__USB_NO_AUTH; + + otgcontrol_activate_gpio_irq(otgc_data); + break; + + default: + dev_dbg(otgc_data->dev, + "%s: unable to set control mode (unknown mode %d)\n", + __func__, + mode); + return -EINVAL; + } + + return 0; +} + +static int otgcontrol_do_device_connected_procedure( + struct rm_otgcontrol_data *otgc_data, + bool authentication_required) +{ + int ret; + + if (authentication_required) { + dev_warn(otgc_data->dev, + "Authenticated connection not supported, ignoring..\n"); + return -ENOTSUPP; + } + else + { + dev_dbg(otgc_data->dev, + "Authentication not required, activating USB connection\n"); + + dev_dbg(otgc_data->dev, + "Powering up connected device (if no charger is connected)\n"); + + ret = otgcontrol_change_otg_charger_mode_int( + otgc_data, + OTG1_CHARGERMODE_OTG); /* OTG POWER ON */ + if (ret < 0) { + dev_dbg(otgc_data->dev, + "Unable to turn on OTG power, check connections\n"); + return ret; + } + + /* Sleep to avoid race, let USB driver handle itself before setting DR mode */ + usleep_range(300000, 400000); + + otgcontrol_set_dr_mode(otgc_data, + OTG1_DR_MODE__HOST); + otgc_data->otg_controlstate = OTG1_STATE__USB_NO_AUTH_DEVICE_CONNECTED; + otgc_data->otg1_controllermode = OTG_MODE__USB_NO_AUTH; + return 0; + } +} + +static int otgcontrol_do_device_disconnected_procedure( + struct rm_otgcontrol_data *otgc_data) +{ + int ret; + + ret = otgcontrol_change_otg_charger_mode_int( + otgc_data, + OTG1_CHARGERMODE_CHARGE); /* OTG POWER OFF */ + if (ret < 0) { + dev_dbg(otgc_data->dev, + "%s: Unable to turn off OTG power\n", + __func__); + return ret; + } + + ret = otgcontrol_set_dr_mode(otgc_data, + OTG1_DR_MODE__DEVICE); + if (ret < 0) { + dev_dbg(otgc_data->dev, + "%s: Unable to set USB OTG device mode\n", + __func__); + return ret; + } + + /* otgcontrol_activate_gpio_irq(otgc_data);*/ + if (otgc_data->otg1_controllermode == OTG_MODE__USB_NO_AUTH) { + dev_dbg(otgc_data->dev, + "%s: Activating one-wire GPIO IRQ\n", + __func__); + + otgcontrol_activate_gpio_irq(otgc_data); + } + return 0; +} + +static int otgcontrol_do_start_onewire_authentication( + struct rm_otgcontrol_data *otgc_data) +{ + dev_dbg(otgc_data->dev, + "%s: Enter\n", + __func__); + + dev_dbg(otgc_data->dev, + "%s: PLEASE IMPLEMENT THIS !\n", + __func__); + return 0; +} diff --git a/drivers/misc/rm-otgcontrol/otgcontrol_fsm.h b/drivers/misc/rm-otgcontrol/otgcontrol_fsm.h new file mode 100644 index 000000000000..bf2045f988b8 --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol_fsm.h @@ -0,0 +1,52 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __OTGCONTROL_FSM_H__ +#define __OTGCONTROL_FSM_H__ + +#include "otgcontrol.h" + +int otgcontrol_init_fsm(struct rm_otgcontrol_data *otgc_data); +int otgcontrol_handleInput(struct rm_otgcontrol_data *otgc_data, + int signal, + void *param); + +#define OTG_MODE__MANUAL_CONTROL 0 +#define OTG_MODE__ONEWIRE_AUTH 1 +#define OTG_MODE__USB_NO_AUTH 2 + +#define OTG1_STATE__MANUAL_CONTROL 0 +#define OTG1_STATE__ONEWIRE_AUTH_NOT_CONNECTED 1 +#define OTG1_STATE__ONEWIRE_AUTH_WAIT_HANDSHAKE_RESPONS 2 +#define OTG1_STATE__ONEWIRE_AUTH_DEVICE_CONNECTED 3 +#define OTG1_STATE__USB_NO_AUTH_WAITING_CHALLENGE_RESPONSE 4 +#define OTG1_STATE__USB_NO_AUTH_NOT_CONNECTED 5 +#define OTG1_STATE__USB_NO_AUTH_DEVICE_CONNECTED 6 +#define OTG1_STATE__HOST_CONNECTED 7 + +#define OTG1_EVENT__CHARGER_CONNECTED 0 +#define OTG1_EVENT__CHARGER_DISCONNECTED 1 +#define OTG1_EVENT__DEVICE_CONNECTED 2 +#define OTG1_EVENT__DEVICE_DISCONNECTED 3 +#define OTG1_EVENT__CHALLENGE_REPLY_RECEIVED 4 +#define OTG1_EVENT__TIMEOUT 5 +#define OTG1_EVENT__MODE_CHANGE_REQUESTED 6 +#define OTG1_EVENT__MODE_CHANGE_CHALLENGE_REPLY 7 +#define OTG1_EVENT__OTG_CHARGERMODE_CHANGE_REQUESTED 8 +#define OTG1_EVENT__OTG_DR_MODE_CHANGE_REQUESTED 9 + +#endif /* __OTGCONTROL_FSM_H__ */ diff --git a/drivers/misc/rm-otgcontrol/otgcontrol_main.c b/drivers/misc/rm-otgcontrol/otgcontrol_main.c new file mode 100644 index 000000000000..8b33208506a6 --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol_main.c @@ -0,0 +1,384 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "otgcontrol.h" +#include "otgcontrol_sysfs.h" +#include "otgcontrol_fsm.h" +#include "otgcontrol_dr_mode.h" +#include "otgcontrol_onewire.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int rm_otgcontrol_init(struct rm_otgcontrol_data *otgc_data) +{ + int ret = 0; + + dev_dbg(otgc_data->dev, + "%s: Initiating sysfs nodes\n", + __func__); + + ret = otgcontrol_init_sysfs_nodes(otgc_data); + if (ret < 0) + return ret; + + dev_dbg(otgc_data->dev, + "%s: Initiating extcon device to control USB OTG dr mode\n", + __func__); + + ret = otgcontrol_init_extcon(otgc_data); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to initiate extron (%d)\n", + __func__, ret); + return ret; + } + + dev_dbg(otgc_data->dev, + "%s: Initiating onewire state and setting to default state " + "(GPIO)\n", + __func__); + + ret = otgcontrol_init_one_wire_mux_state(otgc_data); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to initiate onewire pincontrol " + "configuration\n", + __func__); + return ret; + } + + dev_dbg(otgc_data->dev, + "%s: Initiating one-wire gpio irq\n", + __func__); + + ret = otgcontrol_init_gpio_irq(otgc_data); + if (ret < 0) + return ret; + + dev_dbg(otgc_data->dev, + "%s: Initiating fsm to start in AUTHORIZED MODE !!\n", + __func__); + + ret = otgcontrol_init_fsm(otgc_data); + return ret; +} + +static int rm_otgcontrol_parse_dt(struct rm_otgcontrol_data *otgc_data) +{ + struct device *dev = otgc_data->dev; + struct device_node *np = dev->of_node; + struct rm_otgcontrol_platform_data *pdata = otgc_data->pdata; + const char *vbus_supply_name; + int ret = 0; + + dev_dbg(otgc_data->dev, + "%s: Enter\n", + __func__); + + if (of_find_property(np, "vbus-supply", NULL)) { + dev_dbg(otgc_data->dev, + "%s: Found vbus-supply property, " + "trying to get get vbus powersupply by phandle\n", + __func__); + + pdata->vbus_supply = power_supply_get_by_phandle(np, + "vbus-supply"); + if (IS_ERR_OR_NULL(pdata->vbus_supply)) { + dev_dbg(otgc_data->dev, + "%s: vbus supply not ready, defering probe\n", + __func__); + return -EPROBE_DEFER; + } + } + else if (of_find_property(np, "vbus-supply-name", NULL)) { + dev_dbg(otgc_data->dev, + "%s: Found vbus-supply-name property, " + "trying to read it\n", + __func__); + + ret = of_property_read_string(np, + "vbus-supply-name", + &vbus_supply_name); + if (ret) { + dev_err(otgc_data->dev, + "%s: Failed to read property vbus-supply-name " + "(code %d)\n", + __func__, ret); + return -EINVAL; + } + + dev_dbg(otgc_data->dev, + "%s: Read vbus-supply-name: %s, " + "trying to get reference to it\n", + __func__, vbus_supply_name); + + pdata->vbus_supply = power_supply_get_by_name(vbus_supply_name); + if (IS_ERR(pdata->vbus_supply)) { + dev_dbg(otgc_data->dev, + "%s: vbus supply not ready, defering probe\n", + __func__); + return -EPROBE_DEFER; + } + } + else { + dev_dbg(otgc_data->dev, + "%s: Required vbus-supply-name property not given !\n", + __func__); + return -EINVAL; + } + + dev_dbg(otgc_data->dev, + "%s: Got pointer to vbus-supply\n", + __func__); + + if (of_find_property(np, "one-wire-tty-name", NULL)) { + dev_dbg(otgc_data->dev, + "%s: Found one-wire-tty-name property, " + "trying to read it\n", + __func__); + + ret = of_property_read_string(np, + "one-wire-tty-name", + &otgc_data->pdata->one_wire_tty_name); + if (ret) { + dev_err(otgc_data->dev, + "%s: Failed to read property one-wire-tty-name " + "(code %d)\n", + __func__, ret); + return -EINVAL; + } + } + else { + dev_dbg(otgc_data->dev, + "%s: required property one-wire-tty-name not given !\n", + __func__); + return -EINVAL; + } + + if (of_find_property(np, "one-wire-gpios", NULL)) { + dev_dbg(otgc_data->dev, + "%s: Found one-wire-gpio property, trying to read it\n", + __func__); + + otgc_data->pdata->one_wire_gpio = devm_gpiod_get(otgc_data->dev, + "one-wire", + GPIOD_IN); + if (IS_ERR(otgc_data->pdata->one_wire_gpio)) { + dev_err(otgc_data->dev, + "%s: Failed to read property one-wire-gpio " + "(code %ld)\n", + __func__, + PTR_ERR(otgc_data->pdata->one_wire_gpio)); + return PTR_ERR(otgc_data->pdata->one_wire_gpio); + } + } + else { + dev_dbg(otgc_data->dev, + "%s: required property one-wire-gpio not given !\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int rm_otgcontrol_probe(struct platform_device *pdev) +{ + struct rm_otgcontrol_data *otgc_data; + struct rm_otgcontrol_platform_data *pdata; + + int ret = 0; + + dev_dbg(&pdev->dev, + "%s: rM OTGCONTROL Driver Loading\n", + __func__); + + dev_dbg(&pdev->dev, + "%s: Allocating otgcontrol_data\n", + __func__); + + otgc_data = devm_kzalloc(&pdev->dev, + sizeof(struct rm_otgcontrol_data), + GFP_KERNEL); + if (!otgc_data) { + dev_err(&pdev->dev, + "%s: Failed to allocate otgc_data\n", + __func__); + return -ENOMEM; + } + + dev_dbg(&pdev->dev, + "%s: Allocating otgcontrol_data\n", + __func__); + + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct rm_otgcontrol_platform_data), + GFP_KERNEL); + if (unlikely(!pdata)) { + dev_err(&pdev->dev, + "%s: Failed to allocate pdata\n", + __func__); + pdata = ERR_PTR(-ENOMEM); + ret = -ENOMEM; + goto error_1; + } + + otgc_data->dev = &pdev->dev; + otgc_data->pdata = pdata; + + dev_dbg(&pdev->dev, + "%s: Reading platform data from devicetree\n", + __func__); + + ret = rm_otgcontrol_parse_dt(otgc_data); + if (ret < 0) { + if (ret == -EPROBE_DEFER) { + dev_info(&pdev->dev, + "%s: Defering probe due to charger driver not being" + "loaded/available yet\n", + __func__); + } + else { + dev_err(&pdev->dev, + "%s: Failed to load platform data from devicetree, " + "code %d\n", + __func__, + ret); + } + goto error_1; + } + + dev_dbg(&pdev->dev, + "%s: Setting otgc_data reference in pdev, and initiating\n", + __func__); + + ret = rm_otgcontrol_init(otgc_data); + if(ret < 0) { + dev_err(&pdev->dev, + "%s: Failed to init otgcontrol, " + "code %d\n", + __func__, + ret); + goto error_2; + } + + platform_set_drvdata(pdev, otgc_data); + + dev_info(&pdev->dev, + "Loaded successfully !\n"); + + return 0; + +error_2: + otgcontrol_uninit_sysfs_nodes(otgc_data); + otgcontrol_uninit_gpio_irq(otgc_data); + +error_1: + /* No need to do explicit calls to devm_kfree */ + return ret; +} + +static int rm_otgcontrol_remove(struct platform_device *pdev) +{ + struct rm_otgcontrol_data *otgc_data = platform_get_drvdata(pdev); + + dev_dbg(otgc_data->dev, + "%s: Un-initializing sysfs nodes\n", + __func__); + otgcontrol_uninit_sysfs_nodes(otgc_data); + + dev_dbg(otgc_data->dev, + "%s: Un-initialize gpio irq\n", + __func__); + otgcontrol_uninit_gpio_irq(otgc_data); + + return 0; +} + +#if defined CONFIG_PM +static int rm_otgcontrol_suspend(struct device *dev) +{ + dev_dbg(dev, "%s Enter:\n", __func__); + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int rm_otgcontrol_resume(struct device *dev) +{ + dev_dbg(dev, "%s Enter:\n", __func__); + + pinctrl_pm_select_default_state(dev); + + return 0; +} +#else +#define rm_otgcontrol_suspend NULL +#define rm_otgcontrol_resume NULL +#endif + +static struct of_device_id rm_otgcontrol_dt_ids[] = { + { .compatible = "rm-otgcontrol" }, + { } +}; +MODULE_DEVICE_TABLE(of, rm_otgcontrol_dt_ids); + +static SIMPLE_DEV_PM_OPS(rm_otgcontrol_pm_ops, + rm_otgcontrol_suspend, + rm_otgcontrol_resume); + +static struct platform_driver rm_otgcontrol_driver = { + .driver = { + .name = "rm_otg_control", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &rm_otgcontrol_pm_ops, +#endif + .of_match_table = rm_otgcontrol_dt_ids, + }, + .probe = rm_otgcontrol_probe, + .remove = rm_otgcontrol_remove, +}; + +module_platform_driver(rm_otgcontrol_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("reMarkable OTG control driver, to enable authentication of " + "devices connecting through the USB OTG interface"); +MODULE_VERSION("1.0"); +MODULE_AUTHOR("Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "otgcontrol_onewire.h" +#include "otgcontrol_fsm.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ONE_WIRE_GPIO_DEBOUNCE_MS 500 /* ms */ + +int otgcontrol_init_one_wire_mux_state(struct rm_otgcontrol_data *otgc_data) +{ + int ret; + + dev_dbg(otgc_data->dev, + "%s: Initiating one-wire pinctrl states\n", + __func__); + + otgc_data->one_wire_pinctrl = devm_pinctrl_get(otgc_data->dev); + if (IS_ERR(otgc_data->one_wire_pinctrl)) { + dev_err(otgc_data->dev, + "%s: Failed to get pinctrl\n", + __func__); + + return PTR_ERR(otgc_data->one_wire_pinctrl); + } + + otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__GPIO] = + pinctrl_lookup_state(otgc_data->one_wire_pinctrl, + "default"); + + if (IS_ERR(otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__GPIO])) { + dev_err(otgc_data->dev, + "%s: Failed to configure one-wire-gpio state\n", + __func__); + + devm_pinctrl_put(otgc_data->one_wire_pinctrl); + return PTR_ERR(otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__GPIO]); + } + + otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__UART_TX] = + pinctrl_lookup_state(otgc_data->one_wire_pinctrl, + "one_wire_uart_tx"); + + if (IS_ERR(otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__UART_TX])) { + dev_err(otgc_data->dev, + "%s: Failed to configure one-wire-uart-tx state\n", + __func__); + + devm_pinctrl_put(otgc_data->one_wire_pinctrl); + return PTR_ERR(otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__UART_TX]); + } + + otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__UART_RX] = + pinctrl_lookup_state(otgc_data->one_wire_pinctrl, + "one_wire_uart_rx"); + + if (IS_ERR(otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__UART_RX])) { + dev_err(otgc_data->dev, + "%s: Failed to configure one-wire-uart-rx\n", + __func__); + + devm_pinctrl_put(otgc_data->one_wire_pinctrl); + return PTR_ERR(otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__UART_RX]); + } + + dev_dbg(otgc_data->dev, + "%s: Setting default state (GPIO)\n", + __func__); + + ret = pinctrl_select_state( + otgc_data->one_wire_pinctrl, + otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__GPIO]); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to set default state (GPIO)\n", + __func__); + + devm_pinctrl_put(otgc_data->one_wire_pinctrl); + } + + otgc_data->otg1_pinctrlstate = OTG1_ONEWIRE_STATE__GPIO; + return 0; +} + +int otgcontrol_switch_one_wire_mux_state(struct rm_otgcontrol_data *otgc_data, + int state) +{ + int ret; + + switch(state) + { + case OTG1_ONEWIRE_STATE__GPIO: + dev_dbg(otgc_data->dev, + "%s: Switching onewire state -> GPIO\n", + __func__); + + ret = pinctrl_select_state( + otgc_data->one_wire_pinctrl, + otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__GPIO]); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to set pinctrl state\n", + __func__); + return ret; + } + break; + + case OTG1_ONEWIRE_STATE__UART_RX: + dev_dbg(otgc_data->dev, + "%s: Switching onewire state -> UART RX\n", + __func__); + + ret = pinctrl_select_state( + otgc_data->one_wire_pinctrl, + otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__UART_RX]); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to set pinctrl state\n", + __func__); + return ret; + } + break; + + case OTG1_ONEWIRE_STATE__UART_TX: + dev_dbg(otgc_data->dev, + "%s: switching onewire state -> UART TX\n", + __func__); + + ret = pinctrl_select_state( + otgc_data->one_wire_pinctrl, + otgc_data->one_wire_pinctrl_states[OTG1_ONEWIRE_STATE__UART_TX]); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to set pinctrl state\n", + __func__); + return ret; + } + break; + + default: + dev_err(otgc_data->dev, + "%s: unable to switch onewire state (unknown state %d)\n", + __func__, state); + return -EINVAL; + } + + otgc_data->otg1_pinctrlstate = state; + return 0; +} + +int otgcontrol_get_current_gpio_state(struct rm_otgcontrol_data *otgc_data) +{ + return SYNC_GET_FLAG(gpiod_get_raw_value(otgc_data->pdata->one_wire_gpio), + &otgc_data->lock); +} + +const char *otgcontrol_gpio_state_name(int state) +{ + switch(state) + { + case OTG1_ONEWIRE_GPIO_STATE__DEVICE_CONNECTED: + return "DEVICE CONNECTED"; + break; + case OTG1_ONEWIRE_GPIO_STATE__DEVICE_NOT_CONNECTED: + return "DEVICE NOT CONNECTED"; + break; + default: + return "UNKNOWN DEVICE CONNECTION STATE EXPECTED 0 or 1)"; + } +} + +int otgcontrol_init_gpio_irq(struct rm_otgcontrol_data *otgc_data) +{ + int ret; + + dev_dbg(otgc_data->dev, + "%s: Setting local gpio debounce jiffies\n", + __func__); + + otgc_data->one_wire_gpio_debounce_jiffies = + msecs_to_jiffies(ONE_WIRE_GPIO_DEBOUNCE_MS); + + dev_dbg(otgc_data->dev, + "%s: Initiating irq worker\n", + __func__); + + INIT_DELAYED_WORK(&otgc_data->one_wire_gpio_irq_work_queue, + otgcontrol_gpio_irq_work); + + dev_dbg(otgc_data->dev, + "%s: Getting IRQ from given gpio (%d)\n", + __func__, + desc_to_gpio(otgc_data->pdata->one_wire_gpio)); + + otgc_data->pdata->one_wire_gpio_irq = gpiod_to_irq(otgc_data->pdata->one_wire_gpio); + if (otgc_data->pdata->one_wire_gpio_irq < 0) { + dev_err(otgc_data->dev, + "%s: Failed to get irq for given gpio (%d)\n", + __func__, + desc_to_gpio(otgc_data->pdata->one_wire_gpio)); + return otgc_data->pdata->one_wire_gpio_irq; + } + + otgc_data->one_wire_gpio_state = + otgcontrol_get_current_gpio_state(otgc_data); + + dev_dbg(otgc_data->dev, + "%s: Current GPIO state: %s\n", + __func__, + otgcontrol_gpio_state_name(otgc_data->one_wire_gpio_state)); + + dev_dbg(otgc_data->dev, + "%s: Clearing is-handling flag\n", + __func__); + + otgc_data->one_wire_gpio_irq_is_handling = false; + + dev_dbg(otgc_data->dev, + "%s: Requesting threaded irq\n", + __func__); + + ret = devm_request_threaded_irq( + otgc_data->dev, + otgc_data->pdata->one_wire_gpio_irq, + NULL, + otgcontrol_gpio_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "one_wire_gpio_irq", + otgc_data); + if (ret < 0) { + dev_err(otgc_data->dev, + "%s: Failed to request handler for IRQ (%d) " + "given for GPIO (%d)\n", + __func__, + otgc_data->pdata->one_wire_gpio_irq, + desc_to_gpio(otgc_data->pdata->one_wire_gpio)); + return ret; + } + mutex_lock(&otgc_data->lock); + otgc_data->one_wire_gpio_irq_is_active = true; + mutex_unlock(&otgc_data->lock); + + return 0; +} + +void otgcontrol_uninit_gpio_irq(struct rm_otgcontrol_data *otgc_data) +{ + cancel_delayed_work_sync(&otgc_data->one_wire_gpio_irq_work_queue); +} + +void otgcontrol_activate_gpio_irq(struct rm_otgcontrol_data *otgc_data) +{ + if (!otgc_data->one_wire_gpio_irq_is_active) { + enable_irq(otgc_data->pdata->one_wire_gpio_irq); + otgc_data->one_wire_gpio_irq_is_active = true; + } +} + +void otgcontrol_deactivate_gpio_irq(struct rm_otgcontrol_data *otgc_data) +{ + if (otgc_data->one_wire_gpio_irq_is_active) { + disable_irq(otgc_data->pdata->one_wire_gpio_irq); + otgc_data->one_wire_gpio_irq_is_active = false; + } +} + +static irqreturn_t otgcontrol_gpio_irq_handler(int irq, void *data) +{ + struct rm_otgcontrol_data *otgc_data = (struct rm_otgcontrol_data*)data; + + if (SYNC_GET_FLAG(otgc_data->one_wire_gpio_irq_is_handling, + &otgc_data->lock)) { + dev_dbg(otgc_data->dev, + "%s: Is already handling irq, ignoring this\n", + __func__); + return IRQ_HANDLED; + } + else { + dev_dbg(otgc_data->dev, + "%s: Queueing IRQ handling for execution in %d ms\n", + __func__, + ONE_WIRE_GPIO_DEBOUNCE_MS); + + SYNC_SET_FLAG(otgc_data->one_wire_gpio_irq_is_handling, + &otgc_data->lock); + + queue_delayed_work(system_power_efficient_wq, + &otgc_data->one_wire_gpio_irq_work_queue, + otgc_data->one_wire_gpio_debounce_jiffies); + } + + return IRQ_HANDLED; +} + +static void otgcontrol_gpio_irq_work(struct work_struct *work) +{ + int cur_gpio_state; + + struct delayed_work *delayed_work = + container_of(work, + struct delayed_work, + work); + + struct rm_otgcontrol_data *otgc_data = + container_of(delayed_work, + struct rm_otgcontrol_data, + one_wire_gpio_irq_work_queue); + + dev_dbg(otgc_data->dev, + "%s: Checking current gpio state\n", + __func__); + + cur_gpio_state = otgcontrol_get_current_gpio_state(otgc_data); + if (cur_gpio_state != otgc_data->one_wire_gpio_state) { + dev_dbg(otgc_data->dev, + "%s: GPIO state changed -> %s\n", + __func__, + otgcontrol_gpio_state_name(cur_gpio_state)); + + otgc_data->one_wire_gpio_state = cur_gpio_state; + + if (otgc_data->one_wire_gpio_state == + OTG1_ONEWIRE_GPIO_STATE__DEVICE_CONNECTED) { + SYNC_SET_FLAG(otgc_data->otg1_device_connected, + &otgc_data->lock); + + otgcontrol_handleInput(otgc_data, + OTG1_EVENT__DEVICE_CONNECTED, + NULL); + } + else { + SYNC_CLEAR_FLAG(otgc_data->otg1_device_connected, + &otgc_data->lock); + otgcontrol_handleInput(otgc_data, + OTG1_EVENT__DEVICE_DISCONNECTED, + NULL); + } + } + + SYNC_CLEAR_FLAG(otgc_data->one_wire_gpio_irq_is_handling, + &otgc_data->lock); +} + +int otgcontrol_onewire_write_tty(struct rm_otgcontrol_data *otgc_data, + char *device_name, + char *text_to_send) +{ + struct file *f; + char buf[128]; + mm_segment_t fs; + int i; + + for(i = 0;i < 128;i++) + buf[i] = 0; + + dev_dbg(otgc_data->dev, "%s: Trying to open %s\n", + __func__, + device_name); + + f = filp_open(device_name, O_RDWR, 0); + if(f == NULL) { + dev_err(otgc_data->dev, + "%s: filp_open error!!.\n", + __func__); + return -1; + } + else { + dev_dbg(otgc_data->dev, + "%s: Getting current segment descriptor\n", + __func__); + + fs = get_fs(); + + dev_dbg(otgc_data->dev, + "%s: Setting segment descriptor\n", + __func__); + + set_fs(KERNEL_DS); + + dev_dbg(otgc_data->dev, + "%s: Writing '%s' to file\n", + __func__, + text_to_send); + + kernel_write(f, + text_to_send, + strlen(text_to_send), + &f->f_pos); + + dev_dbg(otgc_data->dev, + "%s: Restoring segment descriptor\n", + __func__); + + set_fs(fs); + + dev_dbg(otgc_data->dev, + "%s: Closing file\n", + __func__); + + filp_close(f,NULL); + return 0; + } +} + +int otgcontrol_onewire_read_until_cr(struct rm_otgcontrol_data *otgc_data, char *device_name, char *buf, int maxlen) +{ + struct file *f; + mm_segment_t fs; + char newchar; + int pos, state; + + f = filp_open(device_name, O_RDONLY, 0); + if(f == NULL) { + dev_err(otgc_data->dev, + "%s: filp_open error!!.\n", + __func__); + return -1; + } + else { + dev_dbg(otgc_data->dev, + "%s: Getting current segment descriptor\n", + __func__); + + fs = get_fs(); + + dev_dbg(otgc_data->dev, + "%s: Setting segment descriptor\n", + __func__); + + set_fs(KERNEL_DS); + + pos = 0; + state = 0; + dev_dbg(otgc_data->dev, + "%s: Starting read loop\n", + __func__); + do { + kernel_read(f, + &newchar, + 1, + &f->f_pos); + + dev_dbg(otgc_data->dev, + "%s: <-%c (0x%02x)\n", + __func__, + newchar, newchar); + + switch(state) + { + case 0: + if (newchar == ':') { + dev_dbg(otgc_data->dev, + "%s: SOF\n", + __func__); + + state = 1; + } + break; + default: + if (newchar != '#') { + buf[pos++] = newchar; + } + } + }while((newchar != '#') && (pos < maxlen)); + + dev_dbg(otgc_data->dev, + "%s: Done\n", + __func__); + + dev_dbg(otgc_data->dev, + "%s: Restoring segment descriptor\n", + __func__); + + set_fs(fs); + + dev_dbg(otgc_data->dev, + "%s: Closing file\n", + __func__); + + filp_close(f, NULL); + + dev_dbg(otgc_data->dev, + "%s: Returning %d bytes read\n", + __func__, + pos); + + return pos; + } +} diff --git a/drivers/misc/rm-otgcontrol/otgcontrol_onewire.h b/drivers/misc/rm-otgcontrol/otgcontrol_onewire.h new file mode 100644 index 000000000000..f572c007ceb1 --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol_onewire.h @@ -0,0 +1,53 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __OTGCONTROL_ONE_WIRE_H__ +#define __OTGCONTROL_ONE_WIRE_H__ + +#include "otgcontrol.h" + +#include +#include + +#define OTG1_ONEWIRE_STATE__GPIO 0 +#define OTG1_ONEWIRE_STATE__UART_TX 1 +#define OTG1_ONEWIRE_STATE__UART_RX 2 + +#define OTG1_ONEWIRE_GPIO_STATE__DEVICE_CONNECTED 0 +#define OTG1_ONEWIRE_GPIO_STATE__DEVICE_NOT_CONNECTED 1 + +int otgcontrol_init_one_wire_mux_state(struct rm_otgcontrol_data *otgc_data); + +int otgcontrol_switch_one_wire_mux_state(struct rm_otgcontrol_data *otgc_data, + int newState); +int otgcontrol_get_current_gpio_state(struct rm_otgcontrol_data *otgc_data); +const char *otgcontrol_gpio_state_name(int state); +int otgcontrol_init_gpio_irq(struct rm_otgcontrol_data *otgc_data); +void otgcontrol_uninit_gpio_irq(struct rm_otgcontrol_data *otgc_data); +void otgcontrol_activate_gpio_irq(struct rm_otgcontrol_data *otgc_data); +void otgcontrol_deactivate_gpio_irq(struct rm_otgcontrol_data *otgc_data); +static irqreturn_t otgcontrol_gpio_irq_handler(int irq, void *data); +static void otgcontrol_gpio_irq_work(struct work_struct *work); +int otgcontrol_onewire_read_until_cr(struct rm_otgcontrol_data *otgc_data, + char *device_name, + char *buf, + int maxlen); +int otgcontrol_onewire_write_tty(struct rm_otgcontrol_data *otgc_data, + char *device_name, + char *text_to_send); + +#endif /* __OTGCONTROL_ONE_WIRE_H__ */ diff --git a/drivers/misc/rm-otgcontrol/otgcontrol_sysfs.c b/drivers/misc/rm-otgcontrol/otgcontrol_sysfs.c new file mode 100644 index 000000000000..367f18e876e7 --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol_sysfs.c @@ -0,0 +1,322 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "otgcontrol_sysfs.h" +#include "otgcontrol_fsm.h" +#include "otgcontrol_dr_mode.h" +#include "otgcontrol_charging_ctrl.h" +#include "otgcontrol_onewire.h" + +#include +#include +#include +#include +#include + +#define to_otgcontrol_data(kobj_attr_ptr, kobj_attr_member) \ + container_of(kobj_attr_ptr, struct rm_otgcontrol_data, kobj_attr_member); + +#define SYSFS_PARENT_NODE NULL +#define SYSFS_NODE_NAME "otgcontrol" + +#define STATUS_GROUP_ATTRIBUTE_COUNT 1 +#define CONTROL_GROUP_ATTRIBUTE_COUNT 4 + +static struct attribute *control_attrs[CONTROL_GROUP_ATTRIBUTE_COUNT + 1] = { + /* CONTROL_GROUP_ATTRIBUTE_COUNT number of initializing NULLs */ + NULL, NULL, NULL, NULL, + + /* NULL terminating the list */ + NULL +}; + +static struct attribute_group control_attr_group = { + .attrs = control_attrs, + .name = "control" +}; + +struct attribute *status_attrs[STATUS_GROUP_ATTRIBUTE_COUNT + 1] = { + /* STATUS_GROUP_ATTRIBUTE_COUNT number of NULLS */ + NULL, + + /* NULL terminating the list */ + NULL +}; + +static struct attribute_group status_attr_group = { + .attrs = status_attrs, + .name = "status" +}; + +static ssize_t attribute_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + int count, var; + struct rm_otgcontrol_data *otgc_data; + + if (strcmp(attr->attr.name, "otg1_device_connected") == 0) { + otgc_data = to_otgcontrol_data(attr, + otg1_device_connected_attribute); + + /* ID = 0 ==> DEVICE CONNECTED */ + var = !otgcontrol_get_current_gpio_state(otgc_data); + + dev_dbg(otgc_data->dev, + "%s: Returning cur otg1_device_connected value (%d)\n", + __func__, + var); + + } + else if (strcmp(attr->attr.name, "otg1_dr_mode") == 0) { + otgc_data = to_otgcontrol_data(attr, + otg1_dr_mode_attribute); + + dev_dbg(otgc_data->dev, + "%s: Returning cur otg1_id_state value (%d)\n", + __func__, + otgc_data->otg1_dr_mode); + + var = otgc_data->otg1_dr_mode; + } + else if (strcmp(attr->attr.name, "otg1_chargermode") == 0) { + otgc_data = to_otgcontrol_data(attr, + otg1_chargermode_attribute); + + count = otgcontrol_get_otg_charger_modes(otgc_data, buf); + + dev_dbg(otgc_data->dev, + "%s: Returning charger mode list: %s\n", + __func__, + buf); + + return count; + } + else if (strcmp(attr->attr.name, "otg1_controllermode") == 0) { + otgc_data = to_otgcontrol_data(attr, + otg1_controllermode_attribute); + + dev_dbg(otgc_data->dev, + "%s: Returning cur otg1_controllermode value (%d)\n", + __func__, + otgc_data->otg1_controllermode); + + var = otgc_data->otg1_controllermode; + } + else if (strcmp(attr->attr.name, "otg1_pinctrlstate") == 0) { + otgc_data = to_otgcontrol_data(attr, + otg1_pinctrlstate_attribute); + dev_dbg(otgc_data->dev, + "%s: Returning cur pinctrlstate (%d)\n", + __func__, + otgc_data->otg1_pinctrlstate); + + var = otgc_data->otg1_pinctrlstate; + } + else { + pr_err("%s: Invalid attribute name (%s), returning 0\n", + __func__, + attr->attr.name); + + var = 0; + } + + return sprintf(buf, "%d\n", var); +} + +static ssize_t attribute_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + struct rm_otgcontrol_data *otgc_data; + int var, ret; + + if (strcmp(attr->attr.name, "otg1_dr_mode") == 0) { + otgc_data = to_otgcontrol_data(attr, + otg1_dr_mode_attribute); + + ret = kstrtoint(buf, 10, &var); + if (ret < 0) + return ret; + + dev_dbg(otgc_data->dev, + "%s: Setting new otg1 dr mode (%d)\n", + __func__, + var); + + ret = otgcontrol_set_dr_mode(otgc_data, + var); + } + else if (strcmp(attr->attr.name, "otg1_chargermode") == 0) { + otgc_data = to_otgcontrol_data(attr, otg1_chargermode_attribute); + + dev_dbg(otgc_data->dev, + "%s: Setting new otg1 chargermode: %s", + __func__, + buf); + + ret = otgcontrol_change_otg_charger_mode_str(otgc_data, buf); + } + else if (strcmp(attr->attr.name, "otg1_controllermode") == 0) { + otgc_data = to_otgcontrol_data(attr, + otg1_controllermode_attribute); + + ret = kstrtoint(buf, 10, &var); + if (ret < 0) + return ret; + + dev_dbg(otgc_data->dev, + "%s: Setting new otg1 controllermode (%d)\n", + __func__, + var); + + ret = otgcontrol_handleInput(otgc_data, + OTG1_EVENT__MODE_CHANGE_REQUESTED, + (void*)&var); + } + else if (strcmp(attr->attr.name, "otg1_pinctrlstate") == 0) { + otgc_data = to_otgcontrol_data(attr, + otg1_pinctrlstate_attribute); + + ret = kstrtoint(buf, 10, &var); + if (ret < 0) + return ret; + + dev_dbg(otgc_data->dev, + "%s: Setting new pinctrlstate (%d)\n", + __func__, + var); + + ret = otgcontrol_switch_one_wire_mux_state(otgc_data, + var); + } + else { + return -EINVAL; + } + + return count; +} + +void otgcontrol_create_kobj_property(struct kobj_attribute *attr, + const char *name, + int permission, + ssize_t (*show)(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf), + ssize_t (*store)(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count)) +{ + attr->attr.name = name; + attr->attr.mode = VERIFY_OCTAL_PERMISSIONS(S_IRUGO | S_IWUSR); + attr->show = show; + attr->store = store; +} + +int otgcontrol_init_sysfs_nodes(struct rm_otgcontrol_data *otgc_data) +{ + struct kobject *otgcontrol_kobj; + int ret; + + otgcontrol_create_kobj_property(&otgc_data->otg1_dr_mode_attribute, + "otg1_dr_mode", + S_IRUGO | S_IWUSR, + attribute_show, + attribute_store); + + otgcontrol_create_kobj_property(&otgc_data->otg1_chargermode_attribute, + "otg1_chargermode", + S_IRUGO | S_IWUSR, + attribute_show, + attribute_store); + + otgcontrol_create_kobj_property(&otgc_data->otg1_controllermode_attribute, + "otg1_controllermode", + S_IRUGO | S_IWUSR, + attribute_show, + attribute_store); + + otgcontrol_create_kobj_property(&otgc_data->otg1_pinctrlstate_attribute, + "otg1_pinctrlstate", + S_IRUGO | S_IWUSR, + attribute_show, + attribute_store); + + control_attrs[0] = &otgc_data->otg1_dr_mode_attribute.attr; + control_attrs[1] = &otgc_data->otg1_chargermode_attribute.attr; + control_attrs[2] = &otgc_data->otg1_controllermode_attribute.attr; + control_attrs[3] = &otgc_data->otg1_pinctrlstate_attribute.attr; + control_attrs[4] = NULL; /* NULL termination of the list */ + + otgcontrol_create_kobj_property(&otgc_data->otg1_device_connected_attribute, + "otg1_device_connected", + S_IRUGO, + attribute_show, + attribute_store); + + status_attrs[0] = &otgc_data->otg1_device_connected_attribute.attr; + status_attrs[1] = NULL; /* NULL termination of the list */ + + otgcontrol_kobj = kobject_create_and_add(SYSFS_NODE_NAME, + SYSFS_PARENT_NODE); + if (!otgcontrol_kobj) { + dev_err(otgc_data->dev, + "%s: Failed to create 'otgcontrol' kobject\n", + __func__); + return -ENOMEM; + } + + ret = sysfs_create_group(otgcontrol_kobj, + &control_attr_group); + if (ret != 0) { + dev_err(otgc_data->dev, + "%s: Failed to create 'control' attribute group\n", + __func__); + goto error_1; + } + + ret = sysfs_create_group(otgcontrol_kobj, + &status_attr_group); + if (ret != 0) { + dev_err(otgc_data->dev, + "%s: Failed to create 'status' attribute group\n", + __func__); + goto error_2; + } + + otgc_data->kobject = otgcontrol_kobj; + return ret; + +error_2: + sysfs_remove_group(otgc_data->kobject, &control_attr_group); + +error_1: + kobject_put(otgc_data->kobject); + return ret; +} + +void otgcontrol_uninit_sysfs_nodes(struct rm_otgcontrol_data *otgc_data) +{ + if((otgc_data->kobject != NULL) && !IS_ERR(otgc_data->kobject)) { + sysfs_remove_group(otgc_data->kobject, &control_attr_group); + sysfs_remove_group(otgc_data->kobject, &status_attr_group); + kobject_put(otgc_data->kobject); + otgc_data->kobject = NULL; + } +} diff --git a/drivers/misc/rm-otgcontrol/otgcontrol_sysfs.h b/drivers/misc/rm-otgcontrol/otgcontrol_sysfs.h new file mode 100644 index 000000000000..bd0107976770 --- /dev/null +++ b/drivers/misc/rm-otgcontrol/otgcontrol_sysfs.h @@ -0,0 +1,26 @@ +/* + * reMarkable OTG Control + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __OTGCONTROL_SYSFS_H__ +#define __OTGCONTROL_SYSFS_H__ + +#include "otgcontrol.h" + +int otgcontrol_init_sysfs_nodes(struct rm_otgcontrol_data *otgc_data); +void otgcontrol_uninit_sysfs_nodes(struct rm_otgcontrol_data *otgc_data); + +#endif /* __OTGCONTROL_SYSFS_H__ */ diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 28413f737e7d..b7b3a05d6c90 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -88,6 +88,12 @@ enum { POWER_SUPPLY_SCOPE_DEVICE, }; +enum { + POWER_SUPPLY_MODE_CHARGER = 0, + POWER_SUPPLY_MODE_OTG_SUPPLY, + POWER_SUPPLY_MODE_ALL_OFF, +}; + enum power_supply_property { /* Properties of type `int' */ POWER_SUPPLY_PROP_STATUS = 0, @@ -162,6 +168,9 @@ enum power_supply_property { POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_SERIAL_NUMBER, + + /* MAX77818 specific mode of operation (OTG supply/charger) */ + POWER_SUPPLY_PROP_CHARGER_MODE, }; enum power_supply_type {