c072cf20d3
Added sysfs_notify call and make status & position show methods waiting for updated values Change-Id: Ib19dd3e362a70b6c5217821a46a2f3eb679452ac Signed-off-by: Konstantin Makariev <hcv867@motorola.com> Reviewed-on: https://gerrit.mot.com/2244143 SME-Granted: SME Approvals Granted SLTApproved: Slta Waiver Tested-by: Jira Key Reviewed-by: Qing Chang <qing@motorola.com> Submit-Approved: Jira Key
1850 lines
48 KiB
C
1850 lines
48 KiB
C
#define pr_fmt(fmt) "drv8424: %s: " fmt, __func__
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/version.h>
|
|
#include <linux/input.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/hrtimer.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/string.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/wait.h>
|
|
#include <uapi/linux/sched/types.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/kfifo.h>
|
|
#include <linux/clk-provider.h>
|
|
|
|
#define DEFAULT_STEP_FREQ 2400
|
|
|
|
#define MOTOR_CLASS_NAME "drv8424"
|
|
#define MOTOR_CONTROL "control"
|
|
#define MOTOR_DEFAULT_EXPIRE 750 //750ms clock time.
|
|
#define MOTOR_HW_CLK 9600 //9.6KHz clock
|
|
#define MOTOR_HW_CLK_NAME "gcc_gp3"
|
|
|
|
#define MOTOR_MODE_SPEED 0
|
|
#define MOTOR_MODE_STEP 1
|
|
|
|
#define LOGD(fmt, args...) pr_err(fmt, ##args)
|
|
#undef MOTOR_SLOT_DBG
|
|
|
|
enum position_id {
|
|
POS_UNKNOWN = 0,
|
|
POS_COMPACT,
|
|
POS_EXPANDED,
|
|
POS_PEEK,
|
|
};
|
|
|
|
static const char *position_labels[] = {
|
|
"UNKNOWN",
|
|
"COMPACT",
|
|
"EXPANDED",
|
|
"PEEK",
|
|
};
|
|
|
|
enum status_id {
|
|
STATUS_UNKNOWN,
|
|
STATUS_STOPPED_COMPACT,
|
|
STATUS_STOPPED_EXPANDED,
|
|
STATUS_STOPPED_PEEK,
|
|
STATUS_MOVING_OUT,
|
|
STATUS_MOVING_IN,
|
|
};
|
|
|
|
static const char *status_labels[] = {
|
|
"UNKNOWN",
|
|
"STOPPED_COMPACT",
|
|
"STOPPED_EXPANDED",
|
|
"STOPPED_PEEK",
|
|
"EXPANDING",
|
|
"WITHDRAWING",
|
|
};
|
|
|
|
enum gpios_index {
|
|
MOTOR_POWER_EN = 0, /* BOOST */
|
|
MOTOR_FAULT_INT, /* nFAULT INT */
|
|
MOTOR_STEP, /* STEP */
|
|
MOTOR_DIR, /* DIR */
|
|
MOTOR_MODE1, /* set to 0 in HW */
|
|
MOTOR_MODE2, /* set to 0 in HW */
|
|
MOTOR_EN, /* set to 1 in HW */
|
|
MOTOR_SLEEP, /* nSLEEP */
|
|
MOTOR_T0, /* set to 0 in HW */
|
|
MOTOR_T1, /* set to 0 in HW */
|
|
MOTOR_ACTIVE, /* signal GPIO */
|
|
MOTOR_UNKNOWN
|
|
};
|
|
|
|
static const char* const gpios_labels[] = {
|
|
"MOTOR_POWER_EN",
|
|
"MOTOR_FAULT_INT",
|
|
"MOTOR_STEP",
|
|
"MOTOR_DIR",
|
|
"MOTOR_MODE1",
|
|
"MOTOR_MODE2",
|
|
"MOTOR_EN",
|
|
"MOTOR_SLEEP",
|
|
"MOTOR_T0",
|
|
"MOTOR_T1",
|
|
"MOTOR_ACTIVE",
|
|
"MOTOR_UNKNOWN"
|
|
};
|
|
|
|
static const int def_gpios_table[] = {
|
|
0, 0, 136, 0, 0, 0, 0, 0, 0, 0, 0
|
|
// 52, 57, 21, 32, 30, 51, 29, 50, 49, 33, -1
|
|
};
|
|
|
|
static const unsigned long hw_clocks[] = {
|
|
DEFAULT_STEP_FREQ, //0,0 software clock
|
|
4800, //0,Z 2*2400
|
|
9600, //0,1 4*2400
|
|
19200, //Z,0 8*2400
|
|
9600, //Z,Z 4*2400
|
|
38400, //Z,1 16*2400
|
|
19200, //1,0 8*2400
|
|
76800, //1,Z 32*2400
|
|
38400, //1,1 16*2400
|
|
};
|
|
|
|
enum motor_step_mode {
|
|
FULL_STEP = 0, //0,0
|
|
STEP_2_RISING, //0,Z
|
|
STEP_4_RISING, //0,1
|
|
STEP_8_RISING, //Z,0
|
|
STEP_8_BOTH_EDEG, //Z,Z
|
|
STEP_16_RISING, //Z,1
|
|
STEP_16_BOTH_EDGE, //1,0
|
|
STEP_32_RISING, //1,Z
|
|
STEP_32_BOTH_EDEG, //1,1
|
|
};
|
|
|
|
enum motor_torque {
|
|
TORQUE_FULL = 0, //0,0
|
|
TORQUE_87, //0,Z
|
|
TORQUE_75, //0,1
|
|
TORQUE_62, //Z,0
|
|
TORQUE_50, //Z,Z
|
|
TORQUE_37, //Z,1
|
|
TORQUE_25, //1,0
|
|
TORQUE_12, //1,Z
|
|
TORQUE_DISABLE, //1,1
|
|
};
|
|
|
|
//Keep in sequence with dtsi.
|
|
enum motor_pins_mode {
|
|
MODE0_LOW = 0,
|
|
MODE0_HIGH,
|
|
MODE0_DISABLE,
|
|
MODE1_LOW,
|
|
MODE1_HIGH,
|
|
MODE1_DISABLE,
|
|
T0_LOW,
|
|
T0_HIGH,
|
|
T0_DISABLE,
|
|
T1_LOW,
|
|
T1_HIGH,
|
|
T1_DISABLE,
|
|
INT_DEFAULT,
|
|
CLK_ACTIVE,
|
|
CLK_SLEEP,
|
|
PINS_END
|
|
};
|
|
|
|
//Keep in same name with the pinctrl-names of dtsi
|
|
static const char* const pins_state[] = {
|
|
"m0_low", "m0_high", "m0_disable",
|
|
"m1_low", "m1_high", "m1_disable",
|
|
"t0_low", "t0_high", "t0_disable",
|
|
"t1_low", "t1_high", "t1_disable",
|
|
"drv8424_int_default",
|
|
"drv8424_clk_active", "drv8424_clk_sleep",
|
|
NULL
|
|
};
|
|
|
|
enum regime_idx {
|
|
SQ_FULL,
|
|
SQ_SHORTENED,
|
|
SQ_SHORT,
|
|
SQ_TINY,
|
|
SQ_MAX
|
|
};
|
|
|
|
static const char *regime_names[SQ_MAX] = {
|
|
"FULL",
|
|
"SHORTENED",
|
|
"SHORT",
|
|
"TINY"
|
|
};
|
|
|
|
/* PPS Pulses
|
|
stage 1: 400 24
|
|
stage 2: 1400 56
|
|
stage 3: 3000 6252
|
|
stage 4: 2000 12
|
|
*/
|
|
#define MAX_STAGE_LEGS 10
|
|
typedef struct {
|
|
unsigned freq, ceiling;
|
|
} motor_stage;
|
|
|
|
static motor_stage initial_data[SQ_MAX][MAX_STAGE_LEGS] = {
|
|
{ /* FULL 44mm */
|
|
{400,24}, {1400,56}, {3000,6252}, {2000,12}
|
|
},
|
|
{ /* SHORTENED 39mm */
|
|
{400,24}, {1400,56}, {3000,5530}, {2000,12}
|
|
},
|
|
{ /* SHORT 5mm */
|
|
{400,24}, {1400,56}, {3000,630}, {2000,12}
|
|
},
|
|
{ /* TINY 1mm */
|
|
{400,24}, {1400,56}, {2000,65}
|
|
}
|
|
};
|
|
|
|
enum cmds {
|
|
CMD_FAULT,
|
|
CMD_STATUS,
|
|
CMD_POSITION,
|
|
CMD_POLL,
|
|
};
|
|
|
|
#define MAX_GPIOS MOTOR_UNKNOWN
|
|
typedef struct motor_control {
|
|
struct regulator *vdd;
|
|
int32_t ptable[MAX_GPIOS];
|
|
size_t tab_cells;
|
|
struct pinctrl* pins;
|
|
struct pinctrl_state *pin_state[PINS_END];
|
|
const char* const * plabels;
|
|
} motor_control;
|
|
|
|
#define CLOCK_NAME_LEN 16
|
|
typedef struct motor_device {
|
|
char clock_name[CLOCK_NAME_LEN];
|
|
struct device* dev;
|
|
struct device* sysfs_dev;
|
|
struct class* drv8424_class;
|
|
struct workqueue_struct* motor_wq;
|
|
struct clk * pwm_clk;
|
|
struct delayed_work motor_work;
|
|
struct task_struct *motor_task;
|
|
wait_queue_head_t sync_complete;
|
|
wait_queue_head_t status_wait;
|
|
wait_queue_head_t position_wait;
|
|
struct hrtimer stepping_timer;
|
|
motor_control mc;
|
|
spinlock_t mlock;
|
|
struct mutex mx_lock;
|
|
int fault_irq;
|
|
bool faulting;
|
|
unsigned step_freq;
|
|
unsigned long step_period;
|
|
unsigned long step_ceiling;
|
|
unsigned long cur_clk;
|
|
atomic_t step_count;
|
|
atomic_t stepping;
|
|
atomic_t status;
|
|
atomic_t position;
|
|
atomic_t destination;
|
|
atomic_t sensor_data;
|
|
unsigned slot;
|
|
unsigned mode, cur_mode;
|
|
unsigned torque;
|
|
unsigned time_out;
|
|
unsigned half;
|
|
unsigned stage, max_stages;
|
|
unsigned regime;
|
|
motor_stage sequencer[SQ_MAX][MAX_STAGE_LEGS];
|
|
bool double_edge;
|
|
bool hw_clock;
|
|
bool power_default_off;
|
|
bool support_mode;
|
|
bool support_torque;
|
|
int level:1;
|
|
unsigned power_en:1;
|
|
unsigned nsleep:1;
|
|
unsigned nEN:1;
|
|
unsigned dir:1;
|
|
unsigned user_sync_complete:1;
|
|
unsigned status_update_ready:1;
|
|
unsigned position_update_ready:1;
|
|
struct kfifo cmd_pipe;
|
|
}motor_device;
|
|
|
|
#define GPIO_OUTPUT_DIR(g, p) do { \
|
|
if (gpio_is_valid(g)) \
|
|
gpio_direction_output(g, p); \
|
|
} while(0)
|
|
|
|
static int set_pinctrl_state(motor_device* md, unsigned state_index);
|
|
static void moto_drv8424_set_step_freq(motor_device* md, unsigned freq);
|
|
|
|
static ktime_t adapt_time_helper(ktime_t usec)
|
|
{
|
|
return ns_to_ktime(usec * 1000);
|
|
}
|
|
#if 0
|
|
static inline bool is_hw_clk(motor_device* md)
|
|
{
|
|
return (md->hw_clock && (md->cur_clk != hw_clocks[0]));
|
|
}
|
|
#endif
|
|
void sleep_helper(unsigned usec)
|
|
{
|
|
if(usec < 500) { //<500us, udelay
|
|
udelay(usec);
|
|
} else if(usec >= 500 && usec < 20000) { //500us - 20ms, usleep_range
|
|
usleep_range(usec, usec + 10);
|
|
} else { //>= 20ms, msleep
|
|
msleep(usec);
|
|
}
|
|
}
|
|
|
|
static int moto_drv8424_set_regulator_power(motor_device* md, bool en)
|
|
{
|
|
motor_control * mc = &md->mc;
|
|
int err = 0;
|
|
//FIXME
|
|
return 0;
|
|
if(en) {
|
|
err = regulator_enable(mc->vdd);
|
|
if (err) {
|
|
dev_err(md->dev, "Failed to enable VDD ret=%d\n", err);
|
|
goto exit;
|
|
}
|
|
} else {
|
|
err = regulator_disable(mc->vdd);
|
|
if (err) {
|
|
dev_err(md->dev, "Failed to disable VDD ret=%d\n", err);
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
return err;
|
|
}
|
|
#if 0
|
|
static int set_motor_clk(motor_device* md, bool en)
|
|
{
|
|
int ret = 0;
|
|
|
|
if(en) {
|
|
set_pinctrl_state(md, CLK_ACTIVE);
|
|
ret = clk_set_rate(md->pwm_clk, md->cur_clk);
|
|
if(ret < 0) {
|
|
dev_err(md->dev, "Failed to set clk rate to %ld\n", md->cur_clk);
|
|
ret = -ENODEV;
|
|
goto soft_clk;
|
|
}
|
|
ret = clk_prepare_enable(md->pwm_clk);
|
|
if(ret < 0) {
|
|
dev_err(md->dev, "Failed to clk_prepare_enable\n");
|
|
ret = -ENODEV;
|
|
goto soft_clk;
|
|
}
|
|
} else {
|
|
clk_disable_unprepare(md->pwm_clk);
|
|
dev_info(md->dev, "disable clock");
|
|
set_pinctrl_state(md, CLK_SLEEP);
|
|
}
|
|
|
|
return 0;
|
|
|
|
soft_clk:
|
|
md->hw_clock = false;
|
|
return ret;
|
|
}
|
|
#endif
|
|
static int init_motor_clk(motor_device* md)
|
|
{
|
|
int ret = 0;
|
|
|
|
md->pwm_clk = devm_clk_get(md->dev, md->clock_name);
|
|
if(IS_ERR(md->pwm_clk)) {
|
|
dev_err(md->dev, "Get clk error, motor is not drived\n");
|
|
ret = -ENODEV;
|
|
goto soft_clk;
|
|
}
|
|
|
|
return 0;
|
|
soft_clk:
|
|
md->hw_clock = false;
|
|
return ret;
|
|
}
|
|
|
|
static int set_pinctrl_state(motor_device* md, unsigned state_index)
|
|
{
|
|
motor_control* mc = &md->mc;
|
|
int ret = 0;
|
|
|
|
if(state_index >= PINS_END) {
|
|
dev_err(md->dev, "Illegal pin index\n");
|
|
goto err;
|
|
}
|
|
|
|
mc->pins = devm_pinctrl_get(md->dev);
|
|
if(IS_ERR_OR_NULL(mc->pins)) {
|
|
ret = PTR_ERR(mc->pins);
|
|
dev_err(md->dev, "Failed to get pinctrl %d\n", ret);
|
|
goto err;
|
|
}
|
|
|
|
mc->pin_state[state_index] = pinctrl_lookup_state(mc->pins, pins_state[state_index]);
|
|
if (IS_ERR_OR_NULL(mc->pin_state[state_index])) {
|
|
ret = PTR_ERR(mc->pin_state[state_index]);
|
|
dev_err(md->dev, "Failed to lookup pin_state[%d] %d\n", state_index, ret);
|
|
goto err_pin_state;
|
|
}
|
|
|
|
ret = pinctrl_select_state(mc->pins, mc->pin_state[state_index]);
|
|
if(ret) {
|
|
dev_err(md->dev, "Failed to set pin_state[%d] %d\n", state_index, ret);
|
|
}
|
|
|
|
err_pin_state:
|
|
pinctrl_put(mc->pins);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static void moto_drv8424_set_motor_torque(motor_device* md)
|
|
{
|
|
if (!md->support_torque)
|
|
return;
|
|
|
|
switch(md->torque) {
|
|
case TORQUE_FULL:
|
|
set_pinctrl_state(md, T0_LOW);
|
|
set_pinctrl_state(md, T1_LOW);
|
|
break;
|
|
case TORQUE_87:
|
|
set_pinctrl_state(md, T0_DISABLE);
|
|
set_pinctrl_state(md, T1_LOW);
|
|
break;
|
|
case TORQUE_75:
|
|
set_pinctrl_state(md, T0_HIGH);
|
|
set_pinctrl_state(md, T1_LOW);
|
|
break;
|
|
case TORQUE_62:
|
|
set_pinctrl_state(md, T0_LOW);
|
|
set_pinctrl_state(md, T1_DISABLE);
|
|
break;
|
|
case TORQUE_50:
|
|
set_pinctrl_state(md, T0_DISABLE);
|
|
set_pinctrl_state(md, T1_DISABLE);
|
|
break;
|
|
case TORQUE_37:
|
|
set_pinctrl_state(md, T0_HIGH);
|
|
set_pinctrl_state(md, T1_DISABLE);
|
|
break;
|
|
case TORQUE_25:
|
|
set_pinctrl_state(md, T0_LOW);
|
|
set_pinctrl_state(md, T1_HIGH);
|
|
break;
|
|
case TORQUE_12:
|
|
set_pinctrl_state(md, T0_DISABLE);
|
|
set_pinctrl_state(md, T1_HIGH);
|
|
break;
|
|
case TORQUE_DISABLE:
|
|
set_pinctrl_state(md, T0_HIGH);
|
|
set_pinctrl_state(md, T1_HIGH);
|
|
break;
|
|
default:
|
|
pr_err("Unsupported torque %d\n", md->torque);
|
|
return;
|
|
}
|
|
LOGD("pins T0 & T1 updated\n");
|
|
}
|
|
|
|
static void moto_drv8424_set_motor_mode(motor_device* md)
|
|
{
|
|
if (!md->support_mode) {
|
|
md->cur_mode = md->mode;
|
|
return;
|
|
}
|
|
|
|
switch(md->mode) {
|
|
case FULL_STEP:
|
|
set_pinctrl_state(md, MODE0_LOW);
|
|
set_pinctrl_state(md, MODE1_LOW);
|
|
break;
|
|
case STEP_2_RISING:
|
|
set_pinctrl_state(md, MODE0_DISABLE);
|
|
set_pinctrl_state(md, MODE1_LOW);
|
|
break;
|
|
case STEP_4_RISING:
|
|
set_pinctrl_state(md, MODE0_HIGH);
|
|
set_pinctrl_state(md, MODE1_LOW);
|
|
break;
|
|
case STEP_8_RISING:
|
|
set_pinctrl_state(md, MODE0_LOW);
|
|
set_pinctrl_state(md, MODE1_DISABLE);
|
|
break;
|
|
case STEP_8_BOTH_EDEG:
|
|
set_pinctrl_state(md, MODE0_DISABLE);
|
|
set_pinctrl_state(md, MODE1_DISABLE);
|
|
break;
|
|
case STEP_16_RISING:
|
|
set_pinctrl_state(md, MODE0_HIGH);
|
|
set_pinctrl_state(md, MODE1_DISABLE);
|
|
break;
|
|
case STEP_16_BOTH_EDGE:
|
|
set_pinctrl_state(md, MODE0_LOW);
|
|
set_pinctrl_state(md, MODE1_HIGH);
|
|
break;
|
|
case STEP_32_RISING:
|
|
set_pinctrl_state(md, MODE0_DISABLE);
|
|
set_pinctrl_state(md, MODE1_HIGH);
|
|
break;
|
|
case STEP_32_BOTH_EDEG:
|
|
set_pinctrl_state(md, MODE0_HIGH);
|
|
set_pinctrl_state(md, MODE1_HIGH);
|
|
break;
|
|
default:
|
|
pr_err("Unsupported mode %d\n", md->mode);
|
|
return;
|
|
}
|
|
|
|
if(md->cur_mode != md->mode) {
|
|
if(md->hw_clock) {
|
|
if(md->mode == FULL_STEP) {
|
|
md->cur_clk = hw_clocks[md->mode];
|
|
set_pinctrl_state(md, CLK_SLEEP);
|
|
dev_info(md->dev, "md->mode is FULL_STEP\n");
|
|
} else {
|
|
md->cur_clk = hw_clocks[md->mode];
|
|
dev_info(md->dev, "Switch hw clock\n");
|
|
}
|
|
}
|
|
md->cur_mode = md->mode;
|
|
md->cur_mode = md->mode;
|
|
}
|
|
usleep_range(800, 900);
|
|
LOGD("pins MODE0 & MODE1 updated\n");
|
|
}
|
|
|
|
static void moto_drv8424_set_motor_dir(motor_device* md)
|
|
{
|
|
motor_control* mc = &md->mc;
|
|
|
|
GPIO_OUTPUT_DIR(mc->ptable[MOTOR_DIR], md->dir);
|
|
usleep_range(800, 900);
|
|
LOGD("pin DIR updated\n");
|
|
}
|
|
|
|
/* Set operating modes, only control nSleep
|
|
* Set nSleep & nEn | H-Bridge | Vreg | Sequencer
|
|
* 1 0 | Op | Op | Op
|
|
* 1 1 | Dis | Op | Op
|
|
* 0 X | Dis | Dis | Dis
|
|
* Fault | Dis |Depends on Fault
|
|
*/
|
|
static void moto_drv8424_set_motor_opmode(motor_device* md)
|
|
{
|
|
motor_control* mc = &md->mc;
|
|
|
|
GPIO_OUTPUT_DIR(mc->ptable[MOTOR_SLEEP], md->nsleep);
|
|
//Twake 0.5ms Tsleep 0.7ms
|
|
usleep_range(800, 1000);
|
|
GPIO_OUTPUT_DIR(mc->ptable[MOTOR_EN], md->nEN);
|
|
LOGD("pins nSLEEP & ENABLE updated\n");
|
|
}
|
|
|
|
static int moto_drv8424_set_opmode(motor_device* md, unsigned opmode)
|
|
{
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
|
|
if(md->nsleep == opmode) {
|
|
dev_info(md->dev, "Unchanged the opmode status, ignore\n");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
md->nsleep = !!opmode;
|
|
md->nEN = !md->nsleep;
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
|
|
LOGD("opmode: nsleep=%d, en=%d\n", md->nsleep, md->nEN);
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
return ret;
|
|
}
|
|
|
|
//Set VDD
|
|
static void moto_drv8424_set_power_en(motor_device* md)
|
|
{
|
|
motor_control* mc = &md->mc;
|
|
|
|
GPIO_OUTPUT_DIR(mc->ptable[MOTOR_POWER_EN], md->power_en);
|
|
//Tpower 0.5ms
|
|
usleep_range(500, 1000);
|
|
/* signal sensor hub to start/stop sampling hall effect */
|
|
GPIO_OUTPUT_DIR(mc->ptable[MOTOR_ACTIVE], md->power_en);
|
|
LOGD("gpios BOOST & ACTIVE set: %u\n", md->power_en);
|
|
}
|
|
|
|
//Power sequence: PowerEn On->nSleep On->nSleep Off->PowerEn Off
|
|
static int moto_drv8424_set_power(motor_device* md, unsigned power)
|
|
{
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
if(md->power_en == power) {
|
|
dev_info(md->dev, "Unchanged the power status, ignore\n");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
LOGD("power_en=%u\n", !!power);
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
md->power_en = !!power;
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
|
|
ret = moto_drv8424_set_opmode(md, md->power_en);
|
|
if(ret < 0 ) {
|
|
goto exit;
|
|
}
|
|
if(!md->power_en) {
|
|
moto_drv8424_set_motor_opmode(md);
|
|
}
|
|
moto_drv8424_set_power_en(md);
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static void moto_drv8424_set_regime(motor_device *md, unsigned regime)
|
|
{
|
|
unsigned msn;
|
|
unsigned long flags;
|
|
|
|
for (msn = 0; msn < MAX_STAGE_LEGS; msn++) {
|
|
if (md->sequencer[regime][msn].freq && md->sequencer[regime][msn].ceiling)
|
|
continue;
|
|
break;
|
|
}
|
|
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
md->regime = regime;
|
|
md->stage = 0;
|
|
md->max_stages = msn;
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
|
|
dev_info(md->dev, "Set active regime: %s, stages # %u\n",
|
|
regime_names[md->regime], msn);
|
|
}
|
|
|
|
#if 0
|
|
static int moto_drv8424_enable_clk(motor_device* md, bool en)
|
|
{
|
|
return set_motor_clk(md, en);
|
|
}
|
|
#endif
|
|
#ifdef MOTOR_SLOT_DBG
|
|
static ktime_t timesA[20];
|
|
static unsigned slot;
|
|
|
|
static void inline logtime_store(void)
|
|
{
|
|
if ((slot + 1) < sizeof(timesA)/sizeof(timesA[0])) {
|
|
timesA[slot++] = ktime_get();
|
|
}
|
|
}
|
|
|
|
static void logtime_show(void)
|
|
{
|
|
int i;
|
|
ktime_t diff;
|
|
|
|
if (!slot)
|
|
return;
|
|
|
|
for (i = 1; i < slot; i++) {
|
|
diff = ktime_sub(timesA[i], timesA[i-1]);
|
|
pr_err("step[%d]=%lldus\n", i, ktime_to_us(diff));
|
|
}
|
|
}
|
|
|
|
static void inline logtime_reset(void)
|
|
{
|
|
slot = 0U;
|
|
}
|
|
#else
|
|
#define logtime_store()
|
|
#define logtime_show()
|
|
#define logtime_reset()
|
|
#endif
|
|
|
|
static void moto_drv8424_drive_stage_init(motor_device* md)
|
|
{
|
|
logtime_reset();
|
|
|
|
atomic_set(&md->stepping, 1);
|
|
atomic_set(&md->step_count, 1);
|
|
md->double_edge = false;
|
|
md->half = md->step_period >> 1;
|
|
md->level = 1;
|
|
if(md->mode == STEP_8_BOTH_EDEG
|
|
|| md->mode == STEP_16_BOTH_EDGE
|
|
|| md->mode == STEP_32_BOTH_EDEG) {
|
|
md->double_edge = true;
|
|
}
|
|
}
|
|
|
|
static bool moto_drv8424_next_stage(motor_device* md)
|
|
{
|
|
unsigned freq;
|
|
motor_stage *ms;
|
|
|
|
if (++md->stage > md->max_stages) {
|
|
md->max_stages = 0;
|
|
md->stage = 0;
|
|
return false;
|
|
}
|
|
|
|
logtime_show();
|
|
|
|
ms = &md->sequencer[md->regime][md->stage - 1];
|
|
freq = ms->freq;
|
|
md->step_ceiling = ms->ceiling;
|
|
moto_drv8424_set_step_freq(md, freq);
|
|
moto_drv8424_drive_stage_init(md);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void inline moto_drv8424_cmd_push(motor_device* md,
|
|
int cmd, unsigned long delay)
|
|
{
|
|
kfifo_put(&md->cmd_pipe, cmd);
|
|
queue_delayed_work(md->motor_wq, &md->motor_work, delay);
|
|
}
|
|
|
|
static int moto_drv8424_drive_sequencer(motor_device* md)
|
|
{
|
|
moto_drv8424_next_stage(md);
|
|
moto_drv8424_set_motor_torque(md);
|
|
moto_drv8424_set_motor_dir(md);
|
|
moto_drv8424_set_motor_mode(md);
|
|
moto_drv8424_set_motor_opmode(md);
|
|
/* can judge about moving direction based on DIR or destination position */
|
|
atomic_set(&md->status, md->dir ? STATUS_MOVING_OUT : STATUS_MOVING_IN);
|
|
moto_drv8424_cmd_push(md, CMD_STATUS, 0);
|
|
LOGD("sequencer init: stages %u ceiling %lu, freq %uHz period %luns, half %uns\n",
|
|
md->max_stages, md->step_ceiling, md->step_freq, md->step_period, md->half);
|
|
|
|
if(atomic_read(&md->stepping)) {
|
|
motor_control* mc = &md->mc;
|
|
|
|
LOGD("Status updated: status %d\n", atomic_read(&md->status));
|
|
GPIO_OUTPUT_DIR(mc->ptable[MOTOR_STEP], md->level);
|
|
atomic_inc(&md->step_count);
|
|
hrtimer_start(&md->stepping_timer, adapt_time_helper(md->half), HRTIMER_MODE_REL);
|
|
logtime_store();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* this function is called with spinlock running!!! */
|
|
static void inline motor_stop(motor_device* md, bool clean)
|
|
{
|
|
//LOGD("step count %d\n", atomic_read(&md->step_count));
|
|
if (clean) {
|
|
atomic_set(&md->step_count, 0);
|
|
atomic_set(&md->stepping, 0);
|
|
//LOGD("step_count & stepping reset\n");
|
|
}
|
|
//LOGD("waking up kthread\n");
|
|
md->user_sync_complete = true;
|
|
wake_up(&md->sync_complete);
|
|
}
|
|
|
|
static __ref int motor_kthread(void *arg)
|
|
{
|
|
motor_device* md = (motor_device*)arg;
|
|
struct sched_param param = {.sched_priority = MAX_USER_RT_PRIO - 1};
|
|
int value, ret = 0;
|
|
|
|
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
|
while (!kthread_should_stop()) {
|
|
LOGD("wait for event\n");
|
|
do {
|
|
ret = wait_event_interruptible(md->sync_complete,
|
|
md->user_sync_complete || kthread_should_stop());
|
|
} while (ret != 0);
|
|
|
|
if(kthread_should_stop())
|
|
break;
|
|
|
|
md->user_sync_complete = false;
|
|
moto_drv8424_set_power(md, 0);
|
|
if (md->power_default_off) {
|
|
dev_info(md->dev, "vdd power off\n");
|
|
moto_drv8424_set_regulator_power(md, false);
|
|
}
|
|
/* FIXME: determine position based on sensor data */
|
|
/* for now just update position with desired destination */
|
|
atomic_set(&md->position, atomic_read(&md->destination));
|
|
moto_drv8424_cmd_push(md, CMD_POSITION, 0);
|
|
/* this will stop polling */
|
|
atomic_set(&md->destination, 0);
|
|
|
|
switch (atomic_read(&md->position)) {
|
|
case POS_COMPACT:
|
|
value = STATUS_STOPPED_COMPACT;
|
|
break;
|
|
case POS_EXPANDED:
|
|
value = STATUS_STOPPED_EXPANDED;
|
|
break;
|
|
case POS_PEEK:
|
|
value = STATUS_STOPPED_PEEK;
|
|
break;
|
|
default: value = STATUS_UNKNOWN;
|
|
}
|
|
|
|
atomic_set(&md->status, value);
|
|
moto_drv8424_cmd_push(md, CMD_STATUS, 0);
|
|
|
|
LOGD("Status updated: status %d, position %d\n",
|
|
atomic_read(&md->status), atomic_read(&md->position));
|
|
logtime_show();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int disable_motor(struct device* dev)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
ktime_t time_rem;
|
|
int ret = 0;
|
|
|
|
if(atomic_read(&md->stepping)) {
|
|
atomic_set(&md->stepping, 0);
|
|
atomic_set(&md->step_count, 0);
|
|
|
|
time_rem = hrtimer_get_remaining(&md->stepping_timer);
|
|
if(ktime_to_us(time_rem) > 0) {
|
|
hrtimer_try_to_cancel(&md->stepping_timer);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int motor_set_enable(struct device* dev, bool enable)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
|
|
if(atomic_read(&md->stepping)) {
|
|
disable_motor(dev);
|
|
}
|
|
|
|
if(enable) {
|
|
moto_drv8424_drive_sequencer(md);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum hrtimer_restart motor_stepping_timer_action(struct hrtimer *h)
|
|
{
|
|
motor_device * md = container_of(h, motor_device, stepping_timer);
|
|
unsigned long flags;
|
|
enum hrtimer_restart ret = HRTIMER_RESTART;
|
|
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
|
|
if(!atomic_read(&md->stepping))
|
|
goto next_stage;
|
|
|
|
md->level = !md->level;
|
|
GPIO_OUTPUT_DIR(md->mc.ptable[MOTOR_STEP], md->level);
|
|
|
|
if(md->double_edge) {
|
|
atomic_inc(&md->step_count);
|
|
} else if(md->level) {
|
|
atomic_inc(&md->step_count);
|
|
}
|
|
|
|
logtime_store();
|
|
|
|
if(md->step_ceiling && atomic_read(&md->step_count) > md->step_ceiling) {
|
|
next_stage:
|
|
/* multi stage sequence */
|
|
if (!moto_drv8424_next_stage(md)) {
|
|
motor_stop(md, true);
|
|
GPIO_OUTPUT_DIR(md->mc.ptable[MOTOR_STEP], 0);
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
}
|
|
|
|
hrtimer_forward_now(h, adapt_time_helper(md->half));
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define RESET_TIME 1000
|
|
static void motor_cmd_work(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dw = container_of(work, struct delayed_work, work);
|
|
motor_device* md = container_of(dw, motor_device, motor_work);
|
|
bool polling = false;
|
|
int cmd = 0;
|
|
|
|
while (kfifo_get(&md->cmd_pipe, &cmd)) {
|
|
switch (cmd) {
|
|
case CMD_FAULT:
|
|
disable_irq(md->fault_irq);
|
|
if(atomic_read(&md->stepping)) {
|
|
disable_motor(md->dev);
|
|
}
|
|
moto_drv8424_set_power(md, 0);
|
|
msleep(RESET_TIME);
|
|
moto_drv8424_set_power(md, 1);
|
|
md->faulting = false;
|
|
enable_irq(md->fault_irq);
|
|
break;
|
|
case CMD_STATUS:
|
|
sysfs_notify(&md->dev->kobj, NULL, "status");
|
|
md->status_update_ready = true;
|
|
wake_up(&md->status_wait);
|
|
break;
|
|
case CMD_POSITION:
|
|
sysfs_notify(&md->dev->kobj, NULL, "position");
|
|
md->position_update_ready = true;
|
|
wake_up(&md->position_wait);
|
|
break;
|
|
case CMD_POLL:
|
|
if (atomic_read(&md->destination) > 0) {
|
|
LOGD("sensor data: %d\n", atomic_read(&md->sensor_data));
|
|
polling = true;
|
|
} else {
|
|
LOGD("stop polling\n");
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(md->dev, "Unsupported command %d\n", cmd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (polling) {
|
|
moto_drv8424_cmd_push(md, CMD_POLL, msecs_to_jiffies(100));
|
|
}
|
|
}
|
|
|
|
static irqreturn_t motor_fault_irq(int irq, void *pdata)
|
|
{
|
|
motor_device * md = (motor_device*) pdata;
|
|
int value = gpio_get_value(md->mc.ptable[MOTOR_FAULT_INT]);
|
|
|
|
if(value) {
|
|
dev_info(md->dev, "dummy motor irq event\n");
|
|
}
|
|
#if 0
|
|
md->faulting = true;
|
|
dev_err(md->dev, "Motor fault irq is happened\n");
|
|
moto_drv8424_cmd_push(md, CMD_FAULT, 0);
|
|
#endif
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
//module node interface
|
|
static void moto_drv8424_set_torque(motor_device* md, unsigned torque)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
if(md->torque == torque) {
|
|
dev_info(md->dev, "Unchanged the torque, ignore\n");
|
|
goto exit;
|
|
}
|
|
|
|
md->torque = torque;
|
|
exit:
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
}
|
|
|
|
static int moto_drv8424_set_mode(motor_device* md, unsigned mode)
|
|
{
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
if(md->mode == mode) {
|
|
dev_info(md->dev, "Unchanged the mode, ignore\n");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
md->mode = mode;
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
return ret;
|
|
}
|
|
|
|
static int moto_drv8424_set_dir(motor_device* md, unsigned dir)
|
|
{
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
if(md->dir == dir) {
|
|
dev_info(md->dev, "Unchanged the dir, ignore\n");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
md->dir = dir;
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
return ret;
|
|
}
|
|
|
|
//Spec step max frequency 250KHz
|
|
#define STEP_MAX_FREQ 250000
|
|
static void moto_drv8424_set_step_freq(motor_device* md, unsigned freq)
|
|
{
|
|
if(md->step_freq == freq) {
|
|
dev_err(md->dev, "Unchanged the freq, ignore\n");
|
|
return;
|
|
} else if(freq == 0) {
|
|
dev_err(md->dev, "Invalid frequency, ignore\n");
|
|
return;
|
|
}
|
|
|
|
if(freq > STEP_MAX_FREQ)
|
|
freq = STEP_MAX_FREQ;
|
|
md->step_freq = freq;
|
|
|
|
md->step_period = 1000000 / md->step_freq;
|
|
}
|
|
|
|
static int moto_drv8424_set_ceiling(motor_device* md, unsigned ceiling)
|
|
{
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
if(md->step_ceiling == ceiling) {
|
|
dev_info(md->dev, "Unchanged the ceiling, ignore\n");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
md->step_ceiling = ceiling;
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t motor_enable_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, 20, "%d\n", md->power_en);
|
|
}
|
|
|
|
static ssize_t motor_enable_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
unsigned enable = 0;
|
|
|
|
if(md->faulting) {
|
|
dev_err(dev, "Device faulting\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if(kstrtouint(buf, 10, &enable)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
goto exit;
|
|
}
|
|
|
|
enable = !!enable;
|
|
|
|
if (md->power_default_off && (enable != 0)) {
|
|
dev_info(md->dev, "vdd power on\n");
|
|
moto_drv8424_set_regulator_power(md, true);
|
|
msleep(1);
|
|
}
|
|
|
|
if(!moto_drv8424_set_power(md, enable)) {
|
|
motor_set_enable(dev, enable);
|
|
}
|
|
|
|
exit:
|
|
return len;
|
|
}
|
|
|
|
static ssize_t motor_dir_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, 20, "%d\n", md->dir);
|
|
}
|
|
|
|
static ssize_t motor_dir_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
unsigned value = 0;
|
|
|
|
if(md->faulting) {
|
|
dev_err(dev, "Device faulting\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if(kstrtouint(buf, 10, &value)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
goto exit;
|
|
}
|
|
|
|
moto_drv8424_set_dir(md, value);
|
|
|
|
exit:
|
|
return len;
|
|
}
|
|
|
|
static ssize_t motor_step_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, 20, "%d\n", md->step_freq);
|
|
}
|
|
|
|
static ssize_t motor_step_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
unsigned long flags;
|
|
unsigned value = 0;
|
|
|
|
if(md->faulting) {
|
|
dev_err(dev, "Device faulting\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if(kstrtouint(buf, 10, &value)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
moto_drv8424_set_step_freq(md, value);
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
dev_info(md->dev, "freq %uHz period %ldus\n", md->step_freq, md->step_period);
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t motor_mode_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, 20, "%d\n", md->mode);
|
|
}
|
|
|
|
static ssize_t motor_mode_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
unsigned value = 0;
|
|
|
|
if(md->faulting) {
|
|
dev_err(dev, "Device faulting\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if(kstrtouint(buf, 10, &value)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
goto exit;
|
|
}
|
|
|
|
moto_drv8424_set_mode(md, value);
|
|
|
|
exit:
|
|
return len;
|
|
}
|
|
|
|
static ssize_t motor_torque_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, 20, "%d\n", md->torque);
|
|
}
|
|
|
|
static ssize_t motor_torque_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
unsigned value = 0;
|
|
|
|
if(md->faulting) {
|
|
dev_err(dev, "Device faulting\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if(kstrtouint(buf, 10, &value)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
goto exit;
|
|
}
|
|
|
|
moto_drv8424_set_torque(md, value);
|
|
|
|
exit:
|
|
return len;
|
|
}
|
|
|
|
static ssize_t motor_ceiling_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, 20, "%ld\n", md->step_ceiling);
|
|
}
|
|
|
|
static ssize_t motor_ceiling_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
unsigned value = 0;
|
|
|
|
if(md->faulting) {
|
|
dev_err(dev, "Device faulting\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if(kstrtouint(buf, 10, &value)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
goto exit;
|
|
}
|
|
|
|
moto_drv8424_set_ceiling(md, value);
|
|
|
|
exit:
|
|
return len;
|
|
}
|
|
|
|
static ssize_t motor_time_out_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, 20, "%d\n", md->time_out);
|
|
}
|
|
|
|
#define STEP_TIME_OUT 3000 //3s for stop motor
|
|
static ssize_t motor_time_out_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
unsigned value = 0;
|
|
unsigned long flags;
|
|
|
|
if(md->faulting) {
|
|
dev_err(dev, "Device faulting\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if(kstrtouint(buf, 10, &value)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
goto exit;
|
|
}
|
|
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
if(value > STEP_TIME_OUT) {
|
|
value = STEP_TIME_OUT;
|
|
}
|
|
md->time_out = value;
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
|
|
exit:
|
|
return len;
|
|
}
|
|
|
|
static ssize_t motor_reset_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
unsigned value = 0;
|
|
|
|
mutex_lock(&md->mx_lock);
|
|
|
|
if(kstrtouint(buf, 10, &value)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
goto exit;
|
|
}
|
|
disable_motor(md->dev);
|
|
moto_drv8424_set_power(md, 0);
|
|
moto_drv8424_set_regulator_power(md, false);
|
|
msleep(10);
|
|
moto_drv8424_set_regulator_power(md, true);
|
|
msleep(10);
|
|
moto_drv8424_set_power(md, 0);
|
|
exit:
|
|
mutex_unlock(&md->mx_lock);
|
|
return len;
|
|
}
|
|
|
|
/* store new motor sequencer */
|
|
/* Format: index freq0:ceiling0 [... freqN:ceilingN] */
|
|
/* Example: 0 400:24 1400:56 3000:6252 2000:12 */
|
|
static ssize_t motor_sequencer_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
char *buffer, *next, *pair;
|
|
unsigned msn = 0;
|
|
unsigned value, idx, freq, ceiling;
|
|
|
|
buffer = kmalloc(len + 1, GFP_KERNEL);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
strlcpy(buffer, buf, len);
|
|
buffer[len + 1] = '\0';
|
|
|
|
next = strnchr(buffer, len, ' ');
|
|
if(kstrtouint(buffer, 10, &value) ||
|
|
(value < 0 || value >= SQ_MAX)) {
|
|
dev_err(dev, "Invalid index: %s\n", buffer);
|
|
goto exit;
|
|
}
|
|
idx = value;
|
|
for (msn = 0, pair = next; pair && msn < MAX_STAGE_LEGS; msn++, pair = next ) {
|
|
next = strnchr(pair, len, ' ');
|
|
if (next) {
|
|
*next++ = '\0';
|
|
len -= strlen(pair);
|
|
}
|
|
if (sscanf(pair, "%u:%u", &freq, &ceiling) == 2) {
|
|
md->sequencer[idx][msn].freq = freq;
|
|
md->sequencer[idx][msn].ceiling = ceiling;
|
|
dev_info(md->dev, "[%u][%u]: freq=%u, ceiling=%u\n", idx, msn, freq, ceiling);
|
|
}
|
|
}
|
|
exit:
|
|
md->max_stages = msn;
|
|
kfree(buffer);
|
|
|
|
if (md->max_stages == 0) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t motor_sequencer_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
ssize_t blen = 0;
|
|
unsigned idx, msn;
|
|
|
|
for (idx = 0; idx < SQ_MAX; idx++) {
|
|
blen += snprintf(buf + blen, PAGE_SIZE - blen, "regime %s: ",
|
|
regime_names[idx]);
|
|
for (msn = 0; msn < MAX_STAGE_LEGS; msn++) {
|
|
if (md->sequencer[idx][msn].freq == 0 &&
|
|
md->sequencer[idx][msn].ceiling == 0)
|
|
continue;
|
|
|
|
blen += snprintf(buf + blen, PAGE_SIZE - blen, "%u:%u ",
|
|
md->sequencer[idx][msn].freq,
|
|
md->sequencer[idx][msn].ceiling);
|
|
}
|
|
blen += snprintf(buf + blen, PAGE_SIZE - blen, "\n");
|
|
}
|
|
|
|
return blen;
|
|
}
|
|
|
|
static ssize_t motor_regime_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
unsigned value;
|
|
|
|
if(kstrtouint(buf, 10, &value) ||
|
|
(value < SQ_FULL || value >= SQ_MAX)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
moto_drv8424_set_regime(md, value);
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t motor_regime_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, 20, "%s\n", regime_names[md->regime]);
|
|
}
|
|
|
|
static DEVICE_ATTR(enable, S_IRUGO|S_IWUSR|S_IWGRP, motor_enable_show, motor_enable_store);
|
|
static DEVICE_ATTR(dir, S_IRUGO|S_IWUSR|S_IWGRP, motor_dir_show, motor_dir_store);
|
|
static DEVICE_ATTR(step, S_IRUGO|S_IWUSR|S_IWGRP, motor_step_show, motor_step_store);
|
|
static DEVICE_ATTR(ceiling, S_IRUGO|S_IWUSR|S_IWGRP, motor_ceiling_show, motor_ceiling_store);
|
|
static DEVICE_ATTR(mode, S_IRUGO|S_IWUSR|S_IWGRP, motor_mode_show, motor_mode_store);
|
|
static DEVICE_ATTR(sequencer, S_IRUGO|S_IWUSR|S_IWGRP, motor_sequencer_show, motor_sequencer_store);
|
|
static DEVICE_ATTR(torque, S_IRUGO|S_IWUSR|S_IWGRP, motor_torque_show, motor_torque_store);
|
|
static DEVICE_ATTR(time_out, S_IRUGO|S_IWUSR|S_IWGRP, motor_time_out_show, motor_time_out_store);
|
|
static DEVICE_ATTR(reset, S_IRUGO|S_IWUSR|S_IWGRP, NULL, motor_reset_store);
|
|
static DEVICE_ATTR(regime, S_IRUGO|S_IWUSR|S_IWGRP, motor_regime_show, motor_regime_store);
|
|
|
|
#define ATTRS_STATIC 8
|
|
#define ATTRS_MAX 11
|
|
|
|
static int last_idx = ATTRS_STATIC;
|
|
|
|
static struct attribute *motor_attributes[ATTRS_MAX + 1] = {
|
|
&dev_attr_dir.attr,
|
|
&dev_attr_enable.attr,
|
|
&dev_attr_step.attr,
|
|
&dev_attr_ceiling.attr,
|
|
&dev_attr_reset.attr,
|
|
&dev_attr_time_out.attr,
|
|
&dev_attr_regime.attr,
|
|
&dev_attr_sequencer.attr,
|
|
NULL
|
|
};
|
|
|
|
#define ATTR_ADD(name) { \
|
|
if (last_idx < ATTRS_MAX) { \
|
|
dev_info(md->dev, "[%d] adding attribute '%s'\n", last_idx, #name); \
|
|
motor_attributes[last_idx++] = &dev_attr_##name.attr; \
|
|
} else { \
|
|
dev_err(md->dev, "cannot add attribute '%s'\n", #name); \
|
|
} \
|
|
}
|
|
|
|
static struct attribute_group motor_attribute_group = {
|
|
.attrs = motor_attributes
|
|
};
|
|
|
|
static const struct attribute_group * motor_attr_groups[] = {
|
|
&motor_attribute_group,
|
|
NULL
|
|
};
|
|
|
|
/* HAL communicates desired position through this sysfs */
|
|
static ssize_t motor_position_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
unsigned long flags;
|
|
unsigned value = 0;
|
|
|
|
if(kstrtouint(buf, 10, &value) ||
|
|
/* at this point it's well known position */
|
|
(value <= POS_UNKNOWN || value > POS_PEEK)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&md->mlock, flags);
|
|
atomic_set(&md->destination, value);
|
|
spin_unlock_irqrestore(&md->mlock, flags);
|
|
moto_drv8424_cmd_push(md, CMD_POLL, msecs_to_jiffies(200));
|
|
dev_info(md->dev, "expected position %s\n", position_labels[value]);
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t motor_position_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = wait_event_interruptible(md->position_wait, md->position_update_ready);
|
|
if (ret)
|
|
return -EFAULT;
|
|
md->position_update_ready = false;
|
|
LOGD("Position update: %s\n", position_labels[atomic_read(&md->position)]);
|
|
|
|
return snprintf(buf, 20, "%d", atomic_read(&md->position));
|
|
}
|
|
|
|
static ssize_t motor_status_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = wait_event_interruptible(md->status_wait, md->status_update_ready);
|
|
if (ret)
|
|
return -EFAULT;
|
|
md->status_update_ready = false;
|
|
LOGD("Status update: %s\n", status_labels[atomic_read(&md->status)]);
|
|
|
|
return snprintf(buf, 20, "%d", atomic_read(&md->status));
|
|
}
|
|
|
|
static ssize_t motor_sensor_data_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
motor_device* md = (motor_device*)dev_get_drvdata(dev);
|
|
int value = 0;
|
|
|
|
if(kstrtoint(buf, 10, &value)) {
|
|
dev_err(dev, "Error value: %s\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
atomic_set(&md->sensor_data, value);
|
|
|
|
return len;
|
|
}
|
|
|
|
/* sysfs polled from HAL */
|
|
static DEVICE_ATTR(position, S_IRUGO|S_IWUSR|S_IWGRP, motor_position_show, motor_position_store);
|
|
static DEVICE_ATTR(status, S_IRUGO, motor_status_show, NULL);
|
|
static DEVICE_ATTR(sensor_data, S_IWUSR|S_IWGRP, NULL, motor_sensor_data_store);
|
|
|
|
static struct attribute *status_attributes[] = {
|
|
&dev_attr_status.attr,
|
|
&dev_attr_position.attr,
|
|
&dev_attr_sensor_data.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group status_attribute_group = {
|
|
.attrs = status_attributes
|
|
};
|
|
|
|
static int moto_drv8424_init_from_dt(motor_device* md)
|
|
{
|
|
struct device* pdev = md->dev;
|
|
struct device_node *np = pdev->of_node;
|
|
motor_control* mc = &md->mc;
|
|
uint32_t temp_value;
|
|
int i, gpio, rc = 0;
|
|
char gpio_name[32];
|
|
const char *clock_name;
|
|
|
|
rc = of_property_read_u32(np, "drv8424-gpios-cells", &temp_value);
|
|
if (rc) {
|
|
dev_err(pdev, "%d:Failed to get gpios cells\n", rc);
|
|
goto exit;
|
|
}
|
|
mc->tab_cells = temp_value;
|
|
|
|
if(mc->tab_cells > MAX_GPIOS) {
|
|
dev_err(pdev, "Occupied too many gpios, max limited is %d\n", MAX_GPIOS);
|
|
mc->tab_cells = MAX_GPIOS;
|
|
}
|
|
|
|
for (i = 0; i < mc->tab_cells; i++) {
|
|
snprintf(gpio_name, sizeof(gpio_name)-1, "%s-gpio", gpios_labels[i]);
|
|
gpio = of_get_named_gpio(np, gpio_name, 0);
|
|
md->mc.ptable[i] = gpio_is_valid(gpio) ? gpio : -EINVAL;
|
|
dev_info(pdev, "gpio %s = %d\n", gpios_labels[i], md->mc.ptable[i]);
|
|
}
|
|
|
|
if (!of_property_read_string(np, "clock-names", &clock_name))
|
|
strlcpy(md->clock_name, clock_name, CLOCK_NAME_LEN);
|
|
else
|
|
strlcpy(md->clock_name, MOTOR_HW_CLK_NAME, CLOCK_NAME_LEN);
|
|
dev_info(pdev, "hw clock name: %s\n", md->clock_name);
|
|
|
|
md->hw_clock = of_property_read_bool(np, "enable-hw-clock");
|
|
dev_info(pdev, "Enable hw clock %d\n", md->hw_clock);
|
|
|
|
md->support_mode = of_property_read_bool(np, "support-mode");
|
|
dev_info(pdev, "Enable hw clock %d\n", md->support_mode);
|
|
if (md->support_mode)
|
|
ATTR_ADD(mode);
|
|
|
|
md->support_torque = of_property_read_bool(np, "support-torque");
|
|
dev_info(pdev, "Enable hw clock %d\n", md->support_torque);
|
|
if (md->support_torque)
|
|
ATTR_ADD(torque);
|
|
|
|
md->power_default_off = of_property_read_bool(np, "power-default-off");
|
|
dev_info(pdev, "power is default off: %d\n", md->power_default_off);
|
|
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
static int moto_drv8424_probe(struct platform_device *pdev)
|
|
{
|
|
struct device* dev = &pdev->dev;
|
|
motor_device* md;
|
|
int i;
|
|
int ret = 0;
|
|
|
|
md = kzalloc(sizeof(motor_device), GFP_KERNEL);
|
|
if(!md) {
|
|
dev_err(dev, "probe: Out of memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
md->dev = dev;
|
|
md->mc.plabels = gpios_labels;
|
|
spin_lock_init(&md->mlock);
|
|
mutex_init(&md->mx_lock);
|
|
platform_set_drvdata(pdev, md);
|
|
moto_drv8424_set_step_freq(md, DEFAULT_STEP_FREQ);
|
|
md->time_out = MOTOR_DEFAULT_EXPIRE;
|
|
|
|
/* populate sequencer with initial values */
|
|
for (i = SQ_FULL; i < SQ_MAX; i++)
|
|
memcpy(md->sequencer[i], initial_data[i], sizeof(initial_data[i]));
|
|
|
|
/* assign default values for optional parameters */
|
|
md->mode = FULL_STEP;
|
|
md->torque = TORQUE_FULL;
|
|
|
|
ret = moto_drv8424_init_from_dt(md);
|
|
if (ret)
|
|
memcpy((void*)md->mc.ptable, def_gpios_table, sizeof(def_gpios_table));
|
|
#if 0
|
|
md->mc.vdd = devm_regulator_get(md->dev, "vdd");
|
|
if (IS_ERR(md->mc.vdd)) {
|
|
ret = PTR_ERR(md->mc.vdd);
|
|
dev_err(md->dev, "Failed to get VDD ret=%d\n", ret);
|
|
goto failed_mem;
|
|
}
|
|
#endif
|
|
if (!md->power_default_off) {
|
|
ret = moto_drv8424_set_regulator_power(md, true);
|
|
if(ret) {
|
|
regulator_put(md->mc.vdd);
|
|
dev_err(dev, "Failed enable regulator\n");
|
|
goto failed_mem;
|
|
}
|
|
}
|
|
|
|
md->motor_wq = alloc_workqueue("motor_wq", WQ_HIGHPRI, 0);
|
|
if(!md->motor_wq) {
|
|
dev_err(dev, "Out of memory for work queue\n");
|
|
goto failed_mem;
|
|
}
|
|
INIT_DELAYED_WORK(&md->motor_work, motor_cmd_work);
|
|
ret = kfifo_alloc(&md->cmd_pipe, sizeof(unsigned int)* 10, GFP_KERNEL);
|
|
if (ret) {
|
|
dev_err(dev, "Failed create fifo\n");
|
|
goto failed_work;
|
|
}
|
|
|
|
md->user_sync_complete = false;
|
|
init_waitqueue_head(&md->sync_complete);
|
|
md->status_update_ready = false;
|
|
init_waitqueue_head(&md->status_wait);
|
|
md->position_update_ready = false;
|
|
init_waitqueue_head(&md->position_wait);
|
|
|
|
/* TODO: replace static assignment with detection */
|
|
atomic_set(&md->position, POS_COMPACT);
|
|
atomic_set(&md->status, STATUS_STOPPED_COMPACT);
|
|
|
|
md->motor_task = kthread_create(motor_kthread, md, "motor_task");
|
|
if (IS_ERR(md->motor_task)) {
|
|
ret = PTR_ERR(md->motor_task);
|
|
dev_err(dev, "Failed create motor kthread\n");
|
|
goto failed_kfifo;
|
|
}
|
|
|
|
hrtimer_init(&md->stepping_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
md->stepping_timer.function = motor_stepping_timer_action;
|
|
|
|
md->drv8424_class = class_create(THIS_MODULE, MOTOR_CLASS_NAME);
|
|
if(IS_ERR(md->drv8424_class)) {
|
|
dev_err(dev, "Failed to create class\n");
|
|
goto failed_sys;
|
|
}
|
|
|
|
md->sysfs_dev = device_create_with_groups(md->drv8424_class,
|
|
dev, MKDEV(0, 0), md, motor_attr_groups,
|
|
"%s", MOTOR_CONTROL);
|
|
if(IS_ERR(md->sysfs_dev)) {
|
|
dev_err(dev, "Failed to create device\n");
|
|
goto failed_sys_dev;
|
|
}
|
|
|
|
if(sysfs_create_group(&md->dev->kobj, &status_attribute_group)) {
|
|
dev_err(dev, "Failed to create status attribute");
|
|
goto failed_device;
|
|
}
|
|
|
|
if(md->hw_clock) {
|
|
if(init_motor_clk(md) < 0) {
|
|
//Should get this gpio from DTB
|
|
dev_err(dev, "HW clock is not setup\n");
|
|
} else {
|
|
md->cur_clk = hw_clocks[0];
|
|
dev_info(dev, "HW clock is setup\n");
|
|
}
|
|
}
|
|
|
|
//Init software clock pin.
|
|
set_pinctrl_state(md, CLK_SLEEP);
|
|
for (i = 0; i < MOTOR_UNKNOWN; i++) {
|
|
if (!gpio_is_valid(md->mc.ptable[i]))
|
|
continue;
|
|
ret = devm_gpio_request(dev, md->mc.ptable[i], gpios_labels[i]);
|
|
if(ret < 0) {
|
|
pr_err("Failed to request %s, errno %d\n", gpios_labels[i--], ret);
|
|
goto failed_gpio;
|
|
}
|
|
gpio_direction_output(md->mc.ptable[i], 0);
|
|
}
|
|
|
|
wake_up_process(md->motor_task);
|
|
|
|
if(!set_pinctrl_state(md, INT_DEFAULT)) {
|
|
md->fault_irq = gpio_to_irq(md->mc.ptable[MOTOR_FAULT_INT]);
|
|
ret = devm_request_threaded_irq(&pdev->dev, md->fault_irq, motor_fault_irq,
|
|
motor_fault_irq, IRQF_TRIGGER_FALLING, "motor_irq", md);
|
|
if(ret < 0) {
|
|
dev_err(dev, "Failed to request irq %d\n", ret);
|
|
goto failed_gpio;
|
|
}
|
|
} else {
|
|
/*Here motor can work, but have not irq*/
|
|
dev_info(dev, "Failed to set device irq\n");
|
|
}
|
|
|
|
dev_info(dev, "Success init device\n");
|
|
return 0;
|
|
|
|
failed_gpio:
|
|
while(i >= MOTOR_POWER_EN) {
|
|
if (gpio_is_valid(md->mc.ptable[i]))
|
|
devm_gpio_free(dev, md->mc.ptable[i]);
|
|
i--;
|
|
}
|
|
sysfs_remove_group(&md->dev->kobj, &status_attribute_group);
|
|
failed_device:
|
|
device_destroy(md->drv8424_class, MKDEV(0, 0));
|
|
failed_sys_dev:
|
|
class_destroy(md->drv8424_class);
|
|
failed_sys:
|
|
kthread_stop(md->motor_task);
|
|
failed_work:
|
|
destroy_workqueue(md->motor_wq);
|
|
failed_kfifo:
|
|
kfifo_free(&md->cmd_pipe);
|
|
failed_mem:
|
|
kfree(md);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int moto_drv8424_remove(struct platform_device *pdev)
|
|
{
|
|
motor_device* md = (motor_device*)platform_get_drvdata(pdev);
|
|
|
|
disable_irq(md->fault_irq);
|
|
moto_drv8424_set_power(md, 0);
|
|
moto_drv8424_set_regulator_power(md, false);
|
|
devm_regulator_put(md->mc.vdd);
|
|
device_destroy(md->drv8424_class, MKDEV(0, 0));
|
|
class_destroy(md->drv8424_class);
|
|
sysfs_remove_group(&md->dev->kobj, &status_attribute_group);
|
|
disable_motor(md->dev);
|
|
kthread_stop(md->motor_task);
|
|
destroy_workqueue(md->motor_wq);
|
|
kfifo_free(&md->cmd_pipe);
|
|
kfree(md);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void moto_drv8424_platform_shutdown(struct platform_device *pdev)
|
|
{
|
|
motor_device* md = (motor_device*)platform_get_drvdata(pdev);
|
|
|
|
if(atomic_read(&md->position) != POS_COMPACT) {
|
|
/* FIXME: check dir, set regime and perhaps more */
|
|
md->dir = 1;
|
|
moto_drv8424_set_motor_dir(md);
|
|
md->power_en = 0;
|
|
moto_drv8424_set_power(md, 1);
|
|
motor_set_enable(md->dev, true);
|
|
}
|
|
msleep(800);
|
|
}
|
|
|
|
static const struct of_device_id moto_drv8424_match_table[] = {
|
|
{.compatible = "moto,drv8424"},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, moto_drv8424_match_table);
|
|
|
|
static struct platform_driver moto_drv8424_driver = {
|
|
.probe = moto_drv8424_probe,
|
|
.remove = moto_drv8424_remove,
|
|
.shutdown = moto_drv8424_platform_shutdown,
|
|
.driver = {
|
|
.name = "moto,drv8424",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = moto_drv8424_match_table,
|
|
},
|
|
};
|
|
|
|
static int __init moto_drv8424_motor_init(void)
|
|
{
|
|
return platform_driver_register(&moto_drv8424_driver);
|
|
}
|
|
|
|
module_init(moto_drv8424_motor_init);
|
|
|
|
static void __exit moto_drv8424_motor_exit(void)
|
|
{
|
|
platform_driver_unregister(&moto_drv8424_driver);
|
|
}
|
|
module_exit(moto_drv8424_motor_exit);
|
|
|
|
MODULE_DESCRIPTION("Motorola TI DRV8424 Driver");
|
|
MODULE_LICENSE("GPL v2");
|