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 <alistair@alistair23.me>5.4-rM2-2.2.x-imx-deep-sleep
parent
c222fce7d8
commit
64aa77fbef
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)"
|
||||
|
|
@ -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
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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 <linux/kobject.h>
|
||||
#include <linux/pinctrl/pinctrl.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/extcon.h>
|
||||
|
||||
#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 */
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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 <linux/errno.h>
|
||||
#include <linux/power_supply.h>
|
||||
|
||||
/* 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);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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__ */
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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 <linux/extcon-provider.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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__
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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__ */
|
|
@ -0,0 +1,384 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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 <linux/kernel.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/extcon.h>
|
||||
|
||||
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 <steinar.bakkemo@remarkable.no");
|
|
@ -0,0 +1,513 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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 <linux/export.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/fs.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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 <linux/workqueue.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#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__ */
|
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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 <linux/kernel.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/power_supply.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* reMarkable OTG Control
|
||||
*
|
||||
* Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/
|
||||
*
|
||||
* Author: Steinar Bakkemo <steinar.bakkemo@remarkable.com>
|
||||
*
|
||||
* 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__ */
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue