diff options
author | 2016-06-17 17:12:04 +0300 | |
---|---|---|
committer | 2016-09-07 19:00:16 +0200 | |
commit | 79640e49a585354e2f7c7c17352b13a5f7da29f6 (patch) | |
tree | 6557033156698f41fc4294745d87865f7bf7884d /meta-rcar-gen2/recipes-kernel | |
parent | a940b05f44de841a19b44c28fc09000f75e1203d (diff) |
Import-kernel-patches-for-porter-ext01-board.patch
Diffstat (limited to 'meta-rcar-gen2/recipes-kernel')
40 files changed, 10804 insertions, 1 deletions
diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0049-Add-lsm9ds0-acc-gyro-mag-driver.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0049-Add-lsm9ds0-acc-gyro-mag-driver.patch new file mode 100644 index 0000000..d461b3d --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0049-Add-lsm9ds0-acc-gyro-mag-driver.patch @@ -0,0 +1,5433 @@ +From dadd0a8a2cf1fecbbb0df7713f3da81ce7c11b00 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Wed, 16 Sep 2015 14:28:22 +0300 +Subject: [PATCH 49/50] Add lsm9ds0 (acc gyro mag) driver + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/input/misc/Kconfig | 10 + + drivers/input/misc/Makefile | 1 + + drivers/input/misc/lsm9ds0_acc_mag.c | 3435 ++++++++++++++++++++++++++++++++++ + drivers/input/misc/lsm9ds0_gyr.c | 1726 +++++++++++++++++ + include/linux/input/lsm9ds0.h | 201 ++ + 5 files changed, 5373 insertions(+) + create mode 100644 drivers/input/misc/lsm9ds0_acc_mag.c + create mode 100644 drivers/input/misc/lsm9ds0_gyr.c + create mode 100644 include/linux/input/lsm9ds0.h + +diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig +index bb698e1..f1eb2c8 100644 +--- a/drivers/input/misc/Kconfig ++++ b/drivers/input/misc/Kconfig +@@ -197,6 +197,16 @@ config INPUT_MPU3050 + To compile this driver as a module, choose M here: the + module will be called mpu3050. + ++config INPUT_LSM9DS0 ++ tristate "LSM9DS0 iNEMO sensor" ++ depends on I2C ++ help ++ Say Y here if you want to support 3D accelerometer, ++ 3D gyroscope, 3D magnetometer LSM9DS0 ++ ++ To compile this driver as a module, choose M here: the ++ module will be called lsm9ds0. ++ + config INPUT_APANEL + tristate "Fujitsu Lifebook Application Panel buttons" + depends on X86 && I2C && LEDS_CLASS +diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile +index d7fc17f..b726294 100644 +--- a/drivers/input/misc/Makefile ++++ b/drivers/input/misc/Makefile +@@ -39,6 +39,7 @@ obj-$(CONFIG_INPUT_MAX8997_HAPTIC) += max8997_haptic.o + obj-$(CONFIG_INPUT_MC13783_PWRBUTTON) += mc13783-pwrbutton.o + obj-$(CONFIG_INPUT_MMA8450) += mma8450.o + obj-$(CONFIG_INPUT_MPU3050) += mpu3050.o ++obj-$(CONFIG_INPUT_LSM9DS0) += lsm9ds0_acc_mag.o lsm9ds0_gyr.o + obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o + obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o + obj-$(CONFIG_INPUT_PCF8574) += pcf8574_keypad.o +diff --git a/drivers/input/misc/lsm9ds0_acc_mag.c b/drivers/input/misc/lsm9ds0_acc_mag.c +new file mode 100644 +index 0000000..3aad517 +--- /dev/null ++++ b/drivers/input/misc/lsm9ds0_acc_mag.c +@@ -0,0 +1,3435 @@ ++/******************** (C) COPYRIGHT 2013 STMicroelectronics ******************* ++* ++* File Name : lsm9ds0_acc_mag.c ++* Authors : AMS - Motion Sensors Div - Application Team ++* : Matteo Dameno (matteo.dameno@st.com) ++* : Denis Ciocca (denis.ciocca@st.com) ++* : Both authors are willing to be considered the contact ++* : and update points for the driver. ++* Version : V.1.0.5 ++* Date : 2013/Oct/23 ++* Description : LSM9DS0 accelerometer & magnetometer driver ++* ++******************************************************************************* ++* ++* This program is free software; you can redistribute it and/or modify ++* it under the terms of the GNU General Public License version 2 as ++* published by the Free Software Foundation. ++* ++* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES ++* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE ++* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. ++* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, ++* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE ++* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING ++* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. ++* ++******************************************************************************/ ++/****************************************************************************** ++Version History. ++ ++Revision 1-0-0 2012/05/04 ++ first revision ++Revision 1-0-1 2012/05/07 ++ New sysfs architecture ++ Support antialiasing filter ++Revision 1-0-2 2012/10/15 ++ I2C address bugfix ++Revision 1-0-3 2013/01/21 ++ Move CTLREG7 resume write from acc_power_on to magn_power_on ++Revision 1-0-4 2013/05/09 ++ Added rotation matrix ++Revision 1-0-5 2013/10/23 ++ Corrects Mag Enable bug, Corrects missing BDU enable ++******************************************************************************/ ++ ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/slab.h> ++#include <linux/i2c.h> ++#include <linux/workqueue.h> ++#include <linux/input.h> ++#include <linux/kernel.h> ++#include <linux/kobject.h> ++#include <linux/gpio.h> ++#include <linux/interrupt.h> ++#include <linux/irq.h> ++#include <linux/hrtimer.h> ++#include <linux/ktime.h> ++ ++#include <linux/input/lsm9ds0.h> ++/* include "lsm9ds0.h" */ ++ ++#define I2C_AUTO_INCREMENT (0x80) ++#define MS_TO_NS(x) (x*1000000L) ++ ++#define ACC_G_MAX_POS 1495040 /** max positive value acc [ug] */ ++#define ACC_G_MAX_NEG 1495770 /** max negative value acc [ug] */ ++#define MAG_G_MAX_POS 983520 /** max positive value mag [ugauss] */ ++#define MAG_G_MAX_NEG 983040 /** max negative value mag [ugauss] */ ++ ++#define FUZZ 0 ++#define FLAT 0 ++ ++/* Address registers */ ++#define REG_WHOAMI_ADDR (0x0F) /** Who am i address register */ ++#define REG_CNTRL0_ADDR (0x1F) /** CNTRL0 address register */ ++#define REG_CNTRL1_ADDR (0x20) /** CNTRL1 address register */ ++#define REG_CNTRL2_ADDR (0x21) /** CNTRL2 address register */ ++#define REG_CNTRL3_ADDR (0x22) /** CNTRL3 address register */ ++#define REG_CNTRL4_ADDR (0x23) /** CNTRL4 address register */ ++#define REG_CNTRL5_ADDR (0x24) /** CNTRL5 address register */ ++#define REG_CNTRL6_ADDR (0x25) /** CNTRL6 address register */ ++#define REG_CNTRL7_ADDR (0x26) /** CNTRL7 address register */ ++ ++#define REG_ACC_DATA_ADDR (0x28) /** Acc. data low address register */ ++#define REG_MAG_DATA_ADDR (0x08) /** Mag. data low address register */ ++#define REG_TEMP_DATA_ADDR (0x05) /** Temp. data low address register */ ++ ++#define REG_GEN_MAG_ADDR (0x12) /** INT_CTRL_REG_M address register */ ++#define INT_SRC_REG_M_ADDR (0x13) /** INT_SRC_REG_M address register */ ++#define REG_GEN_MAG_THR_ADDR (0x14) /** INT_THS_L_M address register */ ++#define MIG_THRESHOLD_ADDR_H (0x15) /** INT_THS_H_M address register */ ++#define REG_GEN1_AXIS_ADDR (0x30) /** INT_GEN1_REG address register */ ++#define INT_GEN1_SRC_ADDR (0x31) /** INT_GEN1_SRC address register */ ++#define REG_GEN1_THR_ADDR (0x32) /** INT_GEN1_THS address register */ ++#define REG_GEN1_DUR_ADDR (0x33) /** INT_GEN1_DUR address register */ ++#define REG_GEN2_AXIS_ADDR (0x34) /** INT_GEN2_REG address register */ ++#define INT_GEN2_SRC_ADDR (0x35) /** INT_GEN2_SRC address register */ ++#define REG_GEN2_THR_ADDR (0x36) /** INT_GEN2_THS address register */ ++#define REG_GEN2_DUR_ADDR (0x37) /** INT_GEN2_DUR address register */ ++ ++/* Sensitivity */ ++#define SENSITIVITY_ACC_2G 60 /** ug/LSB */ ++#define SENSITIVITY_ACC_4G 120 /** ug/LSB */ ++#define SENSITIVITY_ACC_8G 240 /** ug/LSB */ ++#define SENSITIVITY_ACC_16G 730 /** ug/LSB */ ++ ++#define SENSITIVITY_MAG_2G 80 /** ugauss/LSB */ ++#define SENSITIVITY_MAG_4G 160 /** ugauss/LSB */ ++#define SENSITIVITY_MAG_8G 320 /** ugauss/LSB */ ++#define SENSITIVITY_MAG_12G 480 /** ugauss/LSB */ ++ ++/* ODR */ ++#define ODR_ACC_MASK (0XF0) /* Mask for odr change on acc */ ++#define LSM9DS0_ACC_ODR_OFF (0x00) /* Power down */ ++#define LSM9DS0_ACC_ODR3_125 (0x10) /* 3.25Hz output data rate */ ++#define LSM9DS0_ACC_ODR6_25 (0x20) /* 6.25Hz output data rate */ ++#define LSM9DS0_ACC_ODR12_5 (0x30) /* 12.5Hz output data rate */ ++#define LSM9DS0_ACC_ODR25 (0x40) /* 25Hz output data rate */ ++#define LSM9DS0_ACC_ODR50 (0x50) /* 50Hz output data rate */ ++#define LSM9DS0_ACC_ODR100 (0x60) /* 100Hz output data rate */ ++#define LSM9DS0_ACC_ODR200 (0x70) /* 200Hz output data rate */ ++#define LSM9DS0_ACC_ODR400 (0x80) /* 400Hz output data rate */ ++#define LSM9DS0_ACC_ODR800 (0x90) /* 800Hz output data rate */ ++#define LSM9DS0_ACC_ODR1600 (0xA0) /* 1600Hz output data rate */ ++ ++#define ODR_MAG_MASK (0X1C) /* Mask for odr change on mag */ ++#define LSM9DS0_MAG_ODR3_125 (0x00) /* 3.25Hz output data rate */ ++#define LSM9DS0_MAG_ODR6_25 (0x04) /* 6.25Hz output data rate */ ++#define LSM9DS0_MAG_ODR12_5 (0x08) /* 12.5Hz output data rate */ ++#define LSM9DS0_MAG_ODR25 (0x0C) /* 25Hz output data rate */ ++#define LSM9DS0_MAG_ODR50 (0x10) /* 50Hz output data rate */ ++#define LSM9DS0_MAG_ODR100 (0x14) /* 100Hz output data rate */ ++ ++/* Magnetic sensor mode */ ++#define MSMS_MASK (0x03) /* Mask magnetic sensor mode */ ++#define POWEROFF_MAG (0x02) /* Power Down */ ++#define CONTINUOS_CONVERSION (0x00) /* Continuos Conversion */ ++ ++/* Default values loaded in probe function */ ++#define WHOIAM_VALUE (0x49) /** Who Am I default value */ ++#define REG_DEF_CNTRL0 (0x00) /** CNTRL0 default value */ ++#define REG_DEF_CNTRL1 (0x0F) /** CNTRL1 default value */ ++#define REG_DEF_CNTRL2 (0x00) /** CNTRL2 default value */ ++#define REG_DEF_CNTRL3 (0x00) /** CNTRL3 default value */ ++#define REG_DEF_CNTRL4 (0x00) /** CNTRL4 default value */ ++#define REG_DEF_CNTRL5 (0x18) /** CNTRL5 default value */ ++#define REG_DEF_CNTRL6 (0x20) /** CNTRL6 default value */ ++#define REG_DEF_CNTRL7 (0x02) /** CNTRL7 default value */ ++ ++#define REG_DEF_INT_CNTRL_MAG (0x00) /** INT_CTRL_REG_M default value */ ++#define REG_DEF_INT_GEN1 (0x00) /** INT_GEN1_REG default value */ ++#define REG_DEF_INT_GEN2 (0x00) /** INT_GEN2_REG default value */ ++#define REG_DEF_IIG1_DURATION (0x00) /** INT_GEN1_DUR default value */ ++#define REG_DEF_IIG2_DURATION (0x00) /** INT_GEN2_DUR default value */ ++#define REG_DEF_IIG1_THRESHOLD (0x00) /** INT_GEN1_THS default value */ ++#define REG_DEF_IIG2_THRESHOLD (0x00) /** INT_GEN2_THS default value */ ++#define REG_DEF_MIG_THRESHOLD_L (0x00) /** INT_THS_L_M default value */ ++#define REG_DEF_MIG_THRESHOLD_H (0x00) /** INT_THS_H_M default value */ ++ ++#define REG_DEF_ALL_ZEROS (0x00) ++ ++/* Accelerometer Filter */ ++#define LSM9DS0_ACC_FILTER_MASK (0xC0) /* Mask for filter band change on acc */ ++#define FILTER_773 773 /* Anti-Aliasing 773 Hz */ ++#define FILTER_362 362 /* Anti-Aliasing 362 Hz */ ++#define FILTER_194 194 /* Anti-Aliasing 194 Hz */ ++#define FILTER_50 50 /* Anti-Aliasing 50 Hz */ ++ ++/* Temperature */ ++#define TEMP_MASK (0x80) /* Mask for temperature change */ ++#define TEMP_ON (0x80) /* Enable temperature */ ++#define TEMP_OFF (0x00) /* Disable temperature */ ++#define TEMP_SENSITIVITY 8 /* Sensitivity temperature */ ++#define OFFSET_TEMP 25 /* Offset temperature */ ++#define NDTEMP 1000 /* Not Available temperature */ ++ ++/* Interrupt */ ++#define GEN1_PIN1_MASK (0x20) ++#define GEN1_PIN2_MASK (0x40) ++#define GEN2_PIN1_MASK (0x10) ++#define GEN2_PIN2_MASK (0x20) ++#define GEN_MAG_PIN1_MASK (0x08) ++#define GEN_MAG_PIN2_MASK (0x10) ++#define GEN_MAG_EN_MASK (0x01) ++#define MAX_DUR_TH 127 ++#define MAX_TH_MAG 131071 ++#define GEN_X_HIGH_MASK (0x02) ++#define GEN_X_LOW_MASK (0x01) ++#define GEN_Y_HIGH_MASK (0x08) ++#define GEN_Y_LOW_MASK (0x04) ++#define GEN_Z_HIGH_MASK (0x20) ++#define GEN_Z_LOW_MASK (0x10) ++#define GEN_X_MAG_MASK (0x80) ++#define GEN_Y_MAG_MASK (0x40) ++#define GEN_Z_MAG_MASK (0x20) ++ ++#define GEN1_AND_OR_MASK (0x80) ++#define GEN2_AND_OR_MASK (0x83) ++ ++#define INT_PIN_CONF_MASK (0x10) ++#define INT_POLARITY_MASK (0x80) ++ ++#define to_dev(obj) container_of(obj, struct device, kobj) ++#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr) ++ ++static struct kobject *acc_kobj; ++static struct kobject *mag_kobj; ++ ++struct workqueue_struct *lsm9ds0_workqueue = 0; ++ ++struct { ++ unsigned int cutoff_us; ++ u8 value; ++} lsm9ds0_acc_odr_table[] = { ++ { 1, LSM9DS0_ACC_ODR800 }, ++ { 2, LSM9DS0_ACC_ODR400 }, ++ { 5, LSM9DS0_ACC_ODR200 }, ++ { 10, LSM9DS0_ACC_ODR100 }, ++ { 20, LSM9DS0_ACC_ODR50 }, ++ { 40, LSM9DS0_ACC_ODR25 }, ++ { 80, LSM9DS0_ACC_ODR12_5 }, ++ { 160, LSM9DS0_ACC_ODR6_25 }, ++ { 320, LSM9DS0_ACC_ODR3_125}, ++}; ++ ++struct { ++ unsigned int cutoff_us; ++ u8 value; ++} lsm9ds0_mag_odr_table[] = { ++ { 10, LSM9DS0_MAG_ODR100 }, ++ { 20, LSM9DS0_MAG_ODR50 }, ++ { 40, LSM9DS0_MAG_ODR25 }, ++ { 80, LSM9DS0_MAG_ODR12_5 }, ++ { 160, LSM9DS0_MAG_ODR6_25 }, ++ { 320, LSM9DS0_MAG_ODR3_125}, ++}; ++ ++struct interrupt_enable { ++ atomic_t enable; ++ u8 address; ++ u8 mask; ++}; ++ ++struct interrupt_value { ++ int value; ++ u8 address; ++}; ++ ++struct lsm9ds0_interrupt { ++ struct interrupt_enable gen1_pin1; ++ struct interrupt_enable gen1_pin2; ++ struct interrupt_enable gen2_pin1; ++ struct interrupt_enable gen2_pin2; ++ struct interrupt_value gen1_threshold; ++ struct interrupt_value gen2_threshold; ++ struct interrupt_value gen1_duration; ++ struct interrupt_value gen2_duration; ++ struct interrupt_enable gen_mag_pin1; ++ struct interrupt_enable gen_mag_pin2; ++ struct interrupt_enable gen_mag; ++ struct interrupt_value gen_mag_threshold; ++ struct interrupt_enable gen1_axis[6]; ++ struct interrupt_enable gen2_axis[6]; ++ struct interrupt_enable gen_mag_axis[3]; ++ struct interrupt_enable gen1_and_or; ++ struct interrupt_enable gen2_and_or; ++ struct interrupt_enable interrupt_pin_conf; ++ struct interrupt_enable interrupt_polarity; ++}; ++ ++struct lsm9ds0_status { ++ struct i2c_client *client; ++ struct lsm9ds0_acc_platform_data *pdata_acc; ++ struct lsm9ds0_mag_platform_data *pdata_mag; ++ ++ struct mutex lock; ++ struct work_struct input_work_acc; ++ struct work_struct input_work_mag; ++ ++ struct hrtimer hr_timer_acc; ++ ktime_t ktime_acc; ++ struct hrtimer hr_timer_mag; ++ ktime_t ktime_mag; ++ ++ struct input_dev *input_dev_acc; ++ struct input_dev *input_dev_mag; ++ struct input_dev *input_dev_temp; ++ ++ struct lsm9ds0_interrupt *interrupt; ++ ++ int hw_initialized; ++ /* hw_working=-1 means not tested yet */ ++ int hw_working; ++ ++ atomic_t enabled_acc; ++ atomic_t enabled_mag; ++ atomic_t enabled_temp; ++ ++ int temp_value_dec; ++ unsigned int temp_value_flo; ++ ++ int on_before_suspend; ++ int use_smbus; ++ ++ u16 sensitivity_acc; ++ u16 sensitivity_mag; ++ ++ int irq1; ++ struct work_struct irq1_work; ++ struct workqueue_struct *irq1_work_queue; ++ int irq2; ++ struct work_struct irq2_work; ++ struct workqueue_struct *irq2_work_queue; ++}; ++ ++static const struct lsm9ds0_acc_platform_data default_lsm9ds0_acc_pdata = { ++ .fs_range = LSM9DS0_ACC_FS_2G, ++ .rot_matrix = { ++ {1, 0, 0}, ++ {0, 1, 0}, ++ {0, 0, 1}, ++ }, ++ .poll_interval = 100, ++ .min_interval = LSM9DS0_ACC_MIN_POLL_PERIOD_MS, ++ .aa_filter_bandwidth = ANTI_ALIASING_773, ++ .gpio_int1 = DEFAULT_INT1_GPIO, ++ .gpio_int2 = DEFAULT_INT2_GPIO, ++}; ++ ++static const struct lsm9ds0_mag_platform_data default_lsm9ds0_mag_pdata = { ++ .poll_interval = 100, ++ .min_interval = LSM9DS0_MAG_MIN_POLL_PERIOD_MS, ++ .fs_range = LSM9DS0_MAG_FS_2G, ++ .rot_matrix = { ++ {1, 0, 0}, ++ {0, 1, 0}, ++ {0, 0, 1}, ++ }, ++}; ++ ++struct reg_rw { ++ u8 address; ++ u8 default_value; ++ u8 resume_value; ++}; ++ ++struct reg_r { ++ u8 address; ++ u8 value; ++}; ++ ++static struct status_registers { ++ struct reg_r who_am_i; ++ struct reg_rw cntrl0; ++ struct reg_rw cntrl1; ++ struct reg_rw cntrl2; ++ struct reg_rw cntrl3; ++ struct reg_rw cntrl4; ++ struct reg_rw cntrl5; ++ struct reg_rw cntrl6; ++ struct reg_rw cntrl7; ++ struct reg_rw int_ctrl_reg_m; ++ struct reg_rw int_mag_threshold_low; ++ struct reg_rw int_mag_threshold_high; ++ struct reg_rw int_gen1_reg; ++ struct reg_rw int_gen2_reg; ++ struct reg_rw int_gen1_duration; ++ struct reg_rw int_gen2_duration; ++ struct reg_rw int_gen1_threshold; ++ struct reg_rw int_gen2_threshold; ++ struct reg_r int_src_reg_m; ++ struct reg_r int_gen1_src; ++ struct reg_r int_gen2_src; ++ struct reg_r int_gen_mag_src; ++} status_registers = { ++ .who_am_i.address=REG_WHOAMI_ADDR, .who_am_i.value=WHOIAM_VALUE, ++ .cntrl0.address=REG_CNTRL0_ADDR, .cntrl0.default_value=REG_DEF_CNTRL0, ++ .cntrl1.address=REG_CNTRL1_ADDR, .cntrl1.default_value=REG_DEF_CNTRL1, ++ .cntrl2.address=REG_CNTRL2_ADDR, .cntrl2.default_value=REG_DEF_CNTRL2, ++ .cntrl3.address=REG_CNTRL3_ADDR, .cntrl3.default_value=REG_DEF_CNTRL3, ++ .cntrl4.address=REG_CNTRL4_ADDR, .cntrl4.default_value=REG_DEF_CNTRL4, ++ .cntrl5.address=REG_CNTRL5_ADDR, .cntrl5.default_value=REG_DEF_CNTRL5, ++ .cntrl6.address=REG_CNTRL6_ADDR, .cntrl6.default_value=REG_DEF_CNTRL6, ++ .cntrl7.address=REG_CNTRL7_ADDR, .cntrl7.default_value=REG_DEF_CNTRL7, ++ .int_ctrl_reg_m.address=REG_GEN_MAG_ADDR, ++ .int_ctrl_reg_m.default_value=REG_DEF_INT_CNTRL_MAG, ++ .int_mag_threshold_low.address=REG_GEN_MAG_THR_ADDR, ++ .int_mag_threshold_low.default_value=REG_DEF_MIG_THRESHOLD_L, ++ .int_mag_threshold_low.address=MIG_THRESHOLD_ADDR_H, ++ .int_mag_threshold_low.default_value=REG_DEF_MIG_THRESHOLD_H, ++ .int_gen1_reg.address=REG_GEN1_AXIS_ADDR, ++ .int_gen1_reg.default_value=REG_DEF_INT_GEN1, ++ .int_gen2_reg.address=REG_GEN2_AXIS_ADDR, ++ .int_gen2_reg.default_value=REG_DEF_INT_GEN2, ++ .int_gen1_duration.address=REG_GEN1_DUR_ADDR, ++ .int_gen1_duration.default_value=REG_DEF_IIG1_DURATION, ++ .int_gen2_duration.address=REG_GEN2_DUR_ADDR, ++ .int_gen2_duration.default_value=REG_DEF_IIG2_DURATION, ++ .int_gen1_threshold.address=REG_GEN1_THR_ADDR, ++ .int_gen1_threshold.default_value=REG_DEF_IIG1_THRESHOLD, ++ .int_gen2_threshold.address=REG_GEN2_THR_ADDR, ++ .int_gen2_threshold.default_value=REG_DEF_IIG2_THRESHOLD, ++ .int_src_reg_m.address = INT_SRC_REG_M_ADDR, ++ .int_src_reg_m.value = REG_DEF_ALL_ZEROS, ++ .int_gen1_src.address = INT_GEN1_SRC_ADDR, ++ .int_gen1_src.value = REG_DEF_ALL_ZEROS, ++ .int_gen2_src.address = INT_GEN2_SRC_ADDR, ++ .int_gen2_src.value = REG_DEF_ALL_ZEROS, ++ .int_gen_mag_src.address = INT_SRC_REG_M_ADDR, ++ .int_gen_mag_src.value = REG_DEF_ALL_ZEROS, ++}; ++ ++static int lsm9ds0_i2c_read(struct lsm9ds0_status *stat, u8 *buf, int len) ++{ ++ int ret; ++ u8 reg = buf[0]; ++ u8 cmd = reg; ++#ifdef DEBUG ++ unsigned int ii; ++#endif ++ ++ ++ if (len > 1) ++ cmd = (I2C_AUTO_INCREMENT | reg); ++ if (stat->use_smbus) { ++ if (len == 1) { ++ ret = i2c_smbus_read_byte_data(stat->client, cmd); ++ buf[0] = ret & 0xff; ++#ifdef DEBUG ++ dev_warn(&stat->client->dev, ++ "i2c_smbus_read_byte_data: ret=0x%02x, len:%d ," ++ "command=0x%02x, buf[0]=0x%02x\n", ++ ret, len, cmd , buf[0]); ++#endif ++ } else if (len > 1) { ++ ret = i2c_smbus_read_i2c_block_data(stat->client, ++ cmd, len, buf); ++#ifdef DEBUG ++ dev_warn(&stat->client->dev, ++ "i2c_smbus_read_i2c_block_data: ret:%d len:%d, " ++ "command=0x%02x, ", ++ ret, len, cmd); ++ for (ii = 0; ii < len; ii++) ++ printk(KERN_DEBUG "buf[%d]=0x%02x,", ++ ii, buf[ii]); ++ ++ printk("\n"); ++#endif ++ } else ++ ret = -1; ++ ++ if (ret < 0) { ++ dev_err(&stat->client->dev, ++ "read transfer error: len:%d, command=0x%02x\n", ++ len, cmd); ++ return 0; ++ } ++ return len; ++ } ++ ++ ret = i2c_master_send(stat->client, &cmd, sizeof(cmd)); ++ if (ret != sizeof(cmd)) ++ return ret; ++ ++ return i2c_master_recv(stat->client, buf, len); ++} ++ ++static int lsm9ds0_i2c_write(struct lsm9ds0_status *stat, u8 *buf, int len) ++{ ++ int ret; ++ u8 reg, value; ++#ifdef DEBUG ++ unsigned int ii; ++#endif ++ ++ if (len > 1) ++ buf[0] = (I2C_AUTO_INCREMENT | buf[0]); ++ ++ reg = buf[0]; ++ value = buf[1]; ++ ++ if (stat->use_smbus) { ++ if (len == 1) { ++ ret = i2c_smbus_write_byte_data(stat->client, ++ reg, value); ++#ifdef DEBUG ++ dev_warn(&stat->client->dev, ++ "i2c_smbus_write_byte_data: ret=%d, len:%d, " ++ "command=0x%02x, value=0x%02x\n", ++ ret, len, reg , value); ++#endif ++ return ret; ++ } else if (len > 1) { ++ ret = i2c_smbus_write_i2c_block_data(stat->client, ++ reg, len, buf + 1); ++#ifdef DEBUG ++ dev_warn(&stat->client->dev, ++ "i2c_smbus_write_i2c_block_data: ret=%d, " ++ "len:%d, command=0x%02x, ", ++ ret, len, reg); ++ for (ii = 0; ii < (len + 1); ii++) ++ printk(KERN_DEBUG "value[%d]=0x%02x,", ++ ii, buf[ii]); ++ ++ printk("\n"); ++#endif ++ return ret; ++ } ++ } ++ ++ ret = i2c_master_send(stat->client, buf, len+1); ++ return (ret == len+1) ? 0 : ret; ++} ++ ++static int lsm9ds0_hw_init(struct lsm9ds0_status *stat) ++{ ++ int err = -1; ++ u8 buf[1]; ++ int i; ++ ++ pr_info("%s: hw init start\n", LSM9DS0_DEV_NAME); ++ ++ buf[0] = status_registers.who_am_i.address; ++ err = lsm9ds0_i2c_read(stat, buf, 1); ++ ++ if (err < 0) { ++ dev_warn(&stat->client->dev, "Error reading WHO_AM_I: is device" ++ " available/working?\n"); ++ goto err_firstread; ++ } else ++ stat->hw_working = 1; ++ ++ if (buf[0] != status_registers.who_am_i.value) { ++ dev_err(&stat->client->dev, ++ "device unknown. Expected: 0x%02x," ++ " Replies: 0x%02x\n", status_registers.who_am_i.value, buf[0]); ++ err = -1; ++ goto err_unknown_device; ++ } ++ ++ status_registers.cntrl1.resume_value = ++ status_registers.cntrl1.default_value; ++ status_registers.cntrl2.resume_value = ++ status_registers.cntrl2.default_value; ++ status_registers.cntrl3.resume_value = ++ status_registers.cntrl3.default_value; ++ status_registers.cntrl4.resume_value = ++ status_registers.cntrl4.default_value; ++ status_registers.cntrl5.resume_value = ++ status_registers.cntrl5.default_value; ++ status_registers.cntrl6.resume_value = ++ status_registers.cntrl6.default_value; ++ status_registers.cntrl7.resume_value = ++ status_registers.cntrl7.default_value; ++ ++ status_registers.int_ctrl_reg_m.resume_value = ++ status_registers.int_ctrl_reg_m.default_value; ++ status_registers.int_mag_threshold_low.resume_value = ++ status_registers.int_mag_threshold_low.default_value; ++ status_registers.int_mag_threshold_high.resume_value = ++ status_registers.int_mag_threshold_high.default_value; ++ status_registers.int_gen1_reg.resume_value = ++ status_registers.int_gen1_reg.default_value; ++ status_registers.int_gen2_reg.resume_value = ++ status_registers.int_gen2_reg.default_value; ++ status_registers.int_gen1_duration.resume_value = ++ status_registers.int_gen1_duration.default_value; ++ status_registers.int_gen2_duration.resume_value = ++ status_registers.int_gen2_duration.default_value; ++ status_registers.int_gen1_threshold.resume_value = ++ status_registers.int_gen1_threshold.default_value; ++ status_registers.int_gen2_threshold.resume_value = ++ status_registers.int_gen2_threshold.default_value; ++ ++ ++ stat->temp_value_dec = NDTEMP; ++ ++ if((stat->pdata_acc->gpio_int1 >= 0) || ++ (stat->pdata_acc->gpio_int2 >= 0)) { ++ ++ stat->interrupt = kmalloc(sizeof(*stat->interrupt), ++ GFP_KERNEL); ++ ++ if(stat->interrupt == NULL) ++ goto error_interrupt; ++ ++ stat->interrupt->gen1_pin1.address = REG_CNTRL3_ADDR; ++ stat->interrupt->gen1_pin2.address = REG_CNTRL4_ADDR; ++ stat->interrupt->gen2_pin1.address = REG_CNTRL3_ADDR; ++ stat->interrupt->gen2_pin2.address = REG_CNTRL4_ADDR; ++ stat->interrupt->gen_mag_pin1.address = REG_CNTRL3_ADDR; ++ stat->interrupt->gen_mag_pin2.address = REG_CNTRL4_ADDR; ++ stat->interrupt->gen_mag.address = REG_GEN_MAG_ADDR; ++ stat->interrupt->gen1_duration.address = REG_GEN1_DUR_ADDR; ++ stat->interrupt->gen2_duration.address = REG_GEN2_DUR_ADDR; ++ stat->interrupt->gen1_threshold.address = REG_GEN1_THR_ADDR; ++ stat->interrupt->gen2_threshold.address = REG_GEN2_THR_ADDR; ++ stat->interrupt->gen_mag_threshold.address = ++ REG_GEN_MAG_THR_ADDR; ++ ++ stat->interrupt->gen1_pin1.mask = GEN1_PIN1_MASK; ++ stat->interrupt->gen1_pin2.mask = GEN1_PIN2_MASK; ++ stat->interrupt->gen2_pin1.mask = GEN2_PIN1_MASK; ++ stat->interrupt->gen2_pin2.mask = GEN2_PIN2_MASK; ++ stat->interrupt->gen_mag_pin1.mask = GEN_MAG_PIN1_MASK; ++ stat->interrupt->gen_mag_pin2.mask = GEN_MAG_PIN2_MASK; ++ stat->interrupt->gen_mag.mask = GEN_MAG_EN_MASK; ++ ++ atomic_set(&stat->interrupt->gen1_pin1.enable, 0); ++ atomic_set(&stat->interrupt->gen1_pin2.enable, 0); ++ atomic_set(&stat->interrupt->gen2_pin1.enable, 0); ++ atomic_set(&stat->interrupt->gen2_pin2.enable, 0); ++ atomic_set(&stat->interrupt->gen_mag_pin1.enable, 0); ++ atomic_set(&stat->interrupt->gen_mag_pin2.enable, 0); ++ atomic_set(&stat->interrupt->gen_mag.enable, 0); ++ ++ stat->interrupt->gen1_threshold.value = 0; ++ stat->interrupt->gen2_threshold.value = 0; ++ stat->interrupt->gen1_duration.value = 0; ++ stat->interrupt->gen2_duration.value = 0; ++ stat->interrupt->gen_mag_threshold.value = 0; ++ ++ for(i=0; i<6; i++) { ++ stat->interrupt->gen1_axis[i].address = ++ REG_GEN1_AXIS_ADDR; ++ stat->interrupt->gen2_axis[i].address = ++ REG_GEN2_AXIS_ADDR; ++ ++ atomic_set(&stat->interrupt->gen1_axis[i].enable, 0); ++ atomic_set(&stat->interrupt->gen2_axis[i].enable, 0); ++ } ++ for(i=0; i<3; i++) { ++ stat->interrupt->gen_mag_axis[i].address = ++ REG_GEN_MAG_ADDR; ++ atomic_set(&stat->interrupt->gen_mag_axis[i].enable, 0); ++ } ++ ++ stat->interrupt->gen1_axis[0].mask = GEN_X_LOW_MASK; ++ stat->interrupt->gen1_axis[1].mask = GEN_Y_LOW_MASK; ++ stat->interrupt->gen1_axis[2].mask = GEN_Z_LOW_MASK; ++ stat->interrupt->gen1_axis[3].mask = GEN_X_HIGH_MASK; ++ stat->interrupt->gen1_axis[4].mask = GEN_Y_HIGH_MASK; ++ stat->interrupt->gen1_axis[5].mask = GEN_Z_HIGH_MASK; ++ ++ stat->interrupt->gen2_axis[0].mask = GEN_X_LOW_MASK; ++ stat->interrupt->gen2_axis[1].mask = GEN_Y_LOW_MASK; ++ stat->interrupt->gen2_axis[2].mask = GEN_Z_LOW_MASK; ++ stat->interrupt->gen2_axis[3].mask = GEN_X_HIGH_MASK; ++ stat->interrupt->gen2_axis[4].mask = GEN_Y_HIGH_MASK; ++ stat->interrupt->gen2_axis[5].mask = GEN_Z_HIGH_MASK; ++ ++ stat->interrupt->gen_mag_axis[0].mask = GEN_X_MAG_MASK; ++ stat->interrupt->gen_mag_axis[1].mask = GEN_Y_MAG_MASK; ++ stat->interrupt->gen_mag_axis[2].mask = GEN_Z_MAG_MASK; ++ ++ stat->interrupt->gen1_and_or.address = REG_GEN1_AXIS_ADDR; ++ stat->interrupt->gen1_and_or.mask = GEN1_AND_OR_MASK; ++ atomic_set(&stat->interrupt->gen1_and_or.enable, 0); ++ stat->interrupt->gen2_and_or.address = REG_GEN1_DUR_ADDR; ++ stat->interrupt->gen2_and_or.mask = GEN2_AND_OR_MASK; ++ atomic_set(&stat->interrupt->gen2_and_or.enable, 0); ++ ++ stat->interrupt->interrupt_pin_conf.address = REG_GEN_MAG_ADDR; ++ stat->interrupt->interrupt_pin_conf.mask = INT_PIN_CONF_MASK; ++ atomic_set(&stat->interrupt->interrupt_pin_conf.enable, 0); ++ ++ stat->interrupt->interrupt_polarity.address = REG_GEN_MAG_ADDR; ++ stat->interrupt->interrupt_polarity.mask = INT_POLARITY_MASK; ++ atomic_set(&stat->interrupt->interrupt_polarity.enable, 0); ++ } ++ ++ stat->hw_initialized = 1; ++ pr_info("%s: hw init done\n", LSM9DS0_DEV_NAME); ++ ++ return 0; ++ ++error_interrupt: ++err_unknown_device: ++err_firstread: ++ stat->hw_working = 0; ++ stat->hw_initialized = 0; ++ return err; ++} ++ ++static irqreturn_t lsm9ds0_isr1(int irq, void *dev) ++{ ++ struct lsm9ds0_status *stat = dev; ++ ++ disable_irq_nosync(irq); ++ queue_work(stat->irq1_work_queue, &stat->irq1_work); ++ pr_debug("%s: isr1 queued\n", LSM9DS0_DEV_NAME); ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t lsm9ds0_isr2(int irq, void *dev) ++{ ++ struct lsm9ds0_status *stat = dev; ++ ++ disable_irq_nosync(irq); ++ queue_work(stat->irq2_work_queue, &stat->irq2_work); ++ pr_debug("%s: isr2 queued\n", LSM9DS0_DEV_NAME); ++ return IRQ_HANDLED; ++} ++ ++static void lsm9ds0_interrupt_catch(struct lsm9ds0_status *stat, int pin ) ++{ ++ u8 buf[2]; ++ u8 val; ++ ++ if(atomic_read(&stat->interrupt->gen1_pin1.enable) == 1) { ++ buf[0] = status_registers.int_gen1_src.address; ++ val = lsm9ds0_i2c_read(stat, buf, 1); ++ if(val < 0) ++ return; ++ status_registers.int_gen1_src.value = buf[0]; ++ ++ if(((int)status_registers.int_gen1_src.value) > 64) ++ pr_info("interrupt send by accelerometer interrupt " ++ "generator 1\n"); ++ } ++ if(atomic_read(&stat->interrupt->gen_mag_pin1.enable) == 1) { ++ buf[0] = status_registers.int_gen_mag_src.address; ++ val = lsm9ds0_i2c_read(stat, buf, 1); ++ if(val < 0) ++ return; ++ status_registers.int_gen_mag_src.value = buf[0]; ++ ++ if(((int)status_registers.int_gen_mag_src.value) > 1) ++ pr_info("interrupt send by magnetometer interrupt " ++ "generator\n"); ++ } ++ ++} ++ ++static void lsm9ds0_irq1_work_func(struct work_struct *work) ++{ ++ ++ struct lsm9ds0_status *stat = ++ container_of(work, struct lsm9ds0_status, irq1_work); ++ /* TODO add interrupt service procedure. ++ ie:lsm9ds0_get_int1_source(stat); */ ++ ++ lsm9ds0_interrupt_catch(stat,1); ++ pr_info("%s: IRQ1 triggered\n", LSM9DS0_DEV_NAME); ++exit: ++ enable_irq(stat->irq1); ++} ++ ++static void lsm9ds0_irq2_work_func(struct work_struct *work) ++{ ++ ++ struct lsm9ds0_status *stat = ++ container_of(work, struct lsm9ds0_status, irq2_work); ++ /* TODO add interrupt service procedure. ++ ie:lsm9ds0_get_int2_source(stat); */ ++ ++ lsm9ds0_interrupt_catch(stat,2); ++ pr_info("%s: IRQ2 triggered\n", LSM9DS0_DEV_NAME); ++exit: ++ enable_irq(stat->irq2); ++} ++ ++static int lsm9ds0_acc_device_power_off(struct lsm9ds0_status *stat) ++{ ++ int err; ++ u8 buf[2]; ++ ++ buf[0] = status_registers.cntrl1.address; ++ buf[1] = ((ODR_ACC_MASK & LSM9DS0_ACC_ODR_OFF) | ++ ((~ODR_ACC_MASK) & status_registers.cntrl1.resume_value)); ++ ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ dev_err(&stat->client->dev, "accelerometer soft power off " ++ "failed: %d\n", err); ++ ++ if (stat->pdata_acc->power_off) { ++ stat->pdata_acc->power_off(); ++ } ++ ++ atomic_set(&stat->enabled_acc, 0); ++ ++ return 0; ++} ++ ++static int lsm9ds0_mag_device_power_off(struct lsm9ds0_status *stat) ++{ ++ int err; ++ u8 buf[2]; ++ ++ buf[0] = status_registers.cntrl7.address; ++ buf[1] = ((MSMS_MASK & POWEROFF_MAG) | ++ ((~MSMS_MASK) & status_registers.cntrl7.resume_value)); ++ ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ dev_err(&stat->client->dev, "magnetometer soft power off " ++ "failed: %d\n", err); ++ ++ if (stat->pdata_mag->power_off) { ++ stat->pdata_mag->power_off(); ++ } ++ ++ atomic_set(&stat->enabled_mag, 0); ++ ++ return 0; ++} ++ ++static int lsm9ds0_acc_device_power_on(struct lsm9ds0_status *stat) ++{ ++ int err = -1; ++ u8 buf[5]; ++ ++ if (stat->pdata_acc->power_on) { ++ err = stat->pdata_acc->power_on(); ++ if (err < 0) { ++ dev_err(&stat->client->dev, ++ "accelerometer power_on failed: %d\n", err); ++ return err; ++ } ++ } ++ ++ buf[0] = status_registers.cntrl0.address; ++ buf[1] = status_registers.cntrl0.resume_value; ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto err_resume_state; ++ ++ buf[0] = status_registers.cntrl1.address; ++ buf[1] = status_registers.cntrl1.resume_value; ++ buf[2] = status_registers.cntrl2.resume_value; ++ buf[3] = status_registers.cntrl3.resume_value; ++ buf[4] = status_registers.cntrl4.resume_value; ++ err = lsm9ds0_i2c_write(stat, buf, 4); ++ if (err < 0) ++ goto err_resume_state; ++ ++ buf[0] = status_registers.int_gen1_reg.address; ++ buf[1] = status_registers.int_gen1_reg.resume_value; ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto err_resume_state; ++ ++ buf[0] = status_registers.int_gen1_threshold.address; ++ buf[1] = status_registers.int_gen1_threshold.resume_value; ++ buf[2] = status_registers.int_gen1_duration.resume_value; ++ err = lsm9ds0_i2c_write(stat, buf, 2); ++ if (err < 0) ++ goto err_resume_state; ++ ++ buf[0] = status_registers.int_gen2_reg.address; ++ buf[1] = status_registers.int_gen2_reg.resume_value; ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto err_resume_state; ++ ++ buf[0] = status_registers.int_gen2_threshold.address; ++ buf[1] = status_registers.int_gen2_threshold.resume_value; ++ buf[2] = status_registers.int_gen2_duration.resume_value; ++ err = lsm9ds0_i2c_write(stat, buf, 2); ++ if (err < 0) ++ goto err_resume_state; ++ ++ atomic_set(&stat->enabled_acc, 1); ++ ++ return 0; ++ ++err_resume_state: ++ atomic_set(&stat->enabled_acc, 0); ++ dev_err(&stat->client->dev, "accelerometer hw power on error " ++ "0x%02x,0x%02x: %d\n", buf[0], buf[1], err); ++ return err; ++} ++ ++static int lsm9ds0_mag_device_power_on(struct lsm9ds0_status *stat) ++{ ++ int err = -1; ++ u8 buf[6]; ++ ++ if (stat->pdata_mag->power_on) { ++ err = stat->pdata_mag->power_on(); ++ if (err < 0) { ++ dev_err(&stat->client->dev, ++ "magnetometer power_on failed: %d\n", err); ++ return err; ++ } ++ } ++ ++ buf[0] = status_registers.cntrl0.address; ++ buf[1] = status_registers.cntrl0.resume_value; ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto err_resume_state; ++ ++ buf[0] = status_registers.cntrl3.address; ++ buf[1] = status_registers.cntrl3.resume_value; ++ buf[2] = status_registers.cntrl4.resume_value; ++ buf[3] = status_registers.cntrl5.resume_value; ++ buf[4] = status_registers.cntrl6.resume_value; ++ ++ err = lsm9ds0_i2c_write(stat, buf, 4); ++ if (err < 0) ++ goto err_resume_state; ++ ++ buf[0] = status_registers.int_ctrl_reg_m.address; ++ buf[1] = status_registers.int_ctrl_reg_m.resume_value; ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto err_resume_state; ++ ++ buf[0] = status_registers.int_mag_threshold_low.address; ++ buf[1] = status_registers.int_mag_threshold_low.resume_value; ++ buf[2] = status_registers.int_mag_threshold_high.resume_value; ++ err = lsm9ds0_i2c_write(stat, buf, 2); ++ if (err < 0) ++ goto err_resume_state; ++ ++ buf[0] = status_registers.cntrl7.address; ++ buf[1] = ((MSMS_MASK & CONTINUOS_CONVERSION) | ++ ((~MSMS_MASK) & status_registers.cntrl7.resume_value)); ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto err_resume_state; ++ ++ atomic_set(&stat->enabled_mag, 1); ++ ++ return 0; ++ ++err_resume_state: ++ atomic_set(&stat->enabled_mag, 0); ++ dev_err(&stat->client->dev, "magnetometer hw power on error " ++ "0x%02x,0x%02x: %d\n", buf[0], buf[1], err); ++ return err; ++} ++ ++static int lsm9ds0_acc_update_filter(struct lsm9ds0_status *stat, ++ u8 new_bandwidth) ++{ ++ int err=-1; ++ ++ u8 updated_val; ++ u8 buf[2]; ++ ++ switch (new_bandwidth) { ++ case ANTI_ALIASING_50: ++ break; ++ case ANTI_ALIASING_194: ++ break; ++ case ANTI_ALIASING_362: ++ break; ++ case ANTI_ALIASING_773: ++ break; ++ default: ++ dev_err(&stat->client->dev, "invalid accelerometer " ++ "update bandwidth requested: %u\n", new_bandwidth); ++ return -EINVAL; ++ } ++ ++ buf[0] = status_registers.cntrl2.address; ++ err = lsm9ds0_i2c_read(stat, buf, 1); ++ if (err < 0) ++ goto error; ++ ++ status_registers.cntrl2.resume_value = buf[0]; ++ updated_val = ((LSM9DS0_ACC_FILTER_MASK & new_bandwidth) | ++ ((~LSM9DS0_ACC_FILTER_MASK) & buf[0])); ++ buf[1] = updated_val; ++ buf[0] = status_registers.cntrl2.address; ++ ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto error; ++ status_registers.cntrl2.resume_value = updated_val; ++ ++ return err; ++ ++error: ++ dev_err(&stat->client->dev, "update accelerometer fs range failed " ++ "0x%02x,0x%02x: %d\n", buf[0], buf[1], err); ++ return err; ++} ++ ++static int lsm9ds0_acc_update_fs_range(struct lsm9ds0_status *stat, ++ u8 new_fs_range) ++{ ++ int err=-1; ++ ++ u16 sensitivity; ++ u8 updated_val; ++ u8 buf[2]; ++ ++ switch (new_fs_range) { ++ case LSM9DS0_ACC_FS_2G: ++ sensitivity = SENSITIVITY_ACC_2G; ++ break; ++ case LSM9DS0_ACC_FS_4G: ++ sensitivity = SENSITIVITY_ACC_4G; ++ break; ++ case LSM9DS0_ACC_FS_8G: ++ sensitivity = SENSITIVITY_ACC_8G; ++ break; ++ case LSM9DS0_ACC_FS_16G: ++ sensitivity = SENSITIVITY_ACC_16G; ++ break; ++ default: ++ dev_err(&stat->client->dev, "invalid accelerometer " ++ "fs range requested: %u\n", new_fs_range); ++ return -EINVAL; ++ } ++ ++ buf[0] = status_registers.cntrl2.address; ++ err = lsm9ds0_i2c_read(stat, buf, 1); ++ if (err < 0) ++ goto error; ++ ++ status_registers.cntrl2.resume_value = buf[0]; ++ updated_val = ((LSM9DS0_ACC_FS_MASK & new_fs_range) | ++ ((~LSM9DS0_ACC_FS_MASK) & buf[0])); ++ buf[1] = updated_val; ++ buf[0] = status_registers.cntrl2.address; ++ ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto error; ++ status_registers.cntrl2.resume_value = updated_val; ++ stat->sensitivity_acc = sensitivity; ++ ++ return err; ++ ++error: ++ dev_err(&stat->client->dev, "update accelerometer fs range failed " ++ "0x%02x,0x%02x: %d\n", buf[0], buf[1], err); ++ return err; ++} ++ ++static int lsm9ds0_mag_update_fs_range(struct lsm9ds0_status *stat, ++ u8 new_fs_range) ++{ ++ int err=-1; ++ ++ u16 sensitivity; ++ u8 updated_val; ++ u8 buf[2]; ++ ++ switch (new_fs_range) { ++ case LSM9DS0_MAG_FS_2G: ++ sensitivity = SENSITIVITY_MAG_2G; ++ break; ++ case LSM9DS0_MAG_FS_4G: ++ sensitivity = SENSITIVITY_MAG_4G; ++ break; ++ case LSM9DS0_MAG_FS_8G: ++ sensitivity = SENSITIVITY_MAG_8G; ++ break; ++ case LSM9DS0_MAG_FS_12G: ++ sensitivity = SENSITIVITY_MAG_12G; ++ break; ++ default: ++ dev_err(&stat->client->dev, "invalid magnetometer " ++ "fs range requested: %u\n", new_fs_range); ++ return -EINVAL; ++ } ++ ++ buf[0] = status_registers.cntrl6.address; ++ err = lsm9ds0_i2c_read(stat, buf, 1); ++ if (err < 0) ++ goto error; ++ ++ status_registers.cntrl6.resume_value = buf[0]; ++ updated_val = (LSM9DS0_MAG_FS_MASK & new_fs_range); ++ buf[1] = updated_val; ++ buf[0] = status_registers.cntrl6.address; ++ ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto error; ++ status_registers.cntrl6.resume_value = updated_val; ++ stat->sensitivity_mag = sensitivity; ++ ++ return err; ++ ++error: ++ dev_err(&stat->client->dev, "update magnetometer fs range failed " ++ "0x%02x,0x%02x: %d\n", buf[0], buf[1], err); ++ return err; ++} ++ ++static int lsm9ds0_acc_update_odr(struct lsm9ds0_status *stat, ++ unsigned int poll_interval_ms) ++{ ++ int err = -1; ++ u8 config[2]; ++ int i; ++ ++ for (i = ARRAY_SIZE(lsm9ds0_acc_odr_table) - 1; i >= 0; i--) { ++ if ((lsm9ds0_acc_odr_table[i].cutoff_us <= poll_interval_ms) ++ || (i == 0)) ++ break; ++ } ++ ++ config[1] = ((ODR_ACC_MASK & lsm9ds0_acc_odr_table[i].value) | ++ ((~ODR_ACC_MASK) & status_registers.cntrl1.resume_value)); ++ ++ if (atomic_read(&stat->enabled_acc)) { ++ config[0] = status_registers.cntrl1.address; ++ err = lsm9ds0_i2c_write(stat, config, 1); ++ if (err < 0) ++ goto error; ++ status_registers.cntrl1.resume_value = config[1]; ++ stat->ktime_acc = ktime_set(0, MS_TO_NS(poll_interval_ms)); ++ } ++ ++ return err; ++ ++error: ++ dev_err(&stat->client->dev, "update accelerometer odr failed " ++ "0x%02x,0x%02x: %d\n", config[0], config[1], err); ++ ++ return err; ++} ++ ++static int lsm9ds0_mag_update_odr(struct lsm9ds0_status *stat, ++ unsigned int poll_interval_ms) ++{ ++ int err = -1; ++ u8 config[2]; ++ int i; ++ ++ for (i = ARRAY_SIZE(lsm9ds0_mag_odr_table) - 1; i >= 0; i--) { ++ if ((lsm9ds0_mag_odr_table[i].cutoff_us <= poll_interval_ms) ++ || (i == 0)) ++ break; ++ } ++ ++ config[1] = ((ODR_MAG_MASK & lsm9ds0_mag_odr_table[i].value) | ++ ((~ODR_MAG_MASK) & status_registers.cntrl5.resume_value)); ++ ++ if (atomic_read(&stat->enabled_mag)) { ++ config[0] = status_registers.cntrl5.address; ++ err = lsm9ds0_i2c_write(stat, config, 1); ++ if (err < 0) ++ goto error; ++ status_registers.cntrl5.resume_value = config[1]; ++ stat->ktime_mag = ktime_set(0, MS_TO_NS(poll_interval_ms)); ++ } ++ ++ return err; ++ ++error: ++ dev_err(&stat->client->dev, "update magnetometer odr failed " ++ "0x%02x,0x%02x: %d\n", config[0], config[1], err); ++ ++ return err; ++} ++ ++static void lsm9ds0_validate_polling(unsigned int *min_interval, ++ unsigned int *poll_interval, ++ unsigned int min, ++ struct i2c_client *client) ++{ ++ *min_interval = max(min, *min_interval); ++ *poll_interval = max(*poll_interval, *min_interval); ++} ++ ++static int lsm9ds0_acc_validate_pdata(struct lsm9ds0_status *stat) ++{ ++ int res = -EINVAL; ++ ++ lsm9ds0_validate_polling(&stat->pdata_acc->min_interval, ++ &stat->pdata_acc->poll_interval, ++ (unsigned int)LSM9DS0_ACC_MIN_POLL_PERIOD_MS, ++ stat->client); ++ ++ switch (stat->pdata_acc->aa_filter_bandwidth) { ++ case ANTI_ALIASING_50: ++ res = 1; ++ break; ++ case ANTI_ALIASING_194: ++ res = 1; ++ break; ++ case ANTI_ALIASING_362: ++ res = 1; ++ break; ++ case ANTI_ALIASING_773: ++ res = 1; ++ break; ++ default: ++ dev_err(&stat->client->dev, "invalid accelerometer " ++ "bandwidth selected: %u\n", ++ stat->pdata_acc->aa_filter_bandwidth); ++ } ++ ++ return res; ++} ++ ++static int lsm9ds0_mag_validate_pdata(struct lsm9ds0_status *stat) ++{ ++ lsm9ds0_validate_polling(&stat->pdata_mag->min_interval, ++ &stat->pdata_mag->poll_interval, ++ (unsigned int)LSM9DS0_MAG_MIN_POLL_PERIOD_MS, ++ stat->client); ++ ++ return 0; ++} ++ ++static int lsm9ds0_acc_enable(struct lsm9ds0_status *stat) ++{ ++ int err; ++ ++ if (!atomic_cmpxchg(&stat->enabled_acc, 0, 1)) { ++ err = lsm9ds0_acc_device_power_on(stat); ++ if (err < 0) { ++ atomic_set(&stat->enabled_acc, 0); ++ return err; ++ } ++ hrtimer_start(&stat->hr_timer_acc, stat->ktime_acc, HRTIMER_MODE_REL); ++ if(!atomic_read(&stat->enabled_mag)) { ++ if(stat->pdata_acc->gpio_int1 >= 0) ++ enable_irq(stat->irq1); ++ if(stat->pdata_acc->gpio_int2 >= 0) ++ enable_irq(stat->irq2); ++ } ++ } ++ ++ return 0; ++} ++ ++static int lsm9ds0_acc_disable(struct lsm9ds0_status *stat) ++{ ++ if (atomic_cmpxchg(&stat->enabled_acc, 1, 0)) { ++ cancel_work_sync(&stat->input_work_acc); ++ hrtimer_cancel(&stat->hr_timer_acc); ++ lsm9ds0_acc_device_power_off(stat); ++ ++ if(!atomic_read(&stat->enabled_mag)) { ++ if(stat->pdata_acc->gpio_int1 >= 0) ++ disable_irq_nosync(stat->irq1); ++ if(stat->pdata_acc->gpio_int2 >= 0) ++ disable_irq_nosync(stat->irq2); ++ } ++ } ++ ++ return 0; ++} ++ ++static int lsm9ds0_mag_enable(struct lsm9ds0_status *stat) ++{ ++ int err; ++ ++ if (!atomic_cmpxchg(&stat->enabled_mag, 0, 1)) { ++ err = lsm9ds0_mag_device_power_on(stat); ++ if (err < 0) { ++ atomic_set(&stat->enabled_mag, 0); ++ return err; ++ } ++ if(!atomic_read(&stat->enabled_temp)) { ++ hrtimer_start(&stat->hr_timer_mag, stat->ktime_mag, HRTIMER_MODE_REL); ++ } ++ if(!atomic_read(&stat->enabled_acc)) { ++ if(stat->pdata_acc->gpio_int1 >= 0) ++ enable_irq(stat->irq1); ++ if(stat->pdata_acc->gpio_int2 >= 0) ++ enable_irq(stat->irq2); ++ } ++ } ++ ++ return 0; ++} ++ ++static int lsm9ds0_mag_disable(struct lsm9ds0_status *stat) ++{ ++ if (atomic_cmpxchg(&stat->enabled_mag, 1, 0)) { ++ if(!atomic_read(&stat->enabled_temp)) { ++ cancel_work_sync(&stat->input_work_mag); ++ hrtimer_cancel(&stat->hr_timer_mag); ++ } ++ lsm9ds0_mag_device_power_off(stat); ++ if(!atomic_read(&stat->enabled_acc)) { ++ if(stat->pdata_acc->gpio_int1 >= 0) ++ disable_irq(stat->irq1); ++ if(stat->pdata_acc->gpio_int2 >= 0) ++ disable_irq(stat->irq2); ++ } ++ } ++ ++ return 0; ++} ++ ++static int lsm9ds0_temperature_enable(struct lsm9ds0_status *stat) ++{ ++ int err; ++ u8 buf[2]; ++ u8 updated_val; ++ ++ buf[0] = status_registers.cntrl5.address; ++ err = lsm9ds0_i2c_read(stat, buf, 1); ++ if (err < 0) ++ goto error; ++ ++ status_registers.cntrl5.resume_value = buf[0]; ++ updated_val = ((TEMP_MASK & TEMP_ON) | ++ ((~TEMP_MASK) & buf[0])); ++ buf[1] = updated_val; ++ buf[0] = status_registers.cntrl5.address; ++ ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto error; ++ status_registers.cntrl5.resume_value = updated_val; ++ ++ if(!atomic_read(&stat->enabled_mag)) { ++ hrtimer_start(&stat->hr_timer_mag, stat->ktime_mag, HRTIMER_MODE_REL); ++ } ++ atomic_set(&stat->enabled_temp, 1); ++ return 0; ++ ++error: ++ return -1; ++} ++ ++static int lsm9ds0_temperature_disable(struct lsm9ds0_status *stat) ++{ ++ int err; ++ u8 buf[2]; ++ u8 updated_val; ++ ++ buf[0] = status_registers.cntrl5.address; ++ err = lsm9ds0_i2c_read(stat, buf, 1); ++ if (err < 0) ++ goto error; ++ ++ status_registers.cntrl5.resume_value = buf[0]; ++ updated_val = ((TEMP_MASK & TEMP_OFF) | ++ ((~TEMP_MASK) & buf[0])); ++ buf[1] = updated_val; ++ buf[0] = status_registers.cntrl5.address; ++ ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ goto error; ++ status_registers.cntrl5.resume_value = updated_val; ++ ++ if(!atomic_read(&stat->enabled_mag)) { ++ cancel_work_sync(&stat->input_work_mag); ++ hrtimer_cancel(&stat->hr_timer_mag); ++ } ++ atomic_set(&stat->enabled_temp, 0); ++ stat->temp_value_dec = NDTEMP; ++ return 0; ++ ++error: ++ return -1; ++} ++ ++static void lsm9ds0_acc_input_cleanup(struct lsm9ds0_status *stat) ++{ ++ input_unregister_device(stat->input_dev_acc); ++ input_free_device(stat->input_dev_acc); ++} ++ ++static void lsm9ds0_mag_input_cleanup(struct lsm9ds0_status *stat) ++{ ++ input_unregister_device(stat->input_dev_mag); ++ input_free_device(stat->input_dev_mag); ++} ++ ++static ssize_t attr_get_polling_rate_acc(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ unsigned int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ mutex_lock(&stat->lock); ++ val = stat->pdata_acc->poll_interval; ++ mutex_unlock(&stat->lock); ++ return sprintf(buf, "%u\n", val); ++} ++ ++static ssize_t attr_get_polling_rate_mag(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ unsigned int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ mutex_lock(&stat->lock); ++ val = stat->pdata_mag->poll_interval; ++ mutex_unlock(&stat->lock); ++ return sprintf(buf, "%u\n", val); ++} ++ ++static ssize_t attr_set_polling_rate_acc(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ unsigned long interval_ms; ++ ++ if (strict_strtoul(buf, 10, &interval_ms)) ++ return -EINVAL; ++ if (!interval_ms) ++ return -EINVAL; ++ interval_ms = (unsigned int)max((unsigned int)interval_ms, ++ stat->pdata_acc->min_interval); ++ mutex_lock(&stat->lock); ++ stat->pdata_acc->poll_interval = (unsigned int)interval_ms; ++ lsm9ds0_acc_update_odr(stat, interval_ms); ++ mutex_unlock(&stat->lock); ++ return size; ++} ++ ++static ssize_t attr_set_polling_rate_mag(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ unsigned long interval_ms; ++ ++ if (strict_strtoul(buf, 10, &interval_ms)) ++ return -EINVAL; ++ if (!interval_ms) ++ return -EINVAL; ++ interval_ms = (unsigned int)max((unsigned int)interval_ms, ++ stat->pdata_mag->min_interval); ++ mutex_lock(&stat->lock); ++ stat->pdata_mag->poll_interval = (unsigned int)interval_ms; ++ lsm9ds0_mag_update_odr(stat, interval_ms); ++ mutex_unlock(&stat->lock); ++ return size; ++} ++ ++static ssize_t attr_get_enable_acc(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ int val = (int)atomic_read(&stat->enabled_acc); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_get_enable_mag(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ int val = (int)atomic_read(&stat->enabled_mag); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_enable_acc(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ unsigned long val; ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ if (val) ++ lsm9ds0_acc_enable(stat); ++ else ++ lsm9ds0_acc_disable(stat); ++ ++ return size; ++} ++ ++static ssize_t attr_set_enable_mag(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ unsigned long val; ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ if (val) ++ lsm9ds0_mag_enable(stat); ++ else ++ lsm9ds0_mag_disable(stat); ++ ++ return size; ++} ++ ++static ssize_t attr_get_range_acc(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ u8 val; ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ int range = 2; ++ mutex_lock(&stat->lock); ++ val = stat->pdata_acc->fs_range ; ++ switch (val) { ++ case LSM9DS0_ACC_FS_2G: ++ range = 2; ++ break; ++ case LSM9DS0_ACC_FS_4G: ++ range = 4; ++ break; ++ case LSM9DS0_ACC_FS_8G: ++ range = 8; ++ break; ++ case LSM9DS0_ACC_FS_16G: ++ range = 16; ++ break; ++ } ++ mutex_unlock(&stat->lock); ++ return sprintf(buf, "%d\n", range); ++} ++ ++static ssize_t attr_get_range_mag(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ u8 val; ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ int range = 2; ++ mutex_lock(&stat->lock); ++ val = stat->pdata_mag->fs_range ; ++ switch (val) { ++ case LSM9DS0_MAG_FS_2G: ++ range = 2; ++ break; ++ case LSM9DS0_MAG_FS_4G: ++ range = 4; ++ break; ++ case LSM9DS0_MAG_FS_8G: ++ range = 8; ++ break; ++ case LSM9DS0_MAG_FS_12G: ++ range = 12; ++ break; ++ } ++ mutex_unlock(&stat->lock); ++ return sprintf(buf, "%d\n", range); ++} ++ ++static ssize_t attr_set_range_acc(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ unsigned long val; ++ u8 range; ++ int err; ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ switch (val) { ++ case 2: ++ range = LSM9DS0_ACC_FS_2G; ++ break; ++ case 4: ++ range = LSM9DS0_ACC_FS_4G; ++ break; ++ case 8: ++ range = LSM9DS0_ACC_FS_8G; ++ break; ++ case 16: ++ range = LSM9DS0_ACC_FS_16G; ++ break; ++ default: ++ dev_err(&stat->client->dev, "accelerometer invalid range " ++ "request: %lu, discarded\n", val); ++ return -EINVAL; ++ } ++ mutex_lock(&stat->lock); ++ err = lsm9ds0_acc_update_fs_range(stat, range); ++ if (err < 0) { ++ mutex_unlock(&stat->lock); ++ return err; ++ } ++ stat->pdata_acc->fs_range = range; ++ mutex_unlock(&stat->lock); ++ dev_info(&stat->client->dev, "accelerometer range set to:" ++ " %lu g\n", val); ++ ++ return size; ++} ++ ++static ssize_t attr_set_range_mag(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ unsigned long val; ++ u8 range; ++ int err; ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ switch (val) { ++ case 2: ++ range = LSM9DS0_MAG_FS_2G; ++ break; ++ case 4: ++ range = LSM9DS0_MAG_FS_4G; ++ break; ++ case 8: ++ range = LSM9DS0_MAG_FS_8G; ++ break; ++ case 12: ++ range = LSM9DS0_MAG_FS_12G; ++ break; ++ default: ++ dev_err(&stat->client->dev, "magnetometer invalid range " ++ "request: %lu, discarded\n", val); ++ return -EINVAL; ++ } ++ mutex_lock(&stat->lock); ++ err = lsm9ds0_mag_update_fs_range(stat, range); ++ if (err < 0) { ++ mutex_unlock(&stat->lock); ++ return err; ++ } ++ stat->pdata_mag->fs_range = range; ++ mutex_unlock(&stat->lock); ++ dev_info(&stat->client->dev, "magnetometer range set to:" ++ " %lu g\n", val); ++ ++ return size; ++} ++ ++static ssize_t attr_get_aa_filter(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ u8 val; ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ int frequency=FILTER_773; ++ mutex_lock(&stat->lock); ++ val = stat->pdata_acc->aa_filter_bandwidth; ++ switch (val) { ++ case ANTI_ALIASING_50: ++ frequency = FILTER_50; ++ break; ++ case ANTI_ALIASING_194: ++ frequency = FILTER_194; ++ break; ++ case ANTI_ALIASING_362: ++ frequency = FILTER_362; ++ break; ++ case ANTI_ALIASING_773: ++ frequency = FILTER_773; ++ break; ++ } ++ mutex_unlock(&stat->lock); ++ return sprintf(buf, "%d\n", frequency); ++} ++ ++static ssize_t attr_set_aa_filter(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ unsigned long val; ++ u8 frequency; ++ int err; ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ switch (val) { ++ case FILTER_50: ++ frequency = ANTI_ALIASING_50; ++ break; ++ case FILTER_194: ++ frequency = ANTI_ALIASING_194; ++ break; ++ case FILTER_362: ++ frequency = ANTI_ALIASING_362; ++ break; ++ case FILTER_773: ++ frequency = ANTI_ALIASING_773; ++ break; ++ default: ++ dev_err(&stat->client->dev, "accelerometer invalid filter " ++ "request: %lu, discarded\n", val); ++ return -EINVAL; ++ } ++ mutex_lock(&stat->lock); ++ err = lsm9ds0_acc_update_filter(stat, frequency); ++ if (err < 0) { ++ mutex_unlock(&stat->lock); ++ return err; ++ } ++ stat->pdata_acc->aa_filter_bandwidth = frequency; ++ mutex_unlock(&stat->lock); ++ dev_info(&stat->client->dev, "accelerometer anti-aliasing filter " ++ "set to: %lu Hz\n", val); ++ ++ return size; ++} ++ ++static ssize_t attr_get_temp_enable(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ int val = (int)atomic_read(&stat->enabled_temp); ++ ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_temp_enable(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ unsigned long val; ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ atomic_set(&stat->enabled_temp, (int)val); ++ ++ if(val>0) { ++ lsm9ds0_temperature_enable(stat); ++ } else { ++ lsm9ds0_temperature_disable(stat); ++ } ++ ++ return size; ++} ++ ++static ssize_t attr_get_temp(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ int dec; ++ unsigned int flo; ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ dec = stat->temp_value_dec; ++ flo = stat->temp_value_flo; ++ ++ if(dec==NDTEMP) ++ return sprintf(buf, "n.d.\n"); ++ ++ return sprintf(buf, "%d.%u\n", dec, flo); ++} ++ ++static struct kobj_attribute poll_attr_acc = ++__ATTR(pollrate_ms, 0664, attr_get_polling_rate_acc, attr_set_polling_rate_acc); ++static struct kobj_attribute enable_attr_acc = ++__ATTR(enable_device, 0664, attr_get_enable_acc, attr_set_enable_acc); ++static struct kobj_attribute fs_attr_acc = ++__ATTR(full_scale, 0664, attr_get_range_acc, attr_set_range_acc); ++static struct kobj_attribute aa_filter_attr = ++__ATTR(anti_aliasing_frequency, 0664, attr_get_aa_filter, attr_set_aa_filter); ++static struct kobj_attribute poll_attr_mag = ++__ATTR(pollrate_ms, 0664, attr_get_polling_rate_mag, attr_set_polling_rate_mag); ++static struct kobj_attribute enable_attr_mag = ++__ATTR(enable_device, 0664, attr_get_enable_mag, attr_set_enable_mag); ++static struct kobj_attribute fs_attr_mag = ++__ATTR(full_scale, 0664, attr_get_range_mag, attr_set_range_mag); ++ ++static int write_bit_on_register(struct lsm9ds0_status *stat, u8 address, ++ u8 *resume_value, u8 mask, int value) ++{ ++ int err; ++ u8 updated_val; ++ u8 buf[2]; ++ u8 val = 0x00; ++ ++ buf[0] = address; ++ err = lsm9ds0_i2c_read(stat, buf, 1); ++ if (err < 0) ++ return -1; ++ ++ if(resume_value != NULL) ++ *resume_value = buf[0]; ++ ++ if(mask == 0) ++ updated_val = (u8)value; ++ else { ++ if(value>0) ++ val = 0xFF; ++ updated_val = (mask & val) | ((~mask) & buf[0]); ++ } ++ ++ buf[1] = updated_val; ++ buf[0] = address; ++ ++ err = lsm9ds0_i2c_write(stat, buf, 1); ++ if (err < 0) ++ return -1; ++ ++ if(resume_value != NULL) ++ *resume_value = updated_val; ++ ++ return err; ++} ++ ++static int write_gen_int(struct lsm9ds0_status *stat, ++ struct interrupt_enable *ie, int val) ++{ ++ int err; ++ ++ if(val>0) ++ val = 1; ++ else ++ val = 0; ++ ++ err = write_bit_on_register(stat, ie->address, NULL, ie->mask, val); ++ if(err < 0) ++ return -1; ++ ++ atomic_set(&ie->enable, val); ++ return err; ++} ++ ++static int write_duration_threshold_int(struct lsm9ds0_status *stat, ++ struct interrupt_value *ie, int val) ++{ ++ int err; ++ ++ if(val<0) ++ return -1; ++ ++ if(val>MAX_DUR_TH) ++ return -1; ++ ++ err = write_bit_on_register(stat, ie->address, NULL, 0, val); ++ if(err<0) ++ return -1; ++ ++ ie->value = val; ++ ++ return err; ++} ++ ++static int write_threshold_mag_int(struct lsm9ds0_status *stat, ++ struct interrupt_value *ie, int val) ++{ ++ int err; ++ u8 high; ++ u8 low; ++ ++ if(val<0) ++ return -1; ++ ++ if(val>MAX_TH_MAG) ++ return -1; ++ ++ low = (u8)(0xff & val); ++ ++ err = write_bit_on_register(stat, ie->address, NULL, 0, low); ++ if(err<0) ++ return -1; ++ ++ high = (u8)(0xff & (val >> 8)); ++ ++ err = write_bit_on_register(stat, (ie->address)+1, NULL, 0, high); ++ if(err<0) ++ return -1; ++ ++ ie->value = val; ++ ++ return err; ++} ++ ++static ssize_t attr_get_gen1_status(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val = -1; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if(strcmp(attr->attr.name, "pin1_enable") == 0) { ++ val = atomic_read(&stat->interrupt->gen1_pin1.enable); ++ } ++ if(strcmp(attr->attr.name, "pin2_enable") == 0) { ++ val = atomic_read(&stat->interrupt->gen1_pin2.enable); ++ } ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen1_status(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err = -1; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ if(strcmp(attr->attr.name, "pin1_enable") == 0) { ++ err = write_gen_int(stat, ++ &stat->interrupt->gen1_pin1, (int)val); ++ } ++ if(strcmp(attr->attr.name, "pin2_enable") == 0) { ++ err = write_gen_int(stat, ++ &stat->interrupt->gen1_pin2, (int)val); ++ } ++ return size; ++} ++ ++static ssize_t attr_get_gen2_status(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val = -1; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if(strcmp(attr->attr.name, "pin1_enable") == 0) { ++ val = atomic_read(&stat->interrupt->gen2_pin1.enable); ++ } ++ if(strcmp(attr->attr.name, "pin2_enable") == 0) { ++ val = atomic_read(&stat->interrupt->gen2_pin2.enable); ++ } ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen2_status(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err = -1; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ if(strcmp(attr->attr.name, "pin1_enable") == 0) { ++ err = write_gen_int(stat, ++ &stat->interrupt->gen2_pin1, (int)val); ++ } ++ if(strcmp(attr->attr.name, "pin2_enable") == 0) { ++ err = write_gen_int(stat, ++ &stat->interrupt->gen2_pin2, (int)val); ++ } ++ return size; ++} ++ ++static ssize_t attr_get_gen1_duration(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = stat->interrupt->gen1_duration.value; ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen1_duration(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err = -1; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = write_duration_threshold_int(stat, ++ &stat->interrupt->gen1_duration, (int)val); ++ ++ return size; ++} ++ ++static ssize_t attr_get_gen2_duration(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = stat->interrupt->gen2_duration.value; ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen2_duration(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err = -1; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = write_duration_threshold_int(stat, ++ &stat->interrupt->gen2_duration, (int)val); ++ ++ return size; ++} ++ ++static ssize_t attr_get_gen1_threshold(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = stat->interrupt->gen1_threshold.value; ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen1_threshold(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err = -1; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = write_duration_threshold_int(stat, ++ &stat->interrupt->gen1_threshold, (int)val); ++ ++ return size; ++} ++ ++static ssize_t attr_get_gen2_threshold(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = stat->interrupt->gen2_threshold.value; ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen2_threshold(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err = -1; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = write_duration_threshold_int(stat, ++ &stat->interrupt->gen2_threshold, (int)val); ++ ++ return size; ++} ++ ++static ssize_t attr_get_gen_mag_status(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val = -1; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if(strcmp(attr->attr.name, "pin1_enable") == 0) { ++ val = atomic_read(&stat->interrupt->gen_mag_pin1.enable); ++ } ++ if(strcmp(attr->attr.name, "pin2_enable") == 0) { ++ val = atomic_read(&stat->interrupt->gen_mag_pin2.enable); ++ } ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen_mag_status(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err = -1; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ if(strcmp(attr->attr.name, "pin1_enable") == 0) { ++ err = write_gen_int(stat, ++ &stat->interrupt->gen_mag_pin1, (int)val); ++ if(err >= 0) { ++ if((atomic_read(&stat->interrupt->gen_mag_pin2.enable))==0) ++ write_gen_int(stat, ++ &stat->interrupt->gen_mag, (int)val); ++ } ++ } ++ if(strcmp(attr->attr.name, "pin2_enable") == 0) { ++ err = write_gen_int(stat, ++ &stat->interrupt->gen_mag_pin2, (int)val); ++ if(err >= 0) { ++ if((atomic_read(&stat->interrupt->gen_mag_pin1.enable))==0) ++ write_gen_int(stat, ++ &stat->interrupt->gen_mag, (int)val); ++ } ++ } ++ return size; ++} ++ ++static ssize_t attr_get_gen_mag_threshold(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = stat->interrupt->gen_mag_threshold.value; ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen_mag_threshold(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err = -1; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = write_threshold_mag_int(stat, ++ &stat->interrupt->gen_mag_threshold, (int)val); ++ ++ return size; ++} ++ ++static int get_axis(struct lsm9ds0_status *stat, ++ int generator, const char *name) { ++ ++ int val; ++ int axis; ++ ++ if(strcmp(name, "x_high_enable") == 0) { ++ axis = 3; ++ } ++ if(strcmp(name, "x_low_enable") == 0) { ++ axis = 0; ++ } ++ if(strcmp(name, "y_high_enable") == 0) { ++ axis = 4; ++ } ++ if(strcmp(name, "y_low_enable") == 0) { ++ axis = 1; ++ } ++ if(strcmp(name, "z_high_enable") == 0) { ++ axis = 5; ++ } ++ if(strcmp(name, "z_low_enable") == 0) { ++ axis = 2; ++ } ++ ++ if(generator == 1) ++ val = atomic_read(&stat->interrupt->gen1_axis[axis].enable); ++ else ++ val = atomic_read(&stat->interrupt->gen2_axis[axis].enable); ++ ++ return val; ++} ++ ++static int set_axis(struct lsm9ds0_status *stat, int generator, ++ const char *name, unsigned long value) ++{ ++ int err = -1; ++ int axis; ++ ++ if(strcmp(name, "x_high_enable") == 0) { ++ axis = 3; ++ } ++ if((strcmp(name, "x_low_enable") == 0) || ++ (strcmp(name, "x_enable") == 0)) { ++ axis = 0; ++ } ++ if(strcmp(name, "y_high_enable") == 0) { ++ axis = 4; ++ } ++ if((strcmp(name, "y_low_enable") == 0) || ++ (strcmp(name, "y_enable") == 0)) { ++ axis = 1; ++ } ++ if(strcmp(name, "z_high_enable") == 0) { ++ axis = 5; ++ } ++ if((strcmp(name, "z_low_enable") == 0) || ++ (strcmp(name, "z_enable") == 0)) { ++ axis = 2; ++ } ++ ++ if(generator == 1) ++ err = write_gen_int(stat, ++ &(stat->interrupt->gen1_axis[axis]), (int)value); ++ if(generator == 2) ++ err = write_gen_int(stat, ++ &(stat->interrupt->gen2_axis[axis]), (int)value); ++ if(generator == 3) ++ err = write_gen_int(stat, ++ &(stat->interrupt->gen_mag_axis[axis]), (int)value); ++ ++ if(err < 0) ++ return -1; ++ ++ return err; ++} ++ ++static ssize_t attr_get_gen1_axis(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = get_axis(stat,1,attr->attr.name); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen1_axis(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = set_axis(stat, 1, attr->attr.name, val); ++ if(err < 0) ++ return -1; ++ ++ return size; ++} ++ ++static ssize_t attr_get_gen2_axis(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = get_axis(stat,2,attr->attr.name); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen2_axis(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = set_axis(stat, 2, attr->attr.name, val); ++ if(err < 0) ++ return -1; ++ ++ return size; ++} ++ ++static ssize_t attr_get_gen_mag_axis(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = get_axis(stat, 3, attr->attr.name); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen_mag_axis(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = set_axis(stat, 3, attr->attr.name, val); ++ if(err < 0) ++ return -1; ++ ++ return size; ++} ++ ++static ssize_t attr_get_gen1_and_or(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = atomic_read(&stat->interrupt->gen1_and_or.enable); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen1_and_or(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = write_gen_int(stat, &(stat->interrupt->gen1_and_or), (int)val); ++ if(err < 0) ++ return -1; ++ ++ return size; ++} ++ ++static ssize_t attr_get_gen2_and_or(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = atomic_read(&stat->interrupt->gen2_and_or.enable); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_gen2_and_or(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err; ++ unsigned long val; ++ struct device *dev = to_dev(kobj->parent); ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = write_gen_int(stat, &(stat->interrupt->gen2_and_or), (int)val); ++ if(err < 0) ++ return -1; ++ ++ return size; ++} ++ ++static ssize_t attr_set_pin_conf(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err; ++ unsigned long val; ++ ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = write_gen_int(stat, ++ &(stat->interrupt->interrupt_pin_conf), (int)val); ++ if(err < 0) ++ return -1; ++ ++ return size; ++} ++ ++static ssize_t attr_get_pin_conf(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = atomic_read(&stat->interrupt->interrupt_pin_conf.enable); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_set_interrupt_polarity(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err; ++ unsigned long val; ++ ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ err = write_gen_int(stat, ++ &(stat->interrupt->interrupt_polarity), (int)val); ++ if(err < 0) ++ return -1; ++ ++ return size; ++} ++ ++static ssize_t attr_get_interrupt_polarity(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ val = atomic_read(&stat->interrupt->interrupt_polarity.enable); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static struct kobj_attribute gen1_interrupt_pin1_enable = ++__ATTR(pin1_enable, 0664, attr_get_gen1_status, attr_set_gen1_status); ++static struct kobj_attribute gen1_interrupt_pin2_enable = ++__ATTR(pin2_enable, 0664, attr_get_gen1_status, attr_set_gen1_status); ++ ++static struct kobj_attribute gen2_interrupt_pin1_enable = ++__ATTR(pin1_enable, 0664, attr_get_gen2_status, attr_set_gen2_status); ++static struct kobj_attribute gen2_interrupt_pin2_enable = ++__ATTR(pin2_enable, 0664, attr_get_gen2_status, attr_set_gen2_status); ++ ++static struct kobj_attribute gen1_duration = ++__ATTR(duration, 0664, attr_get_gen1_duration, attr_set_gen1_duration); ++static struct kobj_attribute gen2_duration = ++__ATTR(duration, 0664, attr_get_gen2_duration, attr_set_gen2_duration); ++ ++static struct kobj_attribute gen1_threshold = ++__ATTR(threshold, 0664, attr_get_gen1_threshold, attr_set_gen1_threshold); ++static struct kobj_attribute gen2_threshold = ++__ATTR(threshold, 0664, attr_get_gen2_threshold, attr_set_gen2_threshold); ++ ++static struct kobj_attribute mag_gen_interrupt_pin1 = ++__ATTR(pin1_enable, 0664, attr_get_gen_mag_status, attr_set_gen_mag_status); ++static struct kobj_attribute mag_gen_interrupt_pin2 = ++__ATTR(pin2_enable, 0664, attr_get_gen_mag_status, attr_set_gen_mag_status); ++ ++static struct kobj_attribute mag_gen_threshold = ++__ATTR(threshold, 0664, attr_get_gen_mag_threshold, attr_set_gen_mag_threshold); ++ ++static struct kobj_attribute gen1_x_high = ++__ATTR(x_high_enable, 0664, attr_get_gen1_axis, attr_set_gen1_axis); ++static struct kobj_attribute gen1_x_low = ++__ATTR(x_low_enable, 0664, attr_get_gen1_axis, attr_set_gen1_axis); ++ ++static struct kobj_attribute gen2_x_high = ++__ATTR(x_high_enable, 0664, attr_get_gen2_axis, attr_set_gen2_axis); ++static struct kobj_attribute gen2_x_low = ++__ATTR(x_low_enable, 0664, attr_get_gen2_axis, attr_set_gen2_axis); ++ ++static struct kobj_attribute gen1_y_high = ++__ATTR(y_high_enable, 0664, attr_get_gen1_axis, attr_set_gen1_axis); ++static struct kobj_attribute gen1_y_low = ++__ATTR(y_low_enable, 0664, attr_get_gen1_axis, attr_set_gen1_axis); ++ ++static struct kobj_attribute gen2_y_high = ++__ATTR(y_high_enable, 0664, attr_get_gen2_axis, attr_set_gen2_axis); ++static struct kobj_attribute gen2_y_low = ++__ATTR(y_low_enable, 0664, attr_get_gen2_axis, attr_set_gen2_axis); ++ ++static struct kobj_attribute gen1_z_high = ++__ATTR(z_high_enable, 0664, attr_get_gen1_axis, attr_set_gen1_axis); ++static struct kobj_attribute gen1_z_low = ++__ATTR(z_low_enable, 0664, attr_get_gen1_axis, attr_set_gen1_axis); ++ ++static struct kobj_attribute gen2_z_high = ++__ATTR(z_high_enable, 0664, attr_get_gen2_axis, attr_set_gen2_axis); ++static struct kobj_attribute gen2_z_low = ++__ATTR(z_low_enable, 0664, attr_get_gen2_axis, attr_set_gen2_axis); ++ ++static struct kobj_attribute gen_mag_x = ++__ATTR(x_enable, 0664, attr_get_gen_mag_axis, attr_set_gen_mag_axis); ++static struct kobj_attribute gen_mag_y = ++__ATTR(y_enable, 0664, attr_get_gen_mag_axis, attr_set_gen_mag_axis); ++static struct kobj_attribute gen_mag_z = ++__ATTR(z_enable, 0664, attr_get_gen_mag_axis, attr_set_gen_mag_axis); ++ ++static struct kobj_attribute gen1_and_or = ++__ATTR(and(1)_or(0)_combination, 0664, attr_get_gen1_and_or, ++ attr_set_gen1_and_or); ++static struct kobj_attribute gen2_and_or = ++__ATTR(and(1)_or(0)_combination, 0664, attr_get_gen2_and_or, ++ attr_set_gen2_and_or); ++ ++ ++static struct attribute *attributes_acc_interrupt1[] = { ++ &gen1_interrupt_pin1_enable.attr, ++ &gen1_interrupt_pin2_enable.attr, ++ &gen1_duration.attr, ++ &gen1_threshold.attr, ++ &gen1_x_high.attr, ++ &gen1_x_low.attr, ++ &gen1_y_high.attr, ++ &gen1_y_low.attr, ++ &gen1_z_high.attr, ++ &gen1_z_low.attr, ++ &gen1_and_or.attr, ++ NULL, ++}; ++ ++static struct attribute *attributes_acc_interrupt2[] = { ++ &gen2_interrupt_pin1_enable.attr, ++ &gen2_interrupt_pin2_enable.attr, ++ &gen2_duration.attr, ++ &gen2_threshold.attr, ++ &gen2_x_high.attr, ++ &gen2_x_low.attr, ++ &gen2_y_high.attr, ++ &gen2_y_low.attr, ++ &gen2_z_high.attr, ++ &gen2_z_low.attr, ++ &gen2_and_or.attr, ++ NULL, ++}; ++ ++static struct attribute *attributes_mag_interrupt[] = { ++ &mag_gen_interrupt_pin1.attr, ++ &mag_gen_interrupt_pin2.attr, ++ &mag_gen_threshold.attr, ++ &gen_mag_x.attr, ++ &gen_mag_y.attr, ++ &gen_mag_z.attr, ++ NULL, ++}; ++ ++static struct attribute *attributes_acc[] = { ++ &poll_attr_acc.attr, ++ &enable_attr_acc.attr, ++ &fs_attr_acc.attr, ++ &aa_filter_attr.attr, ++ NULL, ++}; ++ ++static struct attribute *attributes_mag[] = { ++ &poll_attr_mag.attr, ++ &enable_attr_mag.attr, ++ &fs_attr_mag.attr, ++ NULL, ++}; ++ ++static struct attribute_group attr_group_acc = { ++ .attrs = attributes_acc, ++}; ++ ++static struct attribute_group attr_group_mag = { ++ .attrs = attributes_mag, ++}; ++ ++static struct attribute_group attr_group_int1_acc = { ++ .attrs = attributes_acc_interrupt1, ++ .name = "interrupt_generator1", ++}; ++ ++static struct attribute_group attr_group_int2_acc = { ++ .attrs = attributes_acc_interrupt2, ++ .name = "interrupt_generator2", ++}; ++ ++static struct attribute_group attr_group_int_mag = { ++ .attrs = attributes_mag_interrupt, ++ .name = "interrupt_generator", ++}; ++ ++static struct device_attribute attributes_com[] = { ++ __ATTR(enable_temperature, 0664, attr_get_temp_enable, ++ attr_set_temp_enable), ++ __ATTR(read_temperature, 0444, attr_get_temp, NULL), ++}; ++ ++static struct device_attribute attributes_interrupt_com[] = { ++ __ATTR(interrupt_pin_configuration, 0664, attr_get_pin_conf, ++ attr_set_pin_conf), ++ __ATTR(interrupt_polarity, 0664, attr_get_interrupt_polarity, ++ attr_set_interrupt_polarity), ++}; ++ ++static int create_sysfs_interfaces(struct device *dev) ++{ ++ int err; ++ int i,n; ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ ++ acc_kobj = kobject_create_and_add("accelerometer", &dev->kobj); ++ if(!acc_kobj) ++ return -ENOMEM; ++ ++ mag_kobj = kobject_create_and_add("magnetometer", &dev->kobj); ++ if(!mag_kobj) ++ return -ENOMEM; ++ ++ err = sysfs_create_group(acc_kobj, &attr_group_acc); ++ if (err) ++ kobject_put(acc_kobj); ++ ++ err = sysfs_create_group(mag_kobj, &attr_group_mag); ++ if (err) ++ kobject_put(mag_kobj); ++ ++ if((stat->pdata_acc->gpio_int1 >= 0)|| ++ (stat->pdata_acc->gpio_int2 >= 0)) { ++ err = sysfs_create_group(acc_kobj, &attr_group_int1_acc); ++ if (err) ++ kobject_put(acc_kobj); ++ ++ err = sysfs_create_group(acc_kobj, &attr_group_int2_acc); ++ if (err) ++ kobject_put(acc_kobj); ++ ++ err = sysfs_create_group(mag_kobj, &attr_group_int_mag); ++ if (err) ++ kobject_put(mag_kobj); ++ ++ for (n = 0; n < ARRAY_SIZE(attributes_interrupt_com); n++) ++ if (device_create_file(dev, attributes_interrupt_com + n)) ++ goto error1; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(attributes_com); i++) ++ if (device_create_file(dev, attributes_com + i)) ++ goto error; ++ ++ ++ ++ return 0; ++ ++error: ++ for ( ; i >= 0; i--) ++ device_remove_file(dev, attributes_com + i); ++ ++error1: ++ for ( ; n >= 0; n--) ++ device_remove_file(dev, attributes_interrupt_com + n); ++ ++ dev_err(dev, "%s:Unable to create interface\n", __func__); ++ return -1; ++} ++ ++static void remove_sysfs_interfaces(struct device *dev) ++{ ++ int i; ++ struct lsm9ds0_status *stat = dev_get_drvdata(dev); ++ kobject_put(acc_kobj); ++ kobject_put(mag_kobj); ++ for (i = 0; i < ARRAY_SIZE(attributes_com); i++) ++ device_remove_file(dev, attributes_com + i); ++ if((stat->pdata_acc->gpio_int1 >= 0)|| ++ (stat->pdata_acc->gpio_int2 >= 0)) { ++ for (i = 0; i < ARRAY_SIZE(attributes_interrupt_com); i++) ++ device_remove_file(dev, attributes_interrupt_com + i); ++ } ++} ++ ++int lsm9ds0_acc_input_open(struct input_dev *input) ++{ ++ struct lsm9ds0_status *stat = input_get_drvdata(input); ++ ++ return lsm9ds0_acc_enable(stat); ++} ++ ++void lsm9ds0_acc_input_close(struct input_dev *dev) ++{ ++ struct lsm9ds0_status *stat = input_get_drvdata(dev); ++ ++ lsm9ds0_acc_disable(stat); ++} ++ ++int lsm9ds0_mag_input_open(struct input_dev *input) ++{ ++ struct lsm9ds0_status *stat = input_get_drvdata(input); ++ ++ return lsm9ds0_mag_enable(stat); ++} ++ ++void lsm9ds0_mag_input_close(struct input_dev *dev) ++{ ++ struct lsm9ds0_status *stat = input_get_drvdata(dev); ++ ++ lsm9ds0_mag_disable(stat); ++} ++ ++static int lsm9ds0_acc_get_data(struct lsm9ds0_status *stat, int *xyz) ++{ ++ int i, err = -1; ++ u8 acc_data[6]; ++ s32 hw_d[3] = { 0 }; ++ ++ acc_data[0] = (REG_ACC_DATA_ADDR); ++ err = lsm9ds0_i2c_read(stat, acc_data, 6); ++ if (err < 0) ++ return err; ++ ++ hw_d[0] = ((s32)( (s16)((acc_data[1] << 8) | (acc_data[0])))); ++ hw_d[1] = ((s32)( (s16)((acc_data[3] << 8) | (acc_data[2])))); ++ hw_d[2] = ((s32)( (s16)((acc_data[5] << 8) | (acc_data[4])))); ++ ++#ifdef DEBUG ++ pr_debug("%s read x=%X %X(regH regL), x=%d(dec) [ug]\n", ++ LSM9DS0_ACC_DEV_NAME, acc_data[1], acc_data[0], hw_d[0]); ++ pr_debug("%s read y=%X %X(regH regL), y=%d(dec) [ug]\n", ++ LSM9DS0_ACC_DEV_NAME, acc_data[3], acc_data[2], hw_d[1]); ++ pr_debug("%s read z=%X %X(regH regL), z=%d(dec) [ug]\n", ++ LSM9DS0_ACC_DEV_NAME, acc_data[5], acc_data[4], hw_d[2]); ++#endif ++ ++ hw_d[0] = hw_d[0] * stat->sensitivity_acc; ++ hw_d[1] = hw_d[1] * stat->sensitivity_acc; ++ hw_d[2] = hw_d[2] * stat->sensitivity_acc; ++ ++ for (i = 0; i < 3; i++) { ++ xyz[i] = stat->pdata_acc->rot_matrix[0][i] * hw_d[0] + ++ stat->pdata_acc->rot_matrix[1][i] * hw_d[1] + ++ stat->pdata_acc->rot_matrix[2][i] * hw_d[2]; ++ } ++ ++ return err; ++} ++ ++static int lsm9ds0_mag_get_data(struct lsm9ds0_status *stat, int *xyz) ++{ ++ int i, err = -1; ++ u8 mag_data[6]; ++ s32 hw_d[3] = { 0 }; ++ ++ mag_data[0] = (REG_MAG_DATA_ADDR); ++ err = lsm9ds0_i2c_read(stat, mag_data, 6); ++ if (err < 0) ++ return err; ++ ++ hw_d[0] = ((s32)( (s16)((mag_data[1] << 8) | (mag_data[0])))); ++ hw_d[1] = ((s32)( (s16)((mag_data[3] << 8) | (mag_data[2])))); ++ hw_d[2] = ((s32)( (s16)((mag_data[5] << 8) | (mag_data[4])))); ++ ++#ifdef DEBUG ++ pr_debug("%s read x=%X %X(regH regL), x=%d(dec) [ug]\n", ++ LSM9DS0_MAG_DEV_NAME, mag_data[1], mag_data[0], hw_d[0]); ++ pr_debug("%s read x=%X %X(regH regL), x=%d(dec) [ug]\n", ++ LSM9DS0_MAG_DEV_NAME, mag_data[3], mag_data[2], hw_d[1]); ++ pr_debug("%s read x=%X %X(regH regL), x=%d(dec) [ug]\n", ++ LSM9DS0_MAG_DEV_NAME, mag_data[5], mag_data[4], hw_d[2]); ++#endif ++ ++ hw_d[0] = hw_d[0] * stat->sensitivity_mag; ++ hw_d[1] = hw_d[1] * stat->sensitivity_mag; ++ hw_d[2] = hw_d[2] * stat->sensitivity_mag; ++ ++ for (i = 0; i < 3; i++) { ++ xyz[i] = stat->pdata_acc->rot_matrix[0][i] * hw_d[0] + ++ stat->pdata_acc->rot_matrix[1][i] * hw_d[1] + ++ stat->pdata_acc->rot_matrix[2][i] * hw_d[2]; ++ } ++ ++ return err; ++} ++ ++static int lsm9ds0_temp_get_data(struct lsm9ds0_status *stat, ++ int *dec, int *flo) ++{ ++ int err = -1; ++ u8 temp_data[2]; ++ s16 hw_d = 0; ++ ++ temp_data[0] = (REG_TEMP_DATA_ADDR); ++ err = lsm9ds0_i2c_read(stat, temp_data, 2); ++ if (err < 0) ++ return err; ++ ++ hw_d = (s16)((temp_data[1] << 8) | (temp_data[0])); ++ ++ ++#ifdef DEBUG ++ pr_debug("%s read T=%X %X(regH regL), T=%d(dec) [C]\n", ++ LSM9DS0_DEV_NAME, temp_data[1], temp_data[0], hw_d); ++#endif ++ ++ *dec = (int)(hw_d/TEMP_SENSITIVITY) + OFFSET_TEMP; ++ *flo = (((unsigned int)hw_d)%TEMP_SENSITIVITY); ++ ++ return err; ++} ++ ++static void lsm9ds0_acc_report_values(struct lsm9ds0_status *stat, int *xyz) ++{ ++ input_report_abs(stat->input_dev_acc, ABS_X, xyz[0]); ++ input_report_abs(stat->input_dev_acc, ABS_Y, xyz[1]); ++ input_report_abs(stat->input_dev_acc, ABS_Z, xyz[2]); ++ input_sync(stat->input_dev_acc); ++} ++ ++static void lsm9ds0_mag_report_values(struct lsm9ds0_status *stat, int *xyz) ++{ ++ input_report_abs(stat->input_dev_mag, ABS_X, xyz[0]); ++ input_report_abs(stat->input_dev_mag, ABS_Y, xyz[1]); ++ input_report_abs(stat->input_dev_mag, ABS_Z, xyz[2]); ++ input_sync(stat->input_dev_mag); ++} ++ ++static int lsm9ds0_acc_input_init(struct lsm9ds0_status *stat) ++{ ++ int err; ++ ++ stat->input_dev_acc = input_allocate_device(); ++ if (!stat->input_dev_acc) { ++ err = -ENOMEM; ++ dev_err(&stat->client->dev, "accelerometer " ++ "input device allocation failed\n"); ++ goto err0; ++ } ++ ++ stat->input_dev_acc->open = lsm9ds0_acc_input_open; ++ stat->input_dev_acc->close = lsm9ds0_acc_input_close; ++ stat->input_dev_acc->name = LSM9DS0_ACC_DEV_NAME; ++ stat->input_dev_acc->id.bustype = BUS_I2C; ++ stat->input_dev_acc->dev.parent = &stat->client->dev; ++ ++ input_set_drvdata(stat->input_dev_acc, stat); ++ ++ set_bit(EV_ABS, stat->input_dev_acc->evbit); ++ ++ input_set_abs_params(stat->input_dev_acc, ABS_X, ++ -ACC_G_MAX_NEG, ACC_G_MAX_POS, FUZZ, FLAT); ++ input_set_abs_params(stat->input_dev_acc, ABS_Y, ++ -ACC_G_MAX_NEG, ACC_G_MAX_POS, FUZZ, FLAT); ++ input_set_abs_params(stat->input_dev_acc, ABS_Z, ++ -ACC_G_MAX_NEG, ACC_G_MAX_POS, FUZZ, FLAT); ++ ++ err = input_register_device(stat->input_dev_acc); ++ if (err) { ++ dev_err(&stat->client->dev, ++ "unable to register accelerometer input device %s\n", ++ stat->input_dev_acc->name); ++ goto err1; ++ } ++ ++ return 0; ++ ++err1: ++ input_free_device(stat->input_dev_acc); ++err0: ++ return err; ++} ++ ++static int lsm9ds0_mag_input_init(struct lsm9ds0_status *stat) ++{ ++ int err; ++ ++ stat->input_dev_mag = input_allocate_device(); ++ if (!stat->input_dev_mag) { ++ err = -ENOMEM; ++ dev_err(&stat->client->dev, "magnetometer " ++ "input device allocation failed\n"); ++ goto err0; ++ } ++ ++ stat->input_dev_mag->open = lsm9ds0_mag_input_open; ++ stat->input_dev_mag->close = lsm9ds0_mag_input_close; ++ stat->input_dev_mag->name = LSM9DS0_MAG_DEV_NAME; ++ stat->input_dev_mag->id.bustype = BUS_I2C; ++ stat->input_dev_mag->dev.parent = &stat->client->dev; ++ ++ input_set_drvdata(stat->input_dev_mag, stat); ++ ++ set_bit(EV_ABS, stat->input_dev_mag->evbit); ++ ++ input_set_abs_params(stat->input_dev_mag, ABS_X, ++ -MAG_G_MAX_NEG, MAG_G_MAX_POS, FUZZ, FLAT); ++ input_set_abs_params(stat->input_dev_mag, ABS_Y, ++ -MAG_G_MAX_NEG, MAG_G_MAX_POS, FUZZ, FLAT); ++ input_set_abs_params(stat->input_dev_mag, ABS_Z, ++ -MAG_G_MAX_NEG, MAG_G_MAX_POS, FUZZ, FLAT); ++ ++ err = input_register_device(stat->input_dev_mag); ++ if (err) { ++ dev_err(&stat->client->dev, ++ "unable to register magnetometer input device %s\n", ++ stat->input_dev_mag->name); ++ goto err1; ++ } ++ ++ return 0; ++ ++err1: ++ input_free_device(stat->input_dev_mag); ++err0: ++ return err; ++} ++ ++static void lsm9ds0_input_cleanup(struct lsm9ds0_status *stat) ++{ ++ input_unregister_device(stat->input_dev_acc); ++ input_free_device(stat->input_dev_acc); ++ ++ input_unregister_device(stat->input_dev_mag); ++ input_free_device(stat->input_dev_mag); ++} ++ ++static void poll_function_work_acc(struct work_struct *input_work_acc) ++{ ++ struct lsm9ds0_status *stat; ++ int xyz[3] = { 0 }; ++ int err; ++ ++ stat = container_of((struct work_struct *)input_work_acc, ++ struct lsm9ds0_status, input_work_acc); ++ ++ mutex_lock(&stat->lock); ++ err = lsm9ds0_acc_get_data(stat, xyz); ++ if (err < 0) ++ dev_err(&stat->client->dev, "get_accelerometer_data failed\n"); ++ else ++ lsm9ds0_acc_report_values(stat, xyz); ++ ++ mutex_unlock(&stat->lock); ++ hrtimer_start(&stat->hr_timer_acc, stat->ktime_acc, HRTIMER_MODE_REL); ++} ++ ++static void poll_function_work_mag(struct work_struct *input_work_mag) ++{ ++ struct lsm9ds0_status *stat; ++ int xyz[3] = { 0 }; ++ int err; ++ int dec; ++ int flo; ++ ++ stat = container_of((struct work_struct *)input_work_mag, ++ struct lsm9ds0_status, input_work_mag); ++ ++ mutex_lock(&stat->lock); ++ ++ if(atomic_read(&stat->enabled_temp)) { ++ err = lsm9ds0_temp_get_data(stat, &dec, &flo); ++ if (err < 0) ++ dev_err(&stat->client->dev, "get_temperature_data" ++ " failed\n"); ++ else { ++ stat->temp_value_dec = dec; ++ stat->temp_value_flo = flo; ++ } ++ } ++ ++ if(atomic_read(&stat->enabled_mag)) { ++ err = lsm9ds0_mag_get_data(stat, xyz); ++ if (err < 0) ++ dev_err(&stat->client->dev, "get_magnetometer_data" ++ " failed\n"); ++ else ++ lsm9ds0_mag_report_values(stat, xyz); ++ } ++ ++ mutex_unlock(&stat->lock); ++ hrtimer_start(&stat->hr_timer_mag, stat->ktime_mag, HRTIMER_MODE_REL); ++} ++ ++enum hrtimer_restart poll_function_read_acc(struct hrtimer *timer) ++{ ++ struct lsm9ds0_status *stat; ++ ++ ++ stat = container_of((struct hrtimer *)timer, ++ struct lsm9ds0_status, hr_timer_acc); ++ ++ queue_work(lsm9ds0_workqueue, &stat->input_work_acc); ++ return HRTIMER_NORESTART; ++} ++ ++enum hrtimer_restart poll_function_read_mag(struct hrtimer *timer) ++{ ++ struct lsm9ds0_status *stat; ++ ++ ++ stat = container_of((struct hrtimer *)timer, ++ struct lsm9ds0_status, hr_timer_mag); ++ ++ queue_work(lsm9ds0_workqueue, &stat->input_work_mag); ++ return HRTIMER_NORESTART; ++} ++ ++static int lsm9ds0_probe(struct i2c_client *client, ++ const struct i2c_device_id *id) ++{ ++ struct lsm9ds0_status *stat; ++ ++ u32 smbus_func = I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK; ++ ++ int err = -1; ++ dev_info(&client->dev, "probe start.\n"); ++ stat = kzalloc(sizeof(struct lsm9ds0_status), GFP_KERNEL); ++ if (stat == NULL) { ++ err = -ENOMEM; ++ dev_err(&client->dev, ++ "failed to allocate memory for module data: " ++ "%d\n", err); ++ goto exit_check_functionality_failed; ++ } ++ ++ stat->use_smbus = 0; ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { ++ dev_warn(&client->dev, "client not i2c capable\n"); ++ if (i2c_check_functionality(client->adapter, smbus_func)){ ++ stat->use_smbus = 1; ++ dev_warn(&client->dev, "client using SMBUS\n"); ++ } else { ++ err = -ENODEV; ++ dev_err(&client->dev, "client nor SMBUS capable\n"); ++ goto exit_check_functionality_failed; ++ } ++ } ++ ++ if(lsm9ds0_workqueue == 0) ++ lsm9ds0_workqueue = create_workqueue("lsm9ds0_workqueue"); ++ ++ hrtimer_init(&stat->hr_timer_acc, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ stat->hr_timer_acc.function = &poll_function_read_acc; ++ hrtimer_init(&stat->hr_timer_mag, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ stat->hr_timer_mag.function = &poll_function_read_mag; ++ ++ mutex_init(&stat->lock); ++ mutex_lock(&stat->lock); ++ ++ stat->client = client; ++ i2c_set_clientdata(client, stat); ++ ++ stat->pdata_acc = kmalloc(sizeof(*stat->pdata_acc), GFP_KERNEL); ++ stat->pdata_mag = kmalloc(sizeof(*stat->pdata_mag), GFP_KERNEL); ++ if ((stat->pdata_acc == NULL)||(stat->pdata_mag == NULL)) { ++ err = -ENOMEM; ++ dev_err(&client->dev, ++ "failed to allocate memory for pdata: %d\n", err); ++ goto err_mutexunlock; ++ } ++ ++ if (client->dev.platform_data == NULL) { ++ memcpy(stat->pdata_acc, &default_lsm9ds0_acc_pdata, ++ sizeof(*stat->pdata_acc)); ++ memcpy(stat->pdata_mag, &default_lsm9ds0_mag_pdata, ++ sizeof(*stat->pdata_mag)); ++ dev_info(&client->dev, "using default plaform_data for " ++ "accelerometer and magnetometer\n"); ++ } else { ++ struct lsm9ds0_main_platform_data *tmp; ++ tmp = kzalloc(sizeof(struct lsm9ds0_main_platform_data), ++ GFP_KERNEL); ++ if(tmp == NULL) ++ goto exit_kfree_pdata; ++ memcpy(tmp, client->dev.platform_data, sizeof(*tmp)); ++ if(tmp->pdata_acc == NULL) { ++ memcpy(stat->pdata_acc, &default_lsm9ds0_acc_pdata, ++ sizeof(*stat->pdata_acc)); ++ dev_info(&client->dev, "using default plaform_data for " ++ "accelerometer\n"); ++ } else { ++ memcpy(stat->pdata_acc, tmp->pdata_acc, ++ sizeof(*stat->pdata_acc)); ++ } ++ if(tmp->pdata_mag == NULL) { ++ memcpy(stat->pdata_mag, &default_lsm9ds0_mag_pdata, ++ sizeof(*stat->pdata_mag)); ++ dev_info(&client->dev, "using default plaform_data for " ++ "magnetometer\n"); ++ } else { ++ memcpy(stat->pdata_mag, tmp->pdata_mag, ++ sizeof(*stat->pdata_mag)); ++ } ++ kfree(tmp); ++ } ++ ++ err = lsm9ds0_acc_validate_pdata(stat); ++ if (err < 0) { ++ dev_err(&client->dev, "failed to validate platform data for " ++ "accelerometer \n"); ++ goto exit_kfree_pdata; ++ } ++ ++ err = lsm9ds0_mag_validate_pdata(stat); ++ if (err < 0) { ++ dev_err(&client->dev, "failed to validate platform data for " ++ "magnetometer\n"); ++ goto exit_kfree_pdata; ++ } ++ ++ if (stat->pdata_acc->init) { ++ err = stat->pdata_acc->init(); ++ if (err < 0) { ++ dev_err(&client->dev, "accelerometer init failed: " ++ "%d\n", err); ++ goto err_pdata_acc_init; ++ } ++ } ++ if (stat->pdata_mag->init) { ++ err = stat->pdata_mag->init(); ++ if (err < 0) { ++ dev_err(&client->dev, "magnetometer init failed: " ++ "%d\n", err); ++ goto err_pdata_mag_init; ++ } ++ } ++ ++ if(stat->pdata_acc->gpio_int1 >= 0) { ++ if (!gpio_is_valid(stat->pdata_acc->gpio_int1)) { ++ dev_err(&client->dev, "The requested GPIO [%d] is not " ++ "available\n", stat->pdata_acc->gpio_int1); ++ err = -EINVAL; ++ goto err_gpio1_valid; ++ } ++ ++ err = gpio_request(stat->pdata_acc->gpio_int1, ++ "INTERRUPT_PIN1_LSM9DS0"); ++ if(err < 0) { ++ dev_err(&client->dev, "Unable to request GPIO [%d].\n", ++ stat->pdata_acc->gpio_int1); ++ err = -EINVAL; ++ goto err_gpio1_valid; ++ } ++ gpio_direction_input(stat->pdata_acc->gpio_int1); ++ stat->irq1 = gpio_to_irq(stat->pdata_acc->gpio_int1); ++ if(stat->irq1 < 0) { ++ dev_err(&client->dev, "GPIO [%d] cannot be used as " ++ "interrupt.\n", stat->pdata_acc->gpio_int1); ++ err = -EINVAL; ++ goto err_gpio1_irq; ++ } ++ pr_info("%s: %s has set irq1 to irq: %d, mapped on gpio:%d\n", ++ LSM9DS0_DEV_NAME, __func__, stat->irq1, ++ stat->pdata_acc->gpio_int1); ++ } ++ ++ if(stat->pdata_acc->gpio_int2 >= 0) { ++ if (!gpio_is_valid(stat->pdata_acc->gpio_int2)) { ++ dev_err(&client->dev, "The requested GPIO [%d] is not " ++ "available\n", stat->pdata_acc->gpio_int2); ++ err = -EINVAL; ++ goto err_gpio2_valid; ++ } ++ ++ err = gpio_request(stat->pdata_acc->gpio_int2, ++ "INTERRUPT_PIN2_LSM9DS0"); ++ if(err < 0) { ++ dev_err(&client->dev, "Unable to request GPIO [%d].\n", ++ stat->pdata_acc->gpio_int2); ++ err = -EINVAL; ++ goto err_gpio2_valid; ++ } ++ gpio_direction_input(stat->pdata_acc->gpio_int2); ++ stat->irq2 = gpio_to_irq(stat->pdata_acc->gpio_int2); ++ if(stat->irq2 < 0) { ++ dev_err(&client->dev, "GPIO [%d] cannot be used as " ++ "interrupt.\n", stat->pdata_acc->gpio_int2); ++ err = -EINVAL; ++ goto err_gpio2_irq; ++ } ++ pr_info("%s: %s has set irq2 to irq: %d, " ++ "mapped on gpio:%d\n", ++ LSM9DS0_DEV_NAME, __func__, stat->irq2, ++ stat->pdata_acc->gpio_int2); ++ } ++ ++ err = lsm9ds0_hw_init(stat); ++ if (err < 0) { ++ dev_err(&client->dev, "hw init failed: %d\n", err); ++ goto err_hw_init; ++ } ++ ++ err = lsm9ds0_acc_device_power_on(stat); ++ if (err < 0) { ++ dev_err(&client->dev, "accelerometer power on failed: " ++ "%d\n", err); ++ goto err_pdata_init; ++ } ++ err = lsm9ds0_mag_device_power_on(stat); ++ if (err < 0) { ++ dev_err(&client->dev, "magnetometer power on failed: " ++ "%d\n", err); ++ goto err_pdata_init; ++ } ++ ++ err = lsm9ds0_acc_update_fs_range(stat, stat->pdata_acc->fs_range); ++ if (err < 0) { ++ dev_err(&client->dev, "update_fs_range on accelerometer " ++ "failed\n"); ++ goto err_power_off_acc; ++ } ++ ++ err = lsm9ds0_mag_update_fs_range(stat, stat->pdata_mag->fs_range); ++ if (err < 0) { ++ dev_err(&client->dev, "update_fs_range on magnetometer " ++ "failed\n"); ++ goto err_power_off_mag; ++ } ++ ++ err = lsm9ds0_acc_update_odr(stat, stat->pdata_acc->poll_interval); ++ if (err < 0) { ++ dev_err(&client->dev, "update_odr on accelerometer failed\n"); ++ goto err_power_off; ++ } ++ ++ err = lsm9ds0_mag_update_odr(stat, stat->pdata_mag->poll_interval); ++ if (err < 0) { ++ dev_err(&client->dev, "update_odr on magnetometer failed\n"); ++ goto err_power_off; ++ } ++ ++ err = lsm9ds0_acc_update_filter(stat, ++ stat->pdata_acc->aa_filter_bandwidth); ++ if (err < 0) { ++ dev_err(&client->dev, "update_filter on accelerometer " ++ "failed\n"); ++ goto err_power_off; ++ } ++ ++ err = lsm9ds0_acc_input_init(stat); ++ if (err < 0) { ++ dev_err(&client->dev, "accelerometer input init failed\n"); ++ goto err_power_off; ++ } ++ ++ err = lsm9ds0_mag_input_init(stat); ++ if (err < 0) { ++ dev_err(&client->dev, "magnetometer input init failed\n"); ++ goto err_power_off; ++ } ++ ++ err = create_sysfs_interfaces(&client->dev); ++ if (err < 0) { ++ dev_err(&client->dev, ++ "device LSM9DS0_DEV_NAME sysfs register failed\n"); ++ goto err_input_cleanup; ++ } ++ ++ lsm9ds0_acc_device_power_off(stat); ++ lsm9ds0_mag_device_power_off(stat); ++ ++ if(stat->pdata_acc->gpio_int1 >= 0){ ++ INIT_WORK(&stat->irq1_work, lsm9ds0_irq1_work_func); ++ stat->irq1_work_queue = ++ create_singlethread_workqueue("lsm9ds0_wq1"); ++ if (!stat->irq1_work_queue) { ++ err = -ENOMEM; ++ dev_err(&client->dev, ++ "cannot create work queue1: %d\n", err); ++ goto err_remove_sysfs_int; ++ } ++ err = request_irq(stat->irq1, lsm9ds0_isr1, ++ IRQF_TRIGGER_RISING, "lsm9ds0_irq1", stat); ++ if (err < 0) { ++ dev_err(&client->dev, "request irq1 failed: %d\n", err); ++ goto err_destoyworkqueue1; ++ } ++ disable_irq_nosync(stat->irq1); ++ } ++ ++ if(stat->pdata_acc->gpio_int2 >= 0){ ++ INIT_WORK(&stat->irq2_work, lsm9ds0_irq2_work_func); ++ stat->irq2_work_queue = ++ create_singlethread_workqueue("lsm9ds0_wq2"); ++ if (!stat->irq2_work_queue) { ++ err = -ENOMEM; ++ dev_err(&client->dev, ++ "cannot create work queue2: %d\n", err); ++ goto err_free_irq1; ++ } ++ err = request_irq(stat->irq2, lsm9ds0_isr2, ++ IRQF_TRIGGER_RISING, "lsm9ds0_irq2", stat); ++ if (err < 0) { ++ dev_err(&client->dev, "request irq2 failed: %d\n", err); ++ goto err_destoyworkqueue2; ++ } ++ disable_irq_nosync(stat->irq2); ++ } ++ ++ INIT_WORK(&stat->input_work_acc, poll_function_work_acc); ++ INIT_WORK(&stat->input_work_mag, poll_function_work_mag); ++ ++ mutex_unlock(&stat->lock); ++ dev_info(&client->dev, "%s: probed\n", LSM9DS0_DEV_NAME); ++ return 0; ++ ++err_destoyworkqueue2: ++ destroy_workqueue(stat->irq2_work_queue); ++err_free_irq1: ++ free_irq(stat->irq1, stat); ++err_destoyworkqueue1: ++ destroy_workqueue(stat->irq1_work_queue); ++err_remove_sysfs_int: ++ remove_sysfs_interfaces(&client->dev); ++err_input_cleanup: ++ lsm9ds0_input_cleanup(stat); ++err_power_off: ++err_power_off_mag: ++ lsm9ds0_mag_device_power_off(stat); ++err_power_off_acc: ++ lsm9ds0_acc_device_power_off(stat); ++ kfree(stat->interrupt); ++err_hw_init: ++err_gpio2_irq: ++ gpio_free(stat->pdata_acc->gpio_int2); ++err_gpio2_valid: ++err_gpio1_irq: ++ gpio_free(stat->pdata_acc->gpio_int1); ++err_gpio1_valid: ++err_pdata_init: ++err_pdata_mag_init: ++ if (stat->pdata_mag->exit) ++ stat->pdata_mag->exit(); ++err_pdata_acc_init: ++ if (stat->pdata_acc->exit) ++ stat->pdata_acc->exit(); ++exit_kfree_pdata: ++ kfree(stat->pdata_acc); ++ kfree(stat->pdata_mag); ++err_mutexunlock: ++ mutex_unlock(&stat->lock); ++ kfree(stat); ++ if(!lsm9ds0_workqueue) { ++ flush_workqueue(lsm9ds0_workqueue); ++ destroy_workqueue(lsm9ds0_workqueue); ++ } ++exit_check_functionality_failed: ++ pr_err("%s: Driver Init failed\n", LSM9DS0_DEV_NAME); ++ return err; ++} ++ ++static int lsm9ds0_remove(struct i2c_client *client) ++{ ++ struct lsm9ds0_status *stat = i2c_get_clientdata(client); ++ ++ lsm9ds0_acc_disable(stat); ++ lsm9ds0_mag_disable(stat); ++ lsm9ds0_temperature_disable(stat); ++ ++ if(stat->pdata_acc->gpio_int1 >= 0) { ++ free_irq(stat->irq1, stat); ++ gpio_free(stat->pdata_acc->gpio_int1); ++ destroy_workqueue(stat->irq1_work_queue); ++ } ++ ++ if(stat->pdata_acc->gpio_int2 >= 0) { ++ free_irq(stat->irq2, stat); ++ gpio_free(stat->pdata_acc->gpio_int2); ++ destroy_workqueue(stat->irq2_work_queue); ++ } ++ ++ lsm9ds0_acc_input_cleanup(stat); ++ lsm9ds0_mag_input_cleanup(stat); ++ ++ remove_sysfs_interfaces(&client->dev); ++ ++ if (stat->pdata_acc->exit) ++ stat->pdata_acc->exit(); ++ ++ if (stat->pdata_mag->exit) ++ stat->pdata_mag->exit(); ++ ++ if((stat->pdata_acc->gpio_int1 >= 0)|| ++ (stat->pdata_acc->gpio_int2 >= 0)) { ++ kfree(stat->interrupt); ++ } ++ ++ if(!lsm9ds0_workqueue) { ++ flush_workqueue(lsm9ds0_workqueue); ++ destroy_workqueue(lsm9ds0_workqueue); ++ } ++ ++ kfree(stat->pdata_acc); ++ kfree(stat->pdata_mag); ++ kfree(stat); ++ return 0; ++} ++ ++static const struct i2c_device_id lsm9ds0_id[] = { ++ { LSM9DS0_DEV_NAME, 0 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(i2c, lsm9ds0_id); ++ ++static const struct of_device_id lsm9ds0_of_match[] = { ++ { .compatible = "st,"LSM9DS0_DEV_NAME, }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, lsm9ds0_of_match); ++ ++static struct i2c_driver lsm9ds0_i2c_driver = { ++ .driver = { ++ .owner = THIS_MODULE, ++ .name = LSM9DS0_DEV_NAME, ++ .of_match_table = lsm9ds0_of_match, ++ }, ++ .probe = lsm9ds0_probe, ++ .remove = lsm9ds0_remove, ++ .id_table = lsm9ds0_id, ++}; ++ ++module_i2c_driver(lsm9ds0_i2c_driver); ++ ++ ++MODULE_DESCRIPTION("lsm9ds0 accelerometer and magnetometer driver"); ++MODULE_AUTHOR("Matteo Dameno, Denis Ciocca, STMicroelectronics"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/misc/lsm9ds0_gyr.c b/drivers/input/misc/lsm9ds0_gyr.c +new file mode 100644 +index 0000000..360f85c +--- /dev/null ++++ b/drivers/input/misc/lsm9ds0_gyr.c +@@ -0,0 +1,1726 @@ ++/******************** (C) COPYRIGHT 2012 STMicroelectronics ******************** ++* ++* File Name : lsm9ds0_gyr_sysfs.c ++* Authors : MEMS Motion Sensors Products Div- Application Team ++* : Matteo Dameno (matteo.dameno@st.com) ++* : Denis Ciocca (denis.ciocca@st.com) ++* : Both authors are willing to be considered the contact ++* : and update points for the driver. ++* Version : V 1.2 sysfs ++* Date : 2012/Jul/10 ++* Description : LSM9DS0 digital output gyroscope sensor API ++* ++******************************************************************************** ++* ++* This program is free software; you can redistribute it and/or modify ++* it under the terms of the GNU General Public License version 2 as ++* published by the Free Software Foundation. ++* ++* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES ++* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE ++* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. ++* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, ++* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE ++* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING ++* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. ++* ++******************************************************************************** ++* REVISON HISTORY ++* ++* VERSION | DATE | AUTHORS | DESCRIPTION ++* 1.0 | 2010/May/02 | Carmine Iascone | First Release ++* 1.1.3 | 2011/Jun/24 | Matteo Dameno | Corrects ODR Bug ++* 1.1.4 | 2011/Sep/02 | Matteo Dameno | SMB Bus Mng, ++* | | | forces BDU setting ++* 1.1.5 | 2011/Sep/24 | Matteo Dameno | Introduces FIFO Feat. ++* 1.1.5.2 | 2011/Nov/11 | Matteo Dameno | enable gpio_int to be ++* | | | passed as parameter at ++* | | | module loading time; ++* | | | corrects polling ++* | | | bug at end of probing; ++* 1.1.5.3 | 2011/Dec/20 | Matteo Dameno | corrects error in ++* | | | I2C SADROOT; Modifies ++* | | | resume suspend func. ++* 1.1.5.4 | 2012/Jan/09 | Matteo Dameno | moved under input/misc; ++* 1.1.5.5 | 2012/Mar/30 | Matteo Dameno | moved watermark, use_smbus, ++* | | | fifomode @ struct foo_status ++* | | | sysfs range input format ++* | | | changed to decimal ++* 1.2 | 2012/Jul/10 | Denis Ciocca | input_poll_dev removal ++* 1.2.1 | 2012/Jul/10 | Denis Ciocca | added high resolution timers ++*******************************************************************************/ ++ ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/i2c.h> ++#include <linux/mutex.h> ++#include <linux/input.h> ++#include <linux/interrupt.h> ++#include <linux/gpio.h> ++#include <linux/slab.h> ++#include <linux/moduleparam.h> ++#include <linux/kernel.h> ++#include <linux/stat.h> ++ ++ ++#include <linux/input/lsm9ds0.h> ++/* #include "lsm9ds0.h" */ ++ ++/* Maximum polled-device-reported rot speed value value in dps */ ++#define FS_MAX 32768 ++#define MS_TO_NS(x) (x*1000000L) ++ ++/* lsm9ds0 gyroscope registers */ ++#define WHO_AM_I (0x0F) ++ ++#define SENSITIVITY_250 8750 /* udps/LSB */ ++#define SENSITIVITY_500 17500 /* udps/LSB */ ++#define SENSITIVITY_2000 70000 /* udps/LSB */ ++ ++#define CTRL_REG1 (0x20) /* CTRL REG1 */ ++#define CTRL_REG2 (0x21) /* CTRL REG2 */ ++#define CTRL_REG3 (0x22) /* CTRL_REG3 */ ++#define CTRL_REG4 (0x23) /* CTRL_REG4 */ ++#define CTRL_REG5 (0x24) /* CTRL_REG5 */ ++#define REFERENCE (0x25) /* REFERENCE REG */ ++#define FIFO_CTRL_REG (0x2E) /* FIFO CONTROL REGISTER */ ++#define FIFO_SRC_REG (0x2F) /* FIFO SOURCE REGISTER */ ++#define OUT_X_L (0x28) /* 1st AXIS OUT REG of 6 */ ++ ++#define AXISDATA_REG OUT_X_L ++ ++/* CTRL_REG1 */ ++#define ALL_ZEROES (0x00) ++#define PM_OFF (0x00) ++#define PM_NORMAL (0x08) ++#define ENABLE_ALL_AXES (0x07) ++#define ENABLE_NO_AXES (0x00) ++#define BW00 (0x00) ++#define BW01 (0x10) ++#define BW10 (0x20) ++#define BW11 (0x30) ++#define ODR095 (0x00) /* ODR = 95Hz */ ++#define ODR190 (0x40) /* ODR = 190Hz */ ++#define ODR380 (0x80) /* ODR = 380Hz */ ++#define ODR760 (0xC0) /* ODR = 760Hz */ ++ ++/* CTRL_REG3 bits */ ++#define I2_DRDY (0x08) ++#define I2_WTM (0x04) ++#define I2_OVRUN (0x02) ++#define I2_EMPTY (0x01) ++#define I2_NONE (0x00) ++#define I2_MASK (0x0F) ++ ++/* CTRL_REG4 bits */ ++#define FS_MASK (0x30) ++#define BDU_ENABLE (0x80) ++ ++/* CTRL_REG5 bits */ ++#define FIFO_ENABLE (0x40) ++#define HPF_ENALBE (0x11) ++ ++/* FIFO_CTRL_REG bits */ ++#define FIFO_MODE_MASK (0xE0) ++#define FIFO_MODE_BYPASS (0x00) ++#define FIFO_MODE_FIFO (0x20) ++#define FIFO_MODE_STREAM (0x40) ++#define FIFO_MODE_STR2FIFO (0x60) ++#define FIFO_MODE_BYPASS2STR (0x80) ++#define FIFO_WATERMARK_MASK (0x1F) ++ ++#define FIFO_STORED_DATA_MASK (0x1F) ++ ++#define I2C_AUTO_INCREMENT (0x80) ++ ++/* RESUME STATE INDICES */ ++#define RES_CTRL_REG1 0 ++#define RES_CTRL_REG2 1 ++#define RES_CTRL_REG3 2 ++#define RES_CTRL_REG4 3 ++#define RES_CTRL_REG5 4 ++#define RES_FIFO_CTRL_REG 5 ++#define RESUME_ENTRIES 6 ++ ++ ++/* #define DEBUG 1 */ ++ ++/** Registers Contents */ ++#define WHOAMI_LSM9DS0_GYR (0xD4) /* Expected content for WAI register*/ ++ ++static int int1_gpio = LSM9DS0_GYR_DEFAULT_INT1_GPIO; ++static int int2_gpio = LSM9DS0_GYR_DEFAULT_INT2_GPIO; ++/* module_param(int1_gpio, int, S_IRUGO); */ ++module_param(int2_gpio, int, S_IRUGO); ++ ++/* ++ * LSM9DS0 gyroscope data ++ * brief structure containing gyroscope values for yaw, pitch and roll in ++ * s32 ++ */ ++ ++struct lsm9ds0_gyr_triple { ++ s32 x, /* x-axis angular rate data. */ ++ y, /* y-axis angluar rate data. */ ++ z; /* z-axis angular rate data. */ ++}; ++ ++struct output_rate { ++ int poll_rate_ms; ++ u8 mask; ++}; ++ ++static const struct output_rate odr_table[] = { ++ ++ { 2, ODR760|BW10}, ++ { 3, ODR380|BW01}, ++ { 6, ODR190|BW00}, ++ { 11, ODR095|BW00}, ++}; ++ ++static struct lsm9ds0_gyr_platform_data default_lsm9ds0_gyr_pdata = { ++ .fs_range = LSM9DS0_GYR_FS_250DPS, ++ .axis_map_x = 0, ++ .axis_map_y = 1, ++ .axis_map_z = 2, ++ .negate_x = 0, ++ .negate_y = 0, ++ .negate_z = 0, ++ ++ .poll_interval = 100, ++ .min_interval = LSM9DS0_GYR_MIN_POLL_PERIOD_MS, /* 2ms */ ++ ++ .gpio_int1 = LSM9DS0_GYR_DEFAULT_INT1_GPIO, ++ .gpio_int2 = LSM9DS0_GYR_DEFAULT_INT2_GPIO, /* int for fifo */ ++ ++}; ++ ++struct workqueue_struct *lsm9ds0_gyr_workqueue = 0; ++ ++struct lsm9ds0_gyr_status { ++ struct i2c_client *client; ++ struct lsm9ds0_gyr_platform_data *pdata; ++ ++ struct mutex lock; ++ ++ struct input_dev *input_dev; ++ ++ int hw_initialized; ++ atomic_t enabled; ++ int use_smbus; ++ ++ u8 reg_addr; ++ u8 resume_state[RESUME_ENTRIES]; ++ ++ u32 sensitivity; ++ ++ /* interrupt related */ ++ int irq2; ++ struct work_struct irq2_work; ++ struct workqueue_struct *irq2_work_queue; ++ ++ bool polling_enabled; ++ /* fifo related */ ++ u8 watermark; ++ u8 fifomode; ++ ++ struct hrtimer hr_timer; ++ ktime_t ktime; ++ struct work_struct polling_task; ++}; ++ ++ ++static int lsm9ds0_gyr_i2c_read(struct lsm9ds0_gyr_status *stat, u8 *buf, ++ int len) ++{ ++ int ret; ++ u8 reg = buf[0]; ++ u8 cmd = reg; ++ ++/* ++ if (len > sizeof(buf)) ++ dev_err(&stat->client->dev, ++ "read error insufficient buffer length: " ++ "len:%d, buf size=%d\n", ++ len, sizeof(buf)); ++*/ ++ if (len > 1) ++ cmd = (I2C_AUTO_INCREMENT | reg); ++ if (stat->use_smbus) { ++ if (len == 1) { ++ ret = i2c_smbus_read_byte_data(stat->client, cmd); ++ buf[0] = ret & 0xff; ++#ifdef DEBUG ++ dev_warn(&stat->client->dev, ++ "i2c_smbus_read_byte_data: ret=0x%02x, len:%d ," ++ "command=0x%02x, buf[0]=0x%02x\n", ++ ret, len, cmd , buf[0]); ++#endif ++ } else if (len > 1) { ++ ret = i2c_smbus_read_i2c_block_data(stat->client, ++ cmd, len, buf); ++#ifdef DEBUG ++ dev_warn(&stat->client->dev, ++ "i2c_smbus_read_i2c_block_data: ret:%d len:%d, " ++ "command=0x%02x, ", ++ ret, len, cmd); ++ unsigned int ii; ++ for (ii = 0; ii < len; ii++) ++ printk(KERN_DEBUG "buf[%d]=0x%02x,", ++ ii, buf[ii]); ++ ++ printk("\n"); ++#endif ++ } else ++ ret = -1; ++ ++ if (ret < 0) { ++ dev_err(&stat->client->dev, ++ "read transfer error: len:%d, command=0x%02x\n", ++ len, cmd); ++ return 0; /* failure */ ++ } ++ return len; /* success */ ++ } ++ ++ ret = i2c_master_send(stat->client, &cmd, sizeof(cmd)); ++ if (ret != sizeof(cmd)) ++ return ret; ++ ++ return i2c_master_recv(stat->client, buf, len); ++} ++ ++static int lsm9ds0_gyr_i2c_write(struct lsm9ds0_gyr_status *stat, u8 *buf, ++ int len) ++{ ++ int ret; ++ u8 reg, value; ++ ++ if (len > 1) ++ buf[0] = (I2C_AUTO_INCREMENT | buf[0]); ++ ++ reg = buf[0]; ++ value = buf[1]; ++ ++ if (stat->use_smbus) { ++ if (len == 1) { ++ ret = i2c_smbus_write_byte_data(stat->client, ++ reg, value); ++#ifdef DEBUG ++ dev_warn(&stat->client->dev, ++ "i2c_smbus_write_byte_data: ret=%d, len:%d, " ++ "command=0x%02x, value=0x%02x\n", ++ ret, len, reg , value); ++#endif ++ return ret; ++ } else if (len > 1) { ++ ret = i2c_smbus_write_i2c_block_data(stat->client, ++ reg, len, buf + 1); ++#ifdef DEBUG ++ dev_warn(&stat->client->dev, ++ "i2c_smbus_write_i2c_block_data: ret=%d, " ++ "len:%d, command=0x%02x, ", ++ ret, len, reg); ++ unsigned int ii; ++ for (ii = 0; ii < (len + 1); ii++) ++ printk(KERN_DEBUG "value[%d]=0x%02x,", ++ ii, buf[ii]); ++ ++ printk("\n"); ++#endif ++ return ret; ++ } ++ } ++ ++ ret = i2c_master_send(stat->client, buf, len+1); ++ return (ret == len+1) ? 0 : ret; ++} ++ ++ ++static int lsm9ds0_gyr_register_write(struct lsm9ds0_gyr_status *stat, ++ u8 *buf, u8 reg_address, u8 new_value) ++{ ++ int err; ++ ++ /* Sets configuration register at reg_address ++ * NOTE: this is a straight overwrite */ ++ buf[0] = reg_address; ++ buf[1] = new_value; ++ err = lsm9ds0_gyr_i2c_write(stat, buf, 1); ++ if (err < 0) ++ return err; ++ ++ return err; ++} ++ ++static int lsm9ds0_gyr_register_read(struct lsm9ds0_gyr_status *stat, ++ u8 *buf, u8 reg_address) ++{ ++ ++ int err = -1; ++ buf[0] = (reg_address); ++ err = lsm9ds0_gyr_i2c_read(stat, buf, 1); ++ return err; ++} ++ ++static int lsm9ds0_gyr_register_update(struct lsm9ds0_gyr_status *stat, ++ u8 *buf, u8 reg_address, u8 mask, u8 new_bit_values) ++{ ++ int err = -1; ++ u8 init_val; ++ u8 updated_val; ++ err = lsm9ds0_gyr_register_read(stat, buf, reg_address); ++ if (!(err < 0)) { ++ init_val = buf[0]; ++ updated_val = ((mask & new_bit_values) | ((~mask) & init_val)); ++ err = lsm9ds0_gyr_register_write(stat, buf, reg_address, ++ updated_val); ++ } ++ return err; ++} ++ ++ ++static int lsm9ds0_gyr_update_watermark(struct lsm9ds0_gyr_status *stat, ++ u8 watermark) ++{ ++ int res = 0; ++ u8 buf[2]; ++ u8 new_value; ++ ++ mutex_lock(&stat->lock); ++ new_value = (watermark % 0x20); ++ res = lsm9ds0_gyr_register_update(stat, buf, FIFO_CTRL_REG, ++ FIFO_WATERMARK_MASK, new_value); ++ if (res < 0) { ++ dev_err(&stat->client->dev, "failed to update watermark\n"); ++ return res; ++ } ++ dev_dbg(&stat->client->dev, "%s new_value:0x%02x,watermark:0x%02x\n", ++ __func__, new_value, watermark); ++ ++ stat->resume_state[RES_FIFO_CTRL_REG] = ++ ((FIFO_WATERMARK_MASK & new_value) | ++ (~FIFO_WATERMARK_MASK & ++ stat->resume_state[RES_FIFO_CTRL_REG])); ++ stat->watermark = new_value; ++ mutex_unlock(&stat->lock); ++ return res; ++} ++ ++static int lsm9ds0_gyr_update_fifomode(struct lsm9ds0_gyr_status *stat, ++ u8 fifomode) ++{ ++ int res; ++ u8 buf[2]; ++ u8 new_value; ++ ++ new_value = fifomode; ++ res = lsm9ds0_gyr_register_update(stat, buf, FIFO_CTRL_REG, ++ FIFO_MODE_MASK, new_value); ++ if (res < 0) { ++ dev_err(&stat->client->dev, "failed to update fifoMode\n"); ++ return res; ++ } ++ /* ++ dev_dbg(&stat->client->dev, "new_value:0x%02x,prev fifomode:0x%02x\n", ++ __func__, new_value, stat->fifomode); ++ */ ++ stat->resume_state[RES_FIFO_CTRL_REG] = ++ ((FIFO_MODE_MASK & new_value) | ++ (~FIFO_MODE_MASK & ++ stat->resume_state[RES_FIFO_CTRL_REG])); ++ stat->fifomode = new_value; ++ ++ return res; ++} ++ ++static int lsm9ds0_gyr_fifo_reset(struct lsm9ds0_gyr_status *stat) ++{ ++ u8 oldmode; ++ int res; ++ ++ oldmode = stat->fifomode; ++ res = lsm9ds0_gyr_update_fifomode(stat, FIFO_MODE_BYPASS); ++ if (res < 0) ++ return res; ++ res = lsm9ds0_gyr_update_fifomode(stat, oldmode); ++ if (res >= 0) ++ dev_dbg(&stat->client->dev, "%s fifo reset to: 0x%02x\n", ++ __func__, oldmode); ++ ++ return res; ++} ++ ++static int lsm9ds0_gyr_fifo_hwenable(struct lsm9ds0_gyr_status *stat, ++ u8 enable) ++{ ++ int res; ++ u8 buf[2]; ++ u8 set = 0x00; ++ if (enable) ++ set = FIFO_ENABLE; ++ ++ res = lsm9ds0_gyr_register_update(stat, buf, CTRL_REG5, ++ FIFO_ENABLE, set); ++ if (res < 0) { ++ dev_err(&stat->client->dev, "fifo_hw switch to:0x%02x failed\n", ++ set); ++ return res; ++ } ++ stat->resume_state[RES_CTRL_REG5] = ++ ((FIFO_ENABLE & set) | ++ (~FIFO_ENABLE & stat->resume_state[RES_CTRL_REG5])); ++ dev_dbg(&stat->client->dev, "%s set to:0x%02x\n", __func__, set); ++ return res; ++} ++ ++static int lsm9ds0_gyr_manage_int2settings(struct lsm9ds0_gyr_status *stat, ++ u8 fifomode) ++{ ++ int res; ++ u8 buf[2]; ++ bool enable_fifo_hw; ++ bool recognized_mode = false; ++ u8 int2bits = I2_NONE; ++/* ++ if (stat->polling_enabled) { ++ fifomode = FIFO_MODE_BYPASS; ++ dbg_warn(&stat->client->dev, "in polling mode, fifo mode forced" ++ " to BYPASS mode\n"); ++ } ++*/ ++ ++ ++ switch (fifomode) { ++ case FIFO_MODE_FIFO: ++ recognized_mode = true; ++ ++ if (stat->polling_enabled) { ++ int2bits = I2_NONE; ++ enable_fifo_hw = false; ++ } else { ++ int2bits = (I2_WTM | I2_OVRUN); ++ enable_fifo_hw = true; ++ } ++ res = lsm9ds0_gyr_register_update(stat, buf, CTRL_REG3, ++ I2_MASK, int2bits); ++ if (res < 0) { ++ dev_err(&stat->client->dev, "%s : failed to update " ++ "CTRL_REG3:0x%02x\n", ++ __func__, fifomode); ++ goto err_mutex_unlock; ++ } ++ stat->resume_state[RES_CTRL_REG3] = ++ ((I2_MASK & int2bits) | ++ (~(I2_MASK) & stat->resume_state[RES_CTRL_REG3])); ++ /* enable_fifo_hw = true; */ ++ break; ++ ++ case FIFO_MODE_BYPASS: ++ recognized_mode = true; ++ ++ if (stat->polling_enabled) ++ int2bits = I2_NONE; ++ else ++ int2bits = I2_DRDY; ++ ++ res = lsm9ds0_gyr_register_update(stat, buf, CTRL_REG3, ++ I2_MASK, int2bits); ++ if (res < 0) { ++ dev_err(&stat->client->dev, "%s : failed to update" ++ " to CTRL_REG3:0x%02x\n", ++ __func__, fifomode); ++ goto err_mutex_unlock; ++ } ++ stat->resume_state[RES_CTRL_REG3] = ++ ((I2_MASK & int2bits) | ++ (~I2_MASK & stat->resume_state[RES_CTRL_REG3])); ++ enable_fifo_hw = false; ++ break; ++ ++ default: ++ recognized_mode = false; ++ res = lsm9ds0_gyr_register_update(stat, buf, CTRL_REG3, ++ I2_MASK, I2_NONE); ++ if (res < 0) { ++ dev_err(&stat->client->dev, "%s : failed to update " ++ "CTRL_REG3:0x%02x\n", ++ __func__, fifomode); ++ goto err_mutex_unlock; ++ } ++ enable_fifo_hw = false; ++ stat->resume_state[RES_CTRL_REG3] = ++ ((I2_MASK & 0x00) | ++ (~I2_MASK & stat->resume_state[RES_CTRL_REG3])); ++ break; ++ ++ } ++ if (recognized_mode) { ++ res = lsm9ds0_gyr_update_fifomode(stat, fifomode); ++ if (res < 0) { ++ dev_err(&stat->client->dev, "%s : failed to " ++ "set fifoMode\n", __func__); ++ goto err_mutex_unlock; ++ } ++ } ++ res = lsm9ds0_gyr_fifo_hwenable(stat, enable_fifo_hw); ++ ++err_mutex_unlock: ++ ++ return res; ++} ++ ++ ++static int lsm9ds0_gyr_update_fs_range(struct lsm9ds0_gyr_status *stat, ++ u8 new_fs) ++{ ++ int res ; ++ u8 buf[2]; ++ ++ u32 sensitivity; ++ ++ switch(new_fs) { ++ case LSM9DS0_GYR_FS_250DPS: ++ sensitivity = SENSITIVITY_250; ++ break; ++ case LSM9DS0_GYR_FS_500DPS: ++ sensitivity = SENSITIVITY_500; ++ break; ++ case LSM9DS0_GYR_FS_2000DPS: ++ sensitivity = SENSITIVITY_2000; ++ break; ++ default: ++ dev_err(&stat->client->dev, "invalid g range " ++ "requested: %u\n", new_fs); ++ return -EINVAL; ++ } ++ ++ ++ buf[0] = CTRL_REG4; ++ ++ res = lsm9ds0_gyr_register_update(stat, buf, CTRL_REG4, ++ FS_MASK, new_fs); ++ ++ if (res < 0) { ++ dev_err(&stat->client->dev, "%s : failed to update fs:0x%02x\n", ++ __func__, new_fs); ++ return res; ++ } ++ stat->resume_state[RES_CTRL_REG4] = ++ ((FS_MASK & new_fs) | ++ (~FS_MASK & stat->resume_state[RES_CTRL_REG4])); ++ ++ stat->sensitivity = sensitivity; ++ return res; ++} ++ ++ ++static int lsm9ds0_gyr_update_odr(struct lsm9ds0_gyr_status *stat, ++ unsigned int poll_interval_ms) ++{ ++ int err = -1; ++ int i; ++ u8 config[2]; ++ ++ for (i = ARRAY_SIZE(odr_table) - 1; i >= 0; i--) { ++ if ((odr_table[i].poll_rate_ms <= poll_interval_ms) || (i == 0)) ++ break; ++ } ++ ++ config[1] = odr_table[i].mask; ++ config[1] |= (ENABLE_ALL_AXES + PM_NORMAL); ++ ++ /* If device is currently enabled, we need to write new ++ * configuration out to it */ ++ if (atomic_read(&stat->enabled)) { ++ config[0] = CTRL_REG1; ++ err = lsm9ds0_gyr_i2c_write(stat, config, 1); ++ if (err < 0) ++ return err; ++ stat->resume_state[RES_CTRL_REG1] = config[1]; ++ stat->ktime = ktime_set(0, MS_TO_NS(poll_interval_ms)); ++ } ++ ++ return err; ++} ++ ++/* gyroscope data readout */ ++static int lsm9ds0_gyr_get_data(struct lsm9ds0_gyr_status *stat, ++ struct lsm9ds0_gyr_triple *data) ++{ ++ int err; ++ unsigned char gyro_out[6]; ++ /* y,p,r hardware data */ ++ s32 hw_d[3] = { 0 }; ++ ++ gyro_out[0] = (AXISDATA_REG); ++ ++ err = lsm9ds0_gyr_i2c_read(stat, gyro_out, 6); ++ ++ if (err < 0) ++ return err; ++ ++ hw_d[0] = (s32) ((s16)((gyro_out[1]) << 8) | gyro_out[0]); ++ hw_d[1] = (s32) ((s16)((gyro_out[3]) << 8) | gyro_out[2]); ++ hw_d[2] = (s32) ((s16)((gyro_out[5]) << 8) | gyro_out[4]); ++ ++ //hw_d[0] = hw_d[0] * stat->sensitivity; ++ //hw_d[1] = hw_d[1] * stat->sensitivity; ++ //hw_d[2] = hw_d[2] * stat->sensitivity; ++ ++ data->x = ((stat->pdata->negate_x) ? (-hw_d[stat->pdata->axis_map_x]) ++ : (hw_d[stat->pdata->axis_map_x])); ++ data->y = ((stat->pdata->negate_y) ? (-hw_d[stat->pdata->axis_map_y]) ++ : (hw_d[stat->pdata->axis_map_y])); ++ data->z = ((stat->pdata->negate_z) ? (-hw_d[stat->pdata->axis_map_z]) ++ : (hw_d[stat->pdata->axis_map_z])); ++ ++#ifdef DEBUG ++ /* dev_info(&stat->client->dev, "gyro_out: x = %d, y = %d, z = %d\n", ++ data->x, data->y, data->z); */ ++#endif ++ ++ return err; ++} ++ ++static void lsm9ds0_gyr_report_values(struct lsm9ds0_gyr_status *stat, ++ struct lsm9ds0_gyr_triple *data) ++{ ++ input_report_abs(stat->input_dev, ABS_X, data->x); ++ input_report_abs(stat->input_dev, ABS_Y, data->y); ++ input_report_abs(stat->input_dev, ABS_Z, data->z); ++ input_sync(stat->input_dev); ++} ++ ++static int lsm9ds0_gyr_hw_init(struct lsm9ds0_gyr_status *stat) ++{ ++ int err; ++ u8 buf[6]; ++ ++ dev_info(&stat->client->dev, "hw init\n"); ++ ++ buf[0] = (CTRL_REG1); ++ buf[1] = stat->resume_state[RES_CTRL_REG1]; ++ buf[2] = stat->resume_state[RES_CTRL_REG2]; ++ buf[3] = stat->resume_state[RES_CTRL_REG3]; ++ buf[4] = stat->resume_state[RES_CTRL_REG4]; ++ buf[5] = stat->resume_state[RES_CTRL_REG5]; ++ ++ err = lsm9ds0_gyr_i2c_write(stat, buf, 5); ++ if (err < 0) ++ return err; ++ ++ buf[0] = (FIFO_CTRL_REG); ++ buf[1] = stat->resume_state[RES_FIFO_CTRL_REG]; ++ err = lsm9ds0_gyr_i2c_write(stat, buf, 1); ++ if (err < 0) ++ return err; ++ ++ stat->hw_initialized = 1; ++ ++ return err; ++} ++ ++static void lsm9ds0_gyr_device_power_off(struct lsm9ds0_gyr_status *stat) ++{ ++ int err; ++ u8 buf[2]; ++ ++ dev_info(&stat->client->dev, "power off\n"); ++ ++ buf[0] = (CTRL_REG1); ++ buf[1] = (PM_OFF); ++ err = lsm9ds0_gyr_i2c_write(stat, buf, 1); ++ if (err < 0) ++ dev_err(&stat->client->dev, "soft power off failed\n"); ++ ++ if (stat->pdata->power_off) { ++ /* disable_irq_nosync(acc->irq1); */ ++ disable_irq_nosync(stat->irq2); ++ stat->pdata->power_off(); ++ stat->hw_initialized = 0; ++ } ++ ++ if (stat->hw_initialized) { ++ /*if (stat->pdata->gpio_int1 >= 0)*/ ++ /* disable_irq_nosync(stat->irq1);*/ ++ if (stat->pdata->gpio_int2 >= 0) { ++ disable_irq_nosync(stat->irq2); ++ dev_info(&stat->client->dev, ++ "power off: irq2 disabled\n"); ++ } ++ stat->hw_initialized = 0; ++ } ++} ++ ++static int lsm9ds0_gyr_device_power_on(struct lsm9ds0_gyr_status *stat) ++{ ++ int err; ++ ++ if (stat->pdata->power_on) { ++ err = stat->pdata->power_on(); ++ if (err < 0) ++ return err; ++ if (stat->pdata->gpio_int2 >= 0) ++ enable_irq(stat->irq2); ++ } ++ ++ ++ if (!stat->hw_initialized) { ++ err = lsm9ds0_gyr_hw_init(stat); ++ if (err < 0) { ++ lsm9ds0_gyr_device_power_off(stat); ++ return err; ++ } ++ } ++ ++ if (stat->hw_initialized) { ++ /* if (stat->pdata->gpio_int1) { ++ enable_irq(stat->irq1); ++ dev_info(&stat->client->dev, ++ "power on: irq1 enabled\n"); ++ } */ ++ dev_dbg(&stat->client->dev, "stat->pdata->gpio_int2 = %d\n", ++ stat->pdata->gpio_int2); ++ if (stat->pdata->gpio_int2 >= 0) { ++ enable_irq(stat->irq2); ++ dev_info(&stat->client->dev, ++ "power on: irq2 enabled\n"); ++ } ++ } ++ ++ return 0; ++} ++ ++static int lsm9ds0_gyr_enable(struct lsm9ds0_gyr_status *stat) ++{ ++ int err; ++ ++ if (!atomic_cmpxchg(&stat->enabled, 0, 1)) { ++ ++ err = lsm9ds0_gyr_device_power_on(stat); ++ if (err < 0) { ++ atomic_set(&stat->enabled, 0); ++ return err; ++ } ++ ++ if (stat->polling_enabled) { ++ hrtimer_start(&(stat->hr_timer), stat->ktime, HRTIMER_MODE_REL); ++ } ++ ++ } ++ ++ return 0; ++} ++ ++static int lsm9ds0_gyr_disable(struct lsm9ds0_gyr_status *stat) ++{ ++ dev_dbg(&stat->client->dev, "%s: stat->enabled = %d\n", __func__, ++ atomic_read(&stat->enabled)); ++ ++ if (atomic_cmpxchg(&stat->enabled, 1, 0)) { ++ ++ lsm9ds0_gyr_device_power_off(stat); ++ hrtimer_cancel(&stat->hr_timer); ++ dev_dbg(&stat->client->dev, "%s: cancel_hrtimer ", __func__); ++ } ++ return 0; ++} ++ ++static ssize_t attr_polling_rate_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ int val; ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ mutex_lock(&stat->lock); ++ val = stat->pdata->poll_interval; ++ mutex_unlock(&stat->lock); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_polling_rate_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int err; ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ unsigned long interval_ms; ++ ++ if (strict_strtoul(buf, 10, &interval_ms)) ++ return -EINVAL; ++ if (!interval_ms) ++ return -EINVAL; ++ ++ mutex_lock(&stat->lock); ++ err = lsm9ds0_gyr_update_odr(stat, interval_ms); ++ if(err >= 0) ++ stat->pdata->poll_interval = interval_ms; ++ mutex_unlock(&stat->lock); ++ return size; ++} ++ ++static ssize_t attr_range_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ int range = 0; ++ u8 val; ++ mutex_lock(&stat->lock); ++ val = stat->pdata->fs_range; ++ ++ switch (val) { ++ case LSM9DS0_GYR_FS_250DPS: ++ range = 250; ++ break; ++ case LSM9DS0_GYR_FS_500DPS: ++ range = 500; ++ break; ++ case LSM9DS0_GYR_FS_2000DPS: ++ range = 2000; ++ break; ++ } ++ mutex_unlock(&stat->lock); ++ /* return sprintf(buf, "0x%02x\n", val); */ ++ return sprintf(buf, "%d\n", range); ++} ++ ++static ssize_t attr_range_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ unsigned long val; ++ u8 range; ++ int err; ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ switch (val) { ++ case 250: ++ range = LSM9DS0_GYR_FS_250DPS; ++ break; ++ case 500: ++ range = LSM9DS0_GYR_FS_500DPS; ++ break; ++ case 2000: ++ range = LSM9DS0_GYR_FS_2000DPS; ++ break; ++ default: ++ dev_err(&stat->client->dev, "invalid range request: %lu," ++ " discarded\n", val); ++ return -EINVAL; ++ } ++ mutex_lock(&stat->lock); ++ err = lsm9ds0_gyr_update_fs_range(stat, range); ++ if (err >= 0) ++ stat->pdata->fs_range = range; ++ mutex_unlock(&stat->lock); ++ dev_info(&stat->client->dev, "range set to: %lu dps\n", val); ++ return size; ++} ++ ++static ssize_t attr_enable_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ int val = atomic_read(&stat->enabled); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_enable_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ unsigned long val; ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ if (val) ++ lsm9ds0_gyr_enable(stat); ++ else ++ lsm9ds0_gyr_disable(stat); ++ ++ return size; ++} ++ ++static ssize_t attr_polling_mode_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ int val = 0; ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ ++ mutex_lock(&stat->lock); ++ if (stat->polling_enabled) ++ val = 1; ++ mutex_unlock(&stat->lock); ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t attr_polling_mode_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ unsigned long val; ++ ++ if (strict_strtoul(buf, 10, &val)) ++ return -EINVAL; ++ ++ mutex_lock(&stat->lock); ++ if (val) { ++ stat->polling_enabled = true; ++ lsm9ds0_gyr_manage_int2settings(stat, stat->fifomode); ++ dev_info(dev, "polling mode enabled\n"); ++ if (atomic_read(&stat->enabled)) { ++ hrtimer_start(&(stat->hr_timer), stat->ktime, HRTIMER_MODE_REL); ++ } ++ } else { ++ if (stat->polling_enabled) { ++ hrtimer_cancel(&stat->hr_timer); ++ } ++ stat->polling_enabled = false; ++ lsm9ds0_gyr_manage_int2settings(stat, stat->fifomode); ++ dev_info(dev, "polling mode disabled\n"); ++ } ++ mutex_unlock(&stat->lock); ++ return size; ++} ++ ++static ssize_t attr_watermark_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ unsigned long watermark; ++ int res; ++ ++ if (strict_strtoul(buf, 16, &watermark)) ++ return -EINVAL; ++ ++ res = lsm9ds0_gyr_update_watermark(stat, watermark); ++ if (res < 0) ++ return res; ++ ++ return size; ++} ++ ++static ssize_t attr_watermark_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ int val = stat->watermark; ++ return sprintf(buf, "0x%02x\n", val); ++} ++ ++static ssize_t attr_fifomode_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ unsigned long fifomode; ++ int res; ++ ++ if (strict_strtoul(buf, 16, &fifomode)) ++ return -EINVAL; ++ /* if (!fifomode) ++ return -EINVAL; */ ++ ++ dev_dbg(dev, "%s, got value:0x%02x\n", __func__, (u8)fifomode); ++ ++ mutex_lock(&stat->lock); ++ res = lsm9ds0_gyr_manage_int2settings(stat, (u8) fifomode); ++ mutex_unlock(&stat->lock); ++ ++ if (res < 0) ++ return res; ++ return size; ++} ++ ++static ssize_t attr_fifomode_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ u8 val = stat->fifomode; ++ return sprintf(buf, "0x%02x\n", val); ++} ++ ++#ifdef DEBUG ++static ssize_t attr_reg_set(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ int rc; ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ u8 x[2]; ++ unsigned long val; ++ ++ if (strict_strtoul(buf, 16, &val)) ++ return -EINVAL; ++ mutex_lock(&stat->lock); ++ x[0] = stat->reg_addr; ++ mutex_unlock(&stat->lock); ++ x[1] = val; ++ rc = lsm9ds0_gyr_i2c_write(stat, x, 1); ++ return size; ++} ++ ++static ssize_t attr_reg_get(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ ssize_t ret; ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ int rc; ++ u8 data; ++ ++ mutex_lock(&stat->lock); ++ data = stat->reg_addr; ++ mutex_unlock(&stat->lock); ++ rc = lsm9ds0_gyr_i2c_read(stat, &data, 1); ++ ret = sprintf(buf, "0x%02x\n", data); ++ return ret; ++} ++ ++static ssize_t attr_addr_set(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct lsm9ds0_gyr_status *stat = dev_get_drvdata(dev); ++ unsigned long val; ++ ++ if (strict_strtoul(buf, 16, &val)) ++ return -EINVAL; ++ ++ mutex_lock(&stat->lock); ++ ++ stat->reg_addr = val; ++ ++ mutex_unlock(&stat->lock); ++ ++ return size; ++} ++#endif /* DEBUG */ ++ ++static struct device_attribute attributes[] = { ++ __ATTR(pollrate_ms, 0666, attr_polling_rate_show, ++ attr_polling_rate_store), ++ __ATTR(range, 0666, attr_range_show, attr_range_store), ++ __ATTR(enable_device, 0666, attr_enable_show, attr_enable_store), ++ __ATTR(enable_polling, 0666, attr_polling_mode_show, ++ attr_polling_mode_store), ++ __ATTR(fifo_samples, 0666, attr_watermark_show, attr_watermark_store), ++ __ATTR(fifo_mode, 0666, attr_fifomode_show, attr_fifomode_store), ++#ifdef DEBUG ++ __ATTR(reg_value, 0600, attr_reg_get, attr_reg_set), ++ __ATTR(reg_addr, 0200, NULL, attr_addr_set), ++#endif ++}; ++ ++static int create_sysfs_interfaces(struct device *dev) ++{ ++ int i; ++ for (i = 0; i < ARRAY_SIZE(attributes); i++) ++ if (device_create_file(dev, attributes + i)) ++ goto error; ++ return 0; ++ ++error: ++ for (; i >= 0; i--) ++ device_remove_file(dev, attributes + i); ++ dev_err(dev, "%s:Unable to create interface\n", __func__); ++ return -1; ++} ++ ++static int remove_sysfs_interfaces(struct device *dev) ++{ ++ int i; ++ for (i = 0; i < ARRAY_SIZE(attributes); i++) ++ device_remove_file(dev, attributes + i); ++ return 0; ++} ++ ++static void lsm9ds0_gyr_report_triple(struct lsm9ds0_gyr_status *stat) ++{ ++ int err; ++ struct lsm9ds0_gyr_triple data_out; ++ ++ err = lsm9ds0_gyr_get_data(stat, &data_out); ++ if (err < 0) ++ dev_err(&stat->client->dev, "get_gyroscope_data failed\n"); ++ else ++ lsm9ds0_gyr_report_values(stat, &data_out); ++} ++ ++ ++static void lsm9ds0_gyr_irq2_fifo(struct lsm9ds0_gyr_status *stat) ++{ ++ int err; ++ u8 buf[2]; ++ u8 int_source; ++ u8 samples; ++ u8 workingmode; ++ u8 stored_samples; ++ ++ mutex_lock(&stat->lock); ++ ++ workingmode = stat->fifomode; ++ ++ ++ dev_dbg(&stat->client->dev, "%s : fifomode:0x%02x\n", __func__, ++ workingmode); ++ ++ ++ switch (workingmode) { ++ case FIFO_MODE_BYPASS: ++ { ++ dev_dbg(&stat->client->dev, "%s : fifomode:0x%02x\n", __func__, ++ stat->fifomode); ++ lsm9ds0_gyr_report_triple(stat); ++ break; ++ } ++ case FIFO_MODE_FIFO: ++ samples = (stat->watermark)+1; ++ dev_dbg(&stat->client->dev, ++ "%s : FIFO_SRC_REG init samples:%d\n", ++ __func__, samples); ++ err = lsm9ds0_gyr_register_read(stat, buf, FIFO_SRC_REG); ++ if (err < 0) ++ dev_err(&stat->client->dev, ++ "error reading fifo source reg\n"); ++ ++ int_source = buf[0]; ++ dev_dbg(&stat->client->dev, "%s :FIFO_SRC_REG content:0x%02x\n", ++ __func__, int_source); ++ ++ stored_samples = int_source & FIFO_STORED_DATA_MASK; ++ dev_dbg(&stat->client->dev, "%s : fifomode:0x%02x\n", __func__, ++ stat->fifomode); ++ ++ dev_dbg(&stat->client->dev, "%s : samples:%d stored:%d\n", ++ __func__, samples, stored_samples); ++ ++ for (; samples > 0; samples--) { ++#ifdef DEBUG ++ input_report_abs(stat->input_dev, ABS_MISC, 1); ++ input_sync(stat->input_dev); ++#endif ++ dev_dbg(&stat->client->dev, "%s : current sample:%d\n", ++ __func__, samples); ++ ++ lsm9ds0_gyr_report_triple(stat); ++ ++#ifdef DEBUG ++ input_report_abs(stat->input_dev, ABS_MISC, 0); ++ input_sync(stat->input_dev); ++#endif ++ } ++ lsm9ds0_gyr_fifo_reset(stat); ++ break; ++ } ++#ifdef DEBUG ++ input_report_abs(stat->input_dev, ABS_MISC, 3); ++ input_sync(stat->input_dev); ++#endif ++ ++ mutex_unlock(&stat->lock); ++} ++ ++static irqreturn_t lsm9ds0_gyr_isr2(int irq, void *dev) ++{ ++ struct lsm9ds0_gyr_status *stat = dev; ++ ++ disable_irq_nosync(irq); ++#ifdef DEBUG ++ input_report_abs(stat->input_dev, ABS_MISC, 2); ++ input_sync(stat->input_dev->input); ++#endif ++ queue_work(stat->irq2_work_queue, &stat->irq2_work); ++ pr_debug("%s %s: isr2 queued\n", LSM9DS0_GYR_DEV_NAME, __func__); ++ ++ return IRQ_HANDLED; ++} ++ ++static void lsm9ds0_gyr_irq2_work_func(struct work_struct *work) ++{ ++ ++ struct lsm9ds0_gyr_status *stat = ++ container_of(work, struct lsm9ds0_gyr_status, irq2_work); ++ /* TODO add interrupt service procedure. ++ ie:lsm9ds0_gyr_irq2_XXX(stat); */ ++ lsm9ds0_gyr_irq2_fifo(stat); ++ /* */ ++ pr_debug("%s %s: IRQ2 served\n", LSM9DS0_GYR_DEV_NAME, __func__); ++/* exit: */ ++ enable_irq(stat->irq2); ++} ++ ++int lsm9ds0_gyr_input_open(struct input_dev *input) ++{ ++ struct lsm9ds0_gyr_status *stat = input_get_drvdata(input); ++ dev_dbg(&stat->client->dev, "%s\n", __func__); ++ return lsm9ds0_gyr_enable(stat); ++} ++ ++void lsm9ds0_gyr_input_close(struct input_dev *dev) ++{ ++ struct lsm9ds0_gyr_status *stat = input_get_drvdata(dev); ++ dev_dbg(&stat->client->dev, "%s\n", __func__); ++ lsm9ds0_gyr_disable(stat); ++} ++ ++static int lsm9ds0_gyr_validate_pdata(struct lsm9ds0_gyr_status *stat) ++{ ++ /* checks for correctness of minimal polling period */ ++ stat->pdata->min_interval = ++ max((unsigned int) LSM9DS0_GYR_MIN_POLL_PERIOD_MS, ++ stat->pdata->min_interval); ++ ++ stat->pdata->poll_interval = max(stat->pdata->poll_interval, ++ stat->pdata->min_interval); ++ ++ if (stat->pdata->axis_map_x > 2 || ++ stat->pdata->axis_map_y > 2 || ++ stat->pdata->axis_map_z > 2) { ++ dev_err(&stat->client->dev, ++ "invalid axis_map value x:%u y:%u z%u\n", ++ stat->pdata->axis_map_x, ++ stat->pdata->axis_map_y, ++ stat->pdata->axis_map_z); ++ return -EINVAL; ++ } ++ ++ /* Only allow 0 and 1 for negation boolean flag */ ++ if (stat->pdata->negate_x > 1 || ++ stat->pdata->negate_y > 1 || ++ stat->pdata->negate_z > 1) { ++ dev_err(&stat->client->dev, ++ "invalid negate value x:%u y:%u z:%u\n", ++ stat->pdata->negate_x, ++ stat->pdata->negate_y, ++ stat->pdata->negate_z); ++ return -EINVAL; ++ } ++ ++ /* Enforce minimum polling interval */ ++ if (stat->pdata->poll_interval < stat->pdata->min_interval) { ++ dev_err(&stat->client->dev, ++ "minimum poll interval violated\n"); ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static int lsm9ds0_gyr_input_init(struct lsm9ds0_gyr_status *stat) ++{ ++ int err = -1; ++ ++ dev_dbg(&stat->client->dev, "%s\n", __func__); ++ ++ stat->input_dev = input_allocate_device(); ++ if (!stat->input_dev) { ++ err = -ENOMEM; ++ dev_err(&stat->client->dev, ++ "input device allocation failed\n"); ++ goto err0; ++ } ++ ++ stat->input_dev->open = lsm9ds0_gyr_input_open; ++ stat->input_dev->close = lsm9ds0_gyr_input_close; ++ stat->input_dev->name = LSM9DS0_GYR_DEV_NAME; ++ ++ stat->input_dev->id.bustype = BUS_I2C; ++ stat->input_dev->dev.parent = &stat->client->dev; ++ ++ input_set_drvdata(stat->input_dev, stat); ++ ++ set_bit(EV_ABS, stat->input_dev->evbit); ++ ++#ifdef DEBUG ++ set_bit(EV_KEY, stat->input_dev->keybit); ++ set_bit(KEY_LEFT, stat->input_dev->keybit); ++ input_set_abs_params(stat->input_dev, ABS_MISC, 0, 1, 0, 0); ++#endif ++ ++ input_set_abs_params(stat->input_dev, ABS_X, -FS_MAX-1, FS_MAX, 0, 0); ++ input_set_abs_params(stat->input_dev, ABS_Y, -FS_MAX-1, FS_MAX, 0, 0); ++ input_set_abs_params(stat->input_dev, ABS_Z, -FS_MAX-1, FS_MAX, 0, 0); ++ ++ ++ err = input_register_device(stat->input_dev); ++ if (err) { ++ dev_err(&stat->client->dev, ++ "unable to register input polled device %s\n", ++ stat->input_dev->name); ++ goto err1; ++ } ++ ++ return 0; ++ ++err1: ++ input_free_device(stat->input_dev); ++err0: ++ return err; ++} ++ ++static void lsm9ds0_gyr_input_cleanup(struct lsm9ds0_gyr_status *stat) ++{ ++ input_unregister_device(stat->input_dev); ++ input_free_device(stat->input_dev); ++} ++ ++static void poll_function_work(struct work_struct *polling_task) ++{ ++ struct lsm9ds0_gyr_status *stat; ++ struct lsm9ds0_gyr_triple data_out; ++ int err; ++ ++ stat = container_of((struct work_struct *)polling_task, ++ struct lsm9ds0_gyr_status, polling_task); ++ ++ err = lsm9ds0_gyr_get_data(stat, &data_out); ++ if (err < 0) ++ dev_err(&stat->client->dev, "get_rotation_data failed.\n"); ++ else ++ lsm9ds0_gyr_report_values(stat, &data_out); ++ ++ hrtimer_start(&stat->hr_timer, stat->ktime, HRTIMER_MODE_REL); ++} ++ ++enum hrtimer_restart poll_function_read(struct hrtimer *timer) ++{ ++ struct lsm9ds0_gyr_status *stat; ++ ++ stat = container_of((struct hrtimer *)timer, ++ struct lsm9ds0_gyr_status, hr_timer); ++ ++ queue_work(lsm9ds0_gyr_workqueue, &stat->polling_task); ++ return HRTIMER_NORESTART; ++} ++ ++static int lsm9ds0_gyr_probe(struct i2c_client *client, ++ const struct i2c_device_id *devid) ++{ ++ struct lsm9ds0_gyr_status *stat; ++ ++ u32 smbus_func = I2C_FUNC_SMBUS_BYTE_DATA | ++ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK ; ++ ++ int err = -1; ++ ++ dev_info(&client->dev, "probe start.\n"); ++ ++ ++ stat = kzalloc(sizeof(*stat), GFP_KERNEL); ++ if (stat == NULL) { ++ dev_err(&client->dev, ++ "failed to allocate memory for module data\n"); ++ err = -ENOMEM; ++ goto err0; ++ } ++ ++ ++ /* Support for both I2C and SMBUS adapter interfaces. */ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { ++ dev_warn(&client->dev, "client not i2c capable\n"); ++ if (i2c_check_functionality(client->adapter, smbus_func)) { ++ stat->use_smbus = 1; ++ dev_warn(&client->dev, "client using SMBUS\n"); ++ } else { ++ err = -ENODEV; ++ dev_err(&client->dev, "client nor SMBUS capable\n"); ++ stat->use_smbus = 0; ++ goto err0; ++ } ++ } ++ ++ if(lsm9ds0_gyr_workqueue == 0) ++ lsm9ds0_gyr_workqueue = create_workqueue("lsm9ds0_gyr_workqueue"); ++ ++ hrtimer_init(&stat->hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ stat->hr_timer.function = &poll_function_read; ++ ++ mutex_init(&stat->lock); ++ mutex_lock(&stat->lock); ++ stat->client = client; ++ ++ stat->pdata = kmalloc(sizeof(*stat->pdata), GFP_KERNEL); ++ if (stat->pdata == NULL) { ++ dev_err(&client->dev, ++ "failed to allocate memory for pdata: %d\n", err); ++ goto err1; ++ } ++ ++ if (client->dev.platform_data == NULL) { ++ default_lsm9ds0_gyr_pdata.gpio_int1 = int1_gpio; ++ default_lsm9ds0_gyr_pdata.gpio_int2 = int2_gpio; ++ memcpy(stat->pdata, &default_lsm9ds0_gyr_pdata, ++ sizeof(*stat->pdata)); ++ dev_info(&client->dev, "using default plaform_data\n"); ++ } else { ++ memcpy(stat->pdata, client->dev.platform_data, ++ sizeof(*stat->pdata)); ++ } ++ ++ err = lsm9ds0_gyr_validate_pdata(stat); ++ if (err < 0) { ++ dev_err(&client->dev, "failed to validate platform data\n"); ++ goto err1_1; ++ } ++ ++ i2c_set_clientdata(client, stat); ++ ++ if (stat->pdata->init) { ++ err = stat->pdata->init(); ++ if (err < 0) { ++ dev_err(&client->dev, "init failed: %d\n", err); ++ goto err1_1; ++ } ++ } ++ ++ ++ memset(stat->resume_state, 0, ARRAY_SIZE(stat->resume_state)); ++ ++ stat->resume_state[RES_CTRL_REG1] = ALL_ZEROES | ENABLE_ALL_AXES ++ | PM_NORMAL; ++ stat->resume_state[RES_CTRL_REG2] = ALL_ZEROES; ++ stat->resume_state[RES_CTRL_REG3] = ALL_ZEROES; ++ stat->resume_state[RES_CTRL_REG4] = ALL_ZEROES | BDU_ENABLE; ++ stat->resume_state[RES_CTRL_REG5] = ALL_ZEROES; ++ stat->resume_state[RES_FIFO_CTRL_REG] = ALL_ZEROES; ++ ++ stat->polling_enabled = true; ++ dev_info(&client->dev, "polling mode enabled\n"); ++ ++ err = lsm9ds0_gyr_device_power_on(stat); ++ if (err < 0) { ++ dev_err(&client->dev, "power on failed: %d\n", err); ++ goto err2; ++ } ++ ++ atomic_set(&stat->enabled, 1); ++ ++ err = lsm9ds0_gyr_update_fs_range(stat, stat->pdata->fs_range); ++ if (err < 0) { ++ dev_err(&client->dev, "update_fs_range failed\n"); ++ goto err2; ++ } ++ ++ err = lsm9ds0_gyr_update_odr(stat, stat->pdata->poll_interval); ++ if (err < 0) { ++ dev_err(&client->dev, "update_odr failed\n"); ++ goto err2; ++ } ++ ++ err = lsm9ds0_gyr_input_init(stat); ++ if (err < 0) ++ goto err3; ++ ++ err = create_sysfs_interfaces(&client->dev); ++ if (err < 0) { ++ dev_err(&client->dev, ++ "%s device register failed\n", LSM9DS0_GYR_DEV_NAME); ++ goto err4; ++ } ++ ++ lsm9ds0_gyr_device_power_off(stat); ++ ++ /* As default, do not report information */ ++ atomic_set(&stat->enabled, 0); ++ ++ ++ if (stat->pdata->gpio_int2 >= 0) { ++ stat->irq2 = gpio_to_irq(stat->pdata->gpio_int2); ++ dev_info(&client->dev, "%s: %s has set irq2 to irq:" ++ " %d mapped on gpio:%d\n", ++ LSM9DS0_GYR_DEV_NAME, __func__, stat->irq2, ++ stat->pdata->gpio_int2); ++ ++ INIT_WORK(&stat->irq2_work, lsm9ds0_gyr_irq2_work_func); ++ stat->irq2_work_queue = ++ create_singlethread_workqueue("lsm9ds0_gyr_irq2_wq"); ++ if (!stat->irq2_work_queue) { ++ err = -ENOMEM; ++ dev_err(&client->dev, "cannot create " ++ "work queue2: %d\n", err); ++ goto err5; ++ } ++ ++ err = request_irq(stat->irq2, lsm9ds0_gyr_isr2, ++ IRQF_TRIGGER_HIGH, "lsm9ds0_gyr_irq2", stat); ++ ++ if (err < 0) { ++ dev_err(&client->dev, "request irq2 failed: %d\n", err); ++ goto err6; ++ } ++ disable_irq_nosync(stat->irq2); ++ } ++ ++ mutex_unlock(&stat->lock); ++ ++ INIT_WORK(&stat->polling_task, poll_function_work); ++ dev_info(&client->dev, "%s probed: device created successfully\n", ++ LSM9DS0_GYR_DEV_NAME); ++ ++ ++ return 0; ++ ++/*err7: ++ free_irq(stat->irq2, stat); ++*/ ++err6: ++ destroy_workqueue(stat->irq2_work_queue); ++err5: ++ lsm9ds0_gyr_device_power_off(stat); ++ remove_sysfs_interfaces(&client->dev); ++err4: ++ lsm9ds0_gyr_input_cleanup(stat); ++err3: ++ lsm9ds0_gyr_device_power_off(stat); ++err2: ++ if (stat->pdata->exit) ++ stat->pdata->exit(); ++err1_1: ++ mutex_unlock(&stat->lock); ++ kfree(stat->pdata); ++err1: ++ destroy_workqueue(lsm9ds0_gyr_workqueue); ++ kfree(stat); ++err0: ++ pr_err("%s: Driver Initialization failed\n", ++ LSM9DS0_GYR_DEV_NAME); ++ return err; ++} ++ ++static int lsm9ds0_gyr_remove(struct i2c_client *client) ++{ ++ struct lsm9ds0_gyr_status *stat = i2c_get_clientdata(client); ++ ++ dev_info(&stat->client->dev, "driver removing\n"); ++ ++ cancel_work_sync(&stat->polling_task); ++ if(!lsm9ds0_gyr_workqueue) { ++ flush_workqueue(lsm9ds0_gyr_workqueue); ++ destroy_workqueue(lsm9ds0_gyr_workqueue); ++ } ++ /* ++ if (stat->pdata->gpio_int1 >= 0) ++ { ++ free_irq(stat->irq1, stat); ++ gpio_free(stat->pdata->gpio_int1); ++ destroy_workqueue(stat->irq1_work_queue); ++ } ++ */ ++ if (stat->pdata->gpio_int2 >= 0) { ++ free_irq(stat->irq2, stat); ++ gpio_free(stat->pdata->gpio_int2); ++ destroy_workqueue(stat->irq2_work_queue); ++ } ++ ++ lsm9ds0_gyr_disable(stat); ++ lsm9ds0_gyr_input_cleanup(stat); ++ ++ remove_sysfs_interfaces(&client->dev); ++ ++ kfree(stat->pdata); ++ kfree(stat); ++ return 0; ++} ++ ++static int lsm9ds0_gyr_suspend(struct device *dev) ++{ ++ int err = 0; ++#define SLEEP ++#ifdef CONFIG_PM ++ struct i2c_client *client = to_i2c_client(dev); ++ struct lsm9ds0_gyr_status *stat = i2c_get_clientdata(client); ++ u8 buf[2]; ++ ++ dev_info(&client->dev, "suspend\n"); ++ ++ dev_dbg(&client->dev, "%s\n", __func__); ++ if (atomic_read(&stat->enabled)) { ++ mutex_lock(&stat->lock); ++ if (stat->polling_enabled) { ++ dev_info(&stat->client->dev, "polling mode disabled\n"); ++ hrtimer_cancel(&stat->hr_timer); ++ } ++#ifdef SLEEP ++ err = lsm9ds0_gyr_register_update(stat, buf, CTRL_REG1, ++ 0x0F, (ENABLE_NO_AXES | PM_NORMAL)); ++#else ++ err = lsm9ds0_gyr_register_update(stat, buf, CTRL_REG1, ++ 0x08, PM_OFF); ++#endif /*SLEEP*/ ++ mutex_unlock(&stat->lock); ++ } ++#endif /*CONFIG_PM*/ ++ return err; ++} ++ ++static int lsm9ds0_gyr_resume(struct device *dev) ++{ ++ int err = 0; ++#ifdef CONFIG_PM ++ struct i2c_client *client = to_i2c_client(dev); ++ struct lsm9ds0_gyr_status *stat = i2c_get_clientdata(client); ++ u8 buf[2]; ++ ++ ++ dev_info(&client->dev, "resume\n"); ++ ++ dev_dbg(&client->dev, "%s\n", __func__); ++ if (atomic_read(&stat->enabled)) { ++ mutex_lock(&stat->lock); ++ if (stat->polling_enabled) { ++ dev_info(&stat->client->dev, "polling mode enabled\n"); ++ hrtimer_init(&stat->hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ } ++#ifdef SLEEP ++ err = lsm9ds0_gyr_register_update(stat, buf, CTRL_REG1, ++ 0x0F, (ENABLE_ALL_AXES | PM_NORMAL)); ++#else ++ err = lsm9ds0_gyr_register_update(stat, buf, CTRL_REG1, ++ 0x08, PM_NORMAL); ++#endif ++ mutex_unlock(&stat->lock); ++ ++ } ++#endif /*CONFIG_PM*/ ++ return err; ++} ++ ++ ++static const struct i2c_device_id lsm9ds0_gyr_id[] = { ++ { LSM9DS0_GYR_DEV_NAME , 0 }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, lsm9ds0_gyr_id); ++ ++static const struct of_device_id lsm9ds0_gyr_of_match[] = { ++ { .compatible = "st,"LSM9DS0_GYR_DEV_NAME, }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, lsm9ds0_gyr_of_match); ++ ++static const struct dev_pm_ops lsm9ds0_gyr_pm = { ++ .suspend = lsm9ds0_gyr_suspend, ++ .resume = lsm9ds0_gyr_resume, ++}; ++ ++static struct i2c_driver lsm9ds0_i2c_gyr_driver = { ++ .driver = { ++ .owner = THIS_MODULE, ++ .name = LSM9DS0_GYR_DEV_NAME, ++ .pm = &lsm9ds0_gyr_pm, ++ .of_match_table = lsm9ds0_gyr_of_match, ++ }, ++ .probe = lsm9ds0_gyr_probe, ++ .remove = lsm9ds0_gyr_remove, ++ .id_table = lsm9ds0_gyr_id, ++ ++}; ++ ++module_i2c_driver(lsm9ds0_i2c_gyr_driver); ++ ++MODULE_DESCRIPTION("lsm9ds0 gyroscope driver"); ++MODULE_AUTHOR("Matteo Dameno, Denis Ciocca, STMicroelectronics"); ++MODULE_LICENSE("GPL"); +diff --git a/include/linux/input/lsm9ds0.h b/include/linux/input/lsm9ds0.h +new file mode 100644 +index 0000000..c115eb7 +--- /dev/null ++++ b/include/linux/input/lsm9ds0.h +@@ -0,0 +1,201 @@ ++/******************** (C) COPYRIGHT 2012 STMicroelectronics ******************** ++* ++* File Name : lsm9ds0.h ++* Authors : MSH - C&I BU - Application Team ++* : Matteo Dameno (matteo.dameno@st.com) ++* : Denis Ciocca (denis.ciocca@st.com) ++* Version : V.1.0.2 ++* Date : 2013/Oct/23 ++* ++******************************************************************************** ++* ++* This program is free software; you can redistribute it and/or modify ++* it under the terms of the GNU General Public License version 2 as ++* published by the Free Software Foundation. ++* ++* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES ++* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE ++* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. ++* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, ++* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE ++* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING ++* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. ++* ++******************************************************************************** ++******************************************************************************** ++* REVISON HISTORY ++* 1.0.1 | 2012/Aug/30 | Denis Ciocca | corrects gyr_get_data func ++* 1.0.2 | 2013/Oct/23 | Matteo Dameno | introduced acc_mag 1.0.5 ++*******************************************************************************/ ++ ++#ifndef __LSM9DS0_H__ ++#define __LSM9DS0_H__ ++ ++#define LSM9DS0_DEV_NAME "lsm9ds0_acc_mag" /* i2c system name */ ++#define LSM9DS0_ACC_DEV_NAME "lsm9ds0_acc" /* Input file name */ ++#define LSM9DS0_MAG_DEV_NAME "lsm9ds0_mag" /* Input file name */ ++#define LSM9DS0_GYR_DEV_NAME "lsm9ds0_gyr" /* Input file name */ ++ ++#define LSM9DS0_SAD0L_ACC_MAG (0x02) ++#define LSM9DS0_SAD0H_ACC_MAG (0x01) ++#define LSM9DS0_SAD0L_GYR (0x00) ++#define LSM9DS0_SAD0H_GYR (0x01) ++ ++/************************************************/ ++/* Output data */ ++/************************************************* ++accelerometer: ug ++magnetometer: ugauss ++gyroscope: udps ++*************************************************/ ++ ++/************************************************/ ++/* sysfs data */ ++/************************************************* ++accelerometer: ++ - pollrate->ms ++ - fullscale->g ++magnetometer: ++ - pollrate->ms ++ - fullscale->gauss ++gyroscope: ++ - pollrate->ms ++ - fullscale->dps ++*************************************************/ ++ ++#define LSM9DS0_ACC_MAG_I2C_SADROOT (0x07) ++ ++/* I2C address if gyr SA0 pin to GND */ ++#define LSM9DS0_ACC_MAG_I2C_SAD_L ((LSM9DS0_ACC_MAG_I2C_SADROOT<<2)| \ ++ LSM9DS0_SAD0L_ACC_MAG) ++/* I2C address if gyr SA0 pin to Vdd */ ++#define LSM9DS0_ACC_MAG_I2C_SAD_H ((LSM9DS0_ACC_MAG_I2C_SADROOT<<2)| \ ++ LSM9DS0_SAD0H_ACC_MAG) ++ ++/************************************************/ ++/* Accelerometer section defines */ ++/************************************************/ ++ ++/* Accelerometer Sensor Full Scale */ ++#define LSM9DS0_ACC_FS_MASK (0x18) ++#define LSM9DS0_ACC_FS_2G (0x00) /* Full scale 2g */ ++#define LSM9DS0_ACC_FS_4G (0x08) /* Full scale 4g */ ++#define LSM9DS0_ACC_FS_8G (0x10) /* Full scale 8g */ ++#define LSM9DS0_ACC_FS_16G (0x18) /* Full scale 16g */ ++ ++/* Accelerometer Anti-Aliasing Filter */ ++#define ANTI_ALIASING_773 (0X00) ++#define ANTI_ALIASING_362 (0X40) ++#define ANTI_ALIASING_194 (0X80) ++#define ANTI_ALIASING_50 (0XC0) ++ ++/************************************************/ ++/* Magnetometer section defines */ ++/************************************************/ ++ ++/* Magnetometer Sensor Full Scale */ ++#define LSM9DS0_MAG_FS_MASK (0x60) ++#define LSM9DS0_MAG_FS_2G (0x00) /* Full scale 2 gauss */ ++#define LSM9DS0_MAG_FS_4G (0x20) /* Full scale 4 gauss */ ++#define LSM9DS0_MAG_FS_8G (0x40) /* Full scale 8 gauss */ ++#define LSM9DS0_MAG_FS_12G (0x60) /* Full scale 12 gauss */ ++ ++/************************************************/ ++/* Gyroscope section defines */ ++/************************************************/ ++ ++#define LSM9DS0_GYR_I2C_SADROOT (0x35) ++ ++/* I2C address if gyr SA0 pin to GND */ ++#define LSM9DS0_GYR_I2C_SAD_L ((LSM9DS0_GYR_I2C_SADROOT<<1)| \ ++ LSM9DS0_SAD0L_GYR) ++/* I2C address if gyr SA0 pin to Vdd */ ++#define LSM9DS0_GYR_I2C_SAD_H ((LSM9DS0_GYR_I2C_SADROOT<<1)| \ ++ LSM9DS0_SAD0H_GYR) ++ ++#ifdef __KERNEL__ ++ ++/* to set gpios numb connected to gyro interrupt pins, ++ * the unused ones havew to be set to -EINVAL ++ */ ++#define DEFAULT_INT1_GPIO (-EINVAL) ++#define DEFAULT_INT2_GPIO (-EINVAL) ++ ++#define LSM9DS0_ACC_MIN_POLL_PERIOD_MS 1 ++#define LSM9DS0_MAG_MIN_POLL_PERIOD_MS 5 ++ ++#define LSM9DS0_GYR_DEFAULT_INT1_GPIO (-EINVAL) ++#define LSM9DS0_GYR_DEFAULT_INT2_GPIO (-EINVAL) ++ ++#define LSM9DS0_GYR_MIN_POLL_PERIOD_MS 2 ++ ++#define LSM9DS0_GYR_FS_250DPS (0x00) ++#define LSM9DS0_GYR_FS_500DPS (0x10) ++#define LSM9DS0_GYR_FS_2000DPS (0x30) ++ ++struct lsm9ds0_acc_platform_data { ++ ++ unsigned int poll_interval; ++ unsigned int min_interval; ++ ++ u8 fs_range; ++ ++ short rot_matrix[3][3]; ++ ++ u8 aa_filter_bandwidth; ++ ++ int (*init)(void); ++ void (*exit)(void); ++ int (*power_on)(void); ++ int (*power_off)(void); ++ ++ int gpio_int1; ++ int gpio_int2; ++}; ++ ++struct lsm9ds0_mag_platform_data { ++ ++ unsigned int poll_interval; ++ unsigned int min_interval; ++ ++ u8 fs_range; ++ ++ short rot_matrix[3][3]; ++ ++ int (*init)(void); ++ void (*exit)(void); ++ int (*power_on)(void); ++ int (*power_off)(void); ++}; ++ ++struct lsm9ds0_gyr_platform_data { ++ int (*init)(void); ++ void (*exit)(void); ++ int (*power_on)(void); ++ int (*power_off)(void); ++ unsigned int poll_interval; ++ unsigned int min_interval; ++ ++ u8 fs_range; ++ ++ /* gpio ports for interrupt pads */ ++ int gpio_int1; ++ int gpio_int2; /* int for fifo */ ++ ++ u8 axis_map_x; ++ u8 axis_map_y; ++ u8 axis_map_z; ++ ++ u8 negate_x; ++ u8 negate_y; ++ u8 negate_z; ++}; ++ ++struct lsm9ds0_main_platform_data { ++ ++ struct lsm9ds0_acc_platform_data *pdata_acc; ++ struct lsm9ds0_mag_platform_data *pdata_mag; ++}; ++ ++#endif /* __KERNEL__ */ ++#endif /* __LSM9DS0_H__ */ +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0051-TI-ADS111X-sigma-delta-ADC-driver.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0051-TI-ADS111X-sigma-delta-ADC-driver.patch new file mode 100644 index 0000000..4b3238d --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0051-TI-ADS111X-sigma-delta-ADC-driver.patch @@ -0,0 +1,320 @@ +From 623921d9a1815cb695b587088ea7909f2996b637 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Fri, 18 Sep 2015 16:19:57 +0300 +Subject: [PATCH 51/52] TI ADS111X sigma-delta ADC driver + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/iio/adc/Kconfig | 10 ++ + drivers/iio/adc/Makefile | 1 + + drivers/iio/adc/ti-ads111x.c | 266 ++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 277 insertions(+) + create mode 100644 drivers/iio/adc/ti-ads111x.c + +diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig +index ab0767e6..2ad9d87 100644 +--- a/drivers/iio/adc/Kconfig ++++ b/drivers/iio/adc/Kconfig +@@ -143,6 +143,16 @@ config TI_ADC081C + This driver can also be built as a module. If so, the module will be + called ti-adc081c. + ++config TI_ADS111X ++ tristate "Texas Instruments ADS1113/4/5" ++ depends on I2C ++ help ++ If you say yes here you get support for Texas Instruments ADS1113/4/5 ++ sigma-delta ADC chips. ++ ++ This driver can also be built as a module. If so, the module will be ++ called ti-ads111x. ++ + config TI_AM335X_ADC + tristate "TI's ADC driver" + depends on MFD_TI_AM335X_TSCADC +diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile +index 0a825be..0a3c03f 100644 +--- a/drivers/iio/adc/Makefile ++++ b/drivers/iio/adc/Makefile +@@ -15,5 +15,6 @@ obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o + obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o + obj-$(CONFIG_MAX1363) += max1363.o + obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o ++obj-$(CONFIG_TI_ADS111X) += ti-ads111x.o + obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o + obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o +diff --git a/drivers/iio/adc/ti-ads111x.c b/drivers/iio/adc/ti-ads111x.c +new file mode 100644 +index 0000000..98a28e3e +--- /dev/null ++++ b/drivers/iio/adc/ti-ads111x.c +@@ -0,0 +1,266 @@ ++/* ++ * Copyright (C) 2015 CogentEmbedded Inc ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#include <linux/err.h> ++#include <linux/i2c.h> ++#include <linux/module.h> ++#include <linux/delay.h> ++ ++#include <linux/iio/iio.h> ++#include <linux/regulator/consumer.h> ++ ++#define REG_CONV_RES 0x00 ++#define REG_CONFIG 0x01 ++#define REG_LO_THRES 0x02 ++#define REG_HI_THRES 0x03 ++ ++/* CONFIG reg bits */ ++/* w: begin sigle conversion; r: operation status */ ++#define CONFIG_OS (1 << 15) ++#define CONFIG_CH(n) (n << 12) ++/* ADS1114/5 only */ ++#define CONFIG_PGA_6144 (0 << 9) ++#define CONFIG_PGA_4096 (1 << 9) ++#define CONFIG_PGA_2048 (2 << 9) /* def */ ++#define CONFIG_PGA_1024 (3 << 9) ++#define CONFIG_PGA_0512 (4 << 9) ++#define CONFIG_PGA_0256 (5 << 9) ++#define CONFIG_MODE_CONT (0 << 8) ++#define CONFIG_MODE_SINGLE (1 << 8) ++#define CONFIG_DR_8SPS (0 << 5) ++#define CONFIG_DR_16SPS (1 << 5) ++#define CONFIG_DR_32SPS (2 << 5) ++#define CONFIG_DR_64SPS (3 << 5) ++#define CONFIG_DR_128SPS (4 << 5) ++#define CONFIG_DR_250SPS (5 << 5) ++#define CONFIG_DR_475SPS (6 << 5) ++#define CONFIG_DR_860SPS (7 << 5) ++/* ADS1114/5 only */ ++#define CONFIG_COMP_HYST (0 << 4) ++#define CONFIG_COMP_WINDOW (1 << 4) ++#define CONFIG_COMP_POL_LOW (0 << 3) ++#define CONFIG_COMP_POL_HIGH (1 << 3) ++#define CONFIG_COMP_NONLATCH (0 << 2) ++#define CONFIG_COMP_LATCH (1 << 2) ++#define CONFIG_COMP_QUE_1 (0 << 0) ++#define CONFIG_COMP_QUE_2 (1 << 0) ++#define CONFIG_COMP_QUE_4 (2 << 0) ++#define CONFIG_COMP_QUE_DIS (3 << 0) ++ ++struct ads111x { ++ struct i2c_client *i2c; ++ int type; ++}; ++ ++enum ads111x_types { ++ ads1113, ++ ads1114, ++ ads1115, ++}; ++ ++/* Applies to ads1115 */ ++enum ads1115_modes { ++ p0n1, p0n3, p1n3, p2n3, ++ p0, p1, p2, p3 ++}; ++ ++/* unipolar channel */ ++#define ADS1115_CHAN_U(num, addr) \ ++ { \ ++ .type = IIO_VOLTAGE, \ ++ .indexed = 1, \ ++ .channel = num, \ ++ .address = addr, \ ++ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ ++ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ ++ .datasheet_name = "AIN"#num, \ ++ .scan_type = { \ ++ .sign = 'u', \ ++ .realbits = 16, \ ++ .storagebits = 16, \ ++ .endianness = IIO_BE, \ ++ }, \ ++ } ++ ++/* bipolar channel */ ++#define ADS111X_CHAN_B(num, num2, addr) \ ++ { \ ++ .type = IIO_VOLTAGE, \ ++ .differential = 1, \ ++ .indexed = 1, \ ++ .channel = num, \ ++ .channel2 = num2, \ ++ .address = addr, \ ++ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ ++ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ ++ .datasheet_name = "AIN"#num"-AIN"#num2, \ ++ .scan_type = { \ ++ .sign = 'u', \ ++ .realbits = 16, \ ++ .storagebits = 16, \ ++ .endianness = IIO_BE, \ ++ }, \ ++ } ++ ++static const struct iio_chan_spec ads111x_channels[] = { ++ /* ADS1113/4/5 */ ++ ADS111X_CHAN_B(0, 1, p0n1), ++ /* ADS1115 only */ ++ ADS111X_CHAN_B(0, 3, p0n3), ++ ADS111X_CHAN_B(1, 3, p1n3), ++ ADS111X_CHAN_B(2, 3, p2n3), ++ ADS1115_CHAN_U(0, p0), ++ ADS1115_CHAN_U(1, p1), ++ ADS1115_CHAN_U(2, p2), ++ ADS1115_CHAN_U(3, p3), ++}; ++ ++static int ads111x_read_raw(struct iio_dev *iio, ++ struct iio_chan_spec const *channel, int *value, ++ int *shift, long mask) ++{ ++ struct ads111x *adc = iio_priv(iio); ++ u16 config_reg; ++ int err; ++ ++ switch (mask) { ++ case IIO_CHAN_INFO_RAW: ++ config_reg = ++ CONFIG_OS | ++ CONFIG_CH(channel->address) | ++ CONFIG_PGA_2048 | ++ CONFIG_MODE_SINGLE | ++ CONFIG_DR_860SPS | ++ CONFIG_COMP_QUE_DIS; ++ err = i2c_smbus_write_word_swapped(adc->i2c, REG_CONFIG, config_reg); ++ if (err < 0) { ++ dev_err(&iio->dev, "REG_CONFIG write err %d\n", err); ++ return err; ++ } ++ mdelay(1); ++ err = i2c_smbus_read_word_swapped(adc->i2c, REG_CONFIG); ++ if (err < 0) { ++ dev_err(&iio->dev, "REG_CONFIG read err %d\n", err); ++ return err; ++ } ++ if (err & CONFIG_OS) { ++ dev_err(&iio->dev, "ADC not ready 0x%04x\n", err); ++ return -EBUSY; ++ } ++ err = i2c_smbus_read_word_swapped(adc->i2c, REG_CONV_RES); ++ if (err < 0) { ++ dev_err(&iio->dev, "REG_CONV_RES read err %d\n", err); ++ return err; ++ } ++ ++ *value = err; ++ return IIO_VAL_INT; ++ ++ case IIO_CHAN_INFO_SCALE: ++ *value = 2048000; ++ *shift = 0; ++ return IIO_VAL_INT_PLUS_MICRO; ++ ++ default: ++ break; ++ } ++ ++ return -EINVAL; ++} ++ ++static const struct iio_info ads111x_info = { ++ .read_raw = ads111x_read_raw, ++ .driver_module = THIS_MODULE, ++}; ++ ++static int ads111x_probe(struct i2c_client *client, ++ const struct i2c_device_id *id) ++{ ++ struct iio_dev *iio; ++ struct ads111x *adc; ++ int err; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) ++ return -ENODEV; ++ ++ iio = iio_device_alloc(sizeof(*adc)); ++ if (!iio) ++ return -ENOMEM; ++ ++ adc = iio_priv(iio); ++ adc->i2c = client; ++ adc->type = id->driver_data; ++ ++ iio->dev.parent = &client->dev; ++ iio->name = dev_name(&client->dev); ++ iio->modes = INDIO_DIRECT_MODE; ++ iio->info = &ads111x_info; ++ ++ iio->channels = ads111x_channels; ++ if (id->driver_data == ads1115) ++ iio->num_channels = ARRAY_SIZE(ads111x_channels); ++ else ++ iio->num_channels = 1; ++ ++ err = iio_device_register(iio); ++ if (err < 0) ++ goto iio_free; ++ ++ i2c_set_clientdata(client, iio); ++ ++ return 0; ++ ++iio_free: ++ iio_device_free(iio); ++ ++ return err; ++} ++ ++static int ads111x_remove(struct i2c_client *client) ++{ ++ struct iio_dev *iio = i2c_get_clientdata(client); ++ ++ iio_device_unregister(iio); ++ iio_device_free(iio); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id ads111x_id[] = { ++ { "ads1113", ads1113 }, ++ { "ads1114", ads1114 }, ++ { "ads1115", ads1115 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, ads111x_id); ++ ++#ifdef CONFIG_OF ++static const struct of_device_id ads111x_of_match[] = { ++ { .compatible = "ti,ads1113" }, ++ { .compatible = "ti,ads1114" }, ++ { .compatible = "ti,ads1115" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, ads111x_of_match); ++#endif ++ ++static struct i2c_driver ads111x_driver = { ++ .driver = { ++ .name = "ads111x", ++ .owner = THIS_MODULE, ++ .of_match_table = of_match_ptr(ads111x_of_match), ++ }, ++ .probe = ads111x_probe, ++ .remove = ads111x_remove, ++ .id_table = ads111x_id, ++}; ++module_i2c_driver(ads111x_driver); ++ ++MODULE_AUTHOR("Andrey Gusakov <andrey.gusakov@cogentembedded.com"); ++MODULE_DESCRIPTION("Texas Instruments ADS1113/4/5 driver"); ++MODULE_LICENSE("GPL v2"); +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0051a-Cleanup-TI-ADS111x-ADC-driver.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0051a-Cleanup-TI-ADS111x-ADC-driver.patch new file mode 100644 index 0000000..c10e75a --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0051a-Cleanup-TI-ADS111x-ADC-driver.patch @@ -0,0 +1,162 @@ +From f73d05506794b6f4090e4c5c701c2362a66b07ec Mon Sep 17 00:00:00 2001 +From: Petr Nechaev <petr.nechaev@cogentembedded.com> +Date: Wed, 28 Oct 2015 16:24:28 +0300 +Subject: [PATCH 1/1] Cleanup TI ADS111x ADC driver + +* added locks for simultaneous multi-channel read +* fixed error messages +* converted result to signed 16-bit value +* added safeguard against channel numbers > 7 +--- + drivers/iio/adc/ti-ads111x.c | 96 ++++++++++++++++++++++++++++++-------------- + 1 file changed, 66 insertions(+), 30 deletions(-) + +diff --git a/drivers/iio/adc/ti-ads111x.c b/drivers/iio/adc/ti-ads111x.c +index 98a28e3e..bf91e04 100644 +--- a/drivers/iio/adc/ti-ads111x.c ++++ b/drivers/iio/adc/ti-ads111x.c +@@ -12,7 +12,6 @@ + #include <linux/delay.h> + + #include <linux/iio/iio.h> +-#include <linux/regulator/consumer.h> + + #define REG_CONV_RES 0x00 + #define REG_CONFIG 0x01 +@@ -22,7 +21,7 @@ + /* CONFIG reg bits */ + /* w: begin sigle conversion; r: operation status */ + #define CONFIG_OS (1 << 15) +-#define CONFIG_CH(n) (n << 12) ++#define CONFIG_CH(n) ((n & 0x7) << 12) + /* ADS1114/5 only */ + #define CONFIG_PGA_6144 (0 << 9) + #define CONFIG_PGA_4096 (1 << 9) +@@ -80,7 +79,7 @@ enum ads1115_modes { + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "AIN"#num, \ + .scan_type = { \ +- .sign = 'u', \ ++ .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ +@@ -100,7 +99,7 @@ enum ads1115_modes { + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "AIN"#num"-AIN"#num2, \ + .scan_type = { \ +- .sign = 'u', \ ++ .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ +@@ -120,45 +119,81 @@ static const struct iio_chan_spec ads111x_channels[] = { + ADS1115_CHAN_U(3, p3), + }; + +-static int ads111x_read_raw(struct iio_dev *iio, +- struct iio_chan_spec const *channel, int *value, +- int *shift, long mask) ++static int ads111x_read_single_channel(struct iio_dev *iio, ++ struct iio_chan_spec const *channel, ++ int *value) + { +- struct ads111x *adc = iio_priv(iio); + u16 config_reg; +- int err; ++ int ret; ++ int iter; ++ struct ads111x *adc = iio_priv(iio); + +- switch (mask) { +- case IIO_CHAN_INFO_RAW: +- config_reg = ++ mutex_lock(&iio->mlock); ++ ++ /* start the conversion */ ++ config_reg = + CONFIG_OS | + CONFIG_CH(channel->address) | + CONFIG_PGA_2048 | + CONFIG_MODE_SINGLE | + CONFIG_DR_860SPS | + CONFIG_COMP_QUE_DIS; +- err = i2c_smbus_write_word_swapped(adc->i2c, REG_CONFIG, config_reg); +- if (err < 0) { +- dev_err(&iio->dev, "REG_CONFIG write err %d\n", err); +- return err; +- } +- mdelay(1); +- err = i2c_smbus_read_word_swapped(adc->i2c, REG_CONFIG); +- if (err < 0) { +- dev_err(&iio->dev, "REG_CONFIG read err %d\n", err); +- return err; ++ ret = i2c_smbus_write_word_swapped(adc->i2c, REG_CONFIG, config_reg); ++ if (ret < 0) { ++ dev_err(&iio->dev, "Error writing to REG_CONFIG %d\n", ret); ++ goto error_ret; ++ } ++ ++ for (iter = 0; iter < 2; iter++) ++ { ++ /* wait for conversion to complete */ ++ ret = msleep_interruptible(2); ++ if (ret < 0) ++ goto error_ret; ++ ++ /* read status */ ++ ret = i2c_smbus_read_word_swapped(adc->i2c, REG_CONFIG); ++ if (ret < 0) { ++ dev_err(&iio->dev, "Error reading REG_CONFIG %d\n", ret); ++ goto error_ret; + } +- if (err & CONFIG_OS) { +- dev_err(&iio->dev, "ADC not ready 0x%04x\n", err); +- return -EBUSY; ++ if ((ret & CONFIG_OS) != CONFIG_OS) { ++ dev_err(&iio->dev, "ADC not ready 0x%04x\n", ret); ++ ret = -EBUSY; ++ continue; + } +- err = i2c_smbus_read_word_swapped(adc->i2c, REG_CONV_RES); +- if (err < 0) { +- dev_err(&iio->dev, "REG_CONV_RES read err %d\n", err); ++ } ++ if (ret < 0) ++ goto error_ret; ++ ++ /* read value */ ++ ret = i2c_smbus_read_word_swapped(adc->i2c, REG_CONV_RES); ++ if (ret < 0) { ++ dev_err(&iio->dev, "Error reading REG_CONV_RES %d\n", ret); ++ goto error_ret; ++ } ++ ++ *value = (s16) (ret & 0xFFFF); ++ ret = 1; ++ ++error_ret: ++ mutex_unlock(&iio->mlock); ++ return ret; ++ ++} ++ ++static int ads111x_read_raw(struct iio_dev *iio, ++ struct iio_chan_spec const *channel, int *value, ++ int *shift, long mask) ++{ ++ int err; ++ ++ switch (mask) { ++ case IIO_CHAN_INFO_RAW: ++ err = ads111x_read_single_channel(iio, channel, value); ++ if (err < 0) + return err; +- } + +- *value = err; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: +-- +2.4.3 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0059-crypto-aead-Add-crypto_aead_set_reqsize-helper.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0059-crypto-aead-Add-crypto_aead_set_reqsize-helper.patch new file mode 100644 index 0000000..6efee7b --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/0059-crypto-aead-Add-crypto_aead_set_reqsize-helper.patch @@ -0,0 +1,32 @@ +From 827723430bd488d9b378ae4bfecfaa9ab198c489 Mon Sep 17 00:00:00 2001 +From: Herbert Xu <herbert@gondor.apana.org.au> +Date: Mon, 11 May 2015 17:47:52 +0800 +Subject: [PATCH] crypto: aead - Add crypto_aead_set_reqsize helper + +This patch adds the helper crypto_aead_set_reqsize so that people +don't have to directly access the aead internals to set the reqsize. + +Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + include/crypto/internal/aead.h | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/include/crypto/internal/aead.h b/include/crypto/internal/aead.h +index 2eba340..750948c 100644 +--- a/include/crypto/internal/aead.h ++++ b/include/crypto/internal/aead.h +@@ -78,5 +78,11 @@ static inline void aead_givcrypt_complete(struct aead_givcrypt_request *req, + aead_request_complete(&req->areq, err); + } + ++static inline void crypto_aead_set_reqsize(struct crypto_aead *aead, ++ unsigned int reqsize) ++{ ++ crypto_aead_crt(aead)->reqsize = reqsize; ++} ++ + #endif /* _CRYPTO_INTERNAL_AEAD_H */ + +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter.cfg b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter.cfg index 5b16fb7..74452bd 100644 --- a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter.cfg +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter.cfg @@ -1,3 +1,21 @@ CONFIG_CAN=y -CONFIG_CAN_VCAN=y CONFIG_CAN_RCAR=y +CONFIG_VIDEO_RENESAS_JPU=y +CONFIG_RAVB=y +CONFIG_CGROUPS=y +CONFIG_RELAY=y +CONFIG_PADATA=y +CONFIG_INPUT_FF_MEMLESS=y +CONFIG_INPUT_JOYDEV=y +CONFIG_INPUT_MISC=y +CONFIG_SPI_GPIO=y +CONFIG_HID_LOGITECH=y +CONFIG_LOGIWHEELS_FF=y +CONFIG_HID_MULTITOUCH=y +CONFIG_USB_SERIAL=y +CONFIG_USB_SERIAL_CONSOLE=y +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_PL2303=y +CONFIG_EXT4_FS=y +CONFIG_JBD2=y +CONFIG_DEBUG_FS=y diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0002-Porter-adapt-max9272-ov10635-driver-for-porter-exp-b.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0002-Porter-adapt-max9272-ov10635-driver-for-porter-exp-b.patch new file mode 100644 index 0000000..eef5751 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0002-Porter-adapt-max9272-ov10635-driver-for-porter-exp-b.patch @@ -0,0 +1,60 @@ +From f54cc5cd6059fd65d88040805a5da72e894ad9de Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Thu, 28 Jan 2016 19:46:49 +0300 +Subject: [PATCH 02/10] Porter: adapt max9272-ov10635 driver for porter exp + board + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/media/i2c/soc_camera/max9272_ov10635.h | 4 ++-- + drivers/media/i2c/soc_camera/max9272_ov10635_setup.c | 12 ++++++------ + 2 files changed, 8 insertions(+), 8 deletions(-) + +diff --git a/drivers/media/i2c/soc_camera/max9272_ov10635.h b/drivers/media/i2c/soc_camera/max9272_ov10635.h +index de1d351..80e9af4 100644 +--- a/drivers/media/i2c/soc_camera/max9272_ov10635.h ++++ b/drivers/media/i2c/soc_camera/max9272_ov10635.h +@@ -17,8 +17,8 @@ + #define dev_dbg dev_info + #endif + +-#define MAX9275_PRESENT /* MAX9275 presents on I2C bus */ +-#define MAXIM_NUM 4 /* number of cameras */ ++//#define MAX9275_PRESENT /* MAX9275 presents on I2C bus */ ++#define MAXIM_NUM 1 /* number of cameras */ + #define MAXIM_NUM_RETRIES 1 /* number of read/write retries */ + + #define MAXIM_I2C_I2C_SPEED_400KHZ (0x5 << 2) /* 339 kbps */ +diff --git a/drivers/media/i2c/soc_camera/max9272_ov10635_setup.c b/drivers/media/i2c/soc_camera/max9272_ov10635_setup.c +index 0d4db0f..d01eb5c 100644 +--- a/drivers/media/i2c/soc_camera/max9272_ov10635_setup.c ++++ b/drivers/media/i2c/soc_camera/max9272_ov10635_setup.c +@@ -39,6 +39,12 @@ static int maxim_probe(struct i2c_client *client, + } + } + ++ tmp_addr = client->addr; ++ ++ /* power up camera */ ++ client->addr = 0x48; /* MAX9272-CAM0 I2C */ ++ maxim_reg8_write(client, 0x0e, 0x00); /* GP0 low - enable power for camera */ ++ + /* + * Powered MCU IMI cameras need delay between power-on and R-Car access to avoid + * i2c bus conflicts since linux kernel does not support i2c multi-mastering, +@@ -47,12 +53,6 @@ static int maxim_probe(struct i2c_client *client, + */ + mdelay(MAXIM_IMI_MCU_DELAY); + +- tmp_addr = client->addr; +- +- /* power down cascaded MAX9272 chips */ +- client->addr = 0x48; /* MAX9272-CAM0 I2C */ +- maxim_reg8_write(client, 0x0e, 0x02); /* GP0 high */ +- + #ifdef MAX9275_PRESENT + /* + * SETUP MAX9275 I2C +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0003-Porter-add-LVDS-camera.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0003-Porter-add-LVDS-camera.patch new file mode 100644 index 0000000..45a93cf --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0003-Porter-add-LVDS-camera.patch @@ -0,0 +1,88 @@ +From 106ac216eb15205cdcbbd681662b3d018670ca54 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Mon, 30 May 2016 16:16:58 +0300 +Subject: [PATCH] Porter: add LVDS camera + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + arch/arm/mach-shmobile/board-porter-reference.c | 21 +++++++++++++++++---- + 1 file changed, 17 insertions(+), 4 deletions(-) + +diff --git a/arch/arm/mach-shmobile/board-porter-reference.c b/arch/arm/mach-shmobile/board-porter-reference.c +index a286a80..a3af264 100644 +--- a/arch/arm/mach-shmobile/board-porter-reference.c ++++ b/arch/arm/mach-shmobile/board-porter-reference.c +@@ -148,6 +148,7 @@ static const struct clk_name clk_names[] __initconst = { + { "lvds0", "lvds.0", "rcar-du-r8a7791" }, + { "hsusb", NULL, "usb_phy_rcar_gen2" }, + { "vin0", NULL, "r8a7791-vin.0" }, ++ { "vin2", NULL, "r8a7791-vin.2" }, + { "vsps", NULL, NULL }, + #if IS_ENABLED(CONFIG_VIDEO_RENESAS_VSP1) && \ + !defined(CONFIG_DRM_RCAR_DU_CONNECT_VSP) +@@ -673,6 +674,9 @@ static const struct resource vin_resources[] __initconst = { + /* VIN1 */ + DEFINE_RES_MEM(0xe6ef1000, 0x1000), + DEFINE_RES_IRQ(gic_spi(189)), ++ /* VIN2 */ ++ DEFINE_RES_MEM(0xe6ef2000, 0x1000), ++ DEFINE_RES_IRQ(gic_spi(190)), + }; + + static void __init porter_add_vin_device(unsigned idx, +@@ -689,12 +693,12 @@ static void __init porter_add_vin_device(unsigned idx, + .size_data = sizeof(*pdata), + }; + +- BUG_ON(idx > 1); ++ BUG_ON(idx > 2); + + platform_device_register_full(&vin_info); + } + +-#define PORTER_CAMERA(idx, name, addr, pdata, flag) \ ++#define PORTER_CAMERA(idx, name, bus, addr, pdata, flag) \ + static struct i2c_board_info i2c_cam##idx##_device = { \ + I2C_BOARD_INFO(name, addr), \ + }; \ +@@ -706,12 +710,13 @@ static struct rcar_vin_platform_data vin##idx##_pdata = { \ + static struct soc_camera_link cam##idx##_link = { \ + .bus_id = idx, \ + .board_info = &i2c_cam##idx##_device, \ +- .i2c_adapter_id = 2, \ ++ .i2c_adapter_id = bus, \ + .module_name = name, \ + .priv = pdata, \ + } + +-PORTER_CAMERA(0, "adv7180", 0x20, NULL, RCAR_VIN_BT656); ++PORTER_CAMERA(0, "adv7180", 2, 0x20, NULL, RCAR_VIN_BT656); ++PORTER_CAMERA(2, "max9272-ov10635", 0, 0x30 + 1, NULL, RCAR_VIN_BT656); + + static void __init porter_add_camera0_device(void) + { +@@ -720,6 +725,13 @@ static void __init porter_add_camera0_device(void) + porter_add_vin_device(0, &vin0_pdata); + } + ++static void __init porter_add_camera2_device(void) ++{ ++ platform_device_register_data(&platform_bus, "soc-camera-pdrv", 2, ++ &cam2_link, sizeof(cam2_link)); ++ porter_add_vin_device(2, &vin2_pdata); ++} ++ + /* VSP1 */ + #if IS_ENABLED(CONFIG_VIDEO_RENESAS_VSP1) && \ + !defined(CONFIG_DRM_RCAR_DU_CONNECT_VSP) +@@ -911,6 +923,7 @@ static void __init porter_add_standard_devices(void) + porter_add_du_device(); + porter_add_usb_devices(); + porter_add_camera0_device(); ++ porter_add_camera2_device(); + #if IS_ENABLED(CONFIG_VIDEO_RENESAS_VSP1) && \ + !defined(CONFIG_DRM_RCAR_DU_CONNECT_VSP) + porter_add_vsp1_devices(); +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0004-GPIO-CPLD-gpio-extender-driver.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0004-GPIO-CPLD-gpio-extender-driver.patch new file mode 100644 index 0000000..5efac9f --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0004-GPIO-CPLD-gpio-extender-driver.patch @@ -0,0 +1,248 @@ +From 2a3918e3dc0cd00c7abf52a0d13dafb458f22acd Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Fri, 29 Jan 2016 15:50:00 +0300 +Subject: [PATCH 04/10] GPIO: CPLD gpio extender driver + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/gpio/Kconfig | 7 ++ + drivers/gpio/Makefile | 1 + + drivers/gpio/gpio-cpld.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 203 insertions(+) + create mode 100644 drivers/gpio/gpio-cpld.c + +diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig +index 4389778..12a2f8a 100644 +--- a/drivers/gpio/Kconfig ++++ b/drivers/gpio/Kconfig +@@ -209,6 +209,13 @@ config GPIO_RCAR + help + Say yes here to support GPIO on Renesas R-Car SoCs. + ++config GPIO_PORTER_CPLD ++ bool "Renesas Porter extension board CPLD gpios" ++ depends on ARM ++ help ++ Say yes here to support GPIOs on Renesas Porter ++ extension board from CogentEmbedded. ++ + config GPIO_SPEAR_SPICS + bool "ST SPEAr13xx SPI Chip Select as GPIO support" + depends on PLAT_SPEAR +diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile +index 2636985..8e66571 100644 +--- a/drivers/gpio/Makefile ++++ b/drivers/gpio/Makefile +@@ -59,6 +59,7 @@ obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o + obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o + obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o + obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o ++obj-$(CONFIG_GPIO_PORTER_CPLD) += gpio-cpld.o + obj-$(CONFIG_PLAT_SAMSUNG) += gpio-samsung.o + obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o + obj-$(CONFIG_GPIO_SCH) += gpio-sch.o +diff --git a/drivers/gpio/gpio-cpld.c b/drivers/gpio/gpio-cpld.c +new file mode 100644 +index 0000000..25b57a3 +--- /dev/null ++++ b/drivers/gpio/gpio-cpld.c +@@ -0,0 +1,195 @@ ++/* ++ * r8a7791-porter extension board CPLD access driver ++ * ++ * Copyright (C) 2016 Cogent Embedded, Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/of_device.h> ++#include <linux/of_gpio.h> ++#include <linux/gpio.h> ++ ++struct porter_cpld_gpio_priv { ++ unsigned data_gpio; /* data gpio */ ++ unsigned ptrc_gpio; /* pointer clock gpio */ ++ unsigned bufc_gpio; /* buffer clock gpio */ ++ ++ struct gpio_chip gpio_chip; ++ ++ uint16_t state; /* saved gpios state */ ++}; ++ ++static int porter_cpld_gpio_clock_val(struct porter_cpld_gpio_priv *priv, ++ int bit, int val) ++{ ++ int i; ++ ++ /* reset pointer */ ++ gpio_set_value(priv->data_gpio, 1); ++ gpio_set_value(priv->ptrc_gpio, 1); ++ gpio_set_value(priv->data_gpio, 0); ++ gpio_set_value(priv->ptrc_gpio, 0); ++ ++ for (i = 0; i < bit; i++) { ++ gpio_set_value(priv->ptrc_gpio, 1); ++ gpio_set_value(priv->ptrc_gpio, 0); ++ } ++ ++ gpio_set_value(priv->data_gpio, val); ++ gpio_set_value(priv->bufc_gpio, 1); ++ gpio_set_value(priv->bufc_gpio, 0); ++ ++ if (val) ++ priv->state |= (1 << bit); ++ else ++ priv->state &= ~(1 << bit); ++ ++ return 0; ++} ++ ++static int porter_cpld_gpio_get_value(struct gpio_chip *gc, unsigned off) ++{ ++ struct porter_cpld_gpio_priv *priv; ++ ++ priv = container_of(gc, struct porter_cpld_gpio_priv, gpio_chip); ++ ++ return !!(priv->state & (1 << off)); ++} ++ ++static void porter_cpld_gpio_set_value(struct gpio_chip *gc, unsigned off, int val) ++{ ++ struct porter_cpld_gpio_priv *priv; ++ ++ priv = container_of(gc, struct porter_cpld_gpio_priv, gpio_chip); ++ ++ porter_cpld_gpio_clock_val(priv, off, val); ++} ++ ++static int porter_cpld_gpio_direction_input(struct gpio_chip *gc, unsigned off) ++{ ++ return -EPERM; ++} ++ ++static int porter_cpld_gpio_direction_output(struct gpio_chip *gc, unsigned off, ++ int val) ++{ ++ struct porter_cpld_gpio_priv *priv; ++ ++ priv = container_of(gc, struct porter_cpld_gpio_priv, gpio_chip); ++ ++ return porter_cpld_gpio_clock_val(priv, off, val); ++} ++ ++static struct porter_cpld_gpio_priv *porter_cpld_gpio_parse_dt(struct device *dev) ++{ ++ struct device_node *np = dev->of_node; ++ struct porter_cpld_gpio_priv *priv; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return NULL; ++ ++ priv->data_gpio = of_get_named_gpio(np, "porter-cpld-gpio,data-gpio", 0); ++ if (priv->data_gpio < 0) ++ return NULL; ++ ++ priv->ptrc_gpio = of_get_named_gpio(np, "porter-cpld-gpio,ptrc-gpio", 0); ++ if (priv->ptrc_gpio < 0) ++ return NULL; ++ ++ priv->bufc_gpio = of_get_named_gpio(np, "porter-cpld-gpio,bufc-gpio", 0); ++ if (priv->bufc_gpio < 0) ++ return NULL; ++ ++ return priv; ++} ++ ++static void porter_cpld_gpio_init(struct porter_cpld_gpio_priv *priv) ++{ ++ gpio_request_one(priv->data_gpio, GPIOF_OUT_INIT_HIGH, "cpld-data"); ++ gpio_request_one(priv->ptrc_gpio, GPIOF_OUT_INIT_HIGH, "cpld-ptrc"); ++ gpio_request_one(priv->bufc_gpio, GPIOF_OUT_INIT_HIGH, "cpld-bufc"); ++} ++ ++static int porter_cpld_gpio_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct porter_cpld_gpio_priv *priv; ++ struct gpio_chip *gc; ++ int ret; ++ ++ priv = porter_cpld_gpio_parse_dt(dev); ++ if (!priv) ++ return -EPROBE_DEFER; ++ ++ /* request gpios */ ++ porter_cpld_gpio_init(priv); ++ ++ priv->state = 0x0; ++ gc = &priv->gpio_chip; ++ gc->of_node = pdev->dev.of_node; ++ gc->direction_input = porter_cpld_gpio_direction_input; ++ gc->direction_output = porter_cpld_gpio_direction_output; ++ gc->get = porter_cpld_gpio_get_value; ++ gc->set = porter_cpld_gpio_set_value; ++ ++ gc->base = -1; ++ gc->ngpio = 16; ++ gc->label = "porter-gpio"; ++ gc->owner = THIS_MODULE; ++ ++ ret = gpiochip_add(&priv->gpio_chip); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int porter_cpld_gpio_remove(struct platform_device *pdev) ++{ ++ int ret; ++ struct device *dev = &pdev->dev; ++ struct porter_cpld_gpio_priv *priv = dev_get_drvdata(dev); ++ ++ ret = gpiochip_remove(&priv->gpio_chip); ++ if (ret) { ++ dev_err(dev, "gpiochip_remove failed %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct of_device_id porter_cpld_gpio_dt_ids[] = { ++ { .compatible = "porter-cpld-gpio" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, porter_cpld_gpio_dt_ids); ++ ++static struct platform_driver porter_cpld_gpio_driver = { ++ .driver = { ++ .name = "porter-cpld-gpio", ++ .owner = THIS_MODULE, ++ .of_match_table = of_match_ptr(porter_cpld_gpio_dt_ids), ++ }, ++ .probe = porter_cpld_gpio_probe, ++ .remove = porter_cpld_gpio_remove, ++}; ++ ++static int __init porter_cpld_gpio_driver_init(void) ++{ ++ return platform_driver_register(&porter_cpld_gpio_driver); ++} ++ ++static void __exit porter_cpld_gpio_driver_exit(void) ++{ ++ platform_driver_unregister(&porter_cpld_gpio_driver); ++} ++ ++subsys_initcall_sync(porter_cpld_gpio_driver_init); ++module_exit(porter_cpld_gpio_driver_exit); +\ No newline at end of file +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0012-regmap-Implemented-default-cache-sync-operation.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0012-regmap-Implemented-default-cache-sync-operation.patch new file mode 100644 index 0000000..b00cfc8 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0012-regmap-Implemented-default-cache-sync-operation.patch @@ -0,0 +1,102 @@ +From 2de1db4a592a5f18d1b3082fb93deb761b7477fb Mon Sep 17 00:00:00 2001 +From: Maarten ter Huurne <maarten@treewalker.org> +Date: Mon, 3 Jun 2013 00:15:26 +0200 +Subject: [PATCH 12/16] regmap: Implemented default cache sync operation + +This can be used for cache types for which syncing values one by one is +equally efficient as syncing a range, such as the flat cache. + +Signed-off-by: Maarten ter Huurne <maarten@treewalker.org> +Signed-off-by: Mark Brown <broonie@linaro.org> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/base/regmap/regcache.c | 46 ++++++++++++++++++++++++++++++++++++---- + 1 file changed, 42 insertions(+), 4 deletions(-) + +diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c +index 46283fd..ffd8db6 100644 +--- a/drivers/base/regmap/regcache.c ++++ b/drivers/base/regmap/regcache.c +@@ -250,6 +250,38 @@ int regcache_write(struct regmap *map, + return 0; + } + ++static int regcache_default_sync(struct regmap *map, unsigned int min, ++ unsigned int max) ++{ ++ unsigned int reg; ++ ++ for (reg = min; reg <= max; reg++) { ++ unsigned int val; ++ int ret; ++ ++ if (regmap_volatile(map, reg)) ++ continue; ++ ++ ret = regcache_read(map, reg, &val); ++ if (ret) ++ return ret; ++ ++ /* Is this the hardware default? If so skip. */ ++ ret = regcache_lookup_reg(map, reg); ++ if (ret >= 0 && val == map->reg_defaults[ret].def) ++ continue; ++ ++ map->cache_bypass = 1; ++ ret = _regmap_write(map, reg, val); ++ map->cache_bypass = 0; ++ if (ret) ++ return ret; ++ dev_dbg(map->dev, "Synced register %#x, value %#x\n", reg, val); ++ } ++ ++ return 0; ++} ++ + /** + * regcache_sync: Sync the register cache with the hardware. + * +@@ -268,7 +300,7 @@ int regcache_sync(struct regmap *map) + const char *name; + unsigned int bypass; + +- BUG_ON(!map->cache_ops || !map->cache_ops->sync); ++ BUG_ON(!map->cache_ops); + + map->lock(map->lock_arg); + /* Remember the initial bypass state */ +@@ -297,7 +329,10 @@ int regcache_sync(struct regmap *map) + } + map->cache_bypass = 0; + +- ret = map->cache_ops->sync(map, 0, map->max_register); ++ if (map->cache_ops->sync) ++ ret = map->cache_ops->sync(map, 0, map->max_register); ++ else ++ ret = regcache_default_sync(map, 0, map->max_register); + + if (ret == 0) + map->cache_dirty = false; +@@ -331,7 +366,7 @@ int regcache_sync_region(struct regmap *map, unsigned int min, + const char *name; + unsigned int bypass; + +- BUG_ON(!map->cache_ops || !map->cache_ops->sync); ++ BUG_ON(!map->cache_ops); + + map->lock(map->lock_arg); + +@@ -346,7 +381,10 @@ int regcache_sync_region(struct regmap *map, unsigned int min, + if (!map->cache_dirty) + goto out; + +- ret = map->cache_ops->sync(map, min, max); ++ if (map->cache_ops->sync) ++ ret = map->cache_ops->sync(map, min, max); ++ else ++ ret = regcache_default_sync(map, min, max); + + out: + trace_regcache_sync(map->dev, name, "stop region"); +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0013-regmap-cache-Don-t-attempt-to-sync-non-writeable-reg.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0013-regmap-cache-Don-t-attempt-to-sync-non-writeable-reg.patch new file mode 100644 index 0000000..c97928d --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0013-regmap-cache-Don-t-attempt-to-sync-non-writeable-reg.patch @@ -0,0 +1,34 @@ +From 20f963cde94f375c8f587b3866e6ef3ca6e3218c Mon Sep 17 00:00:00 2001 +From: Dylan Reid <dgreid@chromium.org> +Date: Tue, 18 Mar 2014 13:45:09 -0700 +Subject: [PATCH 13/16] regmap: cache: Don't attempt to sync non-writeable + registers + +In the regcache_default_sync, if a register isn't writeable, then +_regmap_write will return an error and the rest of the sync will be +aborted. Avoid this by checking if a register is writeable before +trying to sync it. + +Signed-off-by: Dylan Reid <dgreid@chromium.org> +Signed-off-by: Mark Brown <broonie@linaro.org> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/base/regmap/regcache.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c +index ffd8db6..3ec0bfd 100644 +--- a/drivers/base/regmap/regcache.c ++++ b/drivers/base/regmap/regcache.c +@@ -259,7 +259,8 @@ static int regcache_default_sync(struct regmap *map, unsigned int min, + unsigned int val; + int ret; + +- if (regmap_volatile(map, reg)) ++ if (regmap_volatile(map, reg) || ++ !regmap_writeable(map, reg)) + continue; + + ret = regcache_read(map, reg, &val); +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0014-ASoC-Add-SOC_DOUBLE_STS-macro.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0014-ASoC-Add-SOC_DOUBLE_STS-macro.patch new file mode 100644 index 0000000..58d8d28 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0014-ASoC-Add-SOC_DOUBLE_STS-macro.patch @@ -0,0 +1,35 @@ +From 563ca49681d5393ed295310a7835660642f59677 Mon Sep 17 00:00:00 2001 +From: "Damien.Horsley" <Damien.Horsley@imgtec.com> +Date: Tue, 8 Dec 2015 15:58:58 +0000 +Subject: [PATCH 14/16] ASoC: Add SOC_DOUBLE_STS macro + +Add SOC_DOUBLE_STS macro for read-only volatile status controls + +Signed-off-by: Damien.Horsley <Damien.Horsley@imgtec.com> +Signed-off-by: Mark Brown <broonie@kernel.org> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + include/sound/soc.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/include/sound/soc.h b/include/sound/soc.h +index 645bea2..936bddb 100644 +--- a/include/sound/soc.h ++++ b/include/sound/soc.h +@@ -99,6 +99,14 @@ + .put = snd_soc_put_volsw, \ + .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \ + max, invert) } ++#define SOC_DOUBLE_STS(xname, reg, shift_left, shift_right, max, invert) \ ++{ \ ++ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ ++ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ ++ .access = SNDRV_CTL_ELEM_ACCESS_READ | \ ++ SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ ++ .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \ ++ max, invert) } + #define SOC_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .info = snd_soc_info_volsw, \ +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0015-ASoC-pcm3168a-Add-binding-document-for-pcm3168a-code.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0015-ASoC-pcm3168a-Add-binding-document-for-pcm3168a-code.patch new file mode 100644 index 0000000..f4b8e53 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0015-ASoC-pcm3168a-Add-binding-document-for-pcm3168a-code.patch @@ -0,0 +1,72 @@ +From c4b652e226dbd995041addeb934f2a65c82b5bef Mon Sep 17 00:00:00 2001 +From: "Damien.Horsley" <Damien.Horsley@imgtec.com> +Date: Tue, 8 Dec 2015 15:58:59 +0000 +Subject: [PATCH 15/16] ASoC: pcm3168a: Add binding document for pcm3168a + codec + +Add binding document for Texas Instruments pcm3168a codec + +Signed-off-by: Damien.Horsley <Damien.Horsley@imgtec.com> +Signed-off-by: Mark Brown <broonie@kernel.org> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + .../devicetree/bindings/sound/ti,pcm3168a.txt | 48 ++++++++++++++++++++ + 1 file changed, 48 insertions(+) + create mode 100644 Documentation/devicetree/bindings/sound/ti,pcm3168a.txt + +diff --git a/Documentation/devicetree/bindings/sound/ti,pcm3168a.txt b/Documentation/devicetree/bindings/sound/ti,pcm3168a.txt +new file mode 100644 +index 0000000..5d9cb84 +--- /dev/null ++++ b/Documentation/devicetree/bindings/sound/ti,pcm3168a.txt +@@ -0,0 +1,48 @@ ++Texas Instruments pcm3168a DT bindings ++ ++This driver supports both SPI and I2C bus access for this codec ++ ++Required properties: ++ ++ - compatible: "ti,pcm3168a" ++ ++ - clocks : Contains an entry for each entry in clock-names ++ ++ - clock-names : Includes the following entries: ++ "scki" The system clock ++ ++ - VDD1-supply : Digital power supply regulator 1 (+3.3V) ++ ++ - VDD2-supply : Digital power supply regulator 2 (+3.3V) ++ ++ - VCCAD1-supply : ADC power supply regulator 1 (+5V) ++ ++ - VCCAD2-supply : ADC power supply regulator 2 (+5V) ++ ++ - VCCDA1-supply : DAC power supply regulator 1 (+5V) ++ ++ - VCCDA2-supply : DAC power supply regulator 2 (+5V) ++ ++For required properties on SPI/I2C, consult SPI/I2C device tree documentation ++ ++Examples: ++ ++i2c0: i2c0@0 { ++ ++ ... ++ ++ pcm3168a: audio-codec@44 { ++ compatible = "ti,pcm3168a"; ++ reg = <0x44>; ++ clocks = <&clk_core CLK_AUDIO>; ++ clock-names = "scki"; ++ VDD1-supply = <&supply3v3>; ++ VDD2-supply = <&supply3v3>; ++ VCCAD1-supply = <&supply5v0>; ++ VCCAD2-supply = <&supply5v0>; ++ VCCDA1-supply = <&supply5v0>; ++ VCCDA2-supply = <&supply5v0>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&dac_clk_pin>; ++ }; ++}; +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0016-ASoC-pcm3168a-Add-driver-for-pcm3168a-codec.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0016-ASoC-pcm3168a-Add-driver-for-pcm3168a-codec.patch new file mode 100644 index 0000000..24582bf --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0016-ASoC-pcm3168a-Add-driver-for-pcm3168a-codec.patch @@ -0,0 +1,1108 @@ +From 3fa260164b3f5b8f3faa6ada700a3e7addd88c87 Mon Sep 17 00:00:00 2001 +From: "Damien.Horsley" <Damien.Horsley@imgtec.com> +Date: Tue, 8 Dec 2015 15:59:00 +0000 +Subject: [PATCH 16/16] ASoC: pcm3168a: Add driver for pcm3168a codec + +Add driver for Texas Instruments pcm3168a codec + +Signed-off-by: Damien.Horsley <Damien.Horsley@imgtec.com> +Signed-off-by: Mark Brown <broonie@kernel.org> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + sound/soc/codecs/Kconfig | 19 + + sound/soc/codecs/Makefile | 6 + + sound/soc/codecs/pcm3168a-i2c.c | 66 ++++ + sound/soc/codecs/pcm3168a-spi.c | 65 ++++ + sound/soc/codecs/pcm3168a.c | 767 +++++++++++++++++++++++++++++++++++++++ + sound/soc/codecs/pcm3168a.h | 100 +++++ + 6 files changed, 1023 insertions(+) + create mode 100644 sound/soc/codecs/pcm3168a-i2c.c + create mode 100644 sound/soc/codecs/pcm3168a-spi.c + create mode 100644 sound/soc/codecs/pcm3168a.c + create mode 100644 sound/soc/codecs/pcm3168a.h + +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +index 2f45f00..4f0c01a 100644 +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -55,6 +55,8 @@ config SND_SOC_ALL_CODECS + select SND_SOC_ML26124 if I2C + select SND_SOC_OMAP_HDMI_CODEC if OMAP4_DSS_HDMI + select SND_SOC_PCM3008 ++ select SND_SOC_PCM3168A_I2C if I2C ++ select SND_SOC_PCM3168A_SPI if SPI_MASTER + select SND_SOC_RT5631 if I2C + select SND_SOC_SGTL5000 if I2C + select SND_SOC_SI476X if MFD_SI476X_CORE +@@ -293,6 +295,23 @@ config SND_SOC_OMAP_HDMI_CODEC + config SND_SOC_PCM3008 + tristate + ++config SND_SOC_PCM3168A ++ tristate ++ ++config SND_SOC_PCM3168A_I2C ++ tristate "Texas Instruments PCM3168A CODEC - I2C" ++ depends on I2C ++ select SND_SOC_PCM3168A ++ select REGMAP_I2C ++ ++config SND_SOC_PCM3168A_SPI ++ tristate "Texas Instruments PCM3168A CODEC - SPI" ++ depends on SPI_MASTER ++ select SND_SOC_PCM3168A ++ select REGMAP_SPI ++ ++ config SND_SOC_PCM512x ++ tristate + config SND_SOC_RT5631 + tristate + +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +index b9e41c9..513d895 100644 +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -43,6 +43,9 @@ snd-soc-mc13783-objs := mc13783.o + snd-soc-ml26124-objs := ml26124.o + snd-soc-omap-hdmi-codec-objs := omap-hdmi.o + snd-soc-pcm3008-objs := pcm3008.o ++snd-soc-pcm3168a-objs := pcm3168a.o ++snd-soc-pcm3168a-i2c-objs := pcm3168a-i2c.o ++snd-soc-pcm3168a-spi-objs := pcm3168a-spi.o + snd-soc-rt5631-objs := rt5631.o + snd-soc-sgtl5000-objs := sgtl5000.o + snd-soc-alc5623-objs := alc5623.o +@@ -170,6 +173,9 @@ obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o + obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o + obj-$(CONFIG_SND_SOC_OMAP_HDMI_CODEC) += snd-soc-omap-hdmi-codec.o + obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o ++obj-$(CONFIG_SND_SOC_PCM3168A) += snd-soc-pcm3168a.o ++obj-$(CONFIG_SND_SOC_PCM3168A_I2C) += snd-soc-pcm3168a-i2c.o ++obj-$(CONFIG_SND_SOC_PCM3168A_SPI) += snd-soc-pcm3168a-spi.o + obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o + obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o + obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o +diff --git a/sound/soc/codecs/pcm3168a-i2c.c b/sound/soc/codecs/pcm3168a-i2c.c +new file mode 100644 +index 0000000..6feb090 +--- /dev/null ++++ b/sound/soc/codecs/pcm3168a-i2c.c +@@ -0,0 +1,66 @@ ++/* ++ * PCM3168A codec i2c driver ++ * ++ * Copyright (C) 2015 Imagination Technologies Ltd. ++ * ++ * Author: Damien Horsley <Damien.Horsley@imgtec.com> ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ */ ++ ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <linux/module.h> ++ ++#include <sound/soc.h> ++ ++#include "pcm3168a.h" ++ ++static int pcm3168a_i2c_probe(struct i2c_client *i2c, ++ const struct i2c_device_id *id) ++{ ++ struct regmap *regmap; ++ ++ regmap = devm_regmap_init_i2c(i2c, &pcm3168a_regmap); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ return pcm3168a_probe(&i2c->dev, regmap); ++} ++ ++static int pcm3168a_i2c_remove(struct i2c_client *i2c) ++{ ++ pcm3168a_remove(&i2c->dev); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id pcm3168a_i2c_id[] = { ++ { "pcm3168a", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, pcm3168a_i2c_id); ++ ++static const struct of_device_id pcm3168a_of_match[] = { ++ { .compatible = "ti,pcm3168a", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, pcm3168a_of_match); ++ ++static struct i2c_driver pcm3168a_i2c_driver = { ++ .probe = pcm3168a_i2c_probe, ++ .remove = pcm3168a_i2c_remove, ++ .id_table = pcm3168a_i2c_id, ++ .driver = { ++ .name = "pcm3168a", ++ .of_match_table = pcm3168a_of_match, ++ .pm = &pcm3168a_pm_ops, ++ }, ++}; ++module_i2c_driver(pcm3168a_i2c_driver); ++ ++MODULE_DESCRIPTION("PCM3168A I2C codec driver"); ++MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>"); ++MODULE_LICENSE("GPL v2"); +diff --git a/sound/soc/codecs/pcm3168a-spi.c b/sound/soc/codecs/pcm3168a-spi.c +new file mode 100644 +index 0000000..03945a2 +--- /dev/null ++++ b/sound/soc/codecs/pcm3168a-spi.c +@@ -0,0 +1,65 @@ ++/* ++ * PCM3168A codec spi driver ++ * ++ * Copyright (C) 2015 Imagination Technologies Ltd. ++ * ++ * Author: Damien Horsley <Damien.Horsley@imgtec.com> ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ */ ++ ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/spi/spi.h> ++ ++#include <sound/soc.h> ++ ++#include "pcm3168a.h" ++ ++static int pcm3168a_spi_probe(struct spi_device *spi) ++{ ++ struct regmap *regmap; ++ ++ regmap = devm_regmap_init_spi(spi, &pcm3168a_regmap); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ return pcm3168a_probe(&spi->dev, regmap); ++} ++ ++static int pcm3168a_spi_remove(struct spi_device *spi) ++{ ++ pcm3168a_remove(&spi->dev); ++ ++ return 0; ++} ++ ++static const struct spi_device_id pcm3168a_spi_id[] = { ++ { "pcm3168a", }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(spi, pcm3168a_spi_id); ++ ++static const struct of_device_id pcm3168a_of_match[] = { ++ { .compatible = "ti,pcm3168a", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, pcm3168a_of_match); ++ ++static struct spi_driver pcm3168a_spi_driver = { ++ .probe = pcm3168a_spi_probe, ++ .remove = pcm3168a_spi_remove, ++ .id_table = pcm3168a_spi_id, ++ .driver = { ++ .name = "pcm3168a", ++ .of_match_table = pcm3168a_of_match, ++ .pm = &pcm3168a_pm_ops, ++ }, ++}; ++module_spi_driver(pcm3168a_spi_driver); ++ ++MODULE_DESCRIPTION("PCM3168A SPI codec driver"); ++MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>"); ++MODULE_LICENSE("GPL v2"); +diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c +new file mode 100644 +index 0000000..0ee7ae8 +--- /dev/null ++++ b/sound/soc/codecs/pcm3168a.c +@@ -0,0 +1,767 @@ ++/* ++ * PCM3168A codec driver ++ * ++ * Copyright (C) 2015 Imagination Technologies Ltd. ++ * ++ * Author: Damien Horsley <Damien.Horsley@imgtec.com> ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ */ ++ ++#include <linux/clk.h> ++#include <linux/delay.h> ++#include <linux/module.h> ++#include <linux/pm_runtime.h> ++#include <linux/regulator/consumer.h> ++ ++#include <sound/pcm_params.h> ++#include <sound/soc.h> ++#include <sound/tlv.h> ++ ++#include "pcm3168a.h" ++ ++#define PCM3168A_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ ++ SNDRV_PCM_FMTBIT_S24_3LE | \ ++ SNDRV_PCM_FMTBIT_S24_LE | \ ++ SNDRV_PCM_FMTBIT_S32_LE) ++ ++#define PCM3168A_FMT_I2S 0x0 ++#define PCM3168A_FMT_LEFT_J 0x1 ++#define PCM3168A_FMT_RIGHT_J 0x2 ++#define PCM3168A_FMT_RIGHT_J_16 0x3 ++#define PCM3168A_FMT_DSP_A 0x4 ++#define PCM3168A_FMT_DSP_B 0x5 ++#define PCM3168A_FMT_DSP_MASK 0x4 ++ ++#define PCM3168A_NUM_SUPPLIES 6 ++static const char *const pcm3168a_supply_names[PCM3168A_NUM_SUPPLIES] = { ++ "VDD1", ++ "VDD2", ++ "VCCAD1", ++ "VCCAD2", ++ "VCCDA1", ++ "VCCDA2" ++}; ++ ++struct pcm3168a_priv { ++ struct regulator_bulk_data supplies[PCM3168A_NUM_SUPPLIES]; ++ struct regmap *regmap; ++ struct clk *scki; ++ bool adc_master_mode; ++ bool dac_master_mode; ++ unsigned long sysclk; ++ unsigned int adc_fmt; ++ unsigned int dac_fmt; ++}; ++ ++static const char *const pcm3168a_roll_off[] = { "Sharp", "Slow" }; ++ ++static SOC_ENUM_SINGLE_DECL(pcm3168a_d1_roll_off, PCM3168A_DAC_OP_FLT, ++ PCM3168A_DAC_FLT_SHIFT, pcm3168a_roll_off); ++static SOC_ENUM_SINGLE_DECL(pcm3168a_d2_roll_off, PCM3168A_DAC_OP_FLT, ++ PCM3168A_DAC_FLT_SHIFT + 1, pcm3168a_roll_off); ++static SOC_ENUM_SINGLE_DECL(pcm3168a_d3_roll_off, PCM3168A_DAC_OP_FLT, ++ PCM3168A_DAC_FLT_SHIFT + 2, pcm3168a_roll_off); ++static SOC_ENUM_SINGLE_DECL(pcm3168a_d4_roll_off, PCM3168A_DAC_OP_FLT, ++ PCM3168A_DAC_FLT_SHIFT + 3, pcm3168a_roll_off); ++ ++static const char *const pcm3168a_volume_type[] = { ++ "Individual", "Master + Individual" }; ++ ++static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_volume_type, PCM3168A_DAC_ATT_DEMP_ZF, ++ PCM3168A_DAC_ATMDDA_SHIFT, pcm3168a_volume_type); ++ ++static const char *const pcm3168a_att_speed_mult[] = { "2048", "4096" }; ++ ++static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_att_mult, PCM3168A_DAC_ATT_DEMP_ZF, ++ PCM3168A_DAC_ATSPDA_SHIFT, pcm3168a_att_speed_mult); ++ ++static const char *const pcm3168a_demp[] = { ++ "Disabled", "48khz", "44.1khz", "32khz" }; ++ ++static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_demp, PCM3168A_DAC_ATT_DEMP_ZF, ++ PCM3168A_DAC_DEMP_SHIFT, pcm3168a_demp); ++ ++static const char *const pcm3168a_zf_func[] = { ++ "DAC 1/2/3/4 AND", "DAC 1/2/3/4 OR", "DAC 1/2/3 AND", ++ "DAC 1/2/3 OR", "DAC 4 AND", "DAC 4 OR" }; ++ ++static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_func, PCM3168A_DAC_ATT_DEMP_ZF, ++ PCM3168A_DAC_AZRO_SHIFT, pcm3168a_zf_func); ++ ++static const char *const pcm3168a_pol[] = { "Active High", "Active Low" }; ++ ++static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_pol, PCM3168A_DAC_ATT_DEMP_ZF, ++ PCM3168A_DAC_ATSPDA_SHIFT, pcm3168a_pol); ++ ++static const char *const pcm3168a_con[] = { "Differential", "Single-Ended" }; ++ ++static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc1_con, PCM3168A_ADC_SEAD, ++ 0, 1, pcm3168a_con); ++static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc2_con, PCM3168A_ADC_SEAD, ++ 2, 3, pcm3168a_con); ++static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc3_con, PCM3168A_ADC_SEAD, ++ 4, 5, pcm3168a_con); ++ ++static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_volume_type, PCM3168A_ADC_ATT_OVF, ++ PCM3168A_ADC_ATMDAD_SHIFT, pcm3168a_volume_type); ++ ++static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_att_mult, PCM3168A_ADC_ATT_OVF, ++ PCM3168A_ADC_ATSPAD_SHIFT, pcm3168a_att_speed_mult); ++ ++static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_ov_pol, PCM3168A_ADC_ATT_OVF, ++ PCM3168A_ADC_OVFP_SHIFT, pcm3168a_pol); ++ ++/* -100db to 0db, register values 0-54 cause mute */ ++static const DECLARE_TLV_DB_SCALE(pcm3168a_dac_tlv, -10050, 50, 1); ++ ++/* -100db to 20db, register values 0-14 cause mute */ ++static const DECLARE_TLV_DB_SCALE(pcm3168a_adc_tlv, -10050, 50, 1); ++ ++static const struct snd_kcontrol_new pcm3168a_snd_controls[] = { ++ SOC_SINGLE("DAC Power-Save Switch", PCM3168A_DAC_PWR_MST_FMT, ++ PCM3168A_DAC_PSMDA_SHIFT, 1, 1), ++ SOC_ENUM("DAC1 Digital Filter roll-off", pcm3168a_d1_roll_off), ++ SOC_ENUM("DAC2 Digital Filter roll-off", pcm3168a_d2_roll_off), ++ SOC_ENUM("DAC3 Digital Filter roll-off", pcm3168a_d3_roll_off), ++ SOC_ENUM("DAC4 Digital Filter roll-off", pcm3168a_d4_roll_off), ++ SOC_DOUBLE("DAC1 Invert Switch", PCM3168A_DAC_INV, 0, 1, 1, 0), ++ SOC_DOUBLE("DAC2 Invert Switch", PCM3168A_DAC_INV, 2, 3, 1, 0), ++ SOC_DOUBLE("DAC3 Invert Switch", PCM3168A_DAC_INV, 4, 5, 1, 0), ++ SOC_DOUBLE("DAC4 Invert Switch", PCM3168A_DAC_INV, 6, 7, 1, 0), ++ SOC_DOUBLE_STS("DAC1 Zero Flag", PCM3168A_DAC_ZERO, 0, 1, 1, 0), ++ SOC_DOUBLE_STS("DAC2 Zero Flag", PCM3168A_DAC_ZERO, 2, 3, 1, 0), ++ SOC_DOUBLE_STS("DAC3 Zero Flag", PCM3168A_DAC_ZERO, 4, 5, 1, 0), ++ SOC_DOUBLE_STS("DAC4 Zero Flag", PCM3168A_DAC_ZERO, 6, 7, 1, 0), ++ SOC_ENUM("DAC Volume Control Type", pcm3168a_dac_volume_type), ++ SOC_ENUM("DAC Volume Rate Multiplier", pcm3168a_dac_att_mult), ++ SOC_ENUM("DAC De-Emphasis", pcm3168a_dac_demp), ++ SOC_ENUM("DAC Zero Flag Function", pcm3168a_dac_zf_func), ++ SOC_ENUM("DAC Zero Flag Polarity", pcm3168a_dac_zf_pol), ++ SOC_SINGLE_RANGE_TLV("Master Playback Volume", ++ PCM3168A_DAC_VOL_MASTER, 0, 54, 255, 0, ++ pcm3168a_dac_tlv), ++ SOC_DOUBLE_R_RANGE_TLV("DAC1 Playback Volume", ++ PCM3168A_DAC_VOL_CHAN_START, ++ PCM3168A_DAC_VOL_CHAN_START + 1, ++ 0, 54, 255, 0, pcm3168a_dac_tlv), ++ SOC_DOUBLE_R_RANGE_TLV("DAC2 Playback Volume", ++ PCM3168A_DAC_VOL_CHAN_START + 2, ++ PCM3168A_DAC_VOL_CHAN_START + 3, ++ 0, 54, 255, 0, pcm3168a_dac_tlv), ++ SOC_DOUBLE_R_RANGE_TLV("DAC3 Playback Volume", ++ PCM3168A_DAC_VOL_CHAN_START + 4, ++ PCM3168A_DAC_VOL_CHAN_START + 5, ++ 0, 54, 255, 0, pcm3168a_dac_tlv), ++ SOC_DOUBLE_R_RANGE_TLV("DAC4 Playback Volume", ++ PCM3168A_DAC_VOL_CHAN_START + 6, ++ PCM3168A_DAC_VOL_CHAN_START + 7, ++ 0, 54, 255, 0, pcm3168a_dac_tlv), ++ SOC_SINGLE("ADC1 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB, ++ PCM3168A_ADC_BYP_SHIFT, 1, 1), ++ SOC_SINGLE("ADC2 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB, ++ PCM3168A_ADC_BYP_SHIFT + 1, 1, 1), ++ SOC_SINGLE("ADC3 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB, ++ PCM3168A_ADC_BYP_SHIFT + 2, 1, 1), ++ SOC_ENUM("ADC1 Connection Type", pcm3168a_adc1_con), ++ SOC_ENUM("ADC2 Connection Type", pcm3168a_adc2_con), ++ SOC_ENUM("ADC3 Connection Type", pcm3168a_adc3_con), ++ SOC_DOUBLE("ADC1 Invert Switch", PCM3168A_ADC_INV, 0, 1, 1, 0), ++ SOC_DOUBLE("ADC2 Invert Switch", PCM3168A_ADC_INV, 2, 3, 1, 0), ++ SOC_DOUBLE("ADC3 Invert Switch", PCM3168A_ADC_INV, 4, 5, 1, 0), ++ SOC_DOUBLE("ADC1 Mute Switch", PCM3168A_ADC_MUTE, 0, 1, 1, 0), ++ SOC_DOUBLE("ADC2 Mute Switch", PCM3168A_ADC_MUTE, 2, 3, 1, 0), ++ SOC_DOUBLE("ADC3 Mute Switch", PCM3168A_ADC_MUTE, 4, 5, 1, 0), ++ SOC_DOUBLE_STS("ADC1 Overflow Flag", PCM3168A_ADC_OV, 0, 1, 1, 0), ++ SOC_DOUBLE_STS("ADC2 Overflow Flag", PCM3168A_ADC_OV, 2, 3, 1, 0), ++ SOC_DOUBLE_STS("ADC3 Overflow Flag", PCM3168A_ADC_OV, 4, 5, 1, 0), ++ SOC_ENUM("ADC Volume Control Type", pcm3168a_adc_volume_type), ++ SOC_ENUM("ADC Volume Rate Multiplier", pcm3168a_adc_att_mult), ++ SOC_ENUM("ADC Overflow Flag Polarity", pcm3168a_adc_ov_pol), ++ SOC_SINGLE_RANGE_TLV("Master Capture Volume", ++ PCM3168A_ADC_VOL_MASTER, 0, 14, 255, 0, ++ pcm3168a_adc_tlv), ++ SOC_DOUBLE_R_RANGE_TLV("ADC1 Capture Volume", ++ PCM3168A_ADC_VOL_CHAN_START, ++ PCM3168A_ADC_VOL_CHAN_START + 1, ++ 0, 14, 255, 0, pcm3168a_adc_tlv), ++ SOC_DOUBLE_R_RANGE_TLV("ADC2 Capture Volume", ++ PCM3168A_ADC_VOL_CHAN_START + 2, ++ PCM3168A_ADC_VOL_CHAN_START + 3, ++ 0, 14, 255, 0, pcm3168a_adc_tlv), ++ SOC_DOUBLE_R_RANGE_TLV("ADC3 Capture Volume", ++ PCM3168A_ADC_VOL_CHAN_START + 4, ++ PCM3168A_ADC_VOL_CHAN_START + 5, ++ 0, 14, 255, 0, pcm3168a_adc_tlv) ++}; ++ ++static const struct snd_soc_dapm_widget pcm3168a_dapm_widgets[] = { ++ SND_SOC_DAPM_DAC("DAC1", "Playback", PCM3168A_DAC_OP_FLT, ++ PCM3168A_DAC_OPEDA_SHIFT, 1), ++ SND_SOC_DAPM_DAC("DAC2", "Playback", PCM3168A_DAC_OP_FLT, ++ PCM3168A_DAC_OPEDA_SHIFT + 1, 1), ++ SND_SOC_DAPM_DAC("DAC3", "Playback", PCM3168A_DAC_OP_FLT, ++ PCM3168A_DAC_OPEDA_SHIFT + 2, 1), ++ SND_SOC_DAPM_DAC("DAC4", "Playback", PCM3168A_DAC_OP_FLT, ++ PCM3168A_DAC_OPEDA_SHIFT + 3, 1), ++ ++ SND_SOC_DAPM_OUTPUT("AOUT1L"), ++ SND_SOC_DAPM_OUTPUT("AOUT1R"), ++ SND_SOC_DAPM_OUTPUT("AOUT2L"), ++ SND_SOC_DAPM_OUTPUT("AOUT2R"), ++ SND_SOC_DAPM_OUTPUT("AOUT3L"), ++ SND_SOC_DAPM_OUTPUT("AOUT3R"), ++ SND_SOC_DAPM_OUTPUT("AOUT4L"), ++ SND_SOC_DAPM_OUTPUT("AOUT4R"), ++ ++ SND_SOC_DAPM_ADC("ADC1", "Capture", PCM3168A_ADC_PWR_HPFB, ++ PCM3168A_ADC_PSVAD_SHIFT, 1), ++ SND_SOC_DAPM_ADC("ADC2", "Capture", PCM3168A_ADC_PWR_HPFB, ++ PCM3168A_ADC_PSVAD_SHIFT + 1, 1), ++ SND_SOC_DAPM_ADC("ADC3", "Capture", PCM3168A_ADC_PWR_HPFB, ++ PCM3168A_ADC_PSVAD_SHIFT + 2, 1), ++ ++ SND_SOC_DAPM_INPUT("AIN1L"), ++ SND_SOC_DAPM_INPUT("AIN1R"), ++ SND_SOC_DAPM_INPUT("AIN2L"), ++ SND_SOC_DAPM_INPUT("AIN2R"), ++ SND_SOC_DAPM_INPUT("AIN3L"), ++ SND_SOC_DAPM_INPUT("AIN3R") ++}; ++ ++static const struct snd_soc_dapm_route pcm3168a_dapm_routes[] = { ++ /* Playback */ ++ { "AOUT1L", NULL, "DAC1" }, ++ { "AOUT1R", NULL, "DAC1" }, ++ ++ { "AOUT2L", NULL, "DAC2" }, ++ { "AOUT2R", NULL, "DAC2" }, ++ ++ { "AOUT3L", NULL, "DAC3" }, ++ { "AOUT3R", NULL, "DAC3" }, ++ ++ { "AOUT4L", NULL, "DAC4" }, ++ { "AOUT4R", NULL, "DAC4" }, ++ ++ /* Capture */ ++ { "ADC1", NULL, "AIN1L" }, ++ { "ADC1", NULL, "AIN1R" }, ++ ++ { "ADC2", NULL, "AIN2L" }, ++ { "ADC2", NULL, "AIN2R" }, ++ ++ { "ADC3", NULL, "AIN3L" }, ++ { "ADC3", NULL, "AIN3R" } ++}; ++ ++static unsigned int pcm3168a_scki_ratios[] = { ++ 768, ++ 512, ++ 384, ++ 256, ++ 192, ++ 128 ++}; ++ ++#define PCM3168A_NUM_SCKI_RATIOS_DAC ARRAY_SIZE(pcm3168a_scki_ratios) ++#define PCM3168A_NUM_SCKI_RATIOS_ADC (ARRAY_SIZE(pcm3168a_scki_ratios) - 2) ++ ++#define PCM1368A_MAX_SYSCLK 36864000 ++ ++static int pcm3168a_reset(struct pcm3168a_priv *pcm3168a) ++{ ++ int ret; ++ ++ ret = regmap_write(pcm3168a->regmap, PCM3168A_RST_SMODE, 0); ++ if (ret) ++ return ret; ++ ++ /* Internal reset is de-asserted after 3846 SCKI cycles */ ++ msleep(DIV_ROUND_UP(3846 * 1000, pcm3168a->sysclk)); ++ ++ return regmap_write(pcm3168a->regmap, PCM3168A_RST_SMODE, ++ PCM3168A_MRST_MASK | PCM3168A_SRST_MASK); ++} ++ ++static int pcm3168a_digital_mute(struct snd_soc_dai *dai, int mute) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(codec); ++ ++ regmap_write(pcm3168a->regmap, PCM3168A_DAC_MUTE, mute ? 0xff : 0); ++ ++ return 0; ++} ++ ++static int pcm3168a_set_dai_sysclk(struct snd_soc_dai *dai, ++ int clk_id, unsigned int freq, int dir) ++{ ++ struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(dai->codec); ++ ++ if (freq > PCM1368A_MAX_SYSCLK) ++ return -EINVAL; ++ ++ pcm3168a->sysclk = freq; ++ ++ return 0; ++} ++ ++static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, ++ unsigned int format, bool dac) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(codec); ++ u32 fmt, reg, mask, shift; ++ bool master_mode; ++ ++ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { ++ case SND_SOC_DAIFMT_LEFT_J: ++ fmt = PCM3168A_FMT_LEFT_J; ++ break; ++ case SND_SOC_DAIFMT_I2S: ++ fmt = PCM3168A_FMT_I2S; ++ break; ++ case SND_SOC_DAIFMT_RIGHT_J: ++ fmt = PCM3168A_FMT_RIGHT_J; ++ break; ++ case SND_SOC_DAIFMT_DSP_A: ++ fmt = PCM3168A_FMT_DSP_A; ++ break; ++ case SND_SOC_DAIFMT_DSP_B: ++ fmt = PCM3168A_FMT_DSP_B; ++ break; ++ default: ++ dev_err(codec->dev, "unsupported dai format\n"); ++ return -EINVAL; ++ } ++ ++ switch (format & SND_SOC_DAIFMT_MASTER_MASK) { ++ case SND_SOC_DAIFMT_CBS_CFS: ++ master_mode = false; ++ break; ++ case SND_SOC_DAIFMT_CBM_CFM: ++ master_mode = true; ++ break; ++ default: ++ dev_err(codec->dev, "unsupported master/slave mode\n"); ++ return -EINVAL; ++ } ++ ++ switch (format & SND_SOC_DAIFMT_INV_MASK) { ++ case SND_SOC_DAIFMT_NB_NF: ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (dac) { ++ reg = PCM3168A_DAC_PWR_MST_FMT; ++ mask = PCM3168A_DAC_FMT_MASK; ++ shift = PCM3168A_DAC_FMT_SHIFT; ++ pcm3168a->dac_master_mode = master_mode; ++ pcm3168a->dac_fmt = fmt; ++ } else { ++ reg = PCM3168A_ADC_MST_FMT; ++ mask = PCM3168A_ADC_FMTAD_MASK; ++ shift = PCM3168A_ADC_FMTAD_SHIFT; ++ pcm3168a->adc_master_mode = master_mode; ++ pcm3168a->adc_fmt = fmt; ++ } ++ ++ regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift); ++ ++ return 0; ++} ++ ++static int pcm3168a_set_dai_fmt_dac(struct snd_soc_dai *dai, ++ unsigned int format) ++{ ++ return pcm3168a_set_dai_fmt(dai, format, true); ++} ++ ++static int pcm3168a_set_dai_fmt_adc(struct snd_soc_dai *dai, ++ unsigned int format) ++{ ++ return pcm3168a_set_dai_fmt(dai, format, false); ++} ++ ++static int pcm3168a_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(codec); ++ bool tx, master_mode; ++ u32 val, mask, shift, reg; ++ unsigned int rate, channels, fmt, ratio, max_ratio; ++ int i, min_frame_size; ++ snd_pcm_format_t format; ++ ++ rate = params_rate(params); ++ format = params_format(params); ++ channels = params_channels(params); ++ ++ ratio = pcm3168a->sysclk / rate; ++ ++ tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; ++ if (tx) { ++ max_ratio = PCM3168A_NUM_SCKI_RATIOS_DAC; ++ reg = PCM3168A_DAC_PWR_MST_FMT; ++ mask = PCM3168A_DAC_MSDA_MASK; ++ shift = PCM3168A_DAC_MSDA_SHIFT; ++ master_mode = pcm3168a->dac_master_mode; ++ fmt = pcm3168a->dac_fmt; ++ } else { ++ max_ratio = PCM3168A_NUM_SCKI_RATIOS_ADC; ++ reg = PCM3168A_ADC_MST_FMT; ++ mask = PCM3168A_ADC_MSAD_MASK; ++ shift = PCM3168A_ADC_MSAD_SHIFT; ++ master_mode = pcm3168a->adc_master_mode; ++ fmt = pcm3168a->adc_fmt; ++ } ++ ++ for (i = 0; i < max_ratio; i++) { ++ if (pcm3168a_scki_ratios[i] == ratio) ++ break; ++ } ++ ++ if (i == max_ratio) { ++ dev_err(codec->dev, "unsupported sysclk ratio\n"); ++ return -EINVAL; ++ } ++ ++ min_frame_size = snd_pcm_format_width(params_format(params)) * 2; ++ switch (min_frame_size) { ++ case 32: ++ if (master_mode || (fmt != PCM3168A_FMT_RIGHT_J)) { ++ dev_err(codec->dev, "32-bit frames are supported only for slave mode using right justified\n"); ++ return -EINVAL; ++ } ++ fmt = PCM3168A_FMT_RIGHT_J_16; ++ break; ++ case 48: ++ if (master_mode || (fmt & PCM3168A_FMT_DSP_MASK)) { ++ dev_err(codec->dev, "48-bit frames not supported in master mode, or slave mode using DSP\n"); ++ return -EINVAL; ++ } ++ break; ++ case 64: ++ break; ++ default: ++ dev_err(codec->dev, "unsupported frame size: %d\n", min_frame_size); ++ return -EINVAL; ++ } ++ ++ if (master_mode) ++ val = ((i + 1) << shift); ++ else ++ val = 0; ++ ++ regmap_update_bits(pcm3168a->regmap, reg, mask, val); ++ ++ if (tx) { ++ mask = PCM3168A_DAC_FMT_MASK; ++ shift = PCM3168A_DAC_FMT_SHIFT; ++ } else { ++ mask = PCM3168A_ADC_FMTAD_MASK; ++ shift = PCM3168A_ADC_FMTAD_SHIFT; ++ } ++ ++ regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops pcm3168a_dac_dai_ops = { ++ .set_fmt = pcm3168a_set_dai_fmt_dac, ++ .set_sysclk = pcm3168a_set_dai_sysclk, ++ .hw_params = pcm3168a_hw_params, ++ .digital_mute = pcm3168a_digital_mute ++}; ++ ++static const struct snd_soc_dai_ops pcm3168a_adc_dai_ops = { ++ .set_fmt = pcm3168a_set_dai_fmt_adc, ++ .set_sysclk = pcm3168a_set_dai_sysclk, ++ .hw_params = pcm3168a_hw_params ++}; ++ ++static struct snd_soc_dai_driver pcm3168a_dais[] = { ++ { ++ .name = "pcm3168a-dac", ++ .playback = { ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = 8, ++ .rates = SNDRV_PCM_RATE_8000_192000, ++ .formats = PCM3168A_FORMATS ++ }, ++ .ops = &pcm3168a_dac_dai_ops ++ }, ++ { ++ .name = "pcm3168a-adc", ++ .capture = { ++ .stream_name = "Capture", ++ .channels_min = 1, ++ .channels_max = 6, ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = PCM3168A_FORMATS ++ }, ++ .ops = &pcm3168a_adc_dai_ops ++ }, ++}; ++ ++static const struct reg_default pcm3168a_reg_default[] = { ++ { PCM3168A_RST_SMODE, PCM3168A_MRST_MASK | PCM3168A_SRST_MASK }, ++ { PCM3168A_DAC_PWR_MST_FMT, 0x00 }, ++ { PCM3168A_DAC_OP_FLT, 0x00 }, ++ { PCM3168A_DAC_INV, 0x00 }, ++ { PCM3168A_DAC_MUTE, 0x00 }, ++ { PCM3168A_DAC_ZERO, 0x00 }, ++ { PCM3168A_DAC_ATT_DEMP_ZF, 0x00 }, ++ { PCM3168A_DAC_VOL_MASTER, 0xff }, ++ { PCM3168A_DAC_VOL_CHAN_START, 0xff }, ++ { PCM3168A_DAC_VOL_CHAN_START + 1, 0xff }, ++ { PCM3168A_DAC_VOL_CHAN_START + 2, 0xff }, ++ { PCM3168A_DAC_VOL_CHAN_START + 3, 0xff }, ++ { PCM3168A_DAC_VOL_CHAN_START + 4, 0xff }, ++ { PCM3168A_DAC_VOL_CHAN_START + 5, 0xff }, ++ { PCM3168A_DAC_VOL_CHAN_START + 6, 0xff }, ++ { PCM3168A_DAC_VOL_CHAN_START + 7, 0xff }, ++ { PCM3168A_ADC_SMODE, 0x00 }, ++ { PCM3168A_ADC_MST_FMT, 0x00 }, ++ { PCM3168A_ADC_PWR_HPFB, 0x00 }, ++ { PCM3168A_ADC_SEAD, 0x00 }, ++ { PCM3168A_ADC_INV, 0x00 }, ++ { PCM3168A_ADC_MUTE, 0x00 }, ++ { PCM3168A_ADC_OV, 0x00 }, ++ { PCM3168A_ADC_ATT_OVF, 0x00 }, ++ { PCM3168A_ADC_VOL_MASTER, 0xd3 }, ++ { PCM3168A_ADC_VOL_CHAN_START, 0xd3 }, ++ { PCM3168A_ADC_VOL_CHAN_START + 1, 0xd3 }, ++ { PCM3168A_ADC_VOL_CHAN_START + 2, 0xd3 }, ++ { PCM3168A_ADC_VOL_CHAN_START + 3, 0xd3 }, ++ { PCM3168A_ADC_VOL_CHAN_START + 4, 0xd3 }, ++ { PCM3168A_ADC_VOL_CHAN_START + 5, 0xd3 } ++}; ++ ++static bool pcm3168a_readable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg >= PCM3168A_RST_SMODE) ++ return true; ++ else ++ return false; ++} ++ ++static bool pcm3168a_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case PCM3168A_DAC_ZERO: ++ case PCM3168A_ADC_OV: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool pcm3168a_writeable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg < PCM3168A_RST_SMODE) ++ return false; ++ ++ switch (reg) { ++ case PCM3168A_DAC_ZERO: ++ case PCM3168A_ADC_OV: ++ return false; ++ default: ++ return true; ++ } ++} ++ ++const struct regmap_config pcm3168a_regmap = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ ++ .max_register = PCM3168A_ADC_VOL_CHAN_START + 5, ++ .reg_defaults = pcm3168a_reg_default, ++ .num_reg_defaults = ARRAY_SIZE(pcm3168a_reg_default), ++ .readable_reg = pcm3168a_readable_register, ++ .volatile_reg = pcm3168a_volatile_register, ++ .writeable_reg = pcm3168a_writeable_register, ++ .cache_type = REGCACHE_FLAT ++}; ++EXPORT_SYMBOL_GPL(pcm3168a_regmap); ++ ++static const struct snd_soc_codec_driver pcm3168a_driver = { ++ .idle_bias_off = true, ++ .controls = pcm3168a_snd_controls, ++ .num_controls = ARRAY_SIZE(pcm3168a_snd_controls), ++ .dapm_widgets = pcm3168a_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(pcm3168a_dapm_widgets), ++ .dapm_routes = pcm3168a_dapm_routes, ++ .num_dapm_routes = ARRAY_SIZE(pcm3168a_dapm_routes) ++}; ++ ++int pcm3168a_probe(struct device *dev, struct regmap *regmap) ++{ ++ struct pcm3168a_priv *pcm3168a; ++ int ret, i; ++ ++ pcm3168a = devm_kzalloc(dev, sizeof(*pcm3168a), GFP_KERNEL); ++ if (pcm3168a == NULL) ++ return -ENOMEM; ++ ++ dev_set_drvdata(dev, pcm3168a); ++ ++ pcm3168a->scki = devm_clk_get(dev, "scki"); ++ if (IS_ERR(pcm3168a->scki)) { ++ ret = PTR_ERR(pcm3168a->scki); ++ if (ret != -EPROBE_DEFER) ++ dev_err(dev, "failed to acquire clock 'scki': %d\n", ret); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(pcm3168a->scki); ++ if (ret) { ++ dev_err(dev, "Failed to enable mclk: %d\n", ret); ++ return ret; ++ } ++ ++ pcm3168a->sysclk = clk_get_rate(pcm3168a->scki); ++ ++ for (i = 0; i < ARRAY_SIZE(pcm3168a->supplies); i++) ++ pcm3168a->supplies[i].supply = pcm3168a_supply_names[i]; ++ ++ ret = devm_regulator_bulk_get(dev, ++ ARRAY_SIZE(pcm3168a->supplies), pcm3168a->supplies); ++ if (ret) { ++ if (ret != -EPROBE_DEFER) ++ dev_err(dev, "failed to request supplies: %d\n", ret); ++ goto err_clk; ++ } ++ ++ ret = regulator_bulk_enable(ARRAY_SIZE(pcm3168a->supplies), ++ pcm3168a->supplies); ++ if (ret) { ++ dev_err(dev, "failed to enable supplies: %d\n", ret); ++ goto err_clk; ++ } ++ ++ pcm3168a->regmap = regmap; ++ if (IS_ERR(pcm3168a->regmap)) { ++ ret = PTR_ERR(pcm3168a->regmap); ++ dev_err(dev, "failed to allocate regmap: %d\n", ret); ++ goto err_regulator; ++ } ++ ++ ret = pcm3168a_reset(pcm3168a); ++ if (ret) { ++ dev_err(dev, "Failed to reset device: %d\n", ret); ++ goto err_regulator; ++ } ++ ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ pm_runtime_idle(dev); ++ ++ ret = snd_soc_register_codec(dev, &pcm3168a_driver, pcm3168a_dais, ++ ARRAY_SIZE(pcm3168a_dais)); ++ if (ret) { ++ dev_err(dev, "failed to register codec: %d\n", ret); ++ goto err_regulator; ++ } ++ ++ return 0; ++ ++err_regulator: ++ regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), ++ pcm3168a->supplies); ++err_clk: ++ clk_disable_unprepare(pcm3168a->scki); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(pcm3168a_probe); ++ ++void pcm3168a_remove(struct device *dev) ++{ ++ struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); ++ ++ snd_soc_unregister_codec(dev); ++ pm_runtime_disable(dev); ++ regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), ++ pcm3168a->supplies); ++ clk_disable_unprepare(pcm3168a->scki); ++} ++EXPORT_SYMBOL_GPL(pcm3168a_remove); ++ ++#ifdef CONFIG_PM ++static int pcm3168a_rt_resume(struct device *dev) ++{ ++ struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = clk_prepare_enable(pcm3168a->scki); ++ if (ret) { ++ dev_err(dev, "Failed to enable mclk: %d\n", ret); ++ return ret; ++ } ++ ++ ret = regulator_bulk_enable(ARRAY_SIZE(pcm3168a->supplies), ++ pcm3168a->supplies); ++ if (ret) { ++ dev_err(dev, "Failed to enable supplies: %d\n", ret); ++ goto err_clk; ++ } ++ ++ ret = pcm3168a_reset(pcm3168a); ++ if (ret) { ++ dev_err(dev, "Failed to reset device: %d\n", ret); ++ goto err_regulator; ++ } ++ ++ regcache_cache_only(pcm3168a->regmap, false); ++ ++ regcache_mark_dirty(pcm3168a->regmap); ++ ++ ret = regcache_sync(pcm3168a->regmap); ++ if (ret) { ++ dev_err(dev, "Failed to sync regmap: %d\n", ret); ++ goto err_regulator; ++ } ++ ++ return 0; ++ ++err_regulator: ++ regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), ++ pcm3168a->supplies); ++err_clk: ++ clk_disable_unprepare(pcm3168a->scki); ++ ++ return ret; ++} ++ ++static int pcm3168a_rt_suspend(struct device *dev) ++{ ++ struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); ++ ++ regcache_cache_only(pcm3168a->regmap, true); ++ ++ regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), ++ pcm3168a->supplies); ++ ++ clk_disable_unprepare(pcm3168a->scki); ++ ++ return 0; ++} ++#endif ++ ++const struct dev_pm_ops pcm3168a_pm_ops = { ++ SET_RUNTIME_PM_OPS(pcm3168a_rt_suspend, pcm3168a_rt_resume, NULL) ++}; ++EXPORT_SYMBOL_GPL(pcm3168a_pm_ops); ++ ++MODULE_DESCRIPTION("PCM3168A codec driver"); ++MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>"); ++MODULE_LICENSE("GPL v2"); +diff --git a/sound/soc/codecs/pcm3168a.h b/sound/soc/codecs/pcm3168a.h +new file mode 100644 +index 0000000..56c8332 +--- /dev/null ++++ b/sound/soc/codecs/pcm3168a.h +@@ -0,0 +1,100 @@ ++/* ++ * PCM3168A codec driver header ++ * ++ * Copyright (C) 2015 Imagination Technologies Ltd. ++ * ++ * Author: Damien Horsley <Damien.Horsley@imgtec.com> ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ */ ++ ++#ifndef __PCM3168A_H__ ++#define __PCM3168A_H__ ++ ++extern const struct dev_pm_ops pcm3168a_pm_ops; ++extern const struct regmap_config pcm3168a_regmap; ++ ++extern int pcm3168a_probe(struct device *dev, struct regmap *regmap); ++extern void pcm3168a_remove(struct device *dev); ++ ++#define PCM3168A_RST_SMODE 0x40 ++#define PCM3168A_MRST_MASK 0x80 ++#define PCM3168A_SRST_MASK 0x40 ++#define PCM3168A_DAC_SRDA_SHIFT 0 ++#define PCM3168A_DAC_SRDA_MASK 0x3 ++ ++#define PCM3168A_DAC_PWR_MST_FMT 0x41 ++#define PCM3168A_DAC_PSMDA_SHIFT 7 ++#define PCM3168A_DAC_PSMDA_MASK 0x80 ++#define PCM3168A_DAC_MSDA_SHIFT 4 ++#define PCM3168A_DAC_MSDA_MASK 0x70 ++#define PCM3168A_DAC_FMT_SHIFT 0 ++#define PCM3168A_DAC_FMT_MASK 0xf ++ ++#define PCM3168A_DAC_OP_FLT 0x42 ++#define PCM3168A_DAC_OPEDA_SHIFT 4 ++#define PCM3168A_DAC_OPEDA_MASK 0xf0 ++#define PCM3168A_DAC_FLT_SHIFT 0 ++#define PCM3168A_DAC_FLT_MASK 0xf ++ ++#define PCM3168A_DAC_INV 0x43 ++ ++#define PCM3168A_DAC_MUTE 0x44 ++ ++#define PCM3168A_DAC_ZERO 0x45 ++ ++#define PCM3168A_DAC_ATT_DEMP_ZF 0x46 ++#define PCM3168A_DAC_ATMDDA_MASK 0x80 ++#define PCM3168A_DAC_ATMDDA_SHIFT 7 ++#define PCM3168A_DAC_ATSPDA_MASK 0x40 ++#define PCM3168A_DAC_ATSPDA_SHIFT 6 ++#define PCM3168A_DAC_DEMP_SHIFT 4 ++#define PCM3168A_DAC_DEMP_MASK 0x30 ++#define PCM3168A_DAC_AZRO_SHIFT 1 ++#define PCM3168A_DAC_AZRO_MASK 0xe ++#define PCM3168A_DAC_ZREV_MASK 0x1 ++#define PCM3168A_DAC_ZREV_SHIFT 0 ++ ++#define PCM3168A_DAC_VOL_MASTER 0x47 ++ ++#define PCM3168A_DAC_VOL_CHAN_START 0x48 ++ ++#define PCM3168A_ADC_SMODE 0x50 ++#define PCM3168A_ADC_SRAD_SHIFT 0 ++#define PCM3168A_ADC_SRAD_MASK 0x3 ++ ++#define PCM3168A_ADC_MST_FMT 0x51 ++#define PCM3168A_ADC_MSAD_SHIFT 4 ++#define PCM3168A_ADC_MSAD_MASK 0x70 ++#define PCM3168A_ADC_FMTAD_SHIFT 0 ++#define PCM3168A_ADC_FMTAD_MASK 0x7 ++ ++#define PCM3168A_ADC_PWR_HPFB 0x52 ++#define PCM3168A_ADC_PSVAD_SHIFT 4 ++#define PCM3168A_ADC_PSVAD_MASK 0x70 ++#define PCM3168A_ADC_BYP_SHIFT 0 ++#define PCM3168A_ADC_BYP_MASK 0x7 ++ ++#define PCM3168A_ADC_SEAD 0x53 ++ ++#define PCM3168A_ADC_INV 0x54 ++ ++#define PCM3168A_ADC_MUTE 0x55 ++ ++#define PCM3168A_ADC_OV 0x56 ++ ++#define PCM3168A_ADC_ATT_OVF 0x57 ++#define PCM3168A_ADC_ATMDAD_MASK 0x80 ++#define PCM3168A_ADC_ATMDAD_SHIFT 7 ++#define PCM3168A_ADC_ATSPAD_MASK 0x40 ++#define PCM3168A_ADC_ATSPAD_SHIFT 6 ++#define PCM3168A_ADC_OVFP_MASK 0x1 ++#define PCM3168A_ADC_OVFP_SHIFT 0 ++ ++#define PCM3168A_ADC_VOL_MASTER 0x58 ++ ++#define PCM3168A_ADC_VOL_CHAN_START 0x59 ++ ++#endif +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0017-ASoC-PCM3168A-add-TDM-modes.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0017-ASoC-PCM3168A-add-TDM-modes.patch new file mode 100644 index 0000000..ff91821 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0017-ASoC-PCM3168A-add-TDM-modes.patch @@ -0,0 +1,298 @@ +From 7ca8137af6906da63479f5554d28e351218f6aff Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Wed, 13 Apr 2016 15:32:38 +0300 +Subject: [PATCH 17/21] ASoC: PCM3168A: add TDM modes + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + sound/soc/codecs/pcm3168a.c | 166 +++++++++++++++++++++++++++++++++---------- + sound/soc/codecs/pcm3168a.h | 2 +- + 2 files changed, 131 insertions(+), 37 deletions(-) + +diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c +index 0ee7ae8..6287ad9 100644 +--- a/sound/soc/codecs/pcm3168a.c ++++ b/sound/soc/codecs/pcm3168a.c +@@ -33,7 +33,11 @@ + #define PCM3168A_FMT_RIGHT_J_16 0x3 + #define PCM3168A_FMT_DSP_A 0x4 + #define PCM3168A_FMT_DSP_B 0x5 +-#define PCM3168A_FMT_DSP_MASK 0x4 ++#define PCM3168A_FMT_I2S_TDM 0x6 ++#define PCM3168A_FMT_LEFT_J_TDM 0x7 ++/* High speed */ ++#define PCM3168A_FMT_I2S_TDMHS 0x8 ++#define PCM3168A_FMT_LEFT_J_TDMHS 0x9 + + #define PCM3168A_NUM_SUPPLIES 6 + static const char *const pcm3168a_supply_names[PCM3168A_NUM_SUPPLIES] = { +@@ -45,12 +49,19 @@ static const char *const pcm3168a_supply_names[PCM3168A_NUM_SUPPLIES] = { + "VCCDA2" + }; + ++#define TDM_MODE_NONE 0 ++#define TDM_MODE_NORM 1 ++#define TDM_MODE_HS 2 ++ + struct pcm3168a_priv { + struct regulator_bulk_data supplies[PCM3168A_NUM_SUPPLIES]; + struct regmap *regmap; + struct clk *scki; + bool adc_master_mode; + bool dac_master_mode; ++ unsigned int tdm; ++ unsigned int slots; ++ unsigned int slot_width; + unsigned long sysclk; + unsigned int adc_fmt; + unsigned int dac_fmt; +@@ -308,32 +319,43 @@ static int pcm3168a_set_dai_sysclk(struct snd_soc_dai *dai, + return 0; + } + ++int format_table[3][6] = { ++ [TDM_MODE_NONE] = { ++ [0] = -1, ++ [SND_SOC_DAIFMT_I2S] = PCM3168A_FMT_I2S, ++ [SND_SOC_DAIFMT_LEFT_J] = PCM3168A_FMT_LEFT_J, ++ [SND_SOC_DAIFMT_RIGHT_J] = PCM3168A_FMT_RIGHT_J, ++ [SND_SOC_DAIFMT_DSP_A] = PCM3168A_FMT_DSP_A, ++ [SND_SOC_DAIFMT_DSP_B] = PCM3168A_FMT_DSP_B}, ++ [TDM_MODE_NORM] = { ++ [0] = -1, ++ [SND_SOC_DAIFMT_I2S] = PCM3168A_FMT_I2S_TDM, ++ [SND_SOC_DAIFMT_LEFT_J] = PCM3168A_FMT_LEFT_J_TDM, ++ [SND_SOC_DAIFMT_RIGHT_J] = -1, ++ [SND_SOC_DAIFMT_DSP_A] = -1, ++ [SND_SOC_DAIFMT_DSP_B] = -1}, ++ [TDM_MODE_HS] = { ++ [0] = -1, ++ [SND_SOC_DAIFMT_I2S] = PCM3168A_FMT_I2S_TDMHS, ++ [SND_SOC_DAIFMT_LEFT_J] = PCM3168A_FMT_LEFT_J_TDMHS, ++ [SND_SOC_DAIFMT_RIGHT_J] = -1, ++ [SND_SOC_DAIFMT_DSP_A] = -1, ++ [SND_SOC_DAIFMT_DSP_B] = -1}, ++}; ++ + static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int format, bool dac) + { + struct snd_soc_codec *codec = dai->codec; + struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(codec); +- u32 fmt, reg, mask, shift; ++ u32 reg, mask, shift; ++ int fmt; + bool master_mode; + +- switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { +- case SND_SOC_DAIFMT_LEFT_J: +- fmt = PCM3168A_FMT_LEFT_J; +- break; +- case SND_SOC_DAIFMT_I2S: +- fmt = PCM3168A_FMT_I2S; +- break; +- case SND_SOC_DAIFMT_RIGHT_J: +- fmt = PCM3168A_FMT_RIGHT_J; +- break; +- case SND_SOC_DAIFMT_DSP_A: +- fmt = PCM3168A_FMT_DSP_A; +- break; +- case SND_SOC_DAIFMT_DSP_B: +- fmt = PCM3168A_FMT_DSP_B; +- break; +- default: +- dev_err(codec->dev, "unsupported dai format\n"); ++ fmt = format_table[pcm3168a->tdm][format & SND_SOC_DAIFMT_FORMAT_MASK]; ++ ++ if (fmt < 0) { ++ dev_err(codec->dev, "unsupported dai format of TDM mode\n"); + return -EINVAL; + } + +@@ -349,6 +371,16 @@ static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, + return -EINVAL; + } + ++ if ((pcm3168a->tdm == TDM_MODE_HS) && (master_mode)) { ++ dev_err(codec->dev, "TDM high speed supported only in slave mode\n"); ++ return -EINVAL; ++ } ++ ++ if ((pcm3168a->tdm == TDM_MODE_HS) && (!dac)) { ++ dev_err(codec->dev, "TDM high speed not supported for ADC\n"); ++ return -EINVAL; ++ } ++ + switch (format & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; +@@ -391,10 +423,12 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ int bits; + struct snd_soc_codec *codec = dai->codec; + struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(codec); + bool tx, master_mode; + u32 val, mask, shift, reg; ++ u32 sample_rate = 0; /* auto */ + unsigned int rate, channels, fmt, ratio, max_ratio; + int i, min_frame_size; + snd_pcm_format_t format; +@@ -402,6 +436,9 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + rate = params_rate(params); + format = params_format(params); + channels = params_channels(params); ++ bits = params->msbits; ++ ++ tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + ratio = pcm3168a->sysclk / rate; + +@@ -432,26 +469,49 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- min_frame_size = snd_pcm_format_width(params_format(params)) * 2; +- switch (min_frame_size) { +- case 32: +- if (master_mode || (fmt != PCM3168A_FMT_RIGHT_J)) { +- dev_err(codec->dev, "32-bit frames are supported only for slave mode using right justified\n"); ++ if (pcm3168a->tdm == TDM_MODE_NONE) { ++ /* one stereo frame size */ ++ min_frame_size = bits * 2; ++ switch (min_frame_size) { ++ case 32: ++ if (master_mode || (fmt != PCM3168A_FMT_RIGHT_J)) { ++ dev_err(codec->dev, "32-bit frames are supported only for slave mode using right justified\n"); ++ return -EINVAL; ++ } ++ fmt = PCM3168A_FMT_RIGHT_J_16; ++ break; ++ case 48: ++ if (master_mode || ++ (fmt == PCM3168A_FMT_DSP_A) || ++ (fmt == PCM3168A_FMT_DSP_B)) { ++ dev_err(codec->dev, "48-bit frames not supported in master mode, or slave mode using DSP\n"); ++ return -EINVAL; ++ } ++ break; ++ case 64: ++ break; ++ default: ++ dev_err(codec->dev, "unsupported frame size: %d\n", min_frame_size); + return -EINVAL; + } +- fmt = PCM3168A_FMT_RIGHT_J_16; +- break; +- case 48: +- if (master_mode || (fmt & PCM3168A_FMT_DSP_MASK)) { +- dev_err(codec->dev, "48-bit frames not supported in master mode, or slave mode using DSP\n"); ++ } ++ if ((pcm3168a->tdm == TDM_MODE_NORM) || ++ (pcm3168a->tdm == TDM_MODE_HS)) { ++ /* all channels over one or two line */ ++ min_frame_size = bits * channels; ++ ++ /* single rate */ ++ sample_rate = 1; ++ ++ /* ++ * 256fs for single line DIN0/DOUT0 ++ * 128fs for two lines DIN01/DOU01 ++ */ ++ if ((min_frame_size != 256) && ++ (min_frame_size != 128)) { ++ dev_err(codec->dev, "256/128-bit frames only supported in TDM formats\n"); + return -EINVAL; + } +- break; +- case 64: +- break; +- default: +- dev_err(codec->dev, "unsupported frame size: %d\n", min_frame_size); +- return -EINVAL; + } + + if (master_mode) +@@ -460,6 +520,9 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + val = 0; + + regmap_update_bits(pcm3168a->regmap, reg, mask, val); ++ regmap_update_bits(pcm3168a->regmap, PCM3168A_RST_SMODE, ++ PCM3168A_DAC_SRDA_MASK, ++ sample_rate << PCM3168A_DAC_SRDA_SHIFT); + + if (tx) { + mask = PCM3168A_DAC_FMT_MASK; +@@ -474,9 +537,31 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static int pcm3168a_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_mask, ++ unsigned int rx_mask, ++ int slots, ++ int slot_width) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(codec); ++ ++ if ((slots != 8) && (slots != 4)) ++ return -EINVAL; ++ ++ if ((slot_width != 32) && (slot_width != 24)) ++ return -EINVAL; ++ ++ pcm3168a->slots = slots; ++ pcm3168a->slot_width = slot_width; ++ ++ return 0; ++} ++ + static const struct snd_soc_dai_ops pcm3168a_dac_dai_ops = { + .set_fmt = pcm3168a_set_dai_fmt_dac, + .set_sysclk = pcm3168a_set_dai_sysclk, ++ .set_tdm_slot = pcm3168a_set_tdm_slot, + .hw_params = pcm3168a_hw_params, + .digital_mute = pcm3168a_digital_mute + }; +@@ -484,6 +569,7 @@ static const struct snd_soc_dai_ops pcm3168a_dac_dai_ops = { + static const struct snd_soc_dai_ops pcm3168a_adc_dai_ops = { + .set_fmt = pcm3168a_set_dai_fmt_adc, + .set_sysclk = pcm3168a_set_dai_sysclk, ++ .set_tdm_slot = pcm3168a_set_tdm_slot, + .hw_params = pcm3168a_hw_params + }; + +@@ -661,6 +747,14 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap) + goto err_regulator; + } + ++ /* get TDM mode */ ++ if (dev->of_node) { ++ if (of_get_property(dev->of_node, "tdm", NULL)) ++ pcm3168a->tdm = TDM_MODE_NORM; ++ else if (of_get_property(dev->of_node, "tdmhs", NULL)) ++ pcm3168a->tdm = TDM_MODE_HS; ++ } ++ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); +diff --git a/sound/soc/codecs/pcm3168a.h b/sound/soc/codecs/pcm3168a.h +index 56c8332..658507f 100644 +--- a/sound/soc/codecs/pcm3168a.h ++++ b/sound/soc/codecs/pcm3168a.h +@@ -69,7 +69,7 @@ extern void pcm3168a_remove(struct device *dev); + #define PCM3168A_ADC_MSAD_SHIFT 4 + #define PCM3168A_ADC_MSAD_MASK 0x70 + #define PCM3168A_ADC_FMTAD_SHIFT 0 +-#define PCM3168A_ADC_FMTAD_MASK 0x7 ++#define PCM3168A_ADC_FMTAD_MASK 0xf + + #define PCM3168A_ADC_PWR_HPFB 0x52 + #define PCM3168A_ADC_PSVAD_SHIFT 4 +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0018-ASoC-PCM3168A-disable-16-bit-format.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0018-ASoC-PCM3168A-disable-16-bit-format.patch new file mode 100644 index 0000000..70c9510 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0018-ASoC-PCM3168A-disable-16-bit-format.patch @@ -0,0 +1,26 @@ +From 4b228271e1276e67d3892aef2e86644c8f382314 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Wed, 13 Apr 2016 14:36:14 +0300 +Subject: [PATCH 18/21] ASoC: PCM3168A: disable 16 bit format + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + sound/soc/codecs/pcm3168a.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c +index 6287ad9..06b5d53 100644 +--- a/sound/soc/codecs/pcm3168a.c ++++ b/sound/soc/codecs/pcm3168a.c +@@ -22,7 +22,7 @@ + + #include "pcm3168a.h" + +-#define PCM3168A_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ ++#define PCM3168A_FORMATS (/*SNDRV_PCM_FMTBIT_S16_LE | */\ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0019-ASoC-PCM3168A-enable-on-power-on.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0019-ASoC-PCM3168A-enable-on-power-on.patch new file mode 100644 index 0000000..0159985 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0019-ASoC-PCM3168A-enable-on-power-on.patch @@ -0,0 +1,27 @@ +From 881838951504feac2f42e771ed2195b305cf22a1 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Wed, 13 Apr 2016 14:36:50 +0300 +Subject: [PATCH 19/21] ASoC: PCM3168A: enable on power on + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + sound/soc/codecs/pcm3168a.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c +index 06b5d53..719eca1 100644 +--- a/sound/soc/codecs/pcm3168a.c ++++ b/sound/soc/codecs/pcm3168a.c +@@ -600,7 +600,7 @@ static struct snd_soc_dai_driver pcm3168a_dais[] = { + + static const struct reg_default pcm3168a_reg_default[] = { + { PCM3168A_RST_SMODE, PCM3168A_MRST_MASK | PCM3168A_SRST_MASK }, +- { PCM3168A_DAC_PWR_MST_FMT, 0x00 }, ++ { PCM3168A_DAC_PWR_MST_FMT, 0x80 }, + { PCM3168A_DAC_OP_FLT, 0x00 }, + { PCM3168A_DAC_INV, 0x00 }, + { PCM3168A_DAC_MUTE, 0x00 }, +-- +1.7.10.4 + diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0020-ASoC-PCM3168A-merge-ADC-and-DAC-to-single-DAI.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0020-ASoC-PCM3168A-merge-ADC-and-DAC-to-single-DAI.patch new file mode 100644 index 0000000..9565725 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0020-ASoC-PCM3168A-merge-ADC-and-DAC-to-single-DAI.patch @@ -0,0 +1,276 @@ +From 45b88347956046b1a75c791254609ca7becdb0bf Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Wed, 20 Apr 2016 17:52:12 +0300 +Subject: [PATCH] ASoC: PCM3168A: merge ADC and DAC to single DAI + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + sound/soc/codecs/pcm3168a.c | 151 +++++++++++++++++++++---------------------- + 1 file changed, 72 insertions(+), 79 deletions(-) + +diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c +index 719eca1..e1b5feb 100644 +--- a/sound/soc/codecs/pcm3168a.c ++++ b/sound/soc/codecs/pcm3168a.c +@@ -57,8 +57,7 @@ struct pcm3168a_priv { + struct regulator_bulk_data supplies[PCM3168A_NUM_SUPPLIES]; + struct regmap *regmap; + struct clk *scki; +- bool adc_master_mode; +- bool dac_master_mode; ++ bool master_mode; + unsigned int tdm; + unsigned int slots; + unsigned int slot_width; +@@ -343,7 +342,7 @@ int format_table[3][6] = { + [SND_SOC_DAIFMT_DSP_B] = -1}, + }; + +-static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, ++static int __pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int format, bool dac) + { + struct snd_soc_codec *codec = dai->codec; +@@ -392,31 +391,32 @@ static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, + reg = PCM3168A_DAC_PWR_MST_FMT; + mask = PCM3168A_DAC_FMT_MASK; + shift = PCM3168A_DAC_FMT_SHIFT; +- pcm3168a->dac_master_mode = master_mode; + pcm3168a->dac_fmt = fmt; + } else { + reg = PCM3168A_ADC_MST_FMT; + mask = PCM3168A_ADC_FMTAD_MASK; + shift = PCM3168A_ADC_FMTAD_SHIFT; +- pcm3168a->adc_master_mode = master_mode; + pcm3168a->adc_fmt = fmt; + } + ++ pcm3168a->master_mode = master_mode; ++ + regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift); + + return 0; + } + +-static int pcm3168a_set_dai_fmt_dac(struct snd_soc_dai *dai, ++static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int format) + { +- return pcm3168a_set_dai_fmt(dai, format, true); +-} ++ int ret; + +-static int pcm3168a_set_dai_fmt_adc(struct snd_soc_dai *dai, +- unsigned int format) +-{ +- return pcm3168a_set_dai_fmt(dai, format, false); ++ /* dac */ ++ ret = __pcm3168a_set_dai_fmt(dai, format, false); ++ if (ret) ++ return ret; ++ /* adc */ ++ return __pcm3168a_set_dai_fmt(dai, format, true); + } + + static int pcm3168a_hw_params(struct snd_pcm_substream *substream, +@@ -426,7 +426,7 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + int bits; + struct snd_soc_codec *codec = dai->codec; + struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(codec); +- bool tx, master_mode; ++ bool tx; + u32 val, mask, shift, reg; + u32 sample_rate = 0; /* auto */ + unsigned int rate, channels, fmt, ratio, max_ratio; +@@ -440,33 +440,28 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + + tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + +- ratio = pcm3168a->sysclk / rate; +- +- tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + if (tx) { + max_ratio = PCM3168A_NUM_SCKI_RATIOS_DAC; +- reg = PCM3168A_DAC_PWR_MST_FMT; +- mask = PCM3168A_DAC_MSDA_MASK; +- shift = PCM3168A_DAC_MSDA_SHIFT; +- master_mode = pcm3168a->dac_master_mode; + fmt = pcm3168a->dac_fmt; + } else { + max_ratio = PCM3168A_NUM_SCKI_RATIOS_ADC; +- reg = PCM3168A_ADC_MST_FMT; +- mask = PCM3168A_ADC_MSAD_MASK; +- shift = PCM3168A_ADC_MSAD_SHIFT; +- master_mode = pcm3168a->adc_master_mode; + fmt = pcm3168a->adc_fmt; + } + +- for (i = 0; i < max_ratio; i++) { +- if (pcm3168a_scki_ratios[i] == ratio) +- break; +- } ++ if (pcm3168a->master_mode) { ++ ratio = pcm3168a->sysclk / rate; ++ for (i = 0; i < max_ratio; i++) ++ if (pcm3168a_scki_ratios[i] == ratio) ++ break; + +- if (i == max_ratio) { +- dev_err(codec->dev, "unsupported sysclk ratio\n"); +- return -EINVAL; ++ if (i == max_ratio) { ++ dev_err(codec->dev, "unsupported sysclk ratio: %d\n", ratio); ++ return -EINVAL; ++ } ++ val = i + 1; ++ } else { ++ /* slave mode */ ++ val = 0; + } + + if (pcm3168a->tdm == TDM_MODE_NONE) { +@@ -474,14 +469,15 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + min_frame_size = bits * 2; + switch (min_frame_size) { + case 32: +- if (master_mode || (fmt != PCM3168A_FMT_RIGHT_J)) { ++ if (pcm3168a->master_mode || ++ (fmt != PCM3168A_FMT_RIGHT_J)) { + dev_err(codec->dev, "32-bit frames are supported only for slave mode using right justified\n"); + return -EINVAL; + } + fmt = PCM3168A_FMT_RIGHT_J_16; + break; + case 48: +- if (master_mode || ++ if (pcm3168a->master_mode || + (fmt == PCM3168A_FMT_DSP_A) || + (fmt == PCM3168A_FMT_DSP_B)) { + dev_err(codec->dev, "48-bit frames not supported in master mode, or slave mode using DSP\n"); +@@ -514,26 +510,30 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + } + } + +- if (master_mode) +- val = ((i + 1) << shift); +- else ++ /* Setup ADC in master mode, couse it drives ADC */ ++ if ((pcm3168a->master_mode) || (tx)) { ++ fmt = pcm3168a->dac_fmt; ++ reg = PCM3168A_DAC_PWR_MST_FMT; ++ mask = PCM3168A_DAC_MSDA_MASK | PCM3168A_DAC_FMT_MASK; ++ shift = PCM3168A_DAC_MSDA_SHIFT; ++ /* start DAC */ ++ regmap_update_bits(pcm3168a->regmap, reg, mask, (val << shift) | fmt); ++ } ++ /* Do we need also ADC? */ ++ if (!tx) { ++ fmt = pcm3168a->adc_fmt; ++ reg = PCM3168A_ADC_MST_FMT; ++ mask = PCM3168A_ADC_MSAD_MASK | PCM3168A_ADC_FMTAD_MASK; ++ shift = PCM3168A_ADC_MSAD_SHIFT; ++ /* ADC slave mode only, driven by DAC or CPU DAI */ + val = 0; ++ regmap_update_bits(pcm3168a->regmap, reg, mask, (val << shift) | fmt); ++ } + +- regmap_update_bits(pcm3168a->regmap, reg, mask, val); + regmap_update_bits(pcm3168a->regmap, PCM3168A_RST_SMODE, + PCM3168A_DAC_SRDA_MASK, + sample_rate << PCM3168A_DAC_SRDA_SHIFT); + +- if (tx) { +- mask = PCM3168A_DAC_FMT_MASK; +- shift = PCM3168A_DAC_FMT_SHIFT; +- } else { +- mask = PCM3168A_ADC_FMTAD_MASK; +- shift = PCM3168A_ADC_FMTAD_SHIFT; +- } +- +- regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift); +- + return 0; + } + +@@ -558,44 +558,32 @@ static int pcm3168a_set_tdm_slot(struct snd_soc_dai *dai, + return 0; + } + +-static const struct snd_soc_dai_ops pcm3168a_dac_dai_ops = { +- .set_fmt = pcm3168a_set_dai_fmt_dac, ++static const struct snd_soc_dai_ops pcm3168a_dai_ops = { ++ .set_fmt = pcm3168a_set_dai_fmt, + .set_sysclk = pcm3168a_set_dai_sysclk, + .set_tdm_slot = pcm3168a_set_tdm_slot, + .hw_params = pcm3168a_hw_params, + .digital_mute = pcm3168a_digital_mute + }; + +-static const struct snd_soc_dai_ops pcm3168a_adc_dai_ops = { +- .set_fmt = pcm3168a_set_dai_fmt_adc, +- .set_sysclk = pcm3168a_set_dai_sysclk, +- .set_tdm_slot = pcm3168a_set_tdm_slot, +- .hw_params = pcm3168a_hw_params +-}; +- +-static struct snd_soc_dai_driver pcm3168a_dais[] = { +- { +- .name = "pcm3168a-dac", +- .playback = { +- .stream_name = "Playback", +- .channels_min = 1, +- .channels_max = 8, +- .rates = SNDRV_PCM_RATE_8000_192000, +- .formats = PCM3168A_FORMATS +- }, +- .ops = &pcm3168a_dac_dai_ops ++static struct snd_soc_dai_driver pcm3168a_dai = { ++ .name = "pcm3168a", ++ .playback = { ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = 8, ++ .rates = SNDRV_PCM_RATE_8000_192000, ++ .formats = PCM3168A_FORMATS + }, +- { +- .name = "pcm3168a-adc", +- .capture = { +- .stream_name = "Capture", +- .channels_min = 1, +- .channels_max = 6, +- .rates = SNDRV_PCM_RATE_8000_96000, +- .formats = PCM3168A_FORMATS +- }, +- .ops = &pcm3168a_adc_dai_ops ++ .capture = { ++ .stream_name = "Capture", ++ .channels_min = 1, ++ .channels_max = 8, ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = PCM3168A_FORMATS + }, ++ .ops = &pcm3168a_dai_ops, ++ .symmetric_rates = 1, + }; + + static const struct reg_default pcm3168a_reg_default[] = { +@@ -759,8 +747,13 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap) + pm_runtime_enable(dev); + pm_runtime_idle(dev); + +- ret = snd_soc_register_codec(dev, &pcm3168a_driver, pcm3168a_dais, +- ARRAY_SIZE(pcm3168a_dais)); ++ if (pcm3168a->tdm != TDM_MODE_NONE) { ++ pcm3168a_dai.playback.channels_min = 8; ++ pcm3168a_dai.capture.channels_min = 8; ++ } ++ ++ ret = snd_soc_register_codec(dev, &pcm3168a_driver, ++ &pcm3168a_dai, 1); + if (ret) { + dev_err(dev, "failed to register codec: %d\n", ret); + goto err_regulator; +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0021-ASoC-PCM3168A-disable-PM.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0021-ASoC-PCM3168A-disable-PM.patch new file mode 100644 index 0000000..13663e9 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0021-ASoC-PCM3168A-disable-PM.patch @@ -0,0 +1,43 @@ +From 98080a2e34eaf0085c3ea34079dfce89c10d0ee1 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Mon, 30 May 2016 12:30:26 +0300 +Subject: [PATCH] ASoC: PCM3168A: disable PM + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + sound/soc/codecs/pcm3168a.c | 12 +++++++++++- + 1 file changed, 11 insertions(+), 1 deletion(-) + +diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c +index 8113566..2bee442 100644 +--- a/sound/soc/codecs/pcm3168a.c ++++ b/sound/soc/codecs/pcm3168a.c +@@ -797,7 +797,8 @@ void pcm3168a_remove(struct device *dev) + } + EXPORT_SYMBOL_GPL(pcm3168a_remove); + +-#ifdef CONFIG_PM ++ ++#ifdef CONFIG_PM__ + static int pcm3168a_rt_resume(struct device *dev) + { + struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); +@@ -856,6 +857,15 @@ static int pcm3168a_rt_suspend(struct device *dev) + + return 0; + } ++#else ++static int pcm3168a_rt_resume(struct device *dev) ++{ ++ return 0; ++} ++static int pcm3168a_rt_suspend(struct device *dev) ++{ ++ return 0; ++} + #endif + + const struct dev_pm_ops pcm3168a_pm_ops = { +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0022-ASoC-fix-simple-card-do-not-respect-device-id.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0022-ASoC-fix-simple-card-do-not-respect-device-id.patch new file mode 100644 index 0000000..dd1dd3c --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0022-ASoC-fix-simple-card-do-not-respect-device-id.patch @@ -0,0 +1,37 @@ +From 1bee53bce7b8bf48f70e8b22272dceb47dbfc29e Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Wed, 17 Feb 2016 17:45:17 +0300 +Subject: [PATCH 22/31] ASoC: fix simple-card do not respect device id + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + sound/soc/generic/simple-card.c | 13 ------------- + 1 file changed, 13 deletions(-) + +diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c +index 9556644..0bad6f0 100644 +--- a/sound/soc/generic/simple-card.c ++++ b/sound/soc/generic/simple-card.c +@@ -435,19 +435,6 @@ static int asoc_simple_card_probe(struct platform_device *pdev) + dev_err(dev, "parse error %d\n", ret); + goto err; + } +- +- /* +- * soc_bind_dai_link() will check cpu name +- * after of_node matching if dai_link has cpu_dai_name. +- * but, it will never match if name was created by fmt_single_name() +- * remove cpu_dai_name to escape name matching. +- * see +- * fmt_single_name() +- * fmt_multiple_name() +- */ +- if (num_links == 1) +- dai_link->cpu_dai_name = NULL; +- + } else { + struct asoc_simple_card_info *cinfo; + +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0023-SPI-GPIO-get-master-fix.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0023-SPI-GPIO-get-master-fix.patch new file mode 100644 index 0000000..359c7ea --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0023-SPI-GPIO-get-master-fix.patch @@ -0,0 +1,26 @@ +From 95e2424a054402d712feeebdcb3be5db4c545b5d Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Fri, 12 Feb 2016 17:52:21 +0300 +Subject: [PATCH 23/31] SPI-GPIO: get master fix + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/spi/spi-gpio.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/spi/spi-gpio.c b/drivers/spi/spi-gpio.c +index 0021fc4..86f2ecc 100644 +--- a/drivers/spi/spi-gpio.c ++++ b/drivers/spi/spi-gpio.c +@@ -469,7 +469,7 @@ static int spi_gpio_probe(struct platform_device *pdev) + } + #endif + +- spi_gpio->bitbang.master = spi_master_get(master); ++ spi_gpio->bitbang.master = master; + spi_gpio->bitbang.chipselect = spi_gpio_chipselect; + + if ((master_flags & (SPI_MASTER_NO_TX | SPI_MASTER_NO_RX)) == 0) { +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0024-SPIDEV-extend-maximum-transfer-size.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0024-SPIDEV-extend-maximum-transfer-size.patch new file mode 100644 index 0000000..fba1bca --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0024-SPIDEV-extend-maximum-transfer-size.patch @@ -0,0 +1,26 @@ +From 392890512e63267a76263a5c36657a2c38bed099 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Wed, 17 Feb 2016 18:11:55 +0300 +Subject: [PATCH 24/31] SPIDEV: extend maximum transfer size + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/spi/spidev.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c +index 911e9e0..354fd4b 100644 +--- a/drivers/spi/spidev.c ++++ b/drivers/spi/spidev.c +@@ -90,7 +90,7 @@ struct spidev_data { + static LIST_HEAD(device_list); + static DEFINE_MUTEX(device_list_lock); + +-static unsigned bufsiz = 4096; ++static unsigned bufsiz = 8192; + module_param(bufsiz, uint, S_IRUGO); + MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); + +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0025-Radio-add-si468x-to-spidev.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0025-Radio-add-si468x-to-spidev.patch new file mode 100644 index 0000000..749fb89 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0025-Radio-add-si468x-to-spidev.patch @@ -0,0 +1,25 @@ +From 4474e4919ab324979d736d53d05064d4f10bbd28 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Fri, 12 Feb 2016 17:55:25 +0300 +Subject: [PATCH 25/31] Radio: add si468x to spidev + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/spi/spidev.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c +index 354fd4b..794d8d0 100644 +--- a/drivers/spi/spidev.c ++++ b/drivers/spi/spidev.c +@@ -646,6 +646,7 @@ static int spidev_remove(struct spi_device *spi) + + static const struct of_device_id spidev_dt_ids[] = { + { .compatible = "rohm,dh2228fv" }, ++ { .compatible = "si,468x" }, + {}, + }; + +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0026-ASoC-add-dummy-Si468x-driver.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0026-ASoC-add-dummy-Si468x-driver.patch new file mode 100644 index 0000000..58d2769 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0026-ASoC-add-dummy-Si468x-driver.patch @@ -0,0 +1,122 @@ +From 56f1240b9e418ad1196085397597e2c710a73946 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Thu, 18 Feb 2016 18:54:18 +0300 +Subject: [PATCH 26/31] ASoC: add dummy Si468x driver + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + sound/soc/codecs/Kconfig | 3 +++ + sound/soc/codecs/Makefile | 2 ++ + sound/soc/codecs/si468x.c | 66 +++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 71 insertions(+) + create mode 100644 sound/soc/codecs/si468x.c + +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +index 4f0c01a..bf0f022 100644 +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -310,6 +310,9 @@ config SND_SOC_PCM3168A_SPI + select SND_SOC_PCM3168A + select REGMAP_SPI + ++config SND_SOC_SI468X ++ tristate "Dummy sound driver for Si468x radio" ++ + config SND_SOC_PCM512x + tristate + config SND_SOC_RT5631 +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +index 513d895..ba05156 100644 +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -51,6 +51,7 @@ snd-soc-sgtl5000-objs := sgtl5000.o + snd-soc-alc5623-objs := alc5623.o + snd-soc-alc5632-objs := alc5632.o + snd-soc-sigmadsp-objs := sigmadsp.o ++snd-soc-si468x-objs := si468x.o + snd-soc-si476x-objs := si476x.o + snd-soc-sn95031-objs := sn95031.o + snd-soc-spdif-tx-objs := spdif_transciever.o +@@ -180,6 +181,7 @@ obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o + obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o + obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o + obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o ++obj-$(CONFIG_SND_SOC_SI468X) += snd-soc-si468x.o + obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o + obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif-rx.o snd-soc-spdif-tx.o + obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +diff --git a/sound/soc/codecs/si468x.c b/sound/soc/codecs/si468x.c +new file mode 100644 +index 0000000..18b099f +--- /dev/null ++++ b/sound/soc/codecs/si468x.c +@@ -0,0 +1,66 @@ ++/* ++ * Dummy sound driver for Si468x DAB/FM/AM chips ++ * Copyright 2016 Andrey Gusakov <andrey.gusakov@cogentembedded.com> ++ * ++ * Based on: Driver for the DFBM-CS320 bluetooth module ++ * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de> ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the ++ * Free Software Foundation; either version 2 of the License, or (at your ++ * option) any later version. ++ * ++ */ ++ ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++ ++#include <sound/soc.h> ++ ++static struct snd_soc_dai_driver si468x_dai = { ++ .name = "si468x-pcm", ++ .capture = { ++ .channels_min = 2, ++ .channels_max = 2, ++ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE, ++ }, ++}; ++ ++static struct snd_soc_codec_driver soc_codec_dev_si468x; ++ ++static int si468x_probe(struct platform_device *pdev) ++{ ++ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_si468x, ++ &si468x_dai, 1); ++} ++ ++static int si468x_remove(struct platform_device *pdev) ++{ ++ snd_soc_unregister_codec(&pdev->dev); ++ ++ return 0; ++} ++ ++static const struct of_device_id si468x_of_match[] = { ++ { .compatible = "si,si468x-pcm", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, si468x_of_match); ++ ++static struct platform_driver si468x_driver = { ++ .driver = { ++ .name = "si468x", ++ .of_match_table = si468x_of_match, ++ .owner = THIS_MODULE, ++ }, ++ .probe = si468x_probe, ++ .remove = si468x_remove, ++}; ++ ++module_platform_driver(si468x_driver); ++ ++MODULE_AUTHOR("Andrey Gusakov <andrey.gusakov@cogentembedded.com>"); ++MODULE_DESCRIPTION("ASoC Si468x radio chip driver"); ++MODULE_LICENSE("GPL"); +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0027-ASoC-R-Car-initial-TDM-support.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0027-ASoC-R-Car-initial-TDM-support.patch new file mode 100644 index 0000000..b165b44 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0027-ASoC-R-Car-initial-TDM-support.patch @@ -0,0 +1,354 @@ +From cd5612aeb65de5468f793558d6536d1b90b3dc76 Mon Sep 17 00:00:00 2001 +From: Grigory Kletsko <grigory.kletsko@cogentembedded.com> +Date: Wed, 15 Jun 2016 21:13:26 +0300 +Subject: [PATCH 1/5] ASoC: R-Car: initial TDM support + +--- + include/sound/rcar_snd.h | 4 ++ + sound/soc/sh/rcar/core.c | 14 ++++- + sound/soc/sh/rcar/src.c | 12 +++- + sound/soc/sh/rcar/ssi.c | 148 ++++++++++++++++++++++++++++++++++++++++++++--- + 4 files changed, 164 insertions(+), 14 deletions(-) + +diff --git a/include/sound/rcar_snd.h b/include/sound/rcar_snd.h +index e2f1000..37229c1 100644 +--- a/include/sound/rcar_snd.h ++++ b/include/sound/rcar_snd.h +@@ -35,6 +35,10 @@ + */ + #define RSND_SSI_CLK_PIN_SHARE (1 << 31) + #define RSND_SSI_NO_BUSIF (1 << 30) /* SSI+DMA without BUSIF */ ++#define RSND_SSI_TDM_4 (1 << 29) /* use 4ch TDM on this SSI */ ++#define RSND_SSI_TDM_6 (1 << 28) /* use 6ch TDM on this SSI */ ++#define RSND_SSI_TDM_8 (1 << 27) /* use 8ch TDM on this SSI */ ++#define RSND_SSI_TDM_MASK (0x7 << 27) + + #define RSND_SSI(_dma_id, _pio_irq, _flags) \ + { .dma_id = _dma_id, .pio_irq = _pio_irq, .flags = _flags } +diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c +index ede9b5d..0b5c90e 100644 +--- a/sound/soc/sh/rcar/core.c ++++ b/sound/soc/sh/rcar/core.c +@@ -426,6 +426,16 @@ u32 rsnd_get_adinr(struct rsnd_mod *mod) + case 16: + adinr |= (8 << 16); + break; ++ case 18: ++ adinr |= (6 << 16); ++ break; ++ case 20: ++ adinr |= (4 << 16); ++ break; ++ case 22: ++ adinr |= (2 << 16); ++ break; ++ case 24: + case 32: + adinr |= (0 << 16); + break; +@@ -1074,7 +1084,7 @@ static int rsnd_dai_probe(struct platform_device *pdev, + drv[i].playback.rates = RSND_RATES; + drv[i].playback.formats = RSND_FMTS; + drv[i].playback.channels_min = 2; +- drv[i].playback.channels_max = 2; ++ drv[i].playback.channels_max = 8; + + rdai[i].playback.info = &info->dai_info[i].playback; + rsnd_path_init(priv, &rdai[i], &rdai[i].playback); +@@ -1083,7 +1093,7 @@ static int rsnd_dai_probe(struct platform_device *pdev, + drv[i].capture.rates = RSND_RATES; + drv[i].capture.formats = RSND_FMTS; + drv[i].capture.channels_min = 2; +- drv[i].capture.channels_max = 2; ++ drv[i].capture.channels_max = 8; + + rdai[i].capture.info = &info->dai_info[i].capture; + rsnd_path_init(priv, &rdai[i], &rdai[i].capture); +diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c +index 426714e..bbd7fe5 100644 +--- a/sound/soc/sh/rcar/src.c ++++ b/sound/soc/sh/rcar/src.c +@@ -158,6 +158,8 @@ int rsnd_src_ssiu_start(struct rsnd_mod *ssi_mod, + */ + if (rsnd_ssi_is_pin_sharing(ssi_mod)) { + int shift = -1; ++ int sync_34 = 0; ++ int mask = 0; + switch (ssi_id) { + case 1: + shift = 0; +@@ -165,16 +167,20 @@ int rsnd_src_ssiu_start(struct rsnd_mod *ssi_mod, + case 2: + shift = 2; + break; ++ case 3: + case 4: + shift = 16; ++ sync_34 = 1; ++ mask = (1 << 20); + break; + } + + if (shift >= 0) + rsnd_mod_bset(ssi_mod, SSI_MODE1, +- 0x3 << shift, +- rsnd_dai_is_clk_master(rdai) ? +- 0x2 << shift : 0x1 << shift); ++ (0x3 << shift) | mask, ++ (rsnd_dai_is_clk_master(rdai) ? ++ (0x2 << shift) : (0x1 << shift)) | ++ (sync_34 << 20)); + } + + /* +diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c +index 49acffe..a55ac91 100644 +--- a/sound/soc/sh/rcar/ssi.c ++++ b/sound/soc/sh/rcar/ssi.c +@@ -26,6 +26,10 @@ + #define IIEN (1 << 25) /* Idle Mode Interrupt Enable */ + #define DIEN (1 << 24) /* Data Interrupt Enable */ + ++#define CHNL_TDM_4 (1 << 22) /* A TDM frame consists of four system words */ ++#define CHNL_TDM_6 (2 << 22) /* A TDM frame consists of six system words */ ++#define CHNL_TDM_8 (3 << 22) /* A TDM frame consists of eight system words */ ++ + #define DWL_8 (0 << 19) /* Data Word Length */ + #define DWL_16 (1 << 19) /* Data Word Length */ + #define DWL_18 (2 << 19) /* Data Word Length */ +@@ -58,6 +62,8 @@ + * SSIWSR + */ + #define CONT (1 << 8) /* WS Continue Function */ ++#define MONO (1 << 1) /* Monaural format */ ++#define WS_MODE_TDM (1 << 0) /* TDM format, monaural format */ + + #define SSI_NAME "ssi" + +@@ -92,6 +98,24 @@ struct rsnd_ssi { + #define rsnd_ssi_mode_flags(p) ((p)->info->flags) + #define rsnd_ssi_dai_id(ssi) ((ssi)->info->dai_id) + ++int rsnd_ssi_is_tdm(struct rsnd_ssi *ssi) ++{ ++ return !!(rsnd_ssi_mode_flags(ssi) & RSND_SSI_TDM_MASK); ++} ++ ++int rsnd_ssi_channels(struct rsnd_ssi *ssi) ++{ ++ if (rsnd_ssi_mode_flags(ssi) & RSND_SSI_TDM_4) ++ return 4; ++ if (rsnd_ssi_mode_flags(ssi) & RSND_SSI_TDM_6) ++ return 6; ++ if (rsnd_ssi_mode_flags(ssi) & RSND_SSI_TDM_8) ++ return 8; ++ ++ /* no TDM */ ++ return 2; ++} ++ + static int rsnd_ssi_use_busif(struct rsnd_mod *mod) + { + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); +@@ -152,22 +176,32 @@ static int rsnd_ssi_master_clk_start(struct rsnd_ssi *ssi, + for (j = 0; j < ARRAY_SIZE(ssi_clk_mul_table); j++) { + + /* ++ * divider = 1 not allowed in TDM ++ */ ++ if ((ssi_clk_mul_table[j] == 1) && ++ (rsnd_ssi_is_tdm(ssi))) ++ continue; ++ ++ /* + * this driver is assuming that +- * system word is 64fs (= 2 x 32bit) ++ * system word n_channels x 32bit + * see rsnd_ssi_init() + */ + main_rate = rate / adg_clk_div_table[i] +- * 32 * 2 * ssi_clk_mul_table[j]; ++ * 32 * rsnd_ssi_channels(ssi) \ ++ * ssi_clk_mul_table[j]; + + ret = rsnd_adg_ssi_clk_try_start(&ssi->mod, main_rate); + if (0 == ret) { +- ssi->rate = rate; +- ssi->cr_clk = FORCE | SWL_32 | +- SCKD | SWSD | CKDV(j); +- + dev_dbg(dev, "ssi%d outputs %u Hz\n", + rsnd_mod_id(&ssi->mod), rate); ++ dev_dbg(dev, " div %d, mul %d\n", ++ adg_clk_div_table[i], ssi_clk_mul_table[j]); + ++ ++ ssi->rate = rate; ++ ssi->cr_clk = FORCE | SWL_32 | ++ SCKD | SWSD | CKDV(j); + return 0; + } + } +@@ -203,10 +237,13 @@ static void rsnd_ssi_hw_start(struct rsnd_ssi *ssi, + rsnd_mod_write(&ssi->mod, SSIWSR, CONT); + } + +- if (rsnd_ssi_clk_from_parent(ssi)) ++ if (rsnd_ssi_clk_from_parent(ssi)) { ++ /* in TDM mode CKDV=0 is invalid */ ++ ssi->cr_clk = CKDV(1); + rsnd_ssi_hw_start(ssi->parent, rdai, io); +- else ++ } else { + rsnd_ssi_master_clk_start(ssi, io); ++ } + } + } + +@@ -318,14 +355,44 @@ static int rsnd_ssi_init(struct rsnd_mod *mod, + * see also rsnd_ssi_master_clk_enable() + */ + cr |= SWL_32; ++ if (rsnd_ssi_is_tdm(ssi)) { ++ switch (rsnd_ssi_channels(ssi)) { ++ case 4: ++ cr |= CHNL_TDM_4; ++ break; ++ case 6: ++ cr |= CHNL_TDM_6; ++ break; ++ case 8: ++ cr |= CHNL_TDM_8; ++ break; ++ default: ++ return -EIO; ++ } ++ } + + /* + * init clock settings for SSICR + */ + switch (runtime->sample_bits) { ++ case 8: ++ cr |= DWL_8; ++ break; + case 16: + cr |= DWL_16; + break; ++ case 18: ++ cr |= DWL_18; ++ break; ++ case 20: ++ cr |= DWL_20; ++ break; ++ case 22: ++ cr |= DWL_22; ++ break; ++ case 24: ++ cr |= DWL_24; ++ break; + case 32: + cr |= DWL_24; + break; +@@ -394,17 +461,47 @@ static int rsnd_ssi_init_irq(struct rsnd_mod *mod, + * see also rsnd_ssi_master_clk_enable() + */ + cr |= SWL_32; ++ if (rsnd_ssi_is_tdm(ssi)) { ++ switch (rsnd_ssi_channels(ssi)) { ++ case 4: ++ cr |= CHNL_TDM_4; ++ break; ++ case 6: ++ cr |= CHNL_TDM_6; ++ break; ++ case 8: ++ cr |= CHNL_TDM_8; ++ break; ++ default: ++ return -EIO; ++ } ++ } + + /* + * init clock settings for SSICR + */ + switch (runtime->sample_bits) { ++ case 8: ++ cr |= DWL_8; ++ break; + case 16: + cr |= DWL_16; + break; +- case 32: ++ case 18: ++ cr |= DWL_18; ++ break; ++ case 20: ++ cr |= DWL_20; ++ break; ++ case 22: ++ cr |= DWL_22; ++ break; ++ case 24: + cr |= DWL_24; + break; ++ case 32: ++ cr |= DWL_32; ++ break; + default: + return -EIO; + } +@@ -693,9 +790,33 @@ static int rsnd_ssi_dma_start(struct rsnd_mod *mod, + { + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + struct rsnd_dma *dma = rsnd_mod_to_dma(&ssi->mod); ++ struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); ++ u32 wsr = 0; ++ ++ /* enable DMA transfer */ ++ ssi->cr_etc = DMEN; ++ ++ /* enable Overflow and Underflow IRQ */ ++ ssi->cr_etc |= UIEN | OIEN; + + rsnd_dma_start(dma); + ++ rsnd_ssi_hw_start(ssi, ssi->rdai, io); ++ ++ rsnd_src_enable_dma_ssi_irq(mod, rdai, rsnd_ssi_use_busif(mod)); ++ ++ /* enable WS continue */ ++ if (rsnd_dai_is_clk_master(rdai)) ++ wsr |= CONT; ++ ++ /* Enable TDM */ ++ if (rsnd_ssi_is_tdm(ssi)) ++ wsr |= WS_MODE_TDM; ++ ++ rsnd_mod_write(&ssi->mod, SSIWSR, wsr); ++ ++ rsnd_src_ssiu_start(mod, rdai, rsnd_ssi_use_busif(mod)); ++ + return 0; + } + +@@ -916,6 +1036,16 @@ static void rsnd_of_parse_ssi(struct platform_device *pdev, + + if (of_get_property(np, "no-busif", NULL)) + ssi_info->flags |= RSND_SSI_NO_BUSIF; ++ ++ /* ++ * TDM ++ */ ++ if (of_get_property(np, "tdm-4ch", NULL)) ++ ssi_info->flags |= RSND_SSI_TDM_4; ++ if (of_get_property(np, "tdm-6ch", NULL)) ++ ssi_info->flags |= RSND_SSI_TDM_6; ++ if (of_get_property(np, "tdm-8ch", NULL)) ++ ssi_info->flags |= RSND_SSI_TDM_8; + } + + rsnd_of_parse_ssi_end: +-- +2.5.0 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0028-ASoC-rcar-correct-32bit-to-24-bit-sample-conv.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0028-ASoC-rcar-correct-32bit-to-24-bit-sample-conv.patch new file mode 100644 index 0000000..03bc25f --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0028-ASoC-rcar-correct-32bit-to-24-bit-sample-conv.patch @@ -0,0 +1,45 @@ +From a6c0cc499acddaae73904ca2ae145c7d461e7f37 Mon Sep 17 00:00:00 2001 +From: Grigory Kletsko <grigory.kletsko@cogentembedded.com> +Date: Wed, 15 Jun 2016 21:42:20 +0300 +Subject: [PATCH] ASoC: rcar: correct 32bit to 24 bit sample conv + +--- + sound/soc/sh/rcar/src.c | 16 +++++++++++++++- + 1 file changed, 15 insertions(+), 1 deletion(-) + +diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c +index bbd7fe5..1fae6c2 100644 +--- a/sound/soc/sh/rcar/src.c ++++ b/sound/soc/sh/rcar/src.c +@@ -145,6 +145,7 @@ int rsnd_src_ssiu_start(struct rsnd_mod *ssi_mod, + int use_busif) + { + struct rsnd_dai_stream *io = rsnd_mod_to_io(ssi_mod); ++ struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + int ssi_id = rsnd_mod_id(ssi_mod); + + /* +@@ -192,7 +193,20 @@ int rsnd_src_ssiu_start(struct rsnd_mod *ssi_mod, + rsnd_mod_write(ssi_mod, BUSIF_DALIGN, val); + rsnd_mod_write(ssi_mod, SSI_BUSIF_ADINR, + rsnd_get_adinr(ssi_mod)); +- rsnd_mod_write(ssi_mod, SSI_BUSIF_MODE, 1); ++ /* ++ * Bit Length of Output Audio Data is limited to 24 bits ++ * This hack helps to convert (shift) 32 bit samples ++ * to 24 bit data on i2s bus. ++ * Without this hack sound will be too quiet. ++ * Realy not sure why this happens. ++ */ ++ if ((runtime->sample_bits == 32) && (ssi_id == 3)) ++ rsnd_mod_write(ssi_mod, SSI_BUSIF_MODE, 1 | (8 << 16)); ++ else if ((runtime->sample_bits == 32) && (ssi_id == 4)) ++ rsnd_mod_write(ssi_mod, SSI_BUSIF_MODE, 1 | (1 << 20) | (8 << 16)); ++ else ++ rsnd_mod_write(ssi_mod, SSI_BUSIF_MODE, 1); ++ + rsnd_mod_write(ssi_mod, SSI_CTRL, 0x1); + } + +-- +2.5.0 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0029-ASoC-R-Car-fix-debug-output.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0029-ASoC-R-Car-fix-debug-output.patch new file mode 100644 index 0000000..4d94719 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0029-ASoC-R-Car-fix-debug-output.patch @@ -0,0 +1,57 @@ +From bdd78e5d972ec91b756db6aef0eb19f44207d9f6 Mon Sep 17 00:00:00 2001 +From: Grigory Kletsko <grigory.kletsko@cogentembedded.com> +Date: Wed, 15 Jun 2016 21:18:35 +0300 +Subject: [PATCH 3/5] ASoC: R-Car: fix debug output + +--- + sound/soc/sh/rcar/adg.c | 2 +- + sound/soc/sh/rcar/ssi.c | 12 ++++++++---- + 2 files changed, 9 insertions(+), 5 deletions(-) + +diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c +index 7d2858c..dbeb6a3 100644 +--- a/sound/soc/sh/rcar/adg.c ++++ b/sound/soc/sh/rcar/adg.c +@@ -463,7 +463,7 @@ int rsnd_adg_probe(struct platform_device *pdev, + adg->clk[CLKI] = devm_clk_get(dev, "clk_i"); + + for_each_rsnd_clk(clk, adg, i) +- dev_dbg(dev, "clk %d : %p\n", i, clk); ++ dev_dbg(dev, "clk %d : %ld\n", i, clk_get_rate(clk)); + + #ifdef QUICK_HACK + np_root = of_find_node_by_path("/"); +diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c +index a55ac91..a69e621 100644 +--- a/sound/soc/sh/rcar/ssi.c ++++ b/sound/soc/sh/rcar/ssi.c +@@ -146,7 +146,8 @@ static void rsnd_ssi_status_check(struct rsnd_mod *mod, + udelay(50); + } + +- dev_warn(dev, "status check failed\n"); ++ dev_warn(dev, "ssi%d status check failed\n", ++ rsnd_mod_id(mod)); + } + + static int rsnd_ssi_master_clk_start(struct rsnd_ssi *ssi, +@@ -431,11 +432,14 @@ static int rsnd_ssi_quit(struct rsnd_mod *mod, + struct device *dev = rsnd_priv_to_dev(priv); + + if (ssi->err > 0) +- dev_warn(dev, "ssi under/over flow err = %d\n", ssi->err); ++ dev_warn(dev, "ssi%d under/over flow err = %d\n", ++ rsnd_mod_id(mod), ssi->err); + if (ssi->err_uirq > 0) +- dev_warn(dev, "ssi under flow err = %d\n", ssi->err_uirq); ++ dev_warn(dev, "ssi%d under flow err = %d\n", ++ rsnd_mod_id(mod), ssi->err_uirq); + if (ssi->err_oirq > 0) +- dev_warn(dev, "ssi over flow err = %d\n", ssi->err_oirq); ++ dev_warn(dev, "ssi%d over flow err = %d\n", ++ rsnd_mod_id(mod), ssi->err_oirq); + + ssi->rdai = NULL; + ssi->cr_own = 0; +-- +2.5.0 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0030-R-Car-sound-disable-clock-hack.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0030-R-Car-sound-disable-clock-hack.patch new file mode 100644 index 0000000..b07d7ce --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0030-R-Car-sound-disable-clock-hack.patch @@ -0,0 +1,24 @@ +From 9f62d868ad64af881f9dc54471ca0410efe01c2e Mon Sep 17 00:00:00 2001 +From: Grigory Kletsko <grigory.kletsko@cogentembedded.com> +Date: Wed, 15 Jun 2016 21:19:21 +0300 +Subject: [PATCH 4/5] R-Car: sound: disable clock hack + +--- + sound/soc/sh/rcar/rsnd.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h +index d3b544b..1bbd5bc 100644 +--- a/sound/soc/sh/rcar/rsnd.h ++++ b/sound/soc/sh/rcar/rsnd.h +@@ -492,7 +492,7 @@ struct rsnd_mod *rsnd_dvc_mod_get(struct rsnd_priv *priv, int id); + + #define rsnd_dvc_nr(priv) ((priv)->dvc_nr) + +-#define QUICK_HACK ++/* #define QUICK_HACK */ + #ifdef QUICK_HACK + void rsnd_adg_clk_set_rate(struct rsnd_mod *mod, unsigned int rate); + #endif +-- +2.5.0 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0031-ASoC-R-car-SSI-fix-SSI-slave-mode-setup-while-TDM-an.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0031-ASoC-R-car-SSI-fix-SSI-slave-mode-setup-while-TDM-an.patch new file mode 100644 index 0000000..6c469d1 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0031-ASoC-R-car-SSI-fix-SSI-slave-mode-setup-while-TDM-an.patch @@ -0,0 +1,48 @@ +From 9467d97eaa2ede54dc67a0f83eb3cdd30cf9dc15 Mon Sep 17 00:00:00 2001 +From: Grigory Kletsko <grigory.kletsko@cogentembedded.com> +Date: Wed, 15 Jun 2016 21:23:03 +0300 +Subject: [PATCH 5/5] ASoC: R-car: SSI fix SSI slave mode setup while TDM and + +--- + sound/soc/sh/rcar/ssi.c | 22 +++++++++++++++------- + 1 file changed, 15 insertions(+), 7 deletions(-) + +diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c +index a69e621..65af590 100644 +--- a/sound/soc/sh/rcar/ssi.c ++++ b/sound/soc/sh/rcar/ssi.c +@@ -231,17 +231,25 @@ static void rsnd_ssi_hw_start(struct rsnd_ssi *ssi, + rsnd_mod_hw_start(&ssi->mod); + + if (rsnd_dai_is_clk_master(rdai)) { +- /* enable WS continue */ +- if (rsnd_dai_is_clk_master(rdai)) { +- status = rsnd_mod_read(&ssi->mod, SSIWSR); +- if (!(status & CONT)) +- rsnd_mod_write(&ssi->mod, SSIWSR, CONT); +- } +- + if (rsnd_ssi_clk_from_parent(ssi)) { ++ int wsr; + /* in TDM mode CKDV=0 is invalid */ + ssi->cr_clk = CKDV(1); ++ if (ssi->parent->usrcnt == 0) { ++ ssi->parent->cr_own = ssi->cr_own; ++ ++ /* enable WS continue */ ++ wsr = CONT; ++ ++ /* Enable TDM */ ++ if (rsnd_ssi_is_tdm(ssi)) ++ wsr |= WS_MODE_TDM; ++ } + rsnd_ssi_hw_start(ssi->parent, rdai, io); ++ /* set WSR after master mode is set in CR */ ++ if (wsr) ++ rsnd_mod_write(&ssi->parent->mod, SSIWSR, wsr); ++ + } else { + rsnd_ssi_master_clk_start(ssi, io); + } +-- +2.5.0 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0032-mmc-Add-SDIO-function-devicetree-subnode-parsing.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0032-mmc-Add-SDIO-function-devicetree-subnode-parsing.patch new file mode 100644 index 0000000..a95c7bc --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0032-mmc-Add-SDIO-function-devicetree-subnode-parsing.patch @@ -0,0 +1,160 @@ +From 882ed046c6383564f09c89a7c53e28a37373fffc Mon Sep 17 00:00:00 2001 +From: Sascha Hauer <s.hauer@pengutronix.de> +Date: Mon, 30 Jun 2014 11:07:25 +0200 +Subject: [PATCH 32/33] mmc: Add SDIO function devicetree subnode parsing + +This adds SDIO devicetree subnode parsing to the mmc core. While +SDIO devices are runtime probable they sometimes need nonprobable +additional information on embedded systems, like an additional gpio +interrupt or a clock. This patch makes it possible to supply this +information from the devicetree. SDIO drivers will find a pointer +to the devicenode in their devices of_node pointer. + +Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> +[hdegoede@redhat.com: Misc. cleanups] +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/mmc/core/bus.c | 4 ++++ + drivers/mmc/core/core.c | 29 +++++++++++++++++++++++++++++ + drivers/mmc/core/core.h | 3 +++ + drivers/mmc/core/sdio_bus.c | 11 +++++++++++ + 4 files changed, 47 insertions(+) + +diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c +index 419edfa..87049c6 100644 +--- a/drivers/mmc/core/bus.c ++++ b/drivers/mmc/core/bus.c +@@ -16,6 +16,7 @@ + #include <linux/err.h> + #include <linux/slab.h> + #include <linux/stat.h> ++#include <linux/of.h> + #include <linux/pm_runtime.h> + + #include <linux/mmc/card.h> +@@ -335,6 +336,8 @@ int mmc_add_card(struct mmc_card *card) + #endif + mmc_init_context_info(card->host); + ++ card->dev.of_node = mmc_of_find_child_device(card->host, 0); ++ + ret = device_add(&card->dev); + if (ret) + return ret; +@@ -363,6 +366,7 @@ void mmc_remove_card(struct mmc_card *card) + mmc_hostname(card->host), card->rca); + } + device_del(&card->dev); ++ of_node_put(card->dev.of_node); + } + + put_device(&card->dev); +diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c +index 02a40d0..8c85f20a 100644 +--- a/drivers/mmc/core/core.c ++++ b/drivers/mmc/core/core.c +@@ -27,6 +27,7 @@ + #include <linux/fault-inject.h> + #include <linux/random.h> + #include <linux/slab.h> ++#include <linux/of.h> + + #include <linux/mmc/card.h> + #include <linux/mmc/host.h> +@@ -1165,6 +1166,34 @@ u32 mmc_vddrange_to_ocrmask(int vdd_min, int vdd_max) + } + EXPORT_SYMBOL(mmc_vddrange_to_ocrmask); + ++static int mmc_of_get_func_num(struct device_node *node) ++{ ++ u32 reg; ++ int ret; ++ ++ ret = of_property_read_u32(node, "reg", ®); ++ if (ret < 0) ++ return ret; ++ ++ return reg; ++} ++ ++struct device_node *mmc_of_find_child_device(struct mmc_host *host, ++ unsigned func_num) ++{ ++ struct device_node *node; ++ ++ if (!host->parent || !host->parent->of_node) ++ return NULL; ++ ++ for_each_child_of_node(host->parent->of_node, node) { ++ if (mmc_of_get_func_num(node) == func_num) ++ return node; ++ } ++ ++ return NULL; ++} ++ + #ifdef CONFIG_REGULATOR + + /** +diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h +index b9f18a2..6b01037 100644 +--- a/drivers/mmc/core/core.h ++++ b/drivers/mmc/core/core.h +@@ -30,6 +30,9 @@ struct mmc_bus_ops { + void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops); + void mmc_detach_bus(struct mmc_host *host); + ++struct device_node *mmc_of_find_child_device(struct mmc_host *host, ++ unsigned func_num); ++ + void mmc_init_erase(struct mmc_card *card); + + void mmc_set_chip_select(struct mmc_host *host, int mode); +diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c +index 6d67492..b5d8af0 100644 +--- a/drivers/mmc/core/sdio_bus.c ++++ b/drivers/mmc/core/sdio_bus.c +@@ -21,7 +21,9 @@ + #include <linux/mmc/card.h> + #include <linux/mmc/host.h> + #include <linux/mmc/sdio_func.h> ++#include <linux/of.h> + ++#include "core.h" + #include "sdio_cis.h" + #include "sdio_bus.h" + +@@ -312,6 +314,13 @@ static void sdio_acpi_set_handle(struct sdio_func *func) + static inline void sdio_acpi_set_handle(struct sdio_func *func) {} + #endif + ++static void sdio_set_of_node(struct sdio_func *func) ++{ ++ struct mmc_host *host = func->card->host; ++ ++ func->dev.of_node = mmc_of_find_child_device(host, func->num); ++} ++ + /* + * Register a new SDIO function with the driver model. + */ +@@ -321,6 +330,7 @@ int sdio_add_func(struct sdio_func *func) + + dev_set_name(&func->dev, "%s:%d", mmc_card_id(func->card), func->num); + ++ sdio_set_of_node(func); + sdio_acpi_set_handle(func); + ret = device_add(&func->dev); + if (ret == 0) { +@@ -344,6 +354,7 @@ void sdio_remove_func(struct sdio_func *func) + + acpi_dev_pm_detach(&func->dev, false); + device_del(&func->dev); ++ of_node_put(func->dev.of_node); + put_device(&func->dev); + } + +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0038-Porter-LVDS-display-LQ123K1LG03.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0038-Porter-LVDS-display-LQ123K1LG03.patch new file mode 100644 index 0000000..98011f8 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0038-Porter-LVDS-display-LQ123K1LG03.patch @@ -0,0 +1,48 @@ +From b5d05aa9f2995276fd00213e6a48842292aa5889 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Sat, 21 Mar 2015 20:07:20 +0300 +Subject: [PATCH] Porter LVDS display LQ123K1LG03 + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + arch/arm/mach-shmobile/board-porter-reference.c | 23 ++++++++++++----------- + 1 file changed, 12 insertions(+), 11 deletions(-) + +diff --git a/arch/arm/mach-shmobile/board-porter-reference.c b/arch/arm/mach-shmobile/board-porter-reference.c +index d481ecd..6a1110c 100644 +--- a/arch/arm/mach-shmobile/board-porter-reference.c ++++ b/arch/arm/mach-shmobile/board-porter-reference.c +@@ -64,18 +64,19 @@ static struct rcar_du_encoder_data porter_du_encoders[] = { + .type = RCAR_DU_ENCODER_NONE, + .output = RCAR_DU_OUTPUT_LVDS0, + .connector.lvds.panel = { +- .width_mm = 229, +- .height_mm = 149, ++ .width_mm = 291, ++ .height_mm = 109, ++ //.lvds_mode = 4, + .mode = { +- .clock = 69000, +- .hdisplay = 1280, +- .hsync_start = 1280 + 48, +- .hsync_end = 1280 + 48 + 32, +- .htotal = 1280 + 48 + 32 + 80, +- .vdisplay = 800, +- .vsync_start = 800 + 2, +- .vsync_end = 800 + 2 + 6, +- .vtotal = 800 + 2 + 6 + 15, ++ .clock = 53000, ++ .hdisplay = 1280, ++ .hsync_start = 1280, ++ .hsync_end = 1688, ++ .htotal = 1688, ++ .vdisplay = 480, ++ .vsync_start = 480, ++ .vsync_end = 525, ++ .vtotal = 525, + .flags = 0, + }, + }, +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0060-Remove-delay-at-LVDS-camera-initialization.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0060-Remove-delay-at-LVDS-camera-initialization.patch new file mode 100644 index 0000000..7f29f64 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0060-Remove-delay-at-LVDS-camera-initialization.patch @@ -0,0 +1,25 @@ +From 4347453d644e81c81317ce256340a3193c129e6a Mon Sep 17 00:00:00 2001 +From: Andrey Vostrikov <andrey.vostrikov@cogentembedded.com> +Date: Wed, 13 Apr 2016 13:26:25 +0300 +Subject: [PATCH] Remove delay at LVDS camera initialization + +Signed-off-by: Andrey Vostrikov <andrey.vostrikov@cogentembedded.com> +--- + drivers/media/i2c/soc_camera/max9272_ov10635.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/media/i2c/soc_camera/max9272_ov10635.h b/drivers/media/i2c/soc_camera/max9272_ov10635.h +index 80e9af4..49f4d29 100644 +--- a/drivers/media/i2c/soc_camera/max9272_ov10635.h ++++ b/drivers/media/i2c/soc_camera/max9272_ov10635.h +@@ -31,7 +31,7 @@ + #define MAXIM_IMI_MCU_V0_DELAY 8000 /* delay for powered MCU firmware v0 */ + #define MAXIM_IMI_MCU_V1_DELAY 3000 /* delay for powered MCU firmware v1 */ + #define MAXIM_IMI_MCU_NO_DELAY 0 /* delay for unpowered MCU */ +-#define MAXIM_IMI_MCU_DELAY MAXIM_IMI_MCU_V0_DELAY ++#define MAXIM_IMI_MCU_DELAY MAXIM_IMI_MCU_NO_DELAY + //#define MAXIM_IMI_MCU_POWERED /* skip ov10635 setup for IMI powered MCU (only fw later then v1) */ + + /* +-- +2.1.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0099-Porter-add-separate-dts-for-ext01-extension-board.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0099-Porter-add-separate-dts-for-ext01-extension-board.patch new file mode 100644 index 0000000..d2cac49 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0099-Porter-add-separate-dts-for-ext01-extension-board.patch @@ -0,0 +1,805 @@ +From ed11e08a736ac8fcdfdcc45b13d68cd688aacd73 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Mon, 23 May 2016 19:48:54 +0300 +Subject: [PATCH] Porter: add separate dts for ext01 extension board + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + arch/arm/boot/dts/Makefile | 1 + + arch/arm/boot/dts/r8a7791-porter-ext01.dts | 772 ++++++++++++++++++++++++++++ + 2 files changed, 773 insertions(+) + create mode 100644 arch/arm/boot/dts/r8a7791-porter-ext01.dts + +diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile +index 5e951fb..68c86f8 100644 +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -231,6 +231,7 @@ dtb-$(CONFIG_ARCH_ZYNQ) += zynq-zc702.dtb \ + # R-Car Gen2 AVB variants + dtb-$(CONFIG_ARCH_SHMOBILE_MULTI) += \ + r8a7791-koelsch-eavb.dtb \ ++ r8a7791-porter-ext01.dtb \ + r8a7791-porter-eavb.dtb \ + r8a7790-lager-eavb.dtb + +diff --git a/arch/arm/boot/dts/r8a7791-porter-ext01.dts b/arch/arm/boot/dts/r8a7791-porter-ext01.dts +new file mode 100644 +index 0000000..d115b36 +--- /dev/null ++++ b/arch/arm/boot/dts/r8a7791-porter-ext01.dts +@@ -0,0 +1,772 @@ ++/* ++ * Device Tree Source for the Porter board ++ * ++ * Copyright (C) 2015 Renesas Electronics Corporation ++ * Copyright (C) 2015 Cogent Embedded, Inc. ++ * ++ * This file is licensed under the terms of the GNU General Public License ++ * version 2. This program is licensed "as is" without any warranty of any ++ * kind, whether express or implied. ++ */ ++ ++/* ++ * SSI-AK4642 ++ * ++ * these commands are required when playback. ++ * ++ * # amixer set "LINEOUT Mixer DACL" on ++ * # amixer set "Digital" 200 ++ * # amixer set "DVC Out" 50 ++ */ ++ ++/dts-v1/; ++#include "r8a7791.dtsi" ++#include <dt-bindings/gpio/gpio.h> ++#include <dt-bindings/input/input.h> ++ ++/ { ++ model = "Porter"; ++ compatible = "renesas,porter", "renesas,r8a7791"; ++ ++ aliases { ++ serial6 = &scif0; ++ serial7 = &scifa4; ++ serial8 = &scifb2; ++ serial9 = &scif1; ++ }; ++ ++ chosen { ++ bootargs = "console=ttySC6,38400 ignore_loglevel ip=none rw root=/dev/mmcblk0p1 vmalloc=384M"; ++ }; ++ ++ memory@40000000 { ++ device_type = "memory"; ++ reg = <0 0x40000000 0 0x40000000>; ++ }; ++ ++ memory@200000000 { ++ device_type = "memory"; ++ reg = <2 0x00000000 0 0x40000000>; ++ }; ++ ++ ++ snd_clk: snd_clk { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <24576000>; ++ clock-output-names = "scki"; ++ }; ++ ++ leds { ++ compatible = "gpio-leds"; ++ led2 { ++ gpios = <&gpio2 19 GPIO_ACTIVE_HIGH>; ++ }; ++ led3 { ++ gpios = <&gpio2 20 GPIO_ACTIVE_HIGH>; ++ }; ++ led4 { ++ gpios = <&gpio2 21 GPIO_ACTIVE_HIGH>; ++ }; ++ }; ++ ++ gpio-keys { ++ compatible = "gpio-keys"; ++ ++ button@1 { ++ linux,code = <KEY_1>; ++ label = "SW2-1"; ++ gpio-key,wakeup; ++ debounce-interval = <20>; ++ gpios = <&gpio7 0 GPIO_ACTIVE_LOW>; ++ }; ++ button@2 { ++ linux,code = <KEY_2>; ++ label = "SW2-2"; ++ gpio-key,wakeup; ++ debounce-interval = <20>; ++ gpios = <&gpio7 1 GPIO_ACTIVE_LOW>; ++ }; ++ }; ++ ++ vccq_sdhi0: regulator@1 { ++ compatible = "regulator-gpio"; ++ ++ regulator-name = "SDHI0 VccQ"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ ++ gpios = <&gpio2 12 GPIO_ACTIVE_HIGH>; ++ gpios-states = <1>; ++ states = <3300000 1 ++ 1800000 0>; ++ }; ++ ++ vcc_sdhi2: regulator@2 { ++ compatible = "regulator-fixed"; ++ ++ regulator-name = "SDHI2 Vcc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ regulator-always-on; ++ }; ++ ++ vccq_sdhi2: regulator@3 { ++ compatible = "regulator-gpio"; ++ ++ regulator-name = "SDHI2 VccQ"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ ++ gpios = <&gpio2 26 GPIO_ACTIVE_HIGH>; ++ gpios-states = <1>; ++ states = <3300000 1 ++ 1800000 0>; ++ }; ++ ++ codec_en_reg: regulator@4 { ++ compatible = "regulator-fixed"; ++ regulator-name = "codec-en-regulator"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ ++ /* SND_RST GPIO for this board is CPLD gpio 0 */ ++ gpio = <&cpld_gpio 0 0>; ++ ++ /* delay - CHECK */ ++ startup-delay-us = <70000>; ++ enable-active-high; ++ }; ++ ++ amp_en_reg: regulator@5 { ++ compatible = "regulator-fixed"; ++ regulator-name = "amp-en-regulator"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ ++ /* Phones amps en GPIO for this board is CPLD gpio 1 */ ++ gpio = <&cpld_gpio 1 0>; ++ ++ startup-delay-us = <0>; ++ enable-active-high; ++ }; ++ ++ wlan_en_reg: regulator@6 { ++ compatible = "regulator-fixed"; ++ regulator-name = "wlan-en-regulator"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <1800000>; ++ ++ /* WLAN_EN GPIO for this board CPLD gpio 10 */ ++ gpio = <&cpld_gpio 10 0>; ++ ++ /* WLAN card specific delay */ ++ startup-delay-us = <70000>; ++ enable-active-high; ++ }; ++ ++ kim { ++ compatible = "kim"; ++ nshutdown_gpio = <769>; /* FIX! CPLD pin 11 */ ++ /* serial8 */ ++ dev_name = "/dev/ttySC8"; ++ flow_cntrl = <1>; ++ /* maximum clock on scifb@52MHz */ ++ baud_rate = <3250000>; ++ }; ++ ++ btwilink { ++ compatible = "btwilink"; ++ }; ++ ++ sound_ext: sound@0 { ++ compatible = "simple-audio-card"; ++ pinctrl-0 = <&sound_0_pins>; ++ pinctrl-names = "default"; ++ ++ simple-audio-card,format = "left_j"; ++ simple-audio-card,name = "pcm3168a"; ++ ++ simple-audio-card,bitclock-master = <&sound_ext_master>; ++ simple-audio-card,frame-master = <&sound_ext_master>; ++ sound_ext_master: simple-audio-card,cpu@0 { ++ sound-dai = <&rcar_sound 0>; ++ dai-tdm-slot-num = <8>; ++ dai-tdm-slot-width = <32>; ++ }; ++ ++ simple-audio-card,codec@0 { ++ sound-dai = <&pcm3168a>; ++ dai-tdm-slot-num = <8>; ++ dai-tdm-slot-width = <32>; ++ system-clock-frequency = <24576000>; ++ }; ++ }; ++ ++ sound_onboard: sound@1 { ++ compatible = "simple-audio-card"; ++ pinctrl-0 = <&sound_1_pins>; ++ pinctrl-names = "default"; ++ ++ simple-audio-card,format = "i2s"; ++ simple-audio-card,name = "ak464x"; ++ ++ sndcpu_0: simple-audio-card,cpu@1 { ++ sound-dai = <&rcar_sound 1>; ++ }; ++ ++ sndcodec_0: simple-audio-card,codec@1 { ++ sound-dai = <&ak4642>; ++ system-clock-frequency = <11289600>; ++ }; ++ }; ++ ++ sound_radio: sound@2 { ++ compatible = "simple-audio-card"; ++ pinctrl-0 = <&sound_2_pins>; ++ pinctrl-names = "default"; ++ ++ simple-audio-card,format = "i2s"; ++ simple-audio-card,name = "radio"; ++ ++ simple-audio-card,bitclock-master = <&sound_radio_master>; ++ simple-audio-card,frame-master = <&sound_radio_master>; ++ simple-audio-card,cpu@2 { ++ sound-dai = <&rcar_sound 2>; ++ }; ++ ++ sound_radio_master: simple-audio-card,codec@2 { ++ sound-dai = <&radio>; ++ system-clock-frequency = <12288000>; ++ }; ++ }; ++ ++ hdmi_transmitter: adv7511 { ++ compatible = "adi,adv7511"; ++ gpios = <&gpio3 29 GPIO_ACTIVE_LOW>; ++ ++ adi,input-style = <0x02>; ++ adi,input-id = <0x00>; ++ adi,input-color-depth = <0x03>; ++ adi,sync-pulse = <0x03>; ++ adi,bit-justification = <0x01>; ++ adi,up-conversion = <0x00>; ++ adi,timing-generation-sequence = <0x00>; ++ adi,vsync-polarity = <0x02>; ++ adi,hsync-polarity = <0x02>; ++ adi,clock-delay = <0x03>; ++ }; ++ ++ usbhs_udc { ++ gpios = <&gpio5 31 GPIO_ACTIVE_HIGH>; ++ }; ++ ++ i2c0_gpio0: i2c@0 { ++ status = "ok"; ++ clock-frequency = <400000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "i2c-gpio"; ++ gpios = <&gpio6 9 GPIO_ACTIVE_HIGH /* sda */ ++ &gpio7 2 GPIO_ACTIVE_HIGH /* scl */ ++ >; ++ i2c-gpio,delay-us = <50>; ++ ++ maxim_setup@10 { ++ compatible = "maxim,max9272-ov10635-setup"; ++ reg = <0x10>; /* fake address */ ++ gpios = <&gpio4 28 GPIO_ACTIVE_HIGH>; ++ }; ++ }; ++ ++ cpld_gpio: cpld { ++ compatible = "porter-cpld-gpio"; ++ #gpio-cells = <2>; ++ ++ porter-cpld-gpio,data-gpio = <&gpio4 18 GPIO_ACTIVE_HIGH>; ++ porter-cpld-gpio,ptrc-gpio = <&gpio4 15 GPIO_ACTIVE_HIGH>; ++ porter-cpld-gpio,bufc-gpio = <&gpio7 5 GPIO_ACTIVE_HIGH>; ++ }; ++ ++ i2c0_gpio1: i2c@1 { ++ status = "ok"; ++ clock-frequency = <400000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "i2c-gpio"; ++ gpios = <&gpio4 31 GPIO_ACTIVE_HIGH /* sda */ ++ &gpio1 24 GPIO_ACTIVE_HIGH /* scl */ ++ >; ++ i2c-gpio,delay-us = <2>; /* ~100 KHz */ ++ }; ++ ++ spi_gpio: spi@0 { ++ compatible = "spi-gpio"; ++ status = "okay"; ++ gpio-sck = <&gpio4 30 0>; ++ gpio-mosi = <&gpio2 30 0>; ++ gpio-miso = <&gpio7 19 0>; ++ cs-gpios = <&gpio2 18 0>; ++ num-chipselects = <1>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ si4689: si4689@0 { ++ compatible = "si,468x"; ++ reg = <0>; ++ spi-max-frequency = <10000000>; ++ }; ++ }; ++ ++ radio: si468x@0 { ++ compatible = "si,si468x-pcm"; ++ status = "okay"; ++ ++ #sound-dai-cells = <0>; ++ }; ++}; ++ ++&extal_clk { ++ clock-frequency = <20000000>; ++}; ++ ++&pfc { ++ pinctrl-0 = <&du_pins &usb0_pins &usb1_pins &vin0_pins &vin2_pins>; ++ pinctrl-names = "default"; ++ ++ du_pins: du { ++ renesas,groups = "du_rgb666", "du_sync", "du_clk_out_0"; ++ renesas,function = "du"; ++ }; ++ ++ scif0_pins: serial6 { ++ renesas,groups = "scif0_data_d"; ++ renesas,function = "scif0"; ++ }; ++ ++ scif1_pins: serial9 { ++ renesas,groups = "scif1_data_d"; ++ renesas,function = "scif1"; ++ }; ++ ++ scifa4_pins: serial7 { ++ renesas,groups = "scifa4_data"; ++ renesas,function = "scifa4"; ++ }; ++ ++ scifb2_pins: serial8 { ++ renesas,groups = "scifb2_data", "scifb2_ctrl"; ++ renesas,function = "scifb2"; ++ }; ++ ++ i2c1_pins: i2c1 { ++ renesas,groups = "i2c1_e"; ++ renesas,function = "i2c1"; ++ }; ++ ++ i2c2_pins: i2c2 { ++ renesas,groups = "i2c2"; ++ renesas,function = "i2c2"; ++ }; ++ ++ i2c4_pins: i2c4 { ++ renesas,groups = "i2c4_c"; ++ renesas,function = "i2c4"; ++ }; ++ ++ ether_pins: ether { ++ renesas,groups = "eth_link", "eth_mdio", "eth_rmii"; ++ renesas,function = "eth"; ++ }; ++ ++ avb_pins: avb { ++ renesas,groups = "avb_mdio", "avb_gmii"; ++ renesas,function = "avb"; ++ }; ++ ++ mlb3_pins: mlp { ++ renesas,groups = "mlb3pin"; ++ renesas,function = "mlb3pin"; ++ }; ++ ++ phy1_pins: phy1 { ++ renesas,groups = "intc_irq0"; ++ renesas,function = "intc"; ++ }; ++ ++ sdhi0_pins: sd0 { ++ renesas,groups = "sdhi0_data4", "sdhi0_ctrl"; ++ renesas,function = "sdhi0"; ++ }; ++ ++ sdhi2_pins: sd2 { ++ renesas,groups = "sdhi2_data4", "sdhi2_ctrl"; ++ renesas,function = "sdhi2"; ++ }; ++ ++ qspi_pins: spi0 { ++ renesas,groups = "qspi_ctrl", "qspi_data4"; ++ renesas,function = "qspi"; ++ }; ++ ++ msiof0_pins: spi1 { ++ renesas,groups = "msiof0_clk", "msiof0_sync", "msiof0_rx", ++ "msiof0_tx"; ++ renesas,function = "msiof0"; ++ }; ++ ++ sound_clk_pins: sound_clk { ++ renesas,groups = "audio_clk_b_b"; ++ renesas,function = "audio_clk"; ++ }; ++ ++ sound_0_pins: sound1 { ++ renesas,groups = "ssi34_ctrl", "ssi3_data", "ssi4_data"; ++ renesas,function = "ssi"; ++ }; ++ ++ sound_1_pins: sound0 { ++ renesas,groups = "ssi0129_ctrl", "ssi0_data", "ssi1_data"; ++ renesas,function = "ssi"; ++ }; ++ ++ sound_2_pins: sound2 { ++ renesas,groups = "ssi5_ctrl", "ssi5_data"; ++ renesas,function = "ssi"; ++ }; ++ ++ usb0_pins: usb0 { ++ renesas,groups = "usb0"; ++ renesas,function = "usb0"; ++ }; ++ ++ usb1_pins: usb1 { ++ renesas,groups = "usb1"; ++ renesas,function = "usb1"; ++ }; ++ ++ vin0_pins: vin0 { ++ renesas,groups = "vin0_data8", "vin0_clk"; ++ renesas,function = "vin0"; ++ }; ++ ++ vin2_pins: vin2 { ++ renesas,groups = "vin2_data8", "vin2_clk"; ++ renesas,function = "vin2"; ++ }; ++ ++ can0_pins: can0 { ++ renesas,groups = "can0_data_b"; ++ renesas,function = "can0"; ++ }; ++}; ++ ++ðer { ++ pinctrl-0 = <ðer_pins &phy1_pins>; ++ pinctrl-names = "default"; ++ ++ phy-handle = <&phy1>; ++ renesas,ether-link-active-low; ++ status = "disabled"; ++ ++ phy1: ethernet-phy@1 { ++ reg = <1>; ++ interrupt-parent = <&irqc0>; ++ interrupts = <0 IRQ_TYPE_LEVEL_LOW>; ++ micrel,led-mode = <1>; ++ }; ++}; ++ ++&can0 { ++ pinctrl-0 = <&can0_pins>; ++ pinctrl-names = "default"; ++ renesas,can-clock-select = <0x0>; ++ status = "okay"; ++}; ++ ++&avb { ++ pinctrl-0 = <&avb_pins>; ++ pinctrl-names = "default"; ++ ++ renesas,no-ether-link; ++ renesas,phy = <0>; ++ renesas,mii-lite-ignore-pins = <&gpio5 25 0 ++ &gpio5 24 0 ++ &gpio5 23 0 ++ &gpio5 22 0 ++ &gpio5 7 0 ++ &gpio5 8 0 ++ &gpio5 6 0 ++ &gpio5 5 0 ++ &gpio5 4 0 ++ &gpio5 17 0 ++ &gpio5 11 0 ++ &gpio5 30 0 ++ &gpio5 27 0>; ++ phy-int-gpio = <&gpio5 16 GPIO_ACTIVE_LOW>; ++ status = "okay"; ++}; ++ ++&sata0 { ++ status = "okay"; ++}; ++ ++&scif0 { ++ pinctrl-0 = <&scif0_pins>; ++ pinctrl-names = "default"; ++ ++ status = "okay"; ++}; ++ ++&scif1 { ++ pinctrl-0 = <&scif1_pins>; ++ pinctrl-names = "default"; ++ ++ status = "okay"; ++}; ++ ++&scifa4 { ++ pinctrl-0 = <&scifa4_pins>; ++ pinctrl-names = "default"; ++ ++ status = "okay"; ++}; ++ ++&scifb2 { ++ pinctrl-0 = <&scifb2_pins>; ++ pinctrl-names = "default"; ++ ctsrts; ++ ++ status = "okay"; ++}; ++ ++&sdhi0 { ++ pinctrl-0 = <&sdhi0_pins>; ++ pinctrl-names = "default"; ++ ++ vmmc-supply = <&wlan_en_reg>; ++ vqmmc-supply = <&vccq_sdhi0>; ++ ++ keep-power-in-suspend; ++ enable-sdio-wakeup; ++ non-removable; ++ cap-power-off-card; ++ status = "okay"; ++ ++ #address-cells = <1>; ++ #size-cells = <0>; ++ wlcore: wlcore@0 { ++ compatible = "ti,wl1837"; ++ reg = <2>; ++ /* GP6_12 */ ++ interrupt-parent = <&gpio6>; ++ interrupts = <12 IRQ_TYPE_EDGE_RISING>; ++ platform-quirks = <1>; ++ }; ++}; ++ ++&sdhi2 { ++ pinctrl-0 = <&sdhi2_pins>; ++ pinctrl-names = "default"; ++ ++ vmmc-supply = <&vcc_sdhi2>; ++ vqmmc-supply = <&vccq_sdhi2>; ++ cd-gpios = <&gpio6 22 GPIO_ACTIVE_LOW>; ++ toshiba,mmc-wrprotect-disable; ++ status = "okay"; ++}; ++ ++&qspi { ++ pinctrl-0 = <&qspi_pins>; ++ pinctrl-names = "default"; ++ ++ status = "okay"; ++ ++ flash: flash@0 { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ compatible = "spansion,s25fl512s"; ++ reg = <0>; ++ spi-max-frequency = <30000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ m25p,fast-read; ++ spi-cpol; ++ spi-cpha; ++ ++ partition@0 { ++ label = "loader"; ++ reg = <0x00000000 0x00040000>; ++ read-only; ++ }; ++ partition@40000 { ++ label = "user"; ++ reg = <0x00040000 0x00400000>; ++ read-only; ++ }; ++ partition@440000 { ++ label = "flash"; ++ reg = <0x00440000 0x03bc0000>; ++ }; ++ }; ++}; ++ ++&msiof0 { ++ pinctrl-0 = <&msiof0_pins>; ++ pinctrl-names = "default"; ++ ++ status = "okay"; ++}; ++ ++&i2c1 { ++ pinctrl-0 = <&i2c1_pins>; ++ pinctrl-names = "default"; ++ ++ status = "okay"; ++ clock-frequency = <400000>; ++}; ++ ++&i2c2 { ++ pinctrl-0 = <&i2c2_pins>; ++ pinctrl-names = "default"; ++ ++ status = "okay"; ++ clock-frequency = <400000>; ++ ++ eeprom@50 { ++ compatible = "renesas,24c02"; ++ reg = <0x50>; ++ pagesize = <16>; ++ }; ++ ++ ak4642: sound-codec@12 { ++ #sound-dai-cells = <0>; ++ compatible = "asahi-kasei,ak4642"; ++ reg = <0x12>; ++ }; ++}; ++ ++&i2c4 { ++ pinctrl-0 = <&i2c4_pins>; ++ pinctrl-names = "default"; ++ ++ status = "okay"; ++ clock-frequency = <400000>; ++}; ++ ++&i2c5 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ ++ lsm9ds0_acc_mag@1d { ++ compatible = "st,lsm9ds0_acc_mag"; ++ reg = <0x1d>; ++ }; ++ ++ lsm9ds0_gyr@6b { ++ compatible = "st,lsm9ds0_gyr"; ++ reg = <0x6b>; ++ }; ++ ++ pcm3168a: audio-codec@44 { ++ #sound-dai-cells = <0>; ++ compatible = "ti,pcm3168a"; ++ reg = <0x44>; ++ clocks = <&snd_clk>; ++ clock-names = "scki"; ++ tdm; ++ VDD1-supply = <&codec_en_reg>; ++ VDD2-supply = <&codec_en_reg>; ++ VCCAD1-supply = <&codec_en_reg>; ++ VCCAD2-supply = <&codec_en_reg>; ++ VCCDA1-supply = <&_en_reg>; ++ VCCDA2-supply = <&_en_reg>; ++ }; ++}; ++ ++&i2c6 { ++ status = "okay"; ++ clock-frequency = <100000>; ++ ++ vdd_dvfs: regulator@68 { ++ compatible = "diasemi,da9210"; ++ reg = <0x68>; ++ ++ regulator-min-microvolt = <1000000>; ++ regulator-max-microvolt = <1000000>; ++ regulator-boot-on; ++ regulator-always-on; ++ }; ++}; ++ ++&mlp { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&mlb3_pins>; ++ status = "okay"; ++}; ++ ++&pci0 { ++ status = "okay"; ++ pinctrl-0 = <&usb0_pins>; ++ pinctrl-names = "default"; ++}; ++ ++&pci1 { ++ status = "okay"; ++ pinctrl-0 = <&usb1_pins>; ++ pinctrl-names = "default"; ++}; ++ ++&pcie_bus_clk { ++ status = "okay"; ++}; ++ ++&pciec { ++ status = "okay"; ++}; ++ ++&cpu0 { ++ cpu0-supply = <&vdd_dvfs>; ++}; ++ ++&rcar_sound { ++ #sound-dai-cells = <1>; ++ pinctrl-0 = <&sound_clk_pins>; ++ pinctrl-names = "default"; ++ ++ status = "okay"; ++ ++ rcar_sound,dai { ++ dai0 { ++ playback = <&ssi3>; ++ capture = <&ssi4>; ++ }; ++ ++ dai1 { ++ playback = <&ssi0 &src0 &dvc0>; ++ capture = <&ssi1 &src1 &dvc1>; ++ }; ++ ++ dai2 { ++ capture = <&ssi5>; ++ }; ++ }; ++}; ++ ++&ssi1 { ++ shared-pin; ++}; ++ ++&ssi3 { ++ tdm-8ch; ++ shared-pin; ++}; ++ ++&ssi4 { ++ tdm-8ch; ++ shared-pin; ++}; ++ ++&audio_clk_b { ++ clock-frequency = <24576000>; ++}; +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0100-Porter-ext01-add-dummy-regulator-to-select-48000-sou.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0100-Porter-ext01-add-dummy-regulator-to-select-48000-sou.patch new file mode 100644 index 0000000..36313c3 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter/0100-Porter-ext01-add-dummy-regulator-to-select-48000-sou.patch @@ -0,0 +1,53 @@ +From f682ff4fc2efe30131e683acd2924b7f6ce72735 Mon Sep 17 00:00:00 2001 +From: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +Date: Thu, 2 Jun 2016 13:44:14 +0300 +Subject: [PATCH] Porter-ext01: add dummy regulator to select 48000 sound + clock + + +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + arch/arm/boot/dts/r8a7791-porter-ext01.dts | 21 +++++++++++++++++++-- + 1 file changed, 19 insertions(+), 2 deletions(-) + +diff --git a/arch/arm/boot/dts/r8a7791-porter-ext01.dts b/arch/arm/boot/dts/r8a7791-porter-ext01.dts +index d115b36..62c6417 100644 +--- a/arch/arm/boot/dts/r8a7791-porter-ext01.dts ++++ b/arch/arm/boot/dts/r8a7791-porter-ext01.dts +@@ -151,7 +151,24 @@ + enable-active-high; + }; + +- wlan_en_reg: regulator@6 { ++ codec_en_reg_2: regulator@6 { ++ compatible = "regulator-fixed"; ++ regulator-name = "audio-clock-set"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ ++ /* ++ * This regulator will just set clock switch to ++ * select 24.576MHz as audio_clk_b ++ * TODO: add clock switch support ++ */ ++ gpio = <&cpld_gpio 7 0>; ++ ++ startup-delay-us = <0>; ++ enable-active-high; ++ }; ++ ++ wlan_en_reg: regulator@7 { + compatible = "regulator-fixed"; + regulator-name = "wlan-en-regulator"; + regulator-min-microvolt = <1800000>; +@@ -676,7 +693,7 @@ + clock-names = "scki"; + tdm; + VDD1-supply = <&codec_en_reg>; +- VDD2-supply = <&codec_en_reg>; ++ VDD2-supply = <&codec_en_reg_2>; + VCCAD1-supply = <&codec_en_reg>; + VCCAD2-supply = <&codec_en_reg>; + VCCDA1-supply = <&_en_reg>; +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter_ext01.cfg b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter_ext01.cfg new file mode 100644 index 0000000..b9a6fe7 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/porter_ext01.cfg @@ -0,0 +1,46 @@ +CONFIG_WIRELESS_EXT=y +CONFIG_WEXT_CORE=y +CONFIG_WEXT_PROC=y +CONFIG_WEXT_SPY=y +CONFIG_WEXT_PRIV=y +CONFIG_RFKILL=y +CONFIG_LIB80211=y +CONFIG_LIB80211_CRYPT_WEP=y +CONFIG_LIB80211_CRYPT_CCMP=y +CONFIG_LIB80211_CRYPT_TKIP=y +CONFIG_USB_USBNET=y +CONFIG_USB_NET_AX8817X=y +CONFIG_USB_NET_AX88179_178A=y +CONFIG_USB_NET_CDCETHER=y +CONFIG_USB_NET_CDC_NCM=y +CONFIG_USB_NET_NET1080=y +CONFIG_USB_NET_CDC_SUBSET=y +CONFIG_USB_BELKIN=y +CONFIG_USB_ARMLINUX=y +CONFIG_USB_NET_ZAURUS=y +CONFIG_HOSTAP=y +CONFIG_GPIO_PORTER_CPLD=y +CONFIG_INPUT_LSM9DS0=y +CONFIG_BT=n +CONFIG_CRYPTO_AEAD=y +CONFIG_CRYPTO_RNG=y +CONFIG_CRYPTO_USER=y +CONFIG_CRYPTO_GF128MUL=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_PCRYPT=y +CONFIG_CRYPTO_GCM=y +CONFIG_CRYPTO_SEQIV=y +CONFIG_CRYPTO_CTR=y +CONFIG_CRYPTO_CMAC=y +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_GHASH=y +CONFIG_CRYPTO_MICHAEL_MIC=y +CONFIG_CRYPTO_ARC4=y +CONFIG_CRYPTO_SHA256=y +CONFIG_IIO=y +CONFIG_TI_ADS111X=y +CONFIG_SOC_CAMERA_OV10635=y +CONFIG_SOC_CAMERA_MAX9272_OV10635=y +CONFIG_SND_SOC_SI468X=y +CONFIG_SND_SOC_PCM3168A=y +CONFIG_SND_SOC_PCM3168A_I2C=y diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0014-serial-sh-sci-Break-out-default-CTS-RTS-pin-setup.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0014-serial-sh-sci-Break-out-default-CTS-RTS-pin-setup.patch new file mode 100644 index 0000000..6278b9a --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0014-serial-sh-sci-Break-out-default-CTS-RTS-pin-setup.patch @@ -0,0 +1,86 @@ +From 102c206ea8ee28cf677061aa5e4ce509f776a924 Mon Sep 17 00:00:00 2001 +From: Magnus Damm <damm+renesas@opensource.se> +Date: Thu, 19 Mar 2015 10:49:43 +0900 +Subject: [PATCH 14/18] serial: sh-sci: Break out default CTS/RTS pin setup + +Break out CTS/RTS pin setup for the default case. We only +care about those pins in case SCIx_HAVE_RTSCTS is set. + +Signed-off-by: Magnus Damm <damm+renesas@opensource.se> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/tty/serial/sh-sci.c | 45 +++++++++++++++++++++++++++++-------------- + 1 file changed, 31 insertions(+), 14 deletions(-) + +diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c +index 526409d..8465cfd 100644 +--- a/drivers/tty/serial/sh-sci.c ++++ b/drivers/tty/serial/sh-sci.c +@@ -522,10 +522,29 @@ static void sci_poll_put_char(struct uart_port *port, unsigned char c) + } + #endif /* CONFIG_CONSOLE_POLL || CONFIG_SERIAL_SH_SCI_CONSOLE */ + +-static void sci_init_pins(struct uart_port *port, unsigned int cflag) ++static void sci_init_ctsrts_default(struct uart_port *port, bool hwflow_enabled) + { + struct sci_port *s = to_sci_port(port); + struct plat_sci_reg *reg = sci_regmap[s->cfg->regtype] + SCSPTR; ++ unsigned short status; ++ ++ /* If no SCSPTR register exists then skip. Same if hardware flow ++ * control has been enabled, in such case SCFCR.MCE will be set ++ * and the SCSPTR configuration is assumed to be overridden. ++ */ ++ if (!reg->size || hwflow_enabled) ++ return; ++ ++ status = serial_port_in(port, SCSPTR); ++ status &= ~SCSPTR_CTSIO; ++ status |= SCSPTR_RTSIO; ++ serial_port_out(port, SCSPTR, status); /* Set RTS = 1 */ ++} ++ ++static void sci_init_pins(struct uart_port *port, unsigned int cflag) ++{ ++ struct sci_port *s = to_sci_port(port); ++ bool hwflow_enabled = cflag & CRTSCTS; + + /* + * Use port-specific handler if provided. +@@ -535,22 +554,20 @@ static void sci_init_pins(struct uart_port *port, unsigned int cflag) + return; + } + +- /* +- * For the generic path SCSPTR is necessary. Bail out if that's +- * unavailable, too. ++ /* SCIF hardware with RTS/CTS support needs special setup below. ++ * ++ * Please note that if RTS/CTS is available for the hardware ++ * platform depends on the particular SCIF channel on a certain ++ * SoC, and how this channel has been hooked up on the actual board. ++ * ++ * If the RTS/CTS signals will be used or not depends on what user ++ * space requests. In case RTS/CTS is available but not requested ++ * by user space we still need to configure the pins somehow. + */ +- if (!reg->size) ++ if (!(s->cfg->capabilities & SCIx_HAVE_RTSCTS)) + return; + +- if ((s->cfg->capabilities & SCIx_HAVE_RTSCTS) && +- ((!(cflag & CRTSCTS)))) { +- unsigned short status; +- +- status = serial_port_in(port, SCSPTR); +- status &= ~SCSPTR_CTSIO; +- status |= SCSPTR_RTSIO; +- serial_port_out(port, SCSPTR, status); /* Set RTS = 1 */ +- } ++ sci_init_ctsrts_default(port, hwflow_enabled); + } + + static int sci_txfill(struct uart_port *port) +-- +1.7.10.4 + diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0015-serial-sh-sci-Fix-default-RTS-handling.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0015-serial-sh-sci-Fix-default-RTS-handling.patch new file mode 100644 index 0000000..1609d84 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0015-serial-sh-sci-Fix-default-RTS-handling.patch @@ -0,0 +1,50 @@ +From dfe822a2848987263b7ac30b1c20e89d86f63caa Mon Sep 17 00:00:00 2001 +From: Magnus Damm <damm+renesas@opensource.se> +Date: Thu, 19 Mar 2015 10:49:53 +0900 +Subject: [PATCH 15/18] serial: sh-sci: Fix default RTS handling + +Fix the default SCIF handling in case CTS/RTS is available +on the target board but disabled by user space. Without +this patch the RTS output value is not set. + +Signed-off-by: Magnus Damm <damm+renesas@opensource.se> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/tty/serial/sh-sci.c | 7 +++++-- + include/linux/serial_sci.h | 1 + + 2 files changed, 6 insertions(+), 2 deletions(-) + +diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c +index 8465cfd..64bf5b6 100644 +--- a/drivers/tty/serial/sh-sci.c ++++ b/drivers/tty/serial/sh-sci.c +@@ -535,10 +535,13 @@ static void sci_init_ctsrts_default(struct uart_port *port, bool hwflow_enabled) + if (!reg->size || hwflow_enabled) + return; + ++ /* Setup CTS/RTS in the case hardware flow is disabled by user space. ++ * The CTS signal is setup as input and RTS as output with value 0. ++ */ + status = serial_port_in(port, SCSPTR); +- status &= ~SCSPTR_CTSIO; ++ status &= ~(SCSPTR_CTSIO | SCSPTR_RTSDT); + status |= SCSPTR_RTSIO; +- serial_port_out(port, SCSPTR, status); /* Set RTS = 1 */ ++ serial_port_out(port, SCSPTR, status); /* Set RTS = 0 */ + } + + static void sci_init_pins(struct uart_port *port, unsigned int cflag) +diff --git a/include/linux/serial_sci.h b/include/linux/serial_sci.h +index e730b58..39884de 100644 +--- a/include/linux/serial_sci.h ++++ b/include/linux/serial_sci.h +@@ -57,6 +57,7 @@ + + /* SCSPTR (Serial Port Register), optional */ + #define SCSPTR_RTSIO (1 << 7) /* Serial Port RTS Pin Input/Output */ ++#define SCSPTR_RTSDT (1 << 6) /* Serial Port RTS Pin Data */ + #define SCSPTR_CTSIO (1 << 5) /* Serial Port CTS Pin Input/Output */ + #define SCSPTR_SPB2IO (1 << 1) /* Serial Port Break Input/Output */ + #define SCSPTR_SPB2DT (1 << 0) /* Serial Port Break Data */ +-- +1.7.10.4 diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0016-serial-sh-sci-Expose-default-CTS-pin.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0016-serial-sh-sci-Expose-default-CTS-pin.patch new file mode 100644 index 0000000..dcc6134 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0016-serial-sh-sci-Expose-default-CTS-pin.patch @@ -0,0 +1,79 @@ +From f7d7ddb2a3ad4c20dbbd7cbabf56bca4c35b8368 Mon Sep 17 00:00:00 2001 +From: Magnus Damm <damm+renesas@opensource.se> +Date: Thu, 19 Mar 2015 10:50:03 +0900 +Subject: [PATCH 16/18] serial: sh-sci: Expose default CTS pin + +Expose the CTS pin to the ->get_mctrl() callback for the +default case. Without this patch the serial core can not +retrieve the CTS pin state from the SCIF driver. + +Signed-off-by: Magnus Damm <damm+renesas@opensource.se> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/tty/serial/sh-sci.c | 24 ++++++++++++++++++++++-- + include/linux/serial_sci.h | 1 + + 2 files changed, 23 insertions(+), 2 deletions(-) + +diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c +index 64bf5b6..4f82fcc 100644 +--- a/drivers/tty/serial/sh-sci.c ++++ b/drivers/tty/serial/sh-sci.c +@@ -522,6 +522,17 @@ static void sci_poll_put_char(struct uart_port *port, unsigned char c) + } + #endif /* CONFIG_CONSOLE_POLL || CONFIG_SERIAL_SH_SCI_CONSOLE */ + ++static bool sci_cts_asserted_default(struct uart_port *port) ++{ ++ struct sci_port *s = to_sci_port(port); ++ struct plat_sci_reg *reg = sci_regmap[s->cfg->regtype] + SCSPTR; ++ ++ if (reg->size) ++ return !(serial_port_in(port, SCSPTR) & SCSPTR_CTSDT); ++ ++ return true; ++} ++ + static void sci_init_ctsrts_default(struct uart_port *port, bool hwflow_enabled) + { + struct sci_port *s = to_sci_port(port); +@@ -1280,14 +1291,23 @@ static void sci_set_mctrl(struct uart_port *port, unsigned int mctrl) + + static unsigned int sci_get_mctrl(struct uart_port *port) + { ++ unsigned int mctrl; ++ struct sci_port *s = to_sci_port(port); ++ bool cts_asserted = false; ++ + /* + * CTS/RTS is handled in hardware when supported, while nothing + * else is wired up. Keep it simple and simply assert DSR/CAR. + */ +- unsigned int mctrl = TIOCM_DSR | TIOCM_CAR; ++ mctrl = TIOCM_DSR | TIOCM_CAR; ++ + if (port->type == PORT_HSCIF) + mctrl |= TIOCM_CTS; +- return mctrl; ++ ++ if (s->cfg->capabilities & SCIx_HAVE_RTSCTS) ++ cts_asserted = sci_cts_asserted_default(port); ++ ++ return mctrl | (cts_asserted ? TIOCM_CTS : 0); + } + + #ifdef CONFIG_SERIAL_SH_SCI_DMA +diff --git a/include/linux/serial_sci.h b/include/linux/serial_sci.h +index 39884de..d61096e 100644 +--- a/include/linux/serial_sci.h ++++ b/include/linux/serial_sci.h +@@ -59,6 +59,7 @@ + #define SCSPTR_RTSIO (1 << 7) /* Serial Port RTS Pin Input/Output */ + #define SCSPTR_RTSDT (1 << 6) /* Serial Port RTS Pin Data */ + #define SCSPTR_CTSIO (1 << 5) /* Serial Port CTS Pin Input/Output */ ++#define SCSPTR_CTSDT (1 << 4) /* Serial Port CTS Pin Data */ + #define SCSPTR_SPB2IO (1 << 1) /* Serial Port Break Input/Output */ + #define SCSPTR_SPB2DT (1 << 0) /* Serial Port Break Data */ + +-- +1.7.10.4 + diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0017-serial-sh-sci-Add-SCIFA-SCIFB-CTS-RTS-pin-setup.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0017-serial-sh-sci-Add-SCIFA-SCIFB-CTS-RTS-pin-setup.patch new file mode 100644 index 0000000..01c9bf5 --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0017-serial-sh-sci-Add-SCIFA-SCIFB-CTS-RTS-pin-setup.patch @@ -0,0 +1,199 @@ +From 8b89b0c00e23770dab98ed0153fa1c8e5c999194 Mon Sep 17 00:00:00 2001 +From: Magnus Damm <damm+renesas@opensource.se> +Date: Thu, 19 Mar 2015 10:50:13 +0900 +Subject: [PATCH 17/18] serial: sh-sci: Add SCIFA/SCIFB CTS/RTS pin setup + +Add SCIFA/SCIFB pin setup code for CTS/RTS pins to handle +both cases of hardware flow control enabled or disabled. + +Signed-off-by: Magnus Damm <damm+renesas@opensource.se> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/tty/serial/sh-sci.c | 59 ++++++++++++++++++++++++++++++++++++++++++- + include/linux/serial_sci.h | 9 +++++++ + 2 files changed, 67 insertions(+), 1 deletion(-) + +diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c +index 4f82fcc..52fcb41 100644 +--- a/drivers/tty/serial/sh-sci.c ++++ b/drivers/tty/serial/sh-sci.c +@@ -181,6 +181,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = sci_reg_invalid, + [SCLSR] = sci_reg_invalid, + [HSSRR] = sci_reg_invalid, ++ [SCPCR] = sci_reg_invalid, ++ [SCPDR] = sci_reg_invalid, + }, + + /* +@@ -201,6 +203,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = sci_reg_invalid, + [SCLSR] = sci_reg_invalid, + [HSSRR] = sci_reg_invalid, ++ [SCPCR] = sci_reg_invalid, ++ [SCPDR] = sci_reg_invalid, + }, + + /* +@@ -220,6 +224,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = sci_reg_invalid, + [SCLSR] = sci_reg_invalid, + [HSSRR] = sci_reg_invalid, ++ [SCPCR] = { 0x30, 16 }, ++ [SCPDR] = { 0x34, 16 }, + }, + + /* +@@ -239,6 +245,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = sci_reg_invalid, + [SCLSR] = sci_reg_invalid, + [HSSRR] = sci_reg_invalid, ++ [SCPCR] = { 0x30, 16 }, ++ [SCPDR] = { 0x34, 16 }, + }, + + /* +@@ -259,6 +267,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = { 0x20, 16 }, + [SCLSR] = { 0x24, 16 }, + [HSSRR] = sci_reg_invalid, ++ [SCPCR] = sci_reg_invalid, ++ [SCPDR] = sci_reg_invalid, + }, + + /* +@@ -278,6 +288,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = sci_reg_invalid, + [SCLSR] = sci_reg_invalid, + [HSSRR] = sci_reg_invalid, ++ [SCPCR] = sci_reg_invalid, ++ [SCPDR] = sci_reg_invalid, + }, + + /* +@@ -297,6 +309,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = { 0x20, 16 }, + [SCLSR] = { 0x24, 16 }, + [HSSRR] = sci_reg_invalid, ++ [SCPCR] = sci_reg_invalid, ++ [SCPDR] = sci_reg_invalid, + }, + + /* +@@ -316,6 +330,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = { 0x20, 16 }, + [SCLSR] = { 0x24, 16 }, + [HSSRR] = { 0x40, 16 }, ++ [SCPCR] = sci_reg_invalid, ++ [SCPDR] = sci_reg_invalid, + }, + + /* +@@ -336,6 +352,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = sci_reg_invalid, + [SCLSR] = { 0x24, 16 }, + [HSSRR] = sci_reg_invalid, ++ [SCPCR] = sci_reg_invalid, ++ [SCPDR] = sci_reg_invalid, + }, + + /* +@@ -356,6 +374,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = { 0x24, 16 }, + [SCLSR] = { 0x28, 16 }, + [HSSRR] = sci_reg_invalid, ++ [SCPCR] = sci_reg_invalid, ++ [SCPDR] = sci_reg_invalid, + }, + + /* +@@ -376,6 +396,8 @@ static struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = { + [SCSPTR] = sci_reg_invalid, + [SCLSR] = sci_reg_invalid, + [HSSRR] = sci_reg_invalid, ++ [SCPCR] = sci_reg_invalid, ++ [SCPDR] = sci_reg_invalid, + }, + }; + +@@ -555,6 +577,34 @@ static void sci_init_ctsrts_default(struct uart_port *port, bool hwflow_enabled) + serial_port_out(port, SCSPTR, status); /* Set RTS = 0 */ + } + ++static void sci_init_ctsrts_scifab(struct uart_port *port, bool hwflow_enabled) ++{ ++ unsigned short control, data; ++ ++ /* SCIFA/SCIFB CTS/RTS pin configuration depends on user space. ++ * ++ * In case of CTS - (SCPDR.CTSD is always accessible): ++ * - Hardware flow control enabled: "CTS pin function" ++ * - Hardware flow control disabled: "Input port" ++ * ++ * In case of RTS: ++ * - Hardware flow control enabled: "RTS pin function" ++ * - Hardware flow control disabled: "Output port" with value 0 ++ */ ++ control = serial_port_in(port, SCPCR); ++ data = serial_port_in(port, SCPDR); ++ ++ if (hwflow_enabled) { ++ control &= ~(SCPCR_RTSC | SCPCR_CTSC); ++ } else { ++ control |= SCPCR_RTSC | SCPCR_CTSC; ++ data &= ~SCPDR_RTSD; ++ } ++ ++ serial_port_out(port, SCPDR, data); ++ serial_port_out(port, SCPCR, control); ++} ++ + static void sci_init_pins(struct uart_port *port, unsigned int cflag) + { + struct sci_port *s = to_sci_port(port); +@@ -581,7 +631,14 @@ static void sci_init_pins(struct uart_port *port, unsigned int cflag) + if (!(s->cfg->capabilities & SCIx_HAVE_RTSCTS)) + return; + +- sci_init_ctsrts_default(port, hwflow_enabled); ++ switch (s->cfg->type) { ++ case PORT_SCIFA: ++ case PORT_SCIFB: ++ sci_init_ctsrts_scifab(port, hwflow_enabled); ++ break; ++ default: ++ sci_init_ctsrts_default(port, hwflow_enabled); ++ } + } + + static int sci_txfill(struct uart_port *port) +diff --git a/include/linux/serial_sci.h b/include/linux/serial_sci.h +index d61096e..04f0066a 100644 +--- a/include/linux/serial_sci.h ++++ b/include/linux/serial_sci.h +@@ -66,6 +66,13 @@ + /* HSSRR HSCIF */ + #define HSCIF_SRE 0x8000 /* Sampling Rate Register Enable */ + ++/* SCPCR (Serial Port Control Register) */ ++#define SCPCR_RTSC (1 << 4) /* Serial Port RTS Pin / Output Pin */ ++#define SCPCR_CTSC (1 << 3) /* Serial Port CTS Pin / Input Pin */ ++ ++/* SCPDR (Serial Port Data Register) */ ++#define SCPDR_RTSD (1 << 4) /* Serial Port RTS Output Pin Data */ ++ + enum { + SCIx_PROBE_REGTYPE, + +@@ -102,6 +109,8 @@ enum { + SCRFDR, /* Receive FIFO Data Count Register */ + SCSPTR, /* Serial Port Register */ + HSSRR, /* Sampling Rate Register */ ++ SCPCR, /* Serial Port Control Register */ ++ SCPDR, /* Serial Port Data Register */ + + SCIx_NR_REGS, + }; +-- +1.7.10.4 + diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0018-serial-sh-sci-Expose-SCIFA-SCIFB-CTS-pin.patch b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0018-serial-sh-sci-Expose-SCIFA-SCIFB-CTS-pin.patch new file mode 100644 index 0000000..860089b --- /dev/null +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas/sh-sci/0018-serial-sh-sci-Expose-SCIFA-SCIFB-CTS-pin.patch @@ -0,0 +1,65 @@ +From 6979eced0a733f619708391255a92a0f3de36b5f Mon Sep 17 00:00:00 2001 +From: Magnus Damm <damm+renesas@opensource.se> +Date: Thu, 19 Mar 2015 10:50:23 +0900 +Subject: [PATCH 18/18] serial: sh-sci: Expose SCIFA/SCIFB CTS pin + +Expose CTS pin to serial core for the SCIFA/SCIFB case. + +Signed-off-by: Magnus Damm <damm+renesas@opensource.se> +Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com> +--- + drivers/tty/serial/sh-sci.c | 18 +++++++++++++++--- + include/linux/serial_sci.h | 1 + + 2 files changed, 16 insertions(+), 3 deletions(-) + +diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c +index 52fcb41..893f2f0 100644 +--- a/drivers/tty/serial/sh-sci.c ++++ b/drivers/tty/serial/sh-sci.c +@@ -577,6 +577,11 @@ static void sci_init_ctsrts_default(struct uart_port *port, bool hwflow_enabled) + serial_port_out(port, SCSPTR, status); /* Set RTS = 0 */ + } + ++static bool sci_cts_asserted_scifab(struct uart_port *port) ++{ ++ return !(serial_port_in(port, SCPDR) & SCPDR_CTSD); ++} ++ + static void sci_init_ctsrts_scifab(struct uart_port *port, bool hwflow_enabled) + { + unsigned short control, data; +@@ -1360,9 +1365,16 @@ static unsigned int sci_get_mctrl(struct uart_port *port) + + if (port->type == PORT_HSCIF) + mctrl |= TIOCM_CTS; +- +- if (s->cfg->capabilities & SCIx_HAVE_RTSCTS) +- cts_asserted = sci_cts_asserted_default(port); ++ if (s->cfg->capabilities & SCIx_HAVE_RTSCTS) { ++ switch (s->cfg->type) { ++ case PORT_SCIFA: ++ case PORT_SCIFB: ++ cts_asserted = sci_cts_asserted_scifab(port); ++ break; ++ default: ++ cts_asserted = sci_cts_asserted_default(port); ++ } ++ } + + return mctrl | (cts_asserted ? TIOCM_CTS : 0); + } +diff --git a/include/linux/serial_sci.h b/include/linux/serial_sci.h +index 04f0066a..54fd078 100644 +--- a/include/linux/serial_sci.h ++++ b/include/linux/serial_sci.h +@@ -72,6 +72,7 @@ + + /* SCPDR (Serial Port Data Register) */ + #define SCPDR_RTSD (1 << 4) /* Serial Port RTS Output Pin Data */ ++#define SCPDR_CTSD (1 << 3) /* Serial Port CTS Input Pin Data */ + + enum { + SCIx_PROBE_REGTYPE, +-- +1.7.10.4 + diff --git a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas_3.10.bb b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas_3.10.bb index f26443f..bd8817f 100644 --- a/meta-rcar-gen2/recipes-kernel/linux/linux-renesas_3.10.bb +++ b/meta-rcar-gen2/recipes-kernel/linux/linux-renesas_3.10.bb @@ -17,9 +17,19 @@ SRC_URI = "${RENESAS_BACKPORTS_URL};protocol=git;branch=bsp/v3.10.31-ltsi/rcar-g file://0001-arm-koelsch-Add-vmalloc-384M-to-bootargs-of-DTS.patch \ file://0001-arm-alt-Add-vmalloc-384M-to-bootargs-of-DTS.patch \ file://0001-arm-gose-Add-vmalloc-384M-to-bootargs-of-DTS.patch \ + file://0049-Add-lsm9ds0-acc-gyro-mag-driver.patch \ + file://0051-TI-ADS111X-sigma-delta-ADC-driver.patch \ + file://0051a-Cleanup-TI-ADS111x-ADC-driver.patch \ + file://0059-crypto-aead-Add-crypto_aead_set_reqsize-helper.patch \ + file://sh-sci/0014-serial-sh-sci-Break-out-default-CTS-RTS-pin-setup.patch \ + file://sh-sci/0015-serial-sh-sci-Fix-default-RTS-handling.patch \ + file://sh-sci/0016-serial-sh-sci-Expose-default-CTS-pin.patch \ + file://sh-sci/0017-serial-sh-sci-Add-SCIFA-SCIFB-CTS-RTS-pin-setup.patch \ + file://sh-sci/0018-serial-sh-sci-Expose-SCIFA-SCIFB-CTS-pin.patch \ " SRC_URI_append_porter = " file://porter.cfg" +SRC_URI_append_porter = '${@ " file://porter_ext01.cfg" if 'porter-ext01' in '${MACHINE_FEATURES}' else "" }' SRC_URI_append_porter = " \ file://0001-kernel-Silk-board-support.patch \ @@ -54,6 +64,37 @@ SRC_URI_append_porter = " \ file://0022-fanotify-fix-notification-of-groups-with-inode-mount.patch \ " +SRC_URI_append_porter = " \ + file://porter/0002-Porter-adapt-max9272-ov10635-driver-for-porter-exp-b.patch \ + file://porter/0003-Porter-add-LVDS-camera.patch \ + file://porter/0004-GPIO-CPLD-gpio-extender-driver.patch \ + file://porter/0012-regmap-Implemented-default-cache-sync-operation.patch \ + file://porter/0013-regmap-cache-Don-t-attempt-to-sync-non-writeable-reg.patch \ + file://porter/0014-ASoC-Add-SOC_DOUBLE_STS-macro.patch \ + file://porter/0015-ASoC-pcm3168a-Add-binding-document-for-pcm3168a-code.patch \ + file://porter/0016-ASoC-pcm3168a-Add-driver-for-pcm3168a-codec.patch \ + file://porter/0017-ASoC-PCM3168A-add-TDM-modes.patch \ + file://porter/0018-ASoC-PCM3168A-disable-16-bit-format.patch \ + file://porter/0019-ASoC-PCM3168A-enable-on-power-on.patch \ + file://porter/0020-ASoC-PCM3168A-merge-ADC-and-DAC-to-single-DAI.patch \ + file://porter/0021-ASoC-PCM3168A-disable-PM.patch \ + file://porter/0022-ASoC-fix-simple-card-do-not-respect-device-id.patch \ + file://porter/0023-SPI-GPIO-get-master-fix.patch \ + file://porter/0024-SPIDEV-extend-maximum-transfer-size.patch \ + file://porter/0025-Radio-add-si468x-to-spidev.patch \ + file://porter/0026-ASoC-add-dummy-Si468x-driver.patch \ + file://porter/0027-ASoC-R-Car-initial-TDM-support.patch \ + file://porter/0028-ASoC-rcar-correct-32bit-to-24-bit-sample-conv.patch \ + file://porter/0029-ASoC-R-Car-fix-debug-output.patch \ + file://porter/0030-R-Car-sound-disable-clock-hack.patch \ + file://porter/0031-ASoC-R-car-SSI-fix-SSI-slave-mode-setup-while-TDM-an.patch \ + file://porter/0032-mmc-Add-SDIO-function-devicetree-subnode-parsing.patch \ + file://porter/0038-Porter-LVDS-display-LQ123K1LG03.patch \ + file://porter/0060-Remove-delay-at-LVDS-camera-initialization.patch \ + file://porter/0099-Porter-add-separate-dts-for-ext01-extension-board.patch \ + file://porter/0100-Porter-ext01-add-dummy-regulator-to-select-48000-sou.patch \ +" + SRC_URI_append_silk = " \ file://0001-kernel-Silk-board-support.patch \ file://0002-silk-reference-Add-DRM_RCAR_DU_CONNECT_VSP-configura.patch \ |