/**************************************************************************** * drivers/sensors/ak09912.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_AK09912) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define AK09911_DEVID 0x05 #define AK09912_DEVID 0x04 #define AK09912_ADDR 0x0C #define AK09912_FREQ 400000 /* REGISTER: WIA * Who I am. */ #define AK09912_WIA1 0x00 #define AK09912_WIA2 0x01 /* REGISTER: CNTL2 * Power mode */ #define POWER_MODE_ADDR 0x31 /* REGISTER: ASAX * Sensitivity values */ #define AK09912_ASAX 0x60 /* REGISTER: CNTL1 * Enable or disable temparator measure or enable or disable Noise * suppression filter. */ #define AK09912_CTRL1 0x30 /* REGISTER: HXL * The start address of data registers. */ #define AK09912_HXL 0x11 /* Polling timeout * The unit is 10 millisecond. */ #define AK09912_POLLING_TIMEOUT (1) /* 10 ms */ /* The parameter for compensating. */ #define AK09912_SENSITIVITY (128) #define AK09912_SENSITIVITY_DIV (256) /* Noise Suppression Filter */ #define AK09912_NSF_NONE 0b00 #define AK09912_NSF_LOW 0b01 #define AK09912_NSF_MIDDLE 0b10 #define AK09912_NSF_HIGH 0b11 /* Power mode */ #define AKM_POWER_DOWN_MODE 0b0000 #define AKM_SINGL_MEAS_MODE 0b00001 #define AKM_CONT_MEAS_1 0b00010 #define AKM_CONT_MEAS_2 0b00100 #define AKM_CONT_MEAS_3 0b00110 #define AKM_CONT_MEAS_4 0b01000 #define AKM_EXT_TRIG_MODE 0b01010 #define AKM_SELF_TEST_MODE 0b10000 #define AKM_FUSE_ROM_MODE 0b11111 /* REGISTER: ST1 * DRDY: Data ready bit. 0: not ready, 1: ready * DOR: Data overrun. 0: Not overrun, 1: overrun */ #define AK09912_ST1 0x10 #define SET_BITSLICE(s, v, offset, mask) \ do { \ s &= mask; \ s |= (v << offset) & mask;} while(0) #define MERGE_BYTE(low, high) \ ((low & 0xff) | ((high << 8) & ~0xff)) /**************************************************************************** * Private Type Definitions ****************************************************************************/ /* Structure for compensating data. */ struct sensi_data_s { uint8_t x; uint8_t y; uint8_t z; }; /* Structure for ak09912 device */ struct ak09912_dev_s { FAR struct i2c_master_s *i2c; /* I2C interface */ uint8_t addr; /* I2C address */ int freq; /* Frequency <= 3.4MHz */ int compensated; /* 0: uncompensated, 1:compensated */ struct sensi_data_s asa_data; /* sensitivity data */ uint8_t mode; /* power mode */ uint8_t nsf; /* noise suppression filter setting */ struct wdog_s wd; sem_t wait; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Character driver methods */ static int ak09912_open(FAR struct file *filep); static int ak09912_close(FAR struct file *filep); static ssize_t ak09912_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t ak09912_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); static int ak09912_ioctl(FAR struct file *filep, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations g_ak09912fops = { ak09912_open, /* open */ ak09912_close, /* close */ ak09912_read, /* read */ ak09912_write, /* write */ NULL, /* seek */ ak09912_ioctl, /* ioctl */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ak09912_getreg8 * * Description: * Read from an 8-bit ak09912 register * ****************************************************************************/ static uint8_t ak09912_getreg8(FAR struct ak09912_dev_s *priv, uint8_t regaddr) { struct i2c_msg_s msg[2]; uint8_t regval = 0; int ret; msg[0].frequency = priv->freq; msg[0].addr = priv->addr; msg[0].flags = 0; msg[0].buffer = ®addr; msg[0].length = 1; msg[1].frequency = priv->freq; msg[1].addr = priv->addr; msg[1].flags = I2C_M_READ; msg[1].buffer = ®val; msg[1].length = 1; ret = I2C_TRANSFER(priv->i2c, msg, 2); if (ret < 0) { snerr("I2C_TRANSFER failed: %d\n", ret); return 0; } return regval; } /**************************************************************************** * Name: ak09912_putreg8 * * Description: * Write to an 8-bit ak09912 register * ****************************************************************************/ static int ak09912_putreg8(FAR struct ak09912_dev_s *priv, uint8_t regaddr, uint8_t regval) { struct i2c_msg_s msg[2]; int ret; uint8_t txbuffer[2]; txbuffer[0] = regaddr; txbuffer[1] = regval; msg[0].frequency = priv->freq; msg[0].addr = priv->addr; msg[0].flags = 0; msg[0].buffer = txbuffer; msg[0].length = 2; ret = I2C_TRANSFER(priv->i2c, msg, 1); if (ret < 0) { snerr("I2C_TRANSFER failed: %d\n", ret); } return ret; } /**************************************************************************** * Name: ak09912_getreg * * Description: * Read cnt bytes from a ak09912 register * ****************************************************************************/ static int32_t ak09912_getreg(FAR struct ak09912_dev_s *priv, uint8_t regaddr, FAR uint8_t *buffer, uint32_t cnt) { struct i2c_msg_s msg[2]; int ret; msg[0].frequency = priv->freq; msg[0].addr = priv->addr; msg[0].flags = 0; msg[0].buffer = ®addr; msg[0].length = 1; msg[1].frequency = priv->freq; msg[1].addr = priv->addr; msg[1].flags = I2C_M_READ; msg[1].buffer = buffer; msg[1].length = cnt; ret = I2C_TRANSFER(priv->i2c, msg, 2); if (ret < 0) { snerr("I2C_TRANSFER failed: %d\n", ret); return ret; } return ret; } /**************************************************************************** * Name: ak09912_delay_msec * * Description: * System level delay * ****************************************************************************/ void ak09912_delay_msek(uint16_t msek) { /* This is used to short delay without schedule. */ up_udelay(msek * 1000); } /**************************************************************************** * Name: ak09912_set_power_mode * * Description: * set POWER MODE for ak09912 * ****************************************************************************/ static int ak09912_set_power_mode(FAR struct ak09912_dev_s *priv, uint32_t mode) { int ret = 0; ret = ak09912_putreg8(priv, POWER_MODE_ADDR, (uint8_t)mode); ak09912_delay_msek(1); return ret; } /**************************************************************************** * Name: ak09912_read_sensitivity_data * * Description: * read sensitivity date in fuse mode. * ****************************************************************************/ static int ak09912_read_sensitivity_data(FAR struct ak09912_dev_s *priv, FAR struct sensi_data_s *asa_data) { int ret = 0; uint8_t buffer[3]; ret = ak09912_getreg(priv, AK09912_ASAX, buffer, sizeof(buffer)); if (ret == 0) { asa_data->x = buffer[0]; asa_data->y = buffer[1]; asa_data->z = buffer[2]; } return ret; } /**************************************************************************** * Name: ak09912_set_noise_suppr_flt * * Description: * set noise suppression filter for ak09912 * ****************************************************************************/ static int ak09912_set_noise_suppr_flt(FAR struct ak09912_dev_s *priv, uint32_t nsf) { int ret = 0; uint8_t ctrl1 = 0; ctrl1 = ak09912_getreg8(priv, AK09912_CTRL1); SET_BITSLICE(ctrl1, nsf, 5, 0x60); ret = ak09912_putreg8(priv, AK09912_CTRL1, ctrl1); return ret; } /**************************************************************************** * Name: ak09912_wd_timeout * * Description: * a timer to check if data is ready. * ****************************************************************************/ static void ak09912_wd_timeout(wdparm_t arg) { struct ak09912_dev_s *priv = (struct ak09912_dev_s *)arg; irqstate_t flags = enter_critical_section(); nxsem_post(&priv->wait); leave_critical_section(flags); } /**************************************************************************** * Name: ak09912_read_mag_uncomp_data * * Description: * read mag uncompensating data. * ****************************************************************************/ static int ak09912_read_mag_uncomp_data(FAR struct ak09912_dev_s *priv, FAR struct mag_data_s *mag_data) { int ret = 0; uint8_t state = 0; uint8_t buffer[8]; /* TMPS and ST2 is read, but the value is omitted. */ wd_start(&priv->wd, AK09912_POLLING_TIMEOUT, ak09912_wd_timeout, (wdparm_t)priv); state = ak09912_getreg8(priv, AK09912_ST1); while (! (state & 0x1)) { nxsem_wait(&priv->wait); } wd_cancel(&priv->wd); ret = ak09912_getreg(priv, AK09912_HXL, buffer, sizeof(buffer)); mag_data->x = MERGE_BYTE(buffer[0], buffer[1]); mag_data->y = MERGE_BYTE(buffer[2], buffer[3]); mag_data->z = MERGE_BYTE(buffer[4], buffer[5]); return ret; } /**************************************************************************** * Name: ak09912_read_mag_data * * Description: * read mag data with compensation * ****************************************************************************/ static int ak09912_read_mag_data(FAR struct ak09912_dev_s *priv, FAR struct mag_data_s *mag_data) { int ret = 0; ret = ak09912_read_mag_uncomp_data(priv, mag_data); if (ret < 0) { snerr("Failed to read mag data from device.\n"); return ret; } mag_data->x = (int16_t)(mag_data->x * ((int32_t)priv->asa_data.x + AK09912_SENSITIVITY) / AK09912_SENSITIVITY_DIV); mag_data->y = (int16_t)(mag_data->y * ((int32_t)priv->asa_data.y + AK09912_SENSITIVITY) / AK09912_SENSITIVITY_DIV); mag_data->z = (int16_t)(mag_data->z * ((int32_t)priv->asa_data.z + AK09912_SENSITIVITY) / AK09912_SENSITIVITY_DIV); return ret; } /**************************************************************************** * Name: ak09912_checkid * * Description: * Read and verify the AK09912 chip ID * ****************************************************************************/ static int ak09912_checkid(FAR struct ak09912_dev_s *priv) { uint16_t devid = 0; /* Read device ID */ devid = ak09912_getreg8(priv, AK09912_WIA1); devid += ak09912_getreg8(priv, AK09912_WIA2) << 8; sninfo("devid: 0x%02x\n", devid); if (devid != AK09911_DEVID && devid != AK09912_DEVID) { /* ID is not Correct */ snerr("Wrong Device ID! %02x\n", devid); return -ENODEV; } return OK; } /**************************************************************************** * Name: ak09912_updatecaldata * * Description: * Update Calibration Coefficient Data * ****************************************************************************/ static int ak09912_initialize(FAR struct ak09912_dev_s *priv) { int ret = 0; ret += ak09912_set_power_mode(priv, AKM_FUSE_ROM_MODE); ret += ak09912_read_sensitivity_data(priv, &priv->asa_data); ret += ak09912_set_power_mode(priv, AKM_POWER_DOWN_MODE); ret += ak09912_set_noise_suppr_flt(priv, priv->nsf); return ret; } /**************************************************************************** * Name: ak09912_open * * Description: * This function is called whenever the AK09912 device is opened. * ****************************************************************************/ static int ak09912_open(FAR struct file *filep) { FAR struct inode *inode = filep->f_inode; FAR struct ak09912_dev_s *priv = inode->i_private; int ret = 0; ret = ak09912_set_power_mode(priv, priv->mode); if (ret < 0) { snerr("Failed to set power mode to %d.\n", priv->mode); return ret; } return OK; } /**************************************************************************** * Name: ak09912_close * * Description: * This routine is called when the AK09912 device is closed. * ****************************************************************************/ static int ak09912_close(FAR struct file *filep) { FAR struct inode *inode = filep->f_inode; FAR struct ak09912_dev_s *priv = inode->i_private; int ret = 0; ret = ak09912_set_power_mode(priv, AKM_POWER_DOWN_MODE); if (ret < 0) { snerr("Failed to set power mode to %d.\n", AKM_POWER_DOWN_MODE); return ret; } return OK; } /**************************************************************************** * Name: ak09912_read ****************************************************************************/ static ssize_t ak09912_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct inode *inode = filep->f_inode; FAR struct ak09912_dev_s *priv = inode->i_private; int32_t ret = 0; FAR struct mag_data_s *mag_data = (FAR struct mag_data_s *)buffer; if (! buffer) { snerr("Buffer is null\n"); return -1; } if (buflen != sizeof(struct mag_data_s)) { snerr("You can't read something other than 32 bits (4 bytes)\n"); return -1; } if (priv->compensated) { irqstate_t flags = enter_critical_section(); ret = ak09912_read_mag_data(priv, mag_data); leave_critical_section(flags); } else { irqstate_t flags = enter_critical_section(); ret = ak09912_read_mag_uncomp_data(priv, mag_data); leave_critical_section(flags); } if (ret < 0) { snerr("Failed to read data from ak09912.\n"); return ret; } return sizeof(struct mag_data_s); } /**************************************************************************** * Name: ak09912_write ****************************************************************************/ static ssize_t ak09912_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) { return -ENOSYS; } /**************************************************************************** * Name: ak09912_write ****************************************************************************/ static int ak09912_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode = filep->f_inode; FAR struct ak09912_dev_s *priv = inode->i_private; int ret = OK; switch (cmd) { case SNIOC_ENABLE_COMPENSATED: priv->compensated = (int)arg; break; default: snerr("Unrecognized cmd: %d\n", cmd); ret = - ENOTTY; break; } return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ak09912_register * * Description: * Register the AK09912 character device as 'devpath' * * Input Parameters: * devpath - The full path to the driver to register. E.g., "/dev/compass0" * i2c - An instance of the I2C interface to use to communicate with * AK09912 * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ int ak09912_register(FAR const char *devpath, FAR struct i2c_master_s *i2c) { FAR struct ak09912_dev_s *priv; char path[16]; int ret; /* Initialize the AK09912 device structure */ priv = kmm_zalloc(sizeof(struct ak09912_dev_s)); if (!priv) { snerr("Failed to allocate instance\n"); return -ENOMEM; } priv->i2c = i2c; priv->addr = AK09912_ADDR; priv->freq = AK09912_FREQ; priv->compensated = ENABLE_COMPENSATED; nxsem_init(&priv->wait, 0, 0); /* set default noise suppression filter. */ priv->nsf = AK09912_NSF_LOW; /* set default power mode */ priv->mode = AKM_CONT_MEAS_4; /* Check Device ID */ ret = ak09912_checkid(priv); if (ret < 0) { snerr("Failed to register driver: %d\n", ret); kmm_free(priv); return ret; } ret = ak09912_initialize(priv); if (ret < 0) { snerr("Failed to initialize physical device ak09912:%d\n", ret); kmm_free(priv); return ret; } /* Register the character driver */ snprintf(path, sizeof(path), "%s%d", devpath, 0); ret = register_driver(path, &g_ak09912fops, 0666, priv); if (ret < 0) { snerr("Failed to register driver: %d\n", ret); kmm_free(priv); } sninfo("AK09912 driver loaded successfully!\n"); return ret; } #endif /* CONFIG_I2C && CONFIG_SENSORS_AK09912 */