| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2019 TDK-InvenSense, Inc. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/device.h> |
| #include <linux/regmap.h> |
| #include <linux/delay.h> |
| |
| #include "inv_mpu_aux.h" |
| #include "inv_mpu_iio.h" |
| |
| /* |
| * i2c master auxiliary bus transfer function. |
| * Requires the i2c operations to be correctly setup before. |
| */ |
| static int inv_mpu_i2c_master_xfer(const struct inv_mpu6050_state *st) |
| { |
| /* use 50hz frequency for xfer */ |
| const unsigned int freq = 50; |
| const unsigned int period_ms = 1000 / freq; |
| uint8_t d; |
| unsigned int user_ctrl; |
| int ret; |
| |
| /* set sample rate */ |
| d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(freq); |
| ret = regmap_write(st->map, st->reg->sample_rate_div, d); |
| if (ret) |
| return ret; |
| |
| /* start i2c master */ |
| user_ctrl = st->chip_config.user_ctrl | INV_MPU6050_BIT_I2C_MST_EN; |
| ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl); |
| if (ret) |
| goto error_restore_rate; |
| |
| /* wait for xfer: 1 period + half-period margin */ |
| msleep(period_ms + period_ms / 2); |
| |
| /* stop i2c master */ |
| user_ctrl = st->chip_config.user_ctrl; |
| ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl); |
| if (ret) |
| goto error_stop_i2c; |
| |
| /* restore sample rate */ |
| d = st->chip_config.divider; |
| ret = regmap_write(st->map, st->reg->sample_rate_div, d); |
| if (ret) |
| goto error_restore_rate; |
| |
| return 0; |
| |
| error_stop_i2c: |
| regmap_write(st->map, st->reg->user_ctrl, st->chip_config.user_ctrl); |
| error_restore_rate: |
| regmap_write(st->map, st->reg->sample_rate_div, st->chip_config.divider); |
| return ret; |
| } |
| |
| /** |
| * inv_mpu_aux_init() - init i2c auxiliary bus |
| * @st: driver internal state |
| * |
| * Returns 0 on success, a negative error code otherwise. |
| */ |
| int inv_mpu_aux_init(const struct inv_mpu6050_state *st) |
| { |
| unsigned int val; |
| int ret; |
| |
| /* |
| * Code based on the vendor Linux kernel v3.0, |
| * the exact meaning is unknown. |
| */ |
| if (st->chip_type == INV_MPU9150) { |
| unsigned int mask = BIT(7); |
| |
| val = st->level_shifter ? mask : 0; |
| ret = regmap_update_bits(st->map, 0x1, mask, val); |
| if (ret) |
| return ret; |
| } |
| |
| /* configure i2c master */ |
| val = INV_MPU6050_BITS_I2C_MST_CLK_400KHZ | |
| INV_MPU6050_BIT_WAIT_FOR_ES; |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_MST_CTRL, val); |
| if (ret) |
| return ret; |
| |
| /* configure i2c master delay */ |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV4_CTRL, 0); |
| if (ret) |
| return ret; |
| |
| val = INV_MPU6050_BIT_I2C_SLV0_DLY_EN | |
| INV_MPU6050_BIT_I2C_SLV1_DLY_EN | |
| INV_MPU6050_BIT_I2C_SLV2_DLY_EN | |
| INV_MPU6050_BIT_I2C_SLV3_DLY_EN | |
| INV_MPU6050_BIT_DELAY_ES_SHADOW; |
| return regmap_write(st->map, INV_MPU6050_REG_I2C_MST_DELAY_CTRL, val); |
| } |
| |
| /** |
| * inv_mpu_aux_read() - read register function for i2c auxiliary bus |
| * @st: driver internal state. |
| * @addr: chip i2c Address |
| * @reg: chip register address |
| * @val: buffer for storing read bytes |
| * @size: number of bytes to read |
| * |
| * Returns 0 on success, a negative error code otherwise. |
| */ |
| int inv_mpu_aux_read(const struct inv_mpu6050_state *st, uint8_t addr, |
| uint8_t reg, uint8_t *val, size_t size) |
| { |
| unsigned int status; |
| int ret; |
| |
| if (size > 0x0F) |
| return -EINVAL; |
| |
| /* setup i2c SLV0 control: i2c addr, register, enable + size */ |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(0), |
| INV_MPU6050_BIT_I2C_SLV_RNW | addr); |
| if (ret) |
| return ret; |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(0), reg); |
| if (ret) |
| return ret; |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), |
| INV_MPU6050_BIT_SLV_EN | size); |
| if (ret) |
| return ret; |
| |
| /* do i2c xfer */ |
| ret = inv_mpu_i2c_master_xfer(st); |
| if (ret) |
| goto error_disable_i2c; |
| |
| /* disable i2c slave */ |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), 0); |
| if (ret) |
| goto error_disable_i2c; |
| |
| /* check i2c status */ |
| ret = regmap_read(st->map, INV_MPU6050_REG_I2C_MST_STATUS, &status); |
| if (ret) |
| return ret; |
| if (status & INV_MPU6050_BIT_I2C_SLV0_NACK) |
| return -EIO; |
| |
| /* read data in registers */ |
| return regmap_bulk_read(st->map, INV_MPU6050_REG_EXT_SENS_DATA, |
| val, size); |
| |
| error_disable_i2c: |
| regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), 0); |
| return ret; |
| } |
| |
| /** |
| * inv_mpu_aux_write() - write register function for i2c auxiliary bus |
| * @st: driver internal state. |
| * @addr: chip i2c Address |
| * @reg: chip register address |
| * @val: 1 byte value to write |
| * |
| * Returns 0 on success, a negative error code otherwise. |
| */ |
| int inv_mpu_aux_write(const struct inv_mpu6050_state *st, uint8_t addr, |
| uint8_t reg, uint8_t val) |
| { |
| unsigned int status; |
| int ret; |
| |
| /* setup i2c SLV0 control: i2c addr, register, value, enable + size */ |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(0), addr); |
| if (ret) |
| return ret; |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(0), reg); |
| if (ret) |
| return ret; |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_DO(0), val); |
| if (ret) |
| return ret; |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), |
| INV_MPU6050_BIT_SLV_EN | 1); |
| if (ret) |
| return ret; |
| |
| /* do i2c xfer */ |
| ret = inv_mpu_i2c_master_xfer(st); |
| if (ret) |
| goto error_disable_i2c; |
| |
| /* disable i2c slave */ |
| ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), 0); |
| if (ret) |
| goto error_disable_i2c; |
| |
| /* check i2c status */ |
| ret = regmap_read(st->map, INV_MPU6050_REG_I2C_MST_STATUS, &status); |
| if (ret) |
| return ret; |
| if (status & INV_MPU6050_BIT_I2C_SLV0_NACK) |
| return -EIO; |
| |
| return 0; |
| |
| error_disable_i2c: |
| regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), 0); |
| return ret; |
| } |