drivers: misc: Import AKM09970 driver
* From dagu-s-oss * Run clang-format on source files Change-Id: Ib9b9e82a3988116a9bf1121b50e006820d197ac1
This commit is contained in:
committed by
Sebastiano Barezzi
parent
a8c0b9f5ce
commit
194a28afb5
@@ -631,6 +631,11 @@ config KINECTICS_XR_NORDIC
|
||||
this also parses the gpios and interrupts from device tree and sets
|
||||
the gpios and interrupt handler for handling the interrupt.
|
||||
|
||||
config HALL_AKM09970
|
||||
tristate "AKM09970 HALL sensor driver"
|
||||
help
|
||||
Say Y here if you want to enable AKM09970 HALL sensor driver.
|
||||
|
||||
source "drivers/misc/c2port/Kconfig"
|
||||
source "drivers/misc/eeprom/Kconfig"
|
||||
source "drivers/misc/cb710/Kconfig"
|
||||
|
||||
@@ -78,3 +78,5 @@ obj-$(CONFIG_QTI_XR_SMRTVWR_MISC) += qxr-stdalonevwr.o
|
||||
obj-$(CONFIG_FPR_FPC) += fpr_FingerprintCard/
|
||||
obj-y += qrc/
|
||||
obj-$(CONFIG_KINECTICS_XR_NORDIC) += kxrctrl/
|
||||
|
||||
obj-$(CONFIG_HALL_AKM09970) += akm09970.o
|
||||
|
||||
918
drivers/misc/akm09970.c
Normal file
918
drivers/misc/akm09970.c
Normal file
@@ -0,0 +1,918 @@
|
||||
/* drivers/intput/misc/akm09970.c - akm09970 compass driver
|
||||
*
|
||||
* Copyright (c) 2018-2019, Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
//#define DEBUG
|
||||
#define pr_fmt(fmt) "akm09970: %s: %d " fmt, __func__, __LINE__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/unistd.h>
|
||||
#include <linux/initrd.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/string.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include "linux/akm09970.h"
|
||||
|
||||
#define CLEAR_IRQ_TIME 500
|
||||
|
||||
static DECLARE_WAIT_QUEUE_HEAD(poll_wait_queue);
|
||||
|
||||
struct akm09970_soc_ctrl {
|
||||
uint8_t chip_info[AKM_SENSOR_INFO_SIZE];
|
||||
uint8_t chip_data[AKM_SENSOR_DATA_SIZE];
|
||||
uint8_t measure_range;
|
||||
uint32_t measure_freq_hz;
|
||||
int gpio_reset;
|
||||
int gpio_irq;
|
||||
int irq;
|
||||
bool read_flag;
|
||||
|
||||
atomic_t power_enabled;
|
||||
atomic_t data_ready;
|
||||
|
||||
dev_t dev_num;
|
||||
struct device *dev;
|
||||
struct cdev cdev;
|
||||
struct class *chr_class;
|
||||
struct device *chr_dev;
|
||||
|
||||
struct device_node *of_node;
|
||||
|
||||
struct i2c_client *client;
|
||||
|
||||
struct pinctrl *pinctrl;
|
||||
struct pinctrl_state *gpio_state_active;
|
||||
struct pinctrl_state *gpio_state_suspend;
|
||||
|
||||
struct regulator *vdd;
|
||||
|
||||
struct hrtimer timer;
|
||||
|
||||
struct work_struct report_work;
|
||||
struct workqueue_struct *work_queue;
|
||||
|
||||
struct akm09970_platform_data pdata;
|
||||
};
|
||||
|
||||
static int akm09970_write_byte(struct i2c_client *client, u8 reg, u8 val)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (client->irq)
|
||||
disable_irq_nosync(client->irq);
|
||||
|
||||
rc = i2c_smbus_write_byte_data(client, reg, val);
|
||||
|
||||
if (client->irq)
|
||||
enable_irq(client->irq);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int akm09970_set_reg_bits(struct i2c_client *client, int val, int shift,
|
||||
u8 mask, u8 reg)
|
||||
{
|
||||
int data;
|
||||
|
||||
data = i2c_smbus_read_byte_data(client, reg);
|
||||
if (data < 0)
|
||||
return data;
|
||||
|
||||
data = (data & ~mask) | ((val << shift) & mask);
|
||||
pr_debug("reg: 0x%x, data: 0x%x\n", reg, data);
|
||||
return akm09970_write_byte(client, reg, data);
|
||||
}
|
||||
|
||||
static int akm09970_set_mode(struct akm09970_soc_ctrl *c_ctrl, uint8_t mode)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = akm09970_set_reg_bits(c_ctrl->client, mode, AK09970_MODE_POS,
|
||||
AK09970_MODE_MSK, AK09970_MODE_REG);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer_t)
|
||||
{
|
||||
struct akm09970_soc_ctrl *c_ctrl =
|
||||
container_of(timer_t, struct akm09970_soc_ctrl, timer);
|
||||
|
||||
if (false == c_ctrl->read_flag) {
|
||||
queue_work(c_ctrl->work_queue, &c_ctrl->report_work);
|
||||
|
||||
hrtimer_forward_now(&c_ctrl->timer,
|
||||
ktime_set(CLEAR_IRQ_TIME / MSEC_PER_SEC,
|
||||
(CLEAR_IRQ_TIME % MSEC_PER_SEC) *
|
||||
NSEC_PER_MSEC));
|
||||
return HRTIMER_RESTART;
|
||||
} else {
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
}
|
||||
|
||||
static void akm09970_reset(struct akm09970_soc_ctrl *c_ctrl)
|
||||
{
|
||||
gpio_set_value(c_ctrl->gpio_reset, 0);
|
||||
udelay(50);
|
||||
gpio_set_value(c_ctrl->gpio_reset, 1);
|
||||
udelay(100);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int akm09970_power_down(struct akm09970_soc_ctrl *c_ctrl)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (atomic_read(&c_ctrl->power_enabled)) {
|
||||
gpio_set_value(c_ctrl->gpio_reset, 0);
|
||||
|
||||
rc = regulator_disable(c_ctrl->vdd);
|
||||
if (rc) {
|
||||
pr_err("Regulator vdd disable failed rc=%d\n", rc);
|
||||
}
|
||||
atomic_set(&c_ctrl->power_enabled, 0);
|
||||
pr_err("Power down successfully");
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int akm09970_power_up(struct akm09970_soc_ctrl *c_ctrl)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (!atomic_read(&c_ctrl->power_enabled)) {
|
||||
rc = regulator_enable(c_ctrl->vdd);
|
||||
if (rc) {
|
||||
pr_err("Regulator vdd enable failed rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
udelay(20);
|
||||
akm09970_reset(c_ctrl);
|
||||
atomic_set(&c_ctrl->power_enabled, 1);
|
||||
pr_err("Power up successfully");
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int akm09970_active(struct akm09970_soc_ctrl *c_ctrl, bool on)
|
||||
{
|
||||
int rc = 0;
|
||||
uint8_t mode = 0x00;
|
||||
|
||||
pr_info("akm sensor %s\n", on ? "on" : "off");
|
||||
|
||||
if (!atomic_read(&c_ctrl->power_enabled) && on) {
|
||||
rc = akm09970_power_up(c_ctrl);
|
||||
if (rc) {
|
||||
pr_err("Sensor power up fail!\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (c_ctrl->measure_freq_hz >= 100)
|
||||
mode = AK09970_MODE_CONTINUOUS_100HZ;
|
||||
else if (c_ctrl->measure_freq_hz >= 50 &&
|
||||
c_ctrl->measure_freq_hz < 100)
|
||||
mode = AK09970_MODE_CONTINUOUS_50HZ;
|
||||
else if (c_ctrl->measure_freq_hz >= 20 &&
|
||||
c_ctrl->measure_freq_hz < 50)
|
||||
mode = AK09970_MODE_CONTINUOUS_20HZ;
|
||||
else
|
||||
mode = AK09970_MODE_CONTINUOUS_100HZ;
|
||||
|
||||
c_ctrl->measure_range = 0;
|
||||
|
||||
rc = akm09970_write_byte(c_ctrl->client, AK09970_MODE_REG,
|
||||
(mode | c_ctrl->measure_range));
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to set mode and smr\n");
|
||||
akm09970_power_down(c_ctrl);
|
||||
return rc;
|
||||
}
|
||||
pr_debug("reg: 0x%x, data: 0x%x\n", AK09970_MODE_REG,
|
||||
(mode | c_ctrl->measure_range));
|
||||
|
||||
enable_irq(c_ctrl->irq);
|
||||
hrtimer_start(&c_ctrl->timer,
|
||||
ktime_set(CLEAR_IRQ_TIME / MSEC_PER_SEC,
|
||||
(CLEAR_IRQ_TIME % MSEC_PER_SEC) *
|
||||
NSEC_PER_MSEC),
|
||||
HRTIMER_MODE_REL);
|
||||
c_ctrl->read_flag = false;
|
||||
pr_debug("Enable irq successfully\n");
|
||||
} else if (atomic_read(&c_ctrl->power_enabled) && !on) {
|
||||
disable_irq_nosync(c_ctrl->irq);
|
||||
cancel_work_sync(&c_ctrl->report_work);
|
||||
c_ctrl->read_flag = false;
|
||||
pr_debug("Disable irq successfully\n");
|
||||
|
||||
rc = akm09970_set_mode(c_ctrl, AK09970_MODE_POWERDOWN);
|
||||
if (rc)
|
||||
pr_err("Failed to set to POWERDOWN mode\n");
|
||||
|
||||
akm09970_power_down(c_ctrl);
|
||||
hrtimer_cancel(&c_ctrl->timer);
|
||||
} else {
|
||||
pr_info("The same power state, do nothing!\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int akm09970_read_data(struct akm09970_soc_ctrl *c_ctrl)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rc = i2c_smbus_read_i2c_block_data(c_ctrl->client, AK09970_REG_ST_XYZ,
|
||||
AKM_SENSOR_DATA_SIZE,
|
||||
c_ctrl->chip_data);
|
||||
if (rc < 0) {
|
||||
pr_err("read data failed!\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (AKM_ERRADC_IS_HIGH(c_ctrl->chip_data[0])) {
|
||||
pr_err("ADC over run!\n");
|
||||
rc = -EIO;
|
||||
}
|
||||
|
||||
if (AKM_ERRXY_IS_HIGH(c_ctrl->chip_data[1])) {
|
||||
pr_err("Errxy over run!\n");
|
||||
rc = -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void akm09970_dev_work_queue(struct work_struct *work)
|
||||
{
|
||||
int rc = 0;
|
||||
struct akm09970_soc_ctrl *c_ctrl =
|
||||
container_of(work, struct akm09970_soc_ctrl, report_work);
|
||||
|
||||
rc = akm09970_read_data(c_ctrl);
|
||||
if (rc < 0) {
|
||||
atomic_set(&c_ctrl->data_ready, 0);
|
||||
pr_warn("Failed to read data\n");
|
||||
} else {
|
||||
atomic_set(&c_ctrl->data_ready, 1);
|
||||
wake_up(&poll_wait_queue);
|
||||
c_ctrl->read_flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t akm09970_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct akm09970_soc_ctrl *c_ctrl = dev_id;
|
||||
|
||||
queue_work(c_ctrl->work_queue, &c_ctrl->report_work);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int akm09970_release(struct inode *inp, struct file *filp)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int akm09970_open(struct inode *inp, struct file *filp)
|
||||
{
|
||||
int rc = 0;
|
||||
struct akm09970_soc_ctrl *c_ctrl =
|
||||
container_of(inp->i_cdev, struct akm09970_soc_ctrl, cdev);
|
||||
|
||||
filp->private_data = c_ctrl;
|
||||
|
||||
pr_debug("Open enter, irq = %d\n", c_ctrl->irq);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t akm09970_write(struct file *filp, const char *buf, size_t len,
|
||||
loff_t *fseek)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t akm09970_read(struct file *filp, char *buf, size_t len,
|
||||
loff_t *fseek)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static unsigned int akm09970_poll(struct file *filp, poll_table *wait)
|
||||
{
|
||||
unsigned int mask = 0;
|
||||
struct akm09970_soc_ctrl *c_ctrl = filp->private_data;
|
||||
|
||||
pr_debug("Poll enter\n");
|
||||
|
||||
poll_wait(filp, &poll_wait_queue, wait);
|
||||
if (atomic_read(&c_ctrl->data_ready)) {
|
||||
atomic_set(&c_ctrl->data_ready, 0);
|
||||
mask = POLLIN | POLLRDNORM;
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static long akm09970_ioctl(struct file *filp, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
int rc = 0;
|
||||
int i = 0;
|
||||
struct akm09970_soc_ctrl *c_ctrl = filp->private_data;
|
||||
|
||||
if (NULL == c_ctrl) {
|
||||
pr_err("Invalid data\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (_IOC_TYPE(cmd) != AKM_IOC_MAGIC) {
|
||||
pr_err("CMD magic number is worng\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (_IOC_DIR(cmd) & _IOC_READ)
|
||||
rc = !access_ok(VERIFY_WRITE, (void __user *)arg,
|
||||
_IOC_SIZE(cmd));
|
||||
else if (_IOC_DIR(cmd) & _IOC_WRITE)
|
||||
rc = !access_ok(VERIFY_READ, (void __user *)arg,
|
||||
_IOC_SIZE(cmd));
|
||||
if (rc) {
|
||||
pr_err("CMD access failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (_IOC_DIR(cmd) & _IOC_WRITE) {
|
||||
if (copy_from_user(&c_ctrl->pdata, (void __user *)arg,
|
||||
sizeof(struct akm09970_platform_data))) {
|
||||
pr_err("Copy data from user space failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case AKM_IOC_SET_ACTIVE:
|
||||
pr_debug("c_ctrl->pdata.sensor_state = %d\n",
|
||||
c_ctrl->pdata.sensor_state);
|
||||
rc = akm09970_active(c_ctrl, c_ctrl->pdata.sensor_state);
|
||||
break;
|
||||
case AKM_IOC_SET_MODE:
|
||||
pr_debug("c_ctrl->pdata.sensor_mode = %d\n",
|
||||
c_ctrl->pdata.sensor_mode);
|
||||
c_ctrl->measure_freq_hz = c_ctrl->pdata.sensor_mode;
|
||||
//rc = akm09970_set_mode(pctrl, mode);
|
||||
break;
|
||||
case AKM_IOC_GET_SENSSMR:
|
||||
pr_debug("c_ctrl->measure_range = %d\n", c_ctrl->measure_range);
|
||||
c_ctrl->pdata.sensor_smr = c_ctrl->measure_range;
|
||||
break;
|
||||
case AKM_IOC_GET_SENSEDATA:
|
||||
for (i = 0; i < AKM_SENSOR_DATA_SIZE; i++) {
|
||||
if (c_ctrl->read_flag)
|
||||
c_ctrl->pdata.data[i] = c_ctrl->chip_data[i];
|
||||
else
|
||||
c_ctrl->pdata.data[i] = 0x00;
|
||||
pr_debug("data%d = %d\n", i, c_ctrl->chip_data[i]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pr_warn("Unsupport cmd:0x%x\n", cmd);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_IOC_DIR(cmd) & _IOC_READ) {
|
||||
if (copy_to_user((void __user *)arg, &c_ctrl->pdata,
|
||||
sizeof(struct akm09970_platform_data))) {
|
||||
pr_err("Copy data to user space failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static long akm09970_compat_ioctl(struct file *filp, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
return akm09970_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct file_operations akm09970_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = akm09970_open,
|
||||
.release = akm09970_release,
|
||||
.read = akm09970_read,
|
||||
.write = akm09970_write,
|
||||
.unlocked_ioctl = akm09970_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = akm09970_compat_ioctl,
|
||||
#endif
|
||||
.poll = akm09970_poll,
|
||||
};
|
||||
|
||||
static int akm09970_check_device_id(struct akm09970_soc_ctrl *c_ctrl)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rc = akm09970_power_up(c_ctrl);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = i2c_smbus_read_i2c_block_data(c_ctrl->client, AK09970_REG_WIA,
|
||||
AKM_SENSOR_INFO_SIZE,
|
||||
c_ctrl->chip_info);
|
||||
if (rc < 0)
|
||||
goto exit;
|
||||
|
||||
if ((c_ctrl->chip_info[0] != AK09970_WIA1_VALUE) ||
|
||||
(c_ctrl->chip_info[1] != AK09970_WIA2_VALUE)) {
|
||||
pr_err("Check device id failed\n");
|
||||
rc = -ENXIO;
|
||||
}
|
||||
|
||||
exit:
|
||||
akm09970_power_down(c_ctrl);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t akm09970_chip_rev_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct akm09970_soc_ctrl *c_ctrl = dev_get_drvdata(dev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%x,%x\n", (char)c_ctrl->chip_info[0],
|
||||
(char)c_ctrl->chip_info[1]);
|
||||
}
|
||||
static DEVICE_ATTR(chip_rev, S_IRUGO, akm09970_chip_rev_show, NULL);
|
||||
|
||||
static ssize_t akm09970_debug_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct akm09970_soc_ctrl *c_ctrl = dev_get_drvdata(dev);
|
||||
short hall_data[3];
|
||||
|
||||
hall_data[0] =
|
||||
(short)((c_ctrl->chip_data[2] << 8) | c_ctrl->chip_data[3]);
|
||||
hall_data[1] =
|
||||
(short)((c_ctrl->chip_data[4] << 8) | c_ctrl->chip_data[5]);
|
||||
hall_data[2] =
|
||||
(short)((c_ctrl->chip_data[6] << 8) | c_ctrl->chip_data[7]);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "X:%d, Y:%d, Z:%d\n", hall_data[0],
|
||||
hall_data[1], hall_data[2]);
|
||||
}
|
||||
|
||||
static ssize_t akm09970_debug_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
unsigned int enable;
|
||||
int ret = 0;
|
||||
struct akm09970_soc_ctrl *c_ctrl = dev_get_drvdata(dev);
|
||||
|
||||
ret = sscanf(buf, "%d", &enable);
|
||||
if (0 == ret)
|
||||
pr_err("Input %d\n", enable);
|
||||
|
||||
if (10 == enable)
|
||||
akm09970_active(c_ctrl, true);
|
||||
else
|
||||
akm09970_active(c_ctrl, false);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(debug, S_IRUGO | S_IWUSR, akm09970_debug_show,
|
||||
akm09970_debug_store);
|
||||
|
||||
static struct device_attribute *akm_attrs[] = {
|
||||
&dev_attr_chip_rev,
|
||||
&dev_attr_debug,
|
||||
};
|
||||
|
||||
static int akm09970_regulator_init(struct akm09970_soc_ctrl *c_ctrl, bool on)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (on) {
|
||||
c_ctrl->vdd = regulator_get(c_ctrl->dev, "vdd");
|
||||
if (IS_ERR(c_ctrl->vdd)) {
|
||||
rc = PTR_ERR(c_ctrl->vdd);
|
||||
pr_err("Regulator get failed vdd rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (regulator_count_voltages(c_ctrl->vdd) > 0) {
|
||||
rc = regulator_set_voltage(c_ctrl->vdd,
|
||||
AKM09970_VDD_MIN_UV,
|
||||
AKM09970_VDD_MAX_UV);
|
||||
if (rc) {
|
||||
pr_err("Regulator set failed vdd rc=%d\n", rc);
|
||||
regulator_put(c_ctrl->vdd);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (regulator_count_voltages(c_ctrl->vdd) > 0)
|
||||
regulator_set_voltage(c_ctrl->vdd, 0,
|
||||
AKM09970_VDD_MAX_UV);
|
||||
|
||||
regulator_put(c_ctrl->vdd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int akm09970_gpio_config(struct akm09970_soc_ctrl *c_ctrl)
|
||||
{
|
||||
int32_t rc = 0;
|
||||
|
||||
rc = gpio_request_one(c_ctrl->gpio_reset, GPIOF_OUT_INIT_LOW,
|
||||
"akm09970-reset");
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to request power enable GPIO %d\n",
|
||||
c_ctrl->gpio_reset);
|
||||
goto reset_gpio_req_err;
|
||||
}
|
||||
gpio_direction_output(c_ctrl->gpio_reset, 0);
|
||||
|
||||
rc = gpio_request(c_ctrl->gpio_irq, "akm09970-irq");
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to request power enable GPIO %d\n",
|
||||
c_ctrl->gpio_irq);
|
||||
goto irq_gpio_req_err;
|
||||
} else {
|
||||
gpio_direction_input(c_ctrl->gpio_irq);
|
||||
c_ctrl->irq = gpio_to_irq(c_ctrl->gpio_irq);
|
||||
rc = request_threaded_irq(c_ctrl->irq, NULL,
|
||||
akm09970_irq_handler,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
"akm09970_irq", c_ctrl);
|
||||
if (rc < 0) {
|
||||
pr_err("Unable to request irq\n");
|
||||
goto irq_req_err;
|
||||
}
|
||||
disable_irq_nosync(c_ctrl->irq);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
irq_req_err:
|
||||
if (gpio_is_valid(c_ctrl->gpio_irq)) {
|
||||
if (c_ctrl->irq)
|
||||
free_irq(c_ctrl->irq, c_ctrl);
|
||||
gpio_free(c_ctrl->gpio_irq);
|
||||
}
|
||||
|
||||
irq_gpio_req_err:
|
||||
if (gpio_is_valid(c_ctrl->gpio_reset))
|
||||
gpio_free(c_ctrl->gpio_reset);
|
||||
|
||||
reset_gpio_req_err:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int akm09970_pinctrl_select(struct akm09970_soc_ctrl *c_ctrl, bool state)
|
||||
{
|
||||
int rc = 0;
|
||||
struct pinctrl_state *pins_state = state ? (c_ctrl->gpio_state_active) :
|
||||
(c_ctrl->gpio_state_suspend);
|
||||
|
||||
rc = pinctrl_select_state(c_ctrl->pinctrl, pins_state);
|
||||
if (rc < 0)
|
||||
pr_err("Failed to select pins state %s\n",
|
||||
state ? "active" : "suspend");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int akm09970_pinctrl_init(struct akm09970_soc_ctrl *c_ctrl)
|
||||
{
|
||||
int rc = 0;
|
||||
struct device *dev = c_ctrl->dev;
|
||||
|
||||
c_ctrl->pinctrl = devm_pinctrl_get(dev);
|
||||
if (IS_ERR_OR_NULL(c_ctrl->pinctrl)) {
|
||||
rc = PTR_ERR(c_ctrl->pinctrl);
|
||||
pr_err("Unable to acquire pinctrl %d\n", rc);
|
||||
goto err_pinctrl_get;
|
||||
}
|
||||
|
||||
c_ctrl->gpio_state_active =
|
||||
pinctrl_lookup_state(c_ctrl->pinctrl, "akm09970_gpio_active");
|
||||
if (IS_ERR_OR_NULL(c_ctrl->gpio_state_active)) {
|
||||
pr_err("Cannot lookup active pinctrl state\n");
|
||||
rc = PTR_ERR(c_ctrl->gpio_state_active);
|
||||
goto err_pinctrl_lookup;
|
||||
}
|
||||
|
||||
c_ctrl->gpio_state_suspend =
|
||||
pinctrl_lookup_state(c_ctrl->pinctrl, "akm09970_gpio_suspend");
|
||||
if (IS_ERR_OR_NULL(c_ctrl->gpio_state_suspend)) {
|
||||
pr_err("Cannot lookup suspend pinctrl state\n");
|
||||
rc = PTR_ERR(c_ctrl->gpio_state_suspend);
|
||||
goto err_pinctrl_lookup;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_pinctrl_lookup:
|
||||
devm_pinctrl_put(c_ctrl->pinctrl);
|
||||
err_pinctrl_get:
|
||||
c_ctrl->pinctrl = NULL;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int akm09970_parse_dt(struct device *dev,
|
||||
struct akm09970_soc_ctrl *c_ctrl)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
c_ctrl->gpio_reset = of_get_named_gpio_flags(dev->of_node,
|
||||
"akm,gpio-reset", 0, NULL);
|
||||
if (!gpio_is_valid(c_ctrl->gpio_reset)) {
|
||||
pr_err("Invalid gpio reset pin %d\n", c_ctrl->gpio_reset);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
c_ctrl->gpio_irq =
|
||||
of_get_named_gpio_flags(dev->of_node, "akm,gpio-irq", 0, NULL);
|
||||
if (!gpio_is_valid(c_ctrl->gpio_irq)) {
|
||||
pr_err("Invalid gpio irq pin %d\n", c_ctrl->gpio_irq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pr_err("gpio_reset = %d, gpio_irq = %d\n", c_ctrl->gpio_reset,
|
||||
c_ctrl->gpio_irq);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int akm09970_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int rc = 0;
|
||||
struct akm09970_soc_ctrl *c_ctrl = NULL;
|
||||
|
||||
pr_info("Probe enter\n");
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
||||
pr_err("Check_functionality failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Allocate memory for driver data */
|
||||
c_ctrl = devm_kzalloc(&client->dev, sizeof(struct akm09970_soc_ctrl),
|
||||
GFP_KERNEL);
|
||||
if (!c_ctrl) {
|
||||
pr_err("Alloc memory failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pr_debug("Start parse device tree\n");
|
||||
if (client->dev.of_node) {
|
||||
rc = akm09970_parse_dt(&client->dev, c_ctrl);
|
||||
if (rc < 0) {
|
||||
pr_err("Unable to parse platfrom data rc=%d\n", rc);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
c_ctrl->dev = &client->dev;
|
||||
c_ctrl->client = client;
|
||||
i2c_set_clientdata(client, c_ctrl);
|
||||
|
||||
rc = akm09970_pinctrl_init(c_ctrl);
|
||||
if (rc) {
|
||||
pr_err("Failed to initialize pinctrl\n");
|
||||
goto exit;
|
||||
} else {
|
||||
if (c_ctrl->pinctrl) {
|
||||
rc = akm09970_pinctrl_select(c_ctrl, true);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to select default pinstate %d\n",
|
||||
rc);
|
||||
goto err_select_pinctrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc = akm09970_gpio_config(c_ctrl);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to config gpio\n");
|
||||
goto err_select_pinctrl;
|
||||
}
|
||||
|
||||
rc = akm09970_regulator_init(c_ctrl, true);
|
||||
if (rc < 0)
|
||||
goto err_regulator_init;
|
||||
|
||||
rc = akm09970_check_device_id(c_ctrl);
|
||||
if (rc < 0)
|
||||
goto err_check_device;
|
||||
|
||||
atomic_set(&c_ctrl->power_enabled, 0);
|
||||
|
||||
pr_err("IRQ is #%d\n", c_ctrl->irq);
|
||||
|
||||
hrtimer_init(&c_ctrl->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
c_ctrl->timer.function = hrtimer_handler;
|
||||
c_ctrl->work_queue =
|
||||
alloc_workqueue("akm09970_poll_work_queue",
|
||||
WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI, 1);
|
||||
INIT_WORK(&c_ctrl->report_work, akm09970_dev_work_queue);
|
||||
|
||||
c_ctrl->chr_class = class_create(THIS_MODULE, AKM09970_CLASS_NAME);
|
||||
if (c_ctrl->chr_class == NULL) {
|
||||
pr_err("Failed to create class\n");
|
||||
rc = -ENODEV;
|
||||
goto err_check_device;
|
||||
}
|
||||
|
||||
rc = alloc_chrdev_region(&c_ctrl->dev_num, 0, 1, AKM09970_DRV_NAME);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to allocate chrdev region\n");
|
||||
goto err_destroy_class;
|
||||
}
|
||||
|
||||
c_ctrl->chr_dev =
|
||||
device_create(c_ctrl->chr_class, NULL, c_ctrl->dev_num, c_ctrl,
|
||||
AKM09970_DRV_NAME);
|
||||
if (IS_ERR(c_ctrl->chr_dev)) {
|
||||
pr_err("Failed to create char device\n");
|
||||
rc = PTR_ERR(c_ctrl->chr_dev);
|
||||
goto err_unregister_chrdev;
|
||||
}
|
||||
|
||||
cdev_init(&(c_ctrl->cdev), &akm09970_fops);
|
||||
c_ctrl->cdev.owner = THIS_MODULE;
|
||||
|
||||
rc = cdev_add(&(c_ctrl->cdev), c_ctrl->dev_num, 1);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to add cdev\n");
|
||||
goto err_destroy_device;
|
||||
}
|
||||
|
||||
rc = device_create_file(c_ctrl->chr_dev, akm_attrs[0]);
|
||||
if (rc < 0)
|
||||
pr_err("Failed to create debug file: %d\n", rc);
|
||||
|
||||
rc = device_create_file(c_ctrl->chr_dev, akm_attrs[1]);
|
||||
if (rc < 0)
|
||||
pr_err("Failed to create debug file: %d\n", rc);
|
||||
|
||||
pr_info("Probe exit\n");
|
||||
|
||||
return 0;
|
||||
|
||||
err_destroy_device:
|
||||
if (c_ctrl->chr_dev)
|
||||
device_destroy(c_ctrl->chr_class, c_ctrl->dev_num);
|
||||
|
||||
err_unregister_chrdev:
|
||||
unregister_chrdev_region(c_ctrl->dev_num, 1);
|
||||
err_destroy_class:
|
||||
if (c_ctrl->chr_class)
|
||||
class_destroy(c_ctrl->chr_class);
|
||||
|
||||
err_check_device:
|
||||
akm09970_regulator_init(c_ctrl, false);
|
||||
|
||||
err_regulator_init:
|
||||
if (gpio_is_valid(c_ctrl->gpio_irq)) {
|
||||
if (c_ctrl->irq)
|
||||
free_irq(c_ctrl->irq, c_ctrl);
|
||||
gpio_free(c_ctrl->gpio_irq);
|
||||
}
|
||||
|
||||
if (gpio_is_valid(c_ctrl->gpio_reset))
|
||||
gpio_free(c_ctrl->gpio_reset);
|
||||
|
||||
err_select_pinctrl:
|
||||
if (c_ctrl->pinctrl) {
|
||||
devm_pinctrl_put(c_ctrl->pinctrl);
|
||||
c_ctrl->pinctrl = NULL;
|
||||
}
|
||||
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int akm09970_remove(struct i2c_client *client)
|
||||
{
|
||||
struct akm09970_soc_ctrl *c_ctrl = i2c_get_clientdata(client);
|
||||
|
||||
cancel_work_sync(&c_ctrl->report_work);
|
||||
destroy_workqueue(c_ctrl->work_queue);
|
||||
|
||||
if (&(c_ctrl->cdev))
|
||||
cdev_del(&(c_ctrl->cdev));
|
||||
|
||||
if (c_ctrl->chr_dev)
|
||||
device_destroy(c_ctrl->chr_class, c_ctrl->dev_num);
|
||||
|
||||
unregister_chrdev_region(c_ctrl->dev_num, 1);
|
||||
if (c_ctrl->chr_class)
|
||||
class_destroy(c_ctrl->chr_class);
|
||||
|
||||
akm09970_power_down(c_ctrl);
|
||||
|
||||
akm09970_regulator_init(c_ctrl, false);
|
||||
|
||||
if (gpio_is_valid(c_ctrl->gpio_irq)) {
|
||||
if (c_ctrl->irq)
|
||||
free_irq(c_ctrl->irq, c_ctrl);
|
||||
gpio_free(c_ctrl->gpio_irq);
|
||||
}
|
||||
|
||||
if (gpio_is_valid(c_ctrl->gpio_reset))
|
||||
gpio_free(c_ctrl->gpio_reset);
|
||||
|
||||
if (c_ctrl->pinctrl) {
|
||||
devm_pinctrl_put(c_ctrl->pinctrl);
|
||||
c_ctrl->pinctrl = NULL;
|
||||
}
|
||||
|
||||
pr_info("Removed exit\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id akm09970_id[] = {
|
||||
{ AKM09970_DRV_NAME, 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, akm09970_id);
|
||||
|
||||
static struct of_device_id akm09970_match_table[] = {
|
||||
{
|
||||
.compatible = "akm,akm09970",
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct i2c_driver akm09970_driver = {
|
||||
.driver = {
|
||||
.name = AKM09970_DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = akm09970_match_table,
|
||||
},
|
||||
.probe = akm09970_probe,
|
||||
.remove = akm09970_remove,
|
||||
.id_table = akm09970_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(akm09970_driver);
|
||||
|
||||
MODULE_DESCRIPTION("AKM compass driver");
|
||||
MODULE_AUTHOR("Zhu Nengjin <zhunengjin@xiaomi.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_VERSION("2.0");
|
||||
97
include/uapi/linux/akm09970.h
Normal file
97
include/uapi/linux/akm09970.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
|
||||
/*
|
||||
* Copyright (c) 2018-2019, Linux Foundation. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __AKM09970_H__
|
||||
#define __AKM09970_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
//#define pr_fmt(fmt) "akm09970: %s: %d " fmt, __func__, __LINE__
|
||||
|
||||
#define AKM09970_DRV_NAME "akm09970"
|
||||
#define AKM09970_CLASS_NAME "akm"
|
||||
|
||||
/* AKM09970 Driver Magic Number */
|
||||
#define AKM_IOC_MAGIC 'M'
|
||||
|
||||
#define AKM_PRIVATE 109
|
||||
|
||||
/* Device specific constant values */
|
||||
#define AK09970_REG_WIA 0x00
|
||||
#define AK09970_REG_ST_XYZ 0x17
|
||||
#define AK09970_REG_CNTL1 0x20
|
||||
#define AK09970_REG_CNTL2 0x21
|
||||
#define AK09970_REG_RESET 0x30
|
||||
|
||||
#define AK09970_RESET_DATA 0x01
|
||||
|
||||
#define AK09970_WIA1_VALUE 0x48
|
||||
#define AK09970_WIA2_VALUE 0xC0
|
||||
|
||||
#define AK09970_MODE_POWERDOWN 0x00
|
||||
#define AK09970_MODE_CONTINUOUS_10HZ 0x08 /* 10Hz */
|
||||
#define AK09970_MODE_CONTINUOUS_20HZ 0x0A /* 20Hz */
|
||||
#define AK09970_MODE_CONTINUOUS_50HZ 0x0C /* 50Hz */
|
||||
#define AK09970_MODE_CONTINUOUS_100HZ 0x0E /* 100Hz */
|
||||
|
||||
#define AKM_SENSOR_INFO_SIZE 2
|
||||
#define AKM_SENSOR_CONF_SIZE 3
|
||||
#define AKM_SENSOR_DATA_SIZE 8
|
||||
|
||||
#define AK09970_MODE_POS 0
|
||||
#define AK09970_MODE_MSK 0x0F
|
||||
#define AK09970_MODE_REG AK09970_REG_CNTL2
|
||||
|
||||
#define AK09970_SDR_MODE_POS 4
|
||||
#define AK09970_SDR_MODE_MSK 0x10
|
||||
#define AK09970_SDR_MODE_REG AK09970_REG_CNTL2
|
||||
|
||||
#define AK09970_SMR_MODE_POS 5
|
||||
#define AK09970_SMR_MODE_MSK 0x20
|
||||
#define AK09970_SMR_MODE_REG AK09970_REG_CNTL2
|
||||
|
||||
#define AKM_DRDY_IS_HIGH(x) ((x)&0x01)
|
||||
#define AKM_DOR_IS_HIGH(x) ((x)&0x02)
|
||||
#define AKM_ERRADC_IS_HIGH(x) ((x)&0x01)
|
||||
#define AKM_ERRXY_IS_HIGH(x) ((x)&0x80)
|
||||
|
||||
#define AK09970_SENS_Q16 ((int32_t)(72090)) /* 1.1uT in Q16 format */
|
||||
|
||||
#define AKM_DRDY_TIMEOUT_MS 100
|
||||
#define AKM_DEFAULT_MEASURE_HZ 10
|
||||
|
||||
/* POWER SUPPLY VOLTAGE RANGE */
|
||||
#define AKM09970_VDD_MIN_UV 1800000
|
||||
#define AKM09970_VDD_MAX_UV 1800000
|
||||
|
||||
#define PWM_PERIOD_DEFAULT_NS 1000000
|
||||
|
||||
struct akm09970_platform_data {
|
||||
uint8_t sensor_smr;
|
||||
uint8_t sensor_mode;
|
||||
uint8_t sensor_state;
|
||||
uint8_t data[AKM_SENSOR_DATA_SIZE];
|
||||
};
|
||||
|
||||
/* IOC CMD */
|
||||
#define AKM_IOC_SET_ACTIVE \
|
||||
_IOW(AKM_IOC_MAGIC, AKM_PRIVATE + 1, struct akm09970_platform_data)
|
||||
|
||||
#define AKM_IOC_SET_MODE \
|
||||
_IOW(AKM_IOC_MAGIC, AKM_PRIVATE + 2, struct akm09970_platform_data)
|
||||
|
||||
#define AKM_IOC_GET_SENSEDATA \
|
||||
_IOR(AKM_IOC_MAGIC, AKM_PRIVATE + 4, struct akm09970_platform_data)
|
||||
|
||||
#define AKM_IOC_GET_SENSSMR \
|
||||
_IOR(AKM_IOC_MAGIC, AKM_PRIVATE + 5, struct akm09970_platform_data)
|
||||
|
||||
#endif /* __AKM09970_H__ */
|
||||
Reference in New Issue
Block a user