diff --git a/drivers/misc/drv8424/Android.mk b/drivers/misc/drv8424/Android.mk new file mode 100644 index 000000000000..65d10f178694 --- /dev/null +++ b/drivers/misc/drv8424/Android.mk @@ -0,0 +1,8 @@ +DLKM_DIR := motorola/kernel/modules +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := drv8424_mmi.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/AndroidKernelModule.mk diff --git a/drivers/misc/drv8424/Kbuild b/drivers/misc/drv8424/Kbuild new file mode 100644 index 000000000000..a86fd171db15 --- /dev/null +++ b/drivers/misc/drv8424/Kbuild @@ -0,0 +1,10 @@ +# add -Wall to try to catch everything we can. +EXTRA_CFLAGS += -Wall +EXTRA_CFLAGS += -I$(TOP)/motorola/kernel/modules/include + +#ifeq ($(CONFIG_LIBER_DTB), y) +#EXTRA_CFLAGS += -DCONFIG_USE_HW_CLK +#endif + +obj-m := drv8424_mmi.o +drv8424_mmi-objs := drv8424.o diff --git a/drivers/misc/drv8424/Makefile b/drivers/misc/drv8424/Makefile new file mode 100644 index 000000000000..34b09dd815a7 --- /dev/null +++ b/drivers/misc/drv8424/Makefile @@ -0,0 +1,10 @@ +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) modules $(KBUILD_OPTIONS) + +modules_install: + $(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(shell pwd) modules_install + +clean: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean \ No newline at end of file diff --git a/drivers/misc/drv8424/device_tree.dtsi b/drivers/misc/drv8424/device_tree.dtsi new file mode 100644 index 000000000000..bc95303c1916 --- /dev/null +++ b/drivers/misc/drv8424/device_tree.dtsi @@ -0,0 +1,215 @@ +&tlmm { + drv8424_gpios { + drv8424_mode0_low: drv8424_mode0_low { + mux { + pins = "gpio70"; + function = "gpio"; + }; + config { + pins = "gpio70"; + drive-strength = <2>; /* 2 MA */ + output-low; + }; + }; + drv8424_mode0_high: drv8424_mode0_high { + mux { + pins = "gpio70"; + function = "gpio"; + }; + config { + pins = "gpio70"; + drive-strength = <2>; /* 2 MA */ + output-high; + }; + }; + drv8424_mode0_disable: drv8424_mode0_disable { + mux { + pins = "gpio70"; + function = "gpio"; + }; + config { + pins = "gpio70"; + input-enable; + bias-disable; + }; + }; + + drv8424_mode1_low: drv8424_mode1_low { + mux { + pins = "gpio71"; + function = "gpio"; + }; + config { + pins = "gpio71"; + drive-strength = <2>; /* 2 MA */ + output-low; + }; + }; + drv8424_mode1_high: drv8424_mode1_high { + mux { + pins = "gpio71"; + function = "gpio"; + }; + config { + pins = "gpio71"; + drive-strength = <2>; /* 2 MA */ + output-high; + }; + }; + drv8424_mode1_disable: drv8424_mode1_disable { + mux { + pins = "gpio71"; + function = "gpio"; + }; + config { + pins = "gpio71"; + input-enable; + bias-disable; + }; + }; + + drv8424_t0_low: drv8424_t0_low { + mux { + pins = "gpio49"; + function = "gpio"; + }; + config { + pins = "gpio49"; + drive-strength = <2>; /* 2 MA */ + output-low; + }; + }; + drv8424_t0_high: drv8424_t0_high { + mux { + pins = "gpio49"; + function = "gpio"; + }; + config { + pins = "gpio49"; + drive-strength = <2>; /* 2 MA */ + output-high; + }; + }; + drv8424_t0_disable: drv8424_t0_disable { + mux { + pins = "gpio49"; + function = "gpio"; + }; + config { + pins = "gpio49"; + input-enable; + bias-disable; + }; + }; + + drv8424_t1_low: drv8424_t1_low { + mux { + pins = "gpio33"; + function = "gpio"; + }; + config { + pins = "gpio33"; + drive-strength = <2>; /* 2 MA */ + output-low; + }; + }; + drv8424_t1_high: drv8424_t1_high { + mux { + pins = "gpio33"; + function = "gpio"; + }; + config { + pins = "gpio33"; + drive-strength = <2>; /* 2 MA */ + output-high; + }; + }; + drv8424_t1_disable: drv8424_t1_disable { + mux { + pins = "gpio33"; + function = "gpio"; + }; + config { + pins = "gpio33"; + input-enable; + bias-disable; + }; + }; + + drv8424_int_default: drv8424_int_default { + mux { + pins = "gpio57"; + function = "gpio"; + }; + config { + pins = "gpio57"; + drive-strength = <6>; + input-enable; + bias-disable; + }; + }; + + drv8424_clk_active: drv8424_clk_active { + mux { + pins = "gpio21"; + function = "gcc_gp2"; + }; + config { + pins = "gpio21"; + drive-strength = <2>; /* 2 MA */ + bias-pull-none; + }; + }; + drv8424_clk_sleep: drv8424_clk_sleep { + mux { + pins = "gpio21"; + function = "gpio"; + }; + config { + pins = "gpio21"; + /* drive-strength = <2>; */ /* 2 MA */ + /* bias-pull-down; */ + input-enable; + bias-disable; + }; + }; + }; +}; + +&soc { + moto_drv8424_support { + compatible = "moto,drv8424"; + status = "ok"; + pinctrl-names = "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"; + pinctrl-0 = <&drv8424_mode0_low>; + pinctrl-1 = <&drv8424_mode0_high>; + pinctrl-2 = <&drv8424_mode0_disable>; + pinctrl-3 = <&drv8424_mode1_low>; + pinctrl-4 = <&drv8424_mode1_high>; + pinctrl-5 = <&drv8424_mode1_disable>; + pinctrl-6 = <&drv8424_t0_low>; + pinctrl-7 = <&drv8424_t0_high>; + pinctrl-8 = <&drv8424_t0_disable>; + pinctrl-9 = <&drv8424_t1_low>; + pinctrl-10 = <&drv8424_t1_high>; + pinctrl-11 = <&drv8424_t1_disable>; + pinctrl-12 = <&drv8424_int_default>; + pinctrl-13 = <&drv8424_clk_active>; + pinctrl-14 = <&drv8424_clk_sleep>; + interrupt-parent = <&tlmm>; + interrupts = <57 0x2002>; + drv8424-gpios = <52 57 21 32 30 51 29 50 49 33>; + drv8424-gpios-cells = <10>; + drv8424-max-freq = <250000>; + clocks = <&clock_gcc 36>; + clock-names = "gcc_gp2"; + vdd-supply = <&pm6150_l13>; + enable-hw-clock; + power-default-off; + }; +}; diff --git a/drivers/misc/drv8424/drv8424.c b/drivers/misc/drv8424/drv8424.c new file mode 100644 index 000000000000..dbd5eb6ecba0 --- /dev/null +++ b/drivers/misc/drv8424/drv8424.c @@ -0,0 +1,1636 @@ +#define pr_fmt(fmt) "drv8424: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_IN, + STATUS_MOVING_OUT, +}; + +static const char *status_labels[] = { + "UNKNOWN", + "STOPPED_COMPACT", + "STOPPED_EXPANDED", + "STOPPED_PEEK", + "MOVING_IN", + "MOVING_OUT", +}; + +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 +}; + +#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 work_struct motor_irq_work; + struct task_struct *motor_task; + wait_queue_head_t sync_complete; + 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; + unsigned slot; + unsigned mode, cur_mode; + unsigned torque; + unsigned time_out; + unsigned half; + unsigned stage, max_stages; + 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; +}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 bool motor_drive_stage_init(motor_device* md, int step_count); + +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 updated\n"); +} + +//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; +} +#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 void inline logtime_store(motor_device* md) +{ + if ((md->slot + 1) < sizeof(timesA)/sizeof(timesA[0])) { + timesA[md->slot++] = ktime_get(); + } +} + +static void logtime_show(motor_device* md) +{ + int i; + ktime_t diff; + + for (i = 1; i < md->slot; i++) { + diff = ktime_sub(timesA[i], timesA[i-1]); + pr_err("step[%d]=%lldus\n", i, ktime_to_us(diff)); + } +} +#endif +static void moto_drv8424_drive_init(motor_device* md, int step_count) +{ + md->slot = 0U; + atomic_set(&md->stepping, 1); + atomic_set(&md->step_count, step_count); + + 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; + } + atomic_set(&md->status, md->dir ? STATUS_MOVING_IN : STATUS_MOVING_OUT); +} + +static int moto_drv8424_drive_sequencer(motor_device* md) +{ + unsigned long flags; + + spin_lock_irqsave(&md->mlock, flags); + moto_drv8424_drive_init(md, 0); + spin_unlock_irqrestore(&md->mlock, flags); + LOGD("ceiling %lu, freq %uHz period %luns, half %uns\n", md->step_ceiling, md->step_freq, md->step_period, md->half); + moto_drv8424_set_motor_torque(md); + moto_drv8424_set_motor_dir(md); + moto_drv8424_set_motor_mode(md); + moto_drv8424_set_motor_opmode(md); + LOGD("sequencer init done\n"); + if(atomic_read(&md->stepping)) { + motor_control* mc = &md->mc; + + sysfs_notify(&md->dev->kobj, NULL, "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); +#ifdef MOTOR_SLOT_DBG + logtime_store(md); +#endif + } + + 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()) { + dev_info(md->dev, "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); + } + + 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); + dev_info(md->dev, "status %d, position %d\n", + atomic_read(&md->status), atomic_read(&md->position)); + sysfs_notify(&md->dev->kobj, NULL, "status"); +#ifdef MOTOR_SLOT_DBG + logtime_show(md); +#endif + } + + 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)) { + motor_stop(md, true); + GPIO_OUTPUT_DIR(md->mc.ptable[MOTOR_STEP], 0); + spin_unlock_irqrestore(&md->mlock, flags); + return HRTIMER_NORESTART; + } + + 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); + } +#ifdef MOTOR_SLOT_DBG + logtime_store(md); /* log timestamp */ +#endif + if(md->step_ceiling && atomic_read(&md->step_count) > md->step_ceiling) { + /* multi stage sequence */ + if (!(md->max_stages && motor_drive_stage_init(md, 1))) { + 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_fault_work(struct work_struct *work) +{ + motor_device* md = container_of(work, motor_device, motor_irq_work); + + 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); +} + +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"); + queue_work(md->motor_wq, &md->motor_irq_work); +#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; +} + +/* PPS Pulses + stage 1: 400 24 + stage 2: 1400 56 + stage 3: 3000 6252 + stage 4: 2000 12 +*/ +#define MAX_STAGE_LEGS 10 +struct stage_data { + unsigned freq, ceiling; +} multi_stage_sequencer[MAX_STAGE_LEGS]; + +static int motor_stage_sequence_init(char *buffer, size_t len, motor_device* md) +{ + char *next, *pair = buffer; + int msn; + unsigned freq, ceiling; + + for (msn = 0; 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) { + multi_stage_sequencer[msn].freq = freq; + multi_stage_sequencer[msn].ceiling = ceiling; + dev_info(md->dev, "stage[%d]: freq=%u, ceiling=%u\n", msn, freq, ceiling); + } + } + + return msn; +} + +static bool motor_drive_stage_init(motor_device* md, int step_count) +{ + unsigned freq; + + if (++md->stage > md->max_stages) { + md->max_stages = 0; + md->stage = 0; + return false; + } +#ifdef MOTOR_SLOT_DBG + if (md->slot) + logtime_show(md); +#endif + freq = multi_stage_sequencer[md->stage - 1].freq; + md->step_ceiling = multi_stage_sequencer[md->stage - 1].ceiling; + + //LOGD("stage %u, freq %u, ceiling %lu\n", md->stage, freq, md->step_ceiling); + //FIXME the next function calls spinlock!!! + moto_drv8424_set_step_freq(md, freq); + moto_drv8424_drive_init(md, step_count); + + return true; +} + +/* main interface to control motor via sysfs */ +/* valid parameter values: 0 - roll in, 1 - roll out */ +static ssize_t motor_command_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; + char *buffer; + + if(md->faulting) { + dev_err(dev, "Device faulting\n"); + return -EBUSY; + } + + buffer = kmalloc(len + 1, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + strlcpy(buffer, buf, len); + buffer[len + 1] = '\0'; + md->max_stages = motor_stage_sequence_init(buffer, len, md); + kfree(buffer); + if (md->max_stages == 0) { + dev_err(dev, "Error value: %s\n", buf); + return -EINVAL; + } + + LOGD("max_stages %u\n", md->max_stages); + //spin_lock_irqsave(&md->mlock, flags); + /* hrtimer controlled stepper mechanism */ + md->stage = 0; + motor_drive_stage_init(md, 0); + //spin_unlock_irqrestore(&md->mlock, flags); + moto_drv8424_drive_sequencer(md); + + return len; +} + +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(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(command, S_IRUGO|S_IWUSR|S_IWGRP, NULL, motor_command_store); + +#define ATTRS_STATIC 7 +#define ATTRS_MAX 10 + +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_command.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 +}; + +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); + /* cause timer to stop firing */ + //atomic_set(&md->stepping, 0); + atomic_set(&md->position, value); + spin_unlock_irqrestore(&md->mlock, flags); + dev_info(md->dev, "position %s reported\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); + + return snprintf(buf, 20, "%s", position_labels[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); + + return snprintf(buf, 20, "%s", status_labels[atomic_read(&md->status)]); +} + +/* sysfs for sensor HAL to report known position */ +static DEVICE_ATTR(position, S_IRUGO|S_IWUSR|S_IWGRP, motor_position_show, motor_position_store); +static DEVICE_ATTR(status, S_IRUGO|S_IWUSR|S_IWGRP, motor_status_show, NULL); + +static struct attribute *status_attributes[] = { + &dev_attr_status.attr, + &dev_attr_position.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 = MOTOR_POWER_EN; + 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; + + /* 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_WORK(&md->motor_irq_work, motor_fault_work); + + md->user_sync_complete = false; + init_waitqueue_head(&md->sync_complete); + 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_work; + } + + 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_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); + 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->status) != POS_COMPACT) { + 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");