| /* Driver for Glass low-power sensor hub |
| * |
| * Copyright (C) 2012 Google, Inc. |
| * |
| * 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. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include <linux/poll.h> |
| #include <linux/delay.h> |
| #include <linux/i2c.h> |
| #include <linux/gpio.h> |
| #include <linux/interrupt.h> |
| #include <linux/input.h> |
| #include <linux/slab.h> |
| #include <linux/kfifo.h> |
| #include <linux/ctype.h> |
| |
| /* Used to provide access via misc device */ |
| #include <linux/miscdevice.h> |
| |
| #include <linux/i2c/glasshub.h> |
| |
| /* module debug flags */ |
| #define DEBUG_FLASH_MODE 0 |
| |
| /* driver name/version */ |
| #define DEVICE_NAME "glasshub" |
| #define DRIVER_VERSION "0.15" |
| |
| /* minimum MCU firmware version required for this driver */ |
| #define MINIMUM_MCU_VERSION 27 |
| |
| /* experimental MCU firmware version */ |
| #define EXPERIMENTAL_MCU_VERSION 0x8000 |
| |
| /* special app code to flash the bootloader */ |
| #define BOOTLOADER_FLASHER 0xff |
| |
| /* number of retries */ |
| #define NUMBER_OF_I2C_RETRIES 3 |
| |
| /* 8-bit registers for the glass hub MCU */ |
| #define REG_RESET 0 |
| #define REG_STATUS 0 |
| #define REG_PART_ID 1 |
| #define REG_VERSION 2 |
| #define REG_ENABLE_INT 3 |
| #define REG_ENABLE_PASSTHRU 4 |
| #define REG_ENABLE_DON_DOFF 5 |
| #define REG_DON_DOFF 6 |
| #define REG_ENABLE_WINK 7 |
| #define REG_DON_DOFF_HYSTERESIS 8 |
| #define REG_LED_DRIVE 9 |
| #define REG_ERROR_CODE 10 |
| #define REG_FLASH_DATA 11 |
| #define REG_DETECTOR_GAIN 12 |
| #define REG_PROX_PART_ID 13 |
| #define REG_PROX_SEQ_ID 14 |
| #define REG_PAUSE 15 |
| #define REG_WINK_STATUS 16 |
| #define REG_DEBUG 17 |
| #define REG_AMBIENT_ENABLE 18 |
| #define REG_WINK_MIN 19 |
| #define REG_WINK_MAX 20 |
| |
| /* virtual registers */ |
| #define REG_STATUS_READ_ONLY 64 |
| |
| /* 16-bit registers */ |
| #define REG16_DETECTOR_BIAS 0x80 |
| #define REG16_DON_DOFF_THRESH 0x81 |
| #define REG16_MIN_PROX 0x82 |
| #define REG16_PROX_RAW 0x83 |
| #define REG16_PROX_DATA 0x84 |
| #define REG16_ADDRESS 0x85 |
| #define REG16_VIS_DATA 0x86 |
| #define REG16_IR_DATA 0x87 |
| #define REG16_FRAME_COUNT 0x88 |
| #define REG16_DEBUG 0x89 |
| #define REG16_TIMER_COUNT 0x8a |
| #define REG16_DOFF_THRESH 0x8b |
| |
| /* virtual 16-bit registers */ |
| #define REG16_DON_THRESH 0xc1 |
| |
| #define CMD_APP_VERSION 0xf6 |
| #define CMD_BOOTLOADER_VERSION 0xf7 |
| #define CMD_FLASH_STATUS 0xf8 |
| #define CMD_FLASH 0xf9 |
| #define CMD_BOOT 0xfA |
| |
| /* interrupt sources in STATUS register */ |
| #define IRQ_PASSTHRU 0b00000001 |
| #define IRQ_WINK 0b00000010 |
| #define IRQ_DON_DOFF 0b00000100 |
| |
| /* status flags in STATUS register */ |
| #define STATUS_OVERFLOW 0b10000000 |
| |
| /* device ID */ |
| #define GLASSHUB_PART_ID 0xbb |
| |
| #define PROX_DATA_FIFO_SIZE 16 |
| |
| /* input device constants */ |
| #define PS_MIN_VALUE 0 |
| #define PS_MAX_VALUE 65535 |
| |
| /* bit fields for flags */ |
| #define FLAG_WAKE_THREAD 0 |
| #define FLAG_DEVICE_BOOTED 1 |
| #define FLAG_FLASH_MODE 2 |
| #define FLAG_SYSFS_CREATED 3 |
| #define FLAG_DEVICE_DISABLED 4 |
| #define FLAG_WINK_FLAG_ENABLE 5 |
| #define FLAG_DEVICE_MAY_BE_WEDGED 31 |
| |
| /* flags for device permissions */ |
| #define DEV_MODE_RO (S_IRUSR | S_IRGRP) |
| #define DEV_MODE_WO (S_IWUSR | S_IWGRP) |
| #define DEV_MODE_RW (DEV_MODE_RO | DEV_MODE_WO) |
| |
| /* firmware parameters */ |
| #define FIRMWARE_PAGE_SIZE 64 |
| #define FIRMWARE_TOTAL_SIZE (8*1024) |
| #define FIRMWARE_TOTAL_PAGES (FIRMWARE_TOTAL_SIZE / FIRMWARE_PAGE_SIZE) |
| #define FIRMWARE_NUM_DIRTY_BITS ((FIRMWARE_TOTAL_PAGES+ 31) / 32) |
| #define FIRMWARE_BASE_ADDRESS 0x8000 |
| #define FIRMWARE_END_ADDRESS (FIRMWARE_BASE_ADDRESS + FIRMWARE_TOTAL_SIZE - 1) |
| |
| /* firmware state machine */ |
| #define FW_STATE_START 0 |
| #define FW_STATE_TYPE 1 |
| #define FW_STATE_COUNT_HI 2 |
| #define FW_STATE_COUNT_LO 3 |
| #define FW_STATE_ADDR_7 4 |
| #define FW_STATE_ADDR_6 5 |
| #define FW_STATE_ADDR_5 6 |
| #define FW_STATE_ADDR_4 7 |
| #define FW_STATE_ADDR_3 8 |
| #define FW_STATE_ADDR_2 9 |
| #define FW_STATE_ADDR_1 10 |
| #define FW_STATE_ADDR_0 11 |
| #define FW_STATE_BYTE_HI 12 |
| #define FW_STATE_BYTE_LO 13 |
| #define FW_STATE_CHECKSUM_HI 14 |
| #define FW_STATE_CHECKSUM_LO 15 |
| |
| /* location of calibration data */ |
| #define CALIB_ADDRESS 0x9ff0 |
| |
| /* number of samples to take for calibration mean */ |
| #define NUM_CALIBRATION_SAMPLES 3 |
| |
| /* number of samples in prox data buffer, must be a power of 2 */ |
| #define PROX_QUEUE_SZ (1<<6) |
| |
| /* expected prox sample interval in nanoseconds */ |
| #define PROX_INTERVAL (1000000000LL / 32) |
| |
| /* Interval for drift correction in sample frames. Uses shift |
| * operation to avoid divide, so the interval must be a power |
| * of 2. Thus, a value of 5 yields an interval of 32 frames or |
| * 1 second at 32 Hz. |
| */ |
| #define PROX_AVERAGING_SHIFT 5 |
| #define PROX_AVERAGING_INTERVAL (1 << PROX_AVERAGING_SHIFT) |
| |
| /* flags and mask for prox meta data */ |
| #define PROX_DATA_MASK 0x7fff |
| #define PROX_DATA_MASK_NO_WINK_FLAG 0x3fff |
| #define PROX_DATA_END_OF_DATA_FLAG 0x8000 |
| #define PROX_DATA_WINK_DETECTED_FLAG 0x4000 |
| |
| /* values for flash_status */ |
| #define FLASH_STATUS_OK 0 |
| #define FLASH_STATUS_READY 1 |
| #define FLASH_ERROR_DEV_REJECTED_PKT -1 |
| #define FLASH_ERROR_INVALID_S_RECORD -2 |
| #define FLASH_ERROR_ADDRESS_RANGE -3 |
| #define FLASH_ERROR_INVALID_HEX_DIGIT -4 |
| #define FLASH_ERROR_SREC_CHECKSUM_ERROR -5 |
| #define FLASH_ERROR_DEVICE_RESET_ERROR -6 |
| #define FLASH_ERROR_I2C_DEVICE_COMM -7 |
| #define FLASH_ERROR_NO_PAGES_FLASHED -8 |
| #define FLASH_ERROR_BOOTLOADER_VERSION -9 |
| #define FLASH_ERROR_DEVICE_BOOT_ERROR -10 |
| |
| /* |
| * Basic theory of operation: |
| * |
| * The glass hub device comes up in bootloader mode. This mode supports only 5 commands: |
| * |
| * BOOT Jump to the application code stored in flash |
| * FLASH Download a 64-byte page of firmware to flash |
| * FLASH_STATUS Report status of last flash operation |
| * APP_VERSION Returns the 2-byte application version |
| * BOOT_VERSION Returns the 1-byte bootloader version |
| * |
| * It is now possible to flash the bootloader by loading special application |
| * code. It reports a version number of 255.xxx which allows the driver to |
| * identify it as special code to flash the bootloader. Flash operation |
| * proceeds as normal except that at the point where the firmware image is to |
| * be flashed, the driver boots into the special application code. Flashing |
| * takes place as usual, the driver then resets into the new bootloader code |
| * and reads the bootloader version. To enable this process from user space, |
| * first download the special bootloader app code, then write a 1 to the |
| * fw_update_enable sysfs node, download the bootloader app code, then write |
| * a 255 to the fw_update_enable node. It is this last step that tells the |
| * driver code that you want to write bootloader code instead of app code. |
| * There is some risk that this operation could result in a bricked device, |
| * since a failure to write the boot code is unrecoverable. In this case, |
| * it will be necessary to attach a SWIM connector and use the debugger |
| * to flash a new bootloader image. |
| * |
| * The application code can be flashed by a simple script. First, write a 1 |
| * to the enable_fw_update sysfs node. The driver will allocate a block of |
| * memory to save the firmware image and clear the dirty bits for each page |
| * of code. Next, write the .S19 the .S19 application code image in text |
| * format (e.g. "cat") to the update_fw_data sysfs node. The driver will |
| * validate the checksum of each line and store it in the binary code buffer, |
| * setting a dirty bit for each 64-byte page of code it touches. Be aware |
| * that it doesn't backfill missing data, i.e. if you touch a single byte on |
| * a page, the rest of the page will be written with zeroes. Thus, it's |
| * always a good idea to flash the entire image each time. Finally, write a 0 |
| * to the enable_fw_update sysfs node. This will cause the driver to write |
| * each dirty page to the device. Note that the concept of a flash |
| * programming mode is purely an artifact of the driver and not something the |
| * device itself enforces or is even aware of. |
| * |
| * For normal operation, the driver will automatically boot the device to |
| * start running application code the first time it receives any command that |
| * requires the application code. From the application code, a RESET command |
| * will cause the device to return to the bootloader. It is possible that a |
| * bug in the application code will make it impossible to return to the |
| * bootloader. In this case, it may be necessary to power-down the device to |
| * force it back into the bootloader. This allows for recovery in case there |
| * is a serious bug in the application code. |
| */ |
| |
| struct glasshub_data { |
| struct i2c_client *i2c_client; |
| struct input_dev *ps_input_dev; |
| const struct glasshub_platform_data *pdata; |
| uint8_t *fw_image; |
| uint32_t fw_dirty[FIRMWARE_NUM_DIRTY_BITS]; |
| int flash_status; |
| int fw_state; |
| int fw_rec_type; |
| int fw_index; |
| int fw_count; |
| uint32_t fw_value; |
| uint8_t fw_checksum; |
| struct mutex device_lock; |
| uint64_t irq_timestamp; |
| uint64_t last_timestamp; |
| uint64_t start_timestamp; |
| unsigned long count_for_average; |
| unsigned long average_delta; |
| unsigned long sample_count; |
| volatile unsigned long flags; |
| uint8_t don_doff_state; |
| uint8_t bootloader_version; |
| uint8_t app_version_major; |
| uint8_t app_version_minor; |
| uint8_t last_irq_status; |
| int debug; |
| }; |
| |
| struct glasshub_data *glasshub_private = NULL; |
| |
| /* |
| * Kernel FIFO for timestamped prox data |
| */ |
| static DECLARE_WAIT_QUEUE_HEAD(prox_read_wait); |
| static DECLARE_KFIFO(prox_fifo, struct glasshub_data_user, PROX_QUEUE_SZ); |
| static atomic_t glasshub_opened = ATOMIC_INIT(0); |
| |
| static int register_device_files(struct glasshub_data *glasshub); |
| |
| /* |
| * I2C bus transaction read for consecutive data. |
| * Returns 0 on success. |
| */ |
| static int _i2c_read(struct glasshub_data *glasshub, uint8_t *txData, int txLength, |
| uint8_t *rxData,int rxLength) |
| { |
| int i; |
| struct i2c_msg data[] = { |
| { |
| .addr = glasshub->i2c_client->addr, |
| .flags = 0, |
| .len = txLength, |
| .buf = txData, |
| }, |
| { |
| .addr = glasshub->i2c_client->addr, |
| .flags = I2C_M_RD, |
| .len = rxLength, |
| .buf = rxData, |
| }, |
| }; |
| |
| for (i = 0; i < NUMBER_OF_I2C_RETRIES; i++) { |
| if (i2c_transfer(glasshub->i2c_client->adapter, data, 2) > 0) |
| break; |
| /* Delay before retrying */ |
| mdelay(10); |
| } |
| |
| if (i >= NUMBER_OF_I2C_RETRIES) { |
| dev_err(&glasshub->i2c_client->dev, "%s i2c read retry exceeded\n", __FUNCTION__); |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| /* |
| * I2C bus transaction to write consecutive data. |
| * Returns 0 on success. |
| */ |
| static int _i2c_write_mult(struct glasshub_data *glasshub, uint8_t *txData, int length) |
| { |
| int i; |
| struct i2c_msg data[] = { |
| { |
| .addr = glasshub->i2c_client->addr, |
| .flags = 0, |
| .len = length, |
| .buf = txData, |
| }, |
| }; |
| |
| for (i = 0; i < NUMBER_OF_I2C_RETRIES; i++) { |
| if (i2c_transfer(glasshub->i2c_client->adapter, data, 1) > 0) |
| break; |
| /* Delay before retrying */ |
| mdelay(10); |
| } |
| |
| if (i >= NUMBER_OF_I2C_RETRIES) { |
| dev_err(&glasshub->i2c_client->dev, "%s i2c write retry exceeded\n", __FUNCTION__); |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| /* |
| * I2C bus transaction to read a register |
| * Returns 0 on success. |
| */ |
| static int _i2c_read_reg(struct glasshub_data *glasshub, uint8_t reg, unsigned *value) |
| { |
| int rc = 0; |
| uint8_t buffer[2]; |
| *value = 0xffff; |
| |
| buffer[0] = reg; |
| buffer[1] = 0; |
| if (reg & 0x80) { |
| rc = _i2c_read(glasshub, buffer, 1, buffer, 2); |
| if (rc == 0) { |
| *value = buffer[0] | (unsigned) buffer[1] << 8; |
| } |
| } else { |
| rc = _i2c_read(glasshub, buffer, 1, buffer, 1); |
| if (rc == 0) { |
| *value = buffer[0]; |
| } |
| } |
| return rc; |
| } |
| |
| /* |
| * I2C bus transaction to read single byte. |
| * Returns 0 on success. |
| */ |
| static int _i2c_read_reg8(struct glasshub_data *glasshub, uint8_t reg, uint8_t *data) |
| { |
| int rc; |
| unsigned value; |
| rc = _i2c_read_reg(glasshub, reg, &value); |
| *data = (uint8_t) value; |
| return rc; |
| } |
| |
| /* |
| * I2C bus transaction to write register |
| * Returns 0 on success. |
| */ |
| static int _i2c_write_reg(struct glasshub_data *glasshub, uint8_t reg, uint16_t data) |
| { |
| uint8_t buffer[3]; |
| |
| buffer[0] = reg; |
| buffer[1] = data & 0xff; |
| buffer[2] = (data >> 8) & 0xff; |
| return _i2c_write_mult(glasshub, buffer, reg & 0x80 ? 3 : 2); |
| } |
| |
| /* read an 8-bit value from glasshub memory */ |
| static int read_glasshub_memory(struct glasshub_data *glasshub, uint16_t addr, unsigned *value) |
| { |
| int rc; |
| |
| *value = 0; |
| |
| /* send address */ |
| rc = _i2c_write_reg(glasshub, REG16_ADDRESS, addr); |
| if (rc) goto Error; |
| |
| /* read data */ |
| rc = _i2c_read_reg(glasshub, REG_FLASH_DATA, value); |
| |
| Error: |
| return rc; |
| } |
| |
| static int write_glasshub_memory(struct glasshub_data *glasshub, uint16_t addr, uint8_t value) |
| { |
| int rc; |
| |
| rc = _i2c_write_reg(glasshub, REG16_ADDRESS, addr); |
| if (rc == 0) { |
| _i2c_write_reg(glasshub, REG_FLASH_DATA, value); |
| } |
| return rc; |
| } |
| |
| static int _check_part_id(struct glasshub_data *glasshub) |
| { |
| int rc = 0; |
| uint8_t data = 0; |
| struct i2c_client *i2c_client = glasshub->i2c_client; |
| |
| rc = _i2c_read_reg8(glasshub, REG_PART_ID, &data); |
| if (rc < 0) { |
| dev_err(&i2c_client->dev, "%s: Unable to read part identifier\n", __FUNCTION__); |
| set_bit(FLAG_DEVICE_MAY_BE_WEDGED, &glasshub->flags); |
| return -EIO; |
| } |
| |
| if (data != GLASSHUB_PART_ID) { |
| dev_err(&i2c_client->dev, "%s Unexpected part ID = %u\n", |
| __FUNCTION__, (unsigned)data); |
| rc = -ENODEV; |
| } |
| |
| return rc; |
| } |
| |
| /* must hold the device lock */ |
| int boot_device_l(struct glasshub_data *glasshub) |
| { |
| int retry; |
| uint8_t temp; |
| int rc = 0; |
| |
| /* if already booted, do nothing */ |
| if (test_bit(FLAG_DEVICE_BOOTED, &glasshub->flags)) goto err_out; |
| |
| /* don't allow boot if disabled */ |
| if (test_bit(FLAG_DEVICE_DISABLED, &glasshub->flags)) { |
| rc = -ENODEV; |
| goto err_out; |
| } |
| |
| /* don't allow boot from flash mode, unless boot flasher app |
| * code has been loaded. |
| */ |
| if (glasshub->app_version_major != BOOTLOADER_FLASHER) { |
| if (test_bit(FLAG_FLASH_MODE, &glasshub->flags)) { |
| rc = -ENODEV; |
| goto err_out; |
| } |
| } |
| |
| /* tell glass hub to boot */ |
| temp = CMD_BOOT; |
| for (retry = 0; retry < 5; retry++) { |
| rc = _i2c_write_mult(glasshub, &temp, sizeof(temp)); |
| |
| /* takes some time for the app code to start up */ |
| msleep(100); |
| if (rc == 0) break; |
| } |
| if (rc) { |
| set_bit(FLAG_DEVICE_MAY_BE_WEDGED, &glasshub->flags); |
| dev_err(&glasshub->i2c_client->dev, "%s Unable to boot glasshub device\n", |
| __FUNCTION__); |
| goto err_out; |
| } |
| |
| /* if special boot flasher code, don't do anything else */ |
| if (glasshub->app_version_major == BOOTLOADER_FLASHER) return rc; |
| |
| /* verify part ID */ |
| if (_check_part_id(glasshub)) { |
| rc = -ENODEV; |
| goto err_out; |
| } |
| |
| /* get current don/doff state */ |
| _i2c_read_reg8(glasshub, REG_DON_DOFF, &glasshub->don_doff_state); |
| |
| set_bit(FLAG_DEVICE_BOOTED, &glasshub->flags); |
| |
| err_out: |
| return rc; |
| } |
| |
| /* must hold the device lock */ |
| int reset_device_l(struct glasshub_data *glasshub, int force) |
| { |
| int retry; |
| uint8_t temp; |
| int rc = 0; |
| |
| if (!force && !test_bit(FLAG_DEVICE_BOOTED, &glasshub->flags)) goto err_out; |
| |
| /* reset glass hub */ |
| for (retry = 0; retry < 5; retry++) { |
| temp = REG_RESET; |
| rc = _i2c_write_mult(glasshub, &temp, sizeof(temp)); |
| if (rc == 0) break; |
| } |
| if (rc) { |
| set_bit(FLAG_DEVICE_MAY_BE_WEDGED, &glasshub->flags); |
| dev_err(&glasshub->i2c_client->dev, "%s Unable to reset glasshub device\n", |
| __FUNCTION__); |
| goto err_out; |
| } |
| |
| /* takes some time for the app code to shut down */ |
| msleep(50); |
| clear_bit(FLAG_DEVICE_BOOTED, &glasshub->flags); |
| |
| err_out: |
| return rc; |
| } |
| |
| /* Main interrupt handler. We save a timestamp here and schedule |
| * the threaded handler to run later, since we might have to |
| * block on I/O requests from user space. |
| */ |
| static irqreturn_t glasshub_irq_handler(int irq, void *dev_id) |
| { |
| struct glasshub_data *glasshub = (struct glasshub_data*) dev_id; |
| |
| /* if device is not booted, the interrupt must be someone else */ |
| if (!test_bit(FLAG_DEVICE_BOOTED, &glasshub->flags)) goto Handled; |
| |
| /* if threaded handler is already scheduled, don't schedule it again */ |
| if (test_and_set_bit(FLAG_WAKE_THREAD, &glasshub->flags)) goto Handled; |
| |
| /* save timestamp for threaded handler and schedule it */ |
| glasshub->irq_timestamp = read_robust_clock(); |
| return IRQ_WAKE_THREAD; |
| |
| Handled: |
| return IRQ_HANDLED; |
| } |
| |
| /* Threaded interrupt handler. This is where the real work gets done. |
| * We grab a mutex to prevent I/O requests from user space from |
| * running concurrently. The timestamp for critical operations comes |
| * from the main interrupt handler. |
| */ |
| static irqreturn_t glasshub_threaded_irq_handler(int irq, void *dev_id) |
| { |
| struct glasshub_data *glasshub = (struct glasshub_data*) dev_id; |
| int rc = 0; |
| uint8_t status; |
| uint16_t data[PROX_QUEUE_SZ]; |
| int prox_count = 0; |
| int i; |
| uint64_t timestamp; |
| |
| /* clear in-service flag */ |
| timestamp = glasshub->irq_timestamp; |
| clear_bit(FLAG_WAKE_THREAD, &glasshub->flags); |
| |
| mutex_lock(&glasshub->device_lock); |
| |
| /* just in case the glasshub was reset after scheduling this handler */ |
| if (!test_bit(FLAG_DEVICE_BOOTED, &glasshub->flags)) { |
| dev_warn(&glasshub->i2c_client->dev, |
| "%s: Ignoring pending interrupt because device is disabled\n", |
| __FUNCTION__); |
| goto Exit; |
| } |
| |
| /* read the IRQ source */ |
| rc = _i2c_read_reg8(glasshub, REG_STATUS, &status); |
| if (rc) goto Error; |
| glasshub->last_irq_status = status; |
| |
| /* process prox data */ |
| while (status & (IRQ_PASSTHRU | IRQ_WINK)) { |
| unsigned value = 0; |
| |
| /* read value */ |
| rc = _i2c_read_reg(glasshub, REG16_PROX_DATA, &value); |
| if (rc) goto Error; |
| |
| /* shouldn't happen, but 0xffff indicates we read past end of buffer */ |
| if (value == 0xffff) { |
| dev_warn(&glasshub->i2c_client->dev, |
| "%s: read past end of buffer, status = 0x%02x\n", |
| __FUNCTION__, |
| status); |
| break; |
| } |
| ++glasshub->sample_count; |
| |
| /* DEBUG: read frame counter */ |
| if (glasshub->debug && (prox_count == 0)) |
| { |
| unsigned frame_count; |
| rc = _i2c_read_reg(glasshub, REG16_FRAME_COUNT, &frame_count); |
| if (rc) goto Error; |
| printk("%s: Frame count = %u\n", __func__, frame_count); |
| } |
| |
| /* Buffer up data (and drop data that exceeds our buffer |
| * length). Note that when the high bit is set, there is |
| * no more data. This mechanism saves us from doing another |
| * I2C bus transfer to check the status register. |
| */ |
| if (atomic_read(&glasshub_opened) && (prox_count < PROX_QUEUE_SZ)) { |
| data[prox_count++] = (uint16_t) value; |
| } |
| |
| /* check for end of data */ |
| if (value & PROX_DATA_END_OF_DATA_FLAG) break; |
| } |
| |
| /* pass prox data to misc device driver */ |
| if (prox_count) { |
| uint16_t mask = PROX_DATA_MASK_NO_WINK_FLAG; |
| |
| if (glasshub->debug) { |
| printk("%s: Read %d packets\n", __func__, prox_count); |
| } |
| |
| /* should we mask the wink flag bit? */ |
| if (test_bit(FLAG_WINK_FLAG_ENABLE, &glasshub->flags)) { |
| mask = PROX_DATA_MASK; |
| } |
| |
| /* if prox data is not contiguous, start from current timestamp */ |
| if (status & STATUS_OVERFLOW || (glasshub->last_timestamp == 0)) { |
| uint64_t temp = timestamp - prox_count * glasshub->average_delta; |
| |
| /* don't go backwards in time (shouldn't happen) */ |
| if (temp > glasshub->last_timestamp) { |
| glasshub->last_timestamp = temp; |
| } else { |
| dev_warn(&glasshub->i2c_client->dev, |
| "%s: Time travel? %llu is earlier than %llu\n", |
| __FUNCTION__, |
| temp, |
| glasshub->last_timestamp); |
| } |
| glasshub->last_timestamp = |
| (temp < glasshub->last_timestamp ? glasshub->last_timestamp : temp); |
| |
| glasshub->start_timestamp = 0; |
| if (glasshub->debug) { |
| printk("%s: Resync @ %llu\n", __func__, glasshub->start_timestamp); |
| } |
| } |
| |
| /* copy data to FIFO, filling in timestamps */ |
| for (i = 0; i < prox_count; i++) { |
| struct glasshub_data_user rec; |
| rec.value = data[i] & mask; |
| glasshub->last_timestamp += glasshub->average_delta; |
| rec.timestamp = glasshub->last_timestamp; |
| kfifo_put(&prox_fifo, &rec); |
| if (glasshub->debug && (i == 0)) { |
| printk("%s: First sample in packet @ %llu\n", __func__, glasshub->last_timestamp); |
| } |
| } |
| |
| /* calculate average sample rate */ |
| if (prox_count == 1) { |
| if (glasshub->start_timestamp == 0) { |
| glasshub->start_timestamp = timestamp; |
| glasshub->count_for_average = 0; |
| } else { |
| if (++glasshub->count_for_average == PROX_AVERAGING_INTERVAL) { |
| glasshub->average_delta = (timestamp - glasshub->start_timestamp) |
| >> PROX_AVERAGING_SHIFT; |
| glasshub->start_timestamp = timestamp; |
| glasshub->count_for_average = 0; |
| } |
| } |
| |
| |
| } else { |
| glasshub->start_timestamp = 0; |
| } |
| |
| /* wake up user space */ |
| wake_up_interruptible(&prox_read_wait); |
| } |
| |
| /* process don/doff */ |
| if (status & IRQ_DON_DOFF) { |
| rc = _i2c_read_reg8(glasshub, REG_DON_DOFF, &glasshub->don_doff_state); |
| if (rc) goto Error; |
| dev_info(&glasshub->i2c_client->dev, "%s: don/doff state = %u\n", |
| __FUNCTION__, glasshub->don_doff_state); |
| sysfs_notify(&glasshub->i2c_client->dev.kobj, NULL, "don_doff"); |
| } |
| |
| /* process wink signal */ |
| if (status & IRQ_WINK) { |
| dev_info(&glasshub->i2c_client->dev, "%s: wink signal received\n", |
| __FUNCTION__); |
| sysfs_notify(&glasshub->i2c_client->dev.kobj, NULL, "wink"); |
| } |
| goto Exit; |
| |
| Error: |
| dev_err(&glasshub->i2c_client->dev, "%s: device read error\n", __FUNCTION__); |
| set_bit(FLAG_DEVICE_MAY_BE_WEDGED, &glasshub->flags); |
| |
| Exit: |
| mutex_unlock(&glasshub->device_lock); |
| return IRQ_HANDLED; |
| } |
| |
| /* common routine to return an I2C register to userspace */ |
| static ssize_t show_reg(struct device *dev, char *buf, uint8_t reg) |
| { |
| unsigned value = 0xffff; |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| |
| mutex_lock(&glasshub->device_lock); |
| if (boot_device_l(glasshub) == 0) { |
| if (_i2c_read_reg(glasshub, reg, &value)) { |
| set_bit(FLAG_DEVICE_MAY_BE_WEDGED, &glasshub->flags); |
| } |
| } |
| mutex_unlock(&glasshub->device_lock); |
| |
| return sprintf(buf, "%u\n", value); |
| } |
| |
| /* common routine to set an I2C register from userspace */ |
| static ssize_t store_reg(struct device *dev, const char *buf, size_t count, |
| uint8_t reg, uint16_t min, uint16_t max) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| unsigned long value = 0; |
| if (!kstrtoul(buf, 10, &value)) { |
| value = (value < min) ? min : value; |
| value = (value > max) ? max : value; |
| mutex_lock(&glasshub->device_lock); |
| if (boot_device_l(glasshub) == 0) { |
| if (_i2c_write_reg(glasshub, reg, value)) { |
| set_bit(FLAG_DEVICE_MAY_BE_WEDGED, &glasshub->flags); |
| } |
| } |
| mutex_unlock(&glasshub->device_lock); |
| } |
| return count; |
| } |
| |
| /* show the application version number */ |
| static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "%d.%d\n", glasshub->app_version_major, glasshub->app_version_minor); |
| } |
| |
| /* show the bootloader version number */ |
| static ssize_t bootloader_version_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "%d\n", glasshub->bootloader_version); |
| } |
| |
| /* show the driver version number */ |
| static ssize_t driver_version_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "%s\n", DRIVER_VERSION); |
| } |
| |
| /* show the driver status */ |
| static ssize_t driver_flags_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "0x%08lx\n", glasshub->flags); |
| } |
| |
| /* show don/doff status */ |
| static ssize_t don_doff_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_DON_DOFF); |
| } |
| |
| /* show don/doff enable status */ |
| static ssize_t don_doff_enable_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_ENABLE_DON_DOFF); |
| } |
| |
| /* enable/disable don/doff */ |
| static ssize_t don_doff_enable_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG_ENABLE_DON_DOFF, 0, 1); |
| } |
| |
| /* show prox passthrough mode */ |
| static ssize_t passthru_enable_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_ENABLE_PASSTHRU); |
| } |
| |
| /* enable/disable prox passthrough mode */ |
| static ssize_t passthru_enable_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG_ENABLE_PASSTHRU, 0, 1); |
| } |
| |
| /* read prox value */ |
| static int read_prox_raw_l(struct glasshub_data *glasshub, uint16_t *pProxData) |
| { |
| int rc; |
| unsigned data; |
| |
| *pProxData = 0xffff; |
| rc = _i2c_read_reg(glasshub, REG16_PROX_RAW, &data); |
| if (rc == 0) { |
| *pProxData = (uint16_t) data; |
| } |
| return rc; |
| } |
| |
| /* show minimum prox value */ |
| static ssize_t proxmin_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG16_MIN_PROX); |
| } |
| |
| /* show raw prox value */ |
| static ssize_t proxraw_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG16_PROX_RAW); |
| } |
| |
| /* show raw IR value */ |
| static ssize_t ir_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG16_IR_DATA); |
| } |
| |
| /* show raw visible light value */ |
| static ssize_t visible_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG16_VIS_DATA); |
| } |
| |
| /* show ambient enable */ |
| static ssize_t ambient_enable_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_AMBIENT_ENABLE); |
| } |
| |
| /* set ambient enable */ |
| static ssize_t ambient_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, |
| size_t count) |
| { |
| return store_reg(dev, buf, count, REG_AMBIENT_ENABLE, 0, 1); |
| } |
| |
| /* show don/doff threshold value */ |
| static ssize_t don_doff_threshold_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| return show_reg(dev, buf, REG16_DON_DOFF_THRESH); |
| } |
| |
| /* set don/doff threshold value */ |
| static ssize_t don_doff_threshold_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG16_DON_DOFF_THRESH, 1, 5000); |
| } |
| |
| /* show don threshold value */ |
| static ssize_t don_threshold_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| return show_reg(dev, buf, REG16_DON_THRESH); |
| } |
| |
| /* set don threshold value */ |
| static ssize_t don_threshold_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG16_DON_THRESH, 1, 5000); |
| } |
| |
| /* show dof threshold value */ |
| static ssize_t doff_threshold_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| return show_reg(dev, buf, REG16_DOFF_THRESH); |
| } |
| |
| /* set doff threshold value */ |
| static ssize_t doff_threshold_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG16_DOFF_THRESH, 1, 5000); |
| } |
| |
| /* show don/doff hysteresis value */ |
| static ssize_t don_doff_hysteresis_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| return show_reg(dev, buf, REG_DON_DOFF_HYSTERESIS); |
| } |
| |
| /* set don/doff hysteresis value */ |
| static ssize_t don_doff_hysteresis_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG_DON_DOFF_HYSTERESIS, 1, 255); |
| } |
| |
| /* show IR LED drive value */ |
| static ssize_t led_drive_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_LED_DRIVE); |
| } |
| |
| /* set IR LED drive value */ |
| static ssize_t led_drive_store(struct device *dev, struct device_attribute *attr, const char *buf, |
| size_t count) |
| { |
| return store_reg(dev, buf, count, REG_LED_DRIVE, 0, 7); |
| } |
| |
| /* calibrate IR LED drive levels */ |
| static ssize_t calibrate_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct glasshub_data *glasshub; |
| uint32_t sum; |
| uint16_t temp; |
| uint16_t proxValues[8]; |
| int i, j; |
| int rc; |
| uint8_t led_drive = 0x04; |
| uint8_t value = 0; |
| uint16_t addr; |
| |
| glasshub = dev_get_drvdata(dev); |
| if (!kstrtou8(buf, 10, &value) && value) { |
| mutex_lock(&glasshub->device_lock); |
| if (boot_device_l(glasshub)) { |
| rc = -ENODEV; |
| goto Unlock; |
| } |
| |
| /* read current drive level */ |
| _i2c_read_reg8(glasshub, REG_LED_DRIVE, &led_drive); |
| |
| /* test all 7 LED levels */ |
| for (i = 0; i < 8; i++) { |
| |
| /* set LED drive level */ |
| _i2c_write_reg(glasshub, REG_LED_DRIVE, i); |
| |
| /* read multiple values and take the mean */ |
| sum = 0; |
| for (j = 0; j < NUM_CALIBRATION_SAMPLES; j++) { |
| |
| /* allow charge pump to re-charge */ |
| msleep(35); |
| |
| /* read raw prox value */ |
| rc = read_prox_raw_l(glasshub, &temp); |
| if (rc) break; |
| |
| /* read raw prox value and add to running sum */ |
| sum += temp; |
| |
| } |
| |
| /* break on error */ |
| if (rc) break; |
| |
| /* save mean value */ |
| proxValues[i] = sum / NUM_CALIBRATION_SAMPLES; |
| } |
| |
| /* write calibration values to flash */ |
| if (rc == 0) { |
| addr = CALIB_ADDRESS; |
| for (i = 0; i < 8; i++) { |
| |
| /* store calibration data */ |
| write_glasshub_memory(glasshub, addr++, (uint8_t) proxValues[i]); |
| write_glasshub_memory(glasshub, addr++, (uint8_t) (proxValues[i] >> 8)); |
| } |
| } |
| |
| /* restore LED drive level */ |
| _i2c_write_reg(glasshub, REG_LED_DRIVE, led_drive); |
| |
| Unlock: |
| mutex_unlock(&glasshub->device_lock); |
| } |
| return count; |
| } |
| |
| /* read calibration values */ |
| static ssize_t calibration_values_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| int rc; |
| int i; |
| unsigned temp; |
| unsigned proxValues[8]; |
| uint16_t addr; |
| |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| memset(proxValues, 0, sizeof(proxValues)); |
| |
| mutex_lock(&glasshub->device_lock); |
| if (boot_device_l(glasshub)) goto Error; |
| |
| /* read calibration values from flash */ |
| addr = CALIB_ADDRESS; |
| for (i = 0; i < 8; i++) { |
| |
| /* read LSB */ |
| rc = read_glasshub_memory(glasshub, addr++, &temp); |
| if (rc) goto Error; |
| proxValues[i] = temp; |
| |
| /* read MSB */ |
| rc = read_glasshub_memory(glasshub, addr++, &temp); |
| if (rc) goto Error; |
| proxValues[i] |= temp << 8; |
| } |
| |
| Error: |
| mutex_unlock(&glasshub->device_lock); |
| |
| return sprintf(buf, "%u %u %u %u %u %u %u %u\n", |
| proxValues[0], proxValues[1], |
| proxValues[2], proxValues[3], |
| proxValues[4], proxValues[5], |
| proxValues[6], proxValues[7]); |
| } |
| |
| /* return prox version */ |
| static ssize_t prox_version_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| uint8_t partId = 0xff; |
| uint8_t seqId = 0xff; |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| |
| mutex_lock(&glasshub->device_lock); |
| if (boot_device_l(glasshub) == 0) { |
| _i2c_read_reg8(glasshub, REG_PROX_PART_ID, &partId); |
| _i2c_read_reg8(glasshub, REG_PROX_SEQ_ID, &seqId); |
| } |
| mutex_unlock(&glasshub->device_lock); |
| |
| return sprintf(buf, "0x%02x 0x%02x\n", partId, seqId); |
| } |
| |
| /* show wink status (also clears the status) */ |
| static ssize_t wink_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_WINK_STATUS); |
| } |
| |
| /* show wink enable */ |
| static ssize_t wink_enable_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_ENABLE_WINK); |
| } |
| |
| /* enable/disable wink */ |
| static ssize_t wink_enable_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG_ENABLE_WINK, 0, 1); |
| } |
| |
| /* show wink_flag_enable value */ |
| static ssize_t wink_flag_enable_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "%d\n", test_bit(FLAG_WINK_FLAG_ENABLE, &glasshub->flags) ? 1 : 0); |
| } |
| |
| /* write wink_flag_enable value */ |
| static ssize_t wink_flag_enable_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| unsigned long value = 0; |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| |
| /* parse value */ |
| if (kstrtoul(buf, 10, &value)) { |
| goto err_out; |
| } |
| |
| /* disable device */ |
| if (value) { |
| set_bit(FLAG_WINK_FLAG_ENABLE, &glasshub->flags); |
| } else { |
| clear_bit(FLAG_WINK_FLAG_ENABLE, &glasshub->flags); |
| } |
| |
| err_out: |
| return count; |
| } |
| |
| /* show pause*/ |
| static ssize_t pause_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_PAUSE); |
| } |
| |
| /* set pause */ |
| static ssize_t pause_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG_PAUSE, 0, 1); |
| } |
| |
| /* show wink minimum magnitude */ |
| static ssize_t wink_min_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_WINK_MIN); |
| } |
| |
| /* set wink minimum magnitude */ |
| static ssize_t wink_min_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG_WINK_MIN, 1, 255); |
| } |
| |
| /* show wink maximum magnitude */ |
| static ssize_t wink_max_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_WINK_MAX); |
| } |
| |
| /* set wink maximum magnitude */ |
| static ssize_t wink_max_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG_WINK_MAX, 1, 255); |
| } |
| |
| /* show detector gain */ |
| static ssize_t detector_gain_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_DETECTOR_GAIN); |
| } |
| |
| /* set detector gain */ |
| static ssize_t detector_gain_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG_DETECTOR_GAIN, 0x01, 0x80); |
| } |
| |
| /* show detector bias */ |
| static ssize_t detector_bias_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG16_DETECTOR_BIAS); |
| } |
| |
| /* set detector bias */ |
| static ssize_t detector_bias_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG16_DETECTOR_BIAS, 1, 5000); |
| } |
| |
| /* show last error code from device */ |
| static ssize_t error_code_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_ERROR_CODE); |
| } |
| |
| /* show frame_count value */ |
| static ssize_t frame_count_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG16_FRAME_COUNT); |
| } |
| |
| /* show timer value */ |
| static ssize_t timer_count_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG16_TIMER_COUNT); |
| } |
| |
| /* show debug value */ |
| static ssize_t mcu_debug_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG_DEBUG); |
| } |
| |
| /* write debug value */ |
| static ssize_t mcu_debug_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG_DEBUG, 0, 255); |
| } |
| |
| /* show debug value */ |
| static ssize_t mcu_debug16_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| return show_reg(dev, buf, REG16_DEBUG); |
| } |
| |
| /* write debug value */ |
| static ssize_t mcu_debug16_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return store_reg(dev, buf, count, REG16_DEBUG, 0, 255); |
| } |
| |
| /* show disable value */ |
| static ssize_t disable_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "%d\n", test_bit(FLAG_DEVICE_DISABLED, &glasshub->flags) ? 1 : 0); |
| } |
| |
| /* write disable value */ |
| static ssize_t disable_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| unsigned long value = 0; |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| |
| /* parse value */ |
| if (kstrtoul(buf, 10, &value)) { |
| goto err_out; |
| } |
| |
| /* disable device */ |
| if (value) { |
| mutex_lock(&glasshub->device_lock); |
| set_bit(FLAG_DEVICE_DISABLED, &glasshub->flags); |
| reset_device_l(glasshub, 1); |
| mutex_unlock(&glasshub->device_lock); |
| } else { |
| clear_bit(FLAG_DEVICE_DISABLED, &glasshub->flags); |
| } |
| |
| err_out: |
| return count; |
| } |
| |
| /* sysfs node for updating device firmware */ |
| static ssize_t update_fw_data_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count); |
| |
| /* hex converter for parsing .S19 files */ |
| static int convert_hex(const char c, uint32_t *p) |
| { |
| if (!isxdigit(c)) return -1; |
| *p = (*p << 4) | (c <= '9' ? c - '0' : tolower(c) - 'a' + 10); |
| return 0; |
| } |
| |
| /* helper function to get app version */ |
| static int get_app_version_l(struct glasshub_data *glasshub) |
| { |
| int rc; |
| uint8_t buffer[2]; |
| |
| /* get current app version number */ |
| buffer[0] = CMD_APP_VERSION; |
| rc = _i2c_read(glasshub, buffer, 1, buffer, sizeof(buffer)); |
| if (rc) { |
| set_bit(FLAG_DEVICE_MAY_BE_WEDGED, &glasshub->flags); |
| dev_err(&glasshub->i2c_client->dev, "%s Error getting firmware version: %d\n", |
| __FUNCTION__, rc); |
| } else { |
| dev_info(&glasshub->i2c_client->dev, "Firmware version: %d.%d\n", |
| buffer[0], buffer[1]); |
| glasshub->app_version_major = buffer[0]; |
| glasshub->app_version_minor = buffer[1]; |
| } |
| return rc; |
| } |
| |
| /* helper function to exit flash programming mode */ |
| static void exit_flash_mode_l(struct glasshub_data *glasshub) |
| { |
| if (glasshub->fw_image) { |
| kfree(glasshub->fw_image); |
| glasshub->fw_image = NULL; |
| } |
| clear_bit(FLAG_FLASH_MODE, &glasshub->flags); |
| |
| /* get app version number */ |
| get_app_version_l(glasshub); |
| } |
| |
| /* sysfs node to download device firmware to be flashed */ |
| static ssize_t update_fw_data_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| int i; |
| int pageNum; |
| |
| #if DEBUG_FLASH_MODE |
| dev_info(&glasshub->i2c_client->dev, "%s Rx SREC %u bytes\n", __FUNCTION__, count); |
| #endif |
| |
| mutex_lock(&glasshub->device_lock); |
| |
| for (i = 0; i < count; i++) { |
| |
| #if DEBUG_FLASH_MODE |
| dev_dbg(&glasshub->i2c_client->dev, |
| "%s SREC state = %d count = %d input = %c\n", |
| __FUNCTION__, glasshub->fw_state, glasshub->fw_count, buf[i]); |
| #endif |
| |
| switch (glasshub->fw_state) { |
| case FW_STATE_START: |
| if (buf[i] == 'S') { |
| glasshub->fw_state = FW_STATE_TYPE; |
| #if DEBUG_FLASH_MODE |
| dev_dbg(&glasshub->i2c_client->dev, |
| "%s Start of SREC\n", __FUNCTION__); |
| #endif |
| } |
| break; |
| |
| case FW_STATE_TYPE: |
| if (buf[i] < '0' || buf[i] > '9') { |
| glasshub->flash_status = FLASH_ERROR_INVALID_S_RECORD; |
| goto err_out; |
| } |
| |
| /* process only S1-S3 records */ |
| if (buf[i] >= '1' && buf[i] <= '3') { |
| #if DEBUG_FLASH_MODE |
| dev_dbg(&glasshub->i2c_client->dev, |
| "%s SREC type %c\n", __FUNCTION__, buf[i]); |
| #endif |
| glasshub->fw_rec_type = buf[i]; |
| glasshub->fw_value = 0; |
| glasshub->fw_checksum = 0; |
| glasshub->fw_state = FW_STATE_COUNT_HI; |
| } |
| |
| /* ignore other records */ |
| else { |
| glasshub->fw_state = FW_STATE_START; |
| } |
| break; |
| |
| case FW_STATE_ADDR_6: |
| case FW_STATE_ADDR_4: |
| case FW_STATE_ADDR_2: |
| if (convert_hex(buf[i], &glasshub->fw_value)) { |
| glasshub->flash_status = FLASH_ERROR_INVALID_HEX_DIGIT; |
| goto err_out; |
| } |
| glasshub->fw_checksum += glasshub->fw_value & 0xff; |
| glasshub->fw_count--; |
| glasshub->fw_state++; |
| break; |
| |
| case FW_STATE_COUNT_HI: |
| case FW_STATE_ADDR_7: |
| case FW_STATE_ADDR_5: |
| case FW_STATE_ADDR_3: |
| case FW_STATE_ADDR_1: |
| case FW_STATE_BYTE_HI: |
| case FW_STATE_CHECKSUM_HI: |
| if (convert_hex(buf[i], &glasshub->fw_value)) { |
| glasshub->flash_status = FLASH_ERROR_INVALID_HEX_DIGIT; |
| goto err_out; |
| } |
| glasshub->fw_state++; |
| break; |
| |
| case FW_STATE_COUNT_LO: |
| if (convert_hex(buf[i], &glasshub->fw_value)) { |
| glasshub->flash_status = FLASH_ERROR_INVALID_HEX_DIGIT; |
| goto err_out; |
| } |
| glasshub->fw_checksum += glasshub->fw_value & 0xff; |
| #if DEBUG_FLASH_MODE |
| dev_dbg(&glasshub->i2c_client->dev, "%s SREC byte count %u\n", |
| __FUNCTION__, glasshub->fw_value); |
| #endif |
| |
| /* adjust count for address and checksum bytes */ |
| glasshub->fw_count = glasshub->fw_value; |
| glasshub->fw_value = 0; |
| glasshub->fw_state = FW_STATE_ADDR_7 + 2 * |
| ('3' - glasshub->fw_rec_type); |
| break; |
| |
| case FW_STATE_ADDR_0: |
| if (convert_hex(buf[i], &glasshub->fw_value)) { |
| glasshub->flash_status = FLASH_ERROR_INVALID_HEX_DIGIT; |
| goto err_out; |
| } |
| glasshub->fw_checksum += glasshub->fw_value & 0xff; |
| #if DEBUG_FLASH_MODE |
| dev_dbg(&glasshub->i2c_client->dev, "%s SREC address %04xh\n", |
| __FUNCTION__, glasshub->fw_value); |
| #endif |
| glasshub->fw_index = glasshub->fw_value - FIRMWARE_BASE_ADDRESS; |
| glasshub->fw_count--; |
| glasshub->fw_value = 0; |
| glasshub->fw_state = FW_STATE_BYTE_HI; |
| break; |
| |
| case FW_STATE_BYTE_LO: |
| if (convert_hex(buf[i], &glasshub->fw_value)) { |
| glasshub->flash_status = FLASH_ERROR_INVALID_HEX_DIGIT; |
| goto err_out; |
| } |
| glasshub->fw_checksum += glasshub->fw_value & 0xff; |
| |
| /* validate address */ |
| if (glasshub->fw_index < 0 || |
| glasshub->fw_index > FIRMWARE_TOTAL_SIZE) { |
| dev_err(&glasshub->i2c_client->dev, |
| "%s Address out of range: address %u count=%u\n", |
| __FUNCTION__, glasshub->fw_value, |
| glasshub->fw_count); |
| glasshub->flash_status = FLASH_ERROR_ADDRESS_RANGE; |
| goto err_out; |
| } |
| |
| /* store byte in image buffer */ |
| glasshub->fw_image[glasshub->fw_index] = glasshub->fw_value; |
| glasshub->fw_value = 0; |
| pageNum = glasshub->fw_index / FIRMWARE_PAGE_SIZE; |
| glasshub->fw_dirty[pageNum / 32] |= (1 << (pageNum & 31)); |
| glasshub->fw_index++; |
| glasshub->fw_state = FW_STATE_BYTE_HI; |
| |
| /* check for end of data */ |
| if (--glasshub->fw_count == 1) { |
| #if DEBUG_FLASH_MODE |
| dev_dbg(&glasshub->i2c_client->dev, |
| "%s SREC all bytes received\n", |
| __FUNCTION__); |
| #endif |
| glasshub->fw_state = FW_STATE_CHECKSUM_HI; |
| } |
| break; |
| |
| case FW_STATE_CHECKSUM_LO: |
| if (convert_hex(buf[i], &glasshub->fw_value)) { |
| glasshub->flash_status = FLASH_ERROR_INVALID_HEX_DIGIT; |
| goto err_out; |
| } |
| if (glasshub->fw_value != (~glasshub->fw_checksum & 0xff)) { |
| dev_err(&glasshub->i2c_client->dev, |
| "%s SREC checksum mismatch %02xh != %02xh\n", |
| __FUNCTION__, |
| (unsigned)~glasshub->fw_checksum & 0xff, |
| (unsigned)glasshub->fw_value); |
| glasshub->flash_status = FLASH_ERROR_SREC_CHECKSUM_ERROR; |
| goto err_out; |
| } |
| #if DEBUG_FLASH_MODE |
| dev_dbg(&glasshub->i2c_client->dev, "%s SREC checksum OK\n", |
| __FUNCTION__); |
| #endif |
| glasshub->fw_state = FW_STATE_START; |
| break; |
| } |
| } |
| |
| mutex_unlock(&glasshub->device_lock); |
| return count; |
| |
| err_out: |
| exit_flash_mode_l(glasshub); |
| mutex_unlock(&glasshub->device_lock); |
| dev_err(&glasshub->i2c_client->dev, "%s Not an SREC\n", __FUNCTION__); |
| return -EINVAL; |
| } |
| |
| /* sysfs node to enter/exit flash programming mode */ |
| static ssize_t update_fw_enable_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| unsigned long value = 0; |
| int rc = 0; |
| int bootflasher = 0; |
| int old_boot; |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| |
| if (kstrtoul(buf, 10, &value)) { |
| goto err_out; |
| } |
| |
| #if DEBUG_FLASH_MODE |
| dev_info(&glasshub->i2c_client->dev, "%s update_fw_enable = %lu\n", __FUNCTION__, value); |
| #endif |
| |
| mutex_lock(&glasshub->device_lock); |
| |
| /* bootloader version 3 does not support checksum */ |
| old_boot = (glasshub->bootloader_version < 4) ? 1 : 0; |
| |
| /* handle special bootflasher case */ |
| if ((value == BOOTLOADER_FLASHER) && (glasshub->app_version_major == BOOTLOADER_FLASHER)) { |
| bootflasher = 1; |
| old_boot = 0; |
| value = 0; |
| } |
| |
| /* enable firmware flash */ |
| if (value) { |
| if (!test_bit(FLAG_FLASH_MODE, &glasshub->flags)) { |
| /* allocate memory and clear dirty bits */ |
| if (!glasshub->fw_image) { |
| glasshub->fw_image = kzalloc(FIRMWARE_TOTAL_SIZE, GFP_USER); |
| if (!glasshub->fw_image) { |
| dev_err(&glasshub->i2c_client->dev, |
| "%s failed to allocate memory for firmware image\n", |
| __FUNCTION__); |
| goto unlock; |
| } |
| memset(glasshub->fw_dirty, 0, sizeof(glasshub->fw_dirty)); |
| } |
| |
| glasshub->fw_state = FW_STATE_START; |
| set_bit(FLAG_FLASH_MODE, &glasshub->flags); |
| glasshub->flash_status = FLASH_STATUS_READY; |
| } |
| } else { |
| |
| /* exit flash, write code to device */ |
| if (test_bit(FLAG_FLASH_MODE, &glasshub->flags)) { |
| |
| /* put device into bootloader mode */ |
| rc = reset_device_l(glasshub, 0); |
| if (rc) { |
| glasshub->flash_status = FLASH_ERROR_DEVICE_RESET_ERROR; |
| goto ExitFlashMode; |
| } |
| |
| /* if flashing boot code, boot into application code */ |
| if (bootflasher) { |
| rc = boot_device_l(glasshub); |
| if (rc) { |
| glasshub->flash_status = FLASH_ERROR_DEVICE_BOOT_ERROR; |
| goto ExitFlashMode; |
| } |
| } |
| |
| /* here's where we flash the image if it has dirty bits */ |
| if (glasshub->fw_image) { |
| int pagesFlashed = 0; |
| int page; |
| uint8_t checksum; |
| |
| #if DEBUG_FLASH_MODE |
| dev_info(&glasshub->i2c_client->dev, |
| "Update pages: %08x %08x %08x %08x\n", |
| glasshub->fw_dirty[0], |
| glasshub->fw_dirty[1], |
| glasshub->fw_dirty[2], |
| glasshub->fw_dirty[3]); |
| #endif |
| |
| for (page = FIRMWARE_TOTAL_PAGES - 1; page >= 0; page--) { |
| /* flash only dirty pages */ |
| if (glasshub->fw_dirty[page / 32] & |
| (1 << (page & 31))) { |
| uint8_t buffer[FIRMWARE_PAGE_SIZE+3]; |
| int i, j; |
| |
| /* initalize command and page number */ |
| buffer[0] = CMD_FLASH; |
| buffer[1] = page; |
| |
| /* seed checksum */ |
| checksum = 0xa5; |
| |
| /* copy firmware into buffer */ |
| for (i = 2, j = page * FIRMWARE_PAGE_SIZE; |
| i < FIRMWARE_PAGE_SIZE + 2; |
| i++, j++) { |
| buffer[i] = glasshub->fw_image[j]; |
| checksum = (checksum << 1) ^ buffer[i] ^ (checksum >> 7); |
| } |
| |
| /* add checksum to buffer */ |
| buffer[66] = checksum; |
| |
| #if DEBUG_FLASH_MODE |
| dev_info(&glasshub->i2c_client->dev, |
| "%s: Flash page %d\n", |
| __FUNCTION__, page); |
| #endif |
| /* don't send checksum for older bootloader version */ |
| rc = _i2c_write_mult(glasshub, buffer, |
| sizeof(buffer) - old_boot); |
| if (rc) { |
| set_bit(FLAG_DEVICE_MAY_BE_WEDGED, &glasshub->flags); |
| dev_err(&glasshub->i2c_client->dev, |
| "%s Unable to flash glasshub device\n", |
| __FUNCTION__); |
| glasshub->flash_status = FLASH_ERROR_I2C_DEVICE_COMM; |
| goto ExitFlashMode; |
| } |
| |
| /* new bootloader: check status for error */ |
| if (!old_boot) { |
| buffer[0] = CMD_FLASH_STATUS; |
| rc = _i2c_read(glasshub, buffer, 1, buffer, 1); |
| if (rc) { |
| set_bit(FLAG_DEVICE_MAY_BE_WEDGED, &glasshub->flags); |
| dev_err(&glasshub->i2c_client->dev, |
| "%s Error reading flash status\n", |
| __FUNCTION__); |
| glasshub->flash_status = FLASH_ERROR_I2C_DEVICE_COMM; |
| goto ExitFlashMode; |
| } else if (buffer[0] != 0) { |
| dev_err(&glasshub->i2c_client->dev, |
| "%s Device rejected packet: %u\n", |
| __FUNCTION__, buffer[0]); |
| glasshub->flash_status = FLASH_ERROR_DEV_REJECTED_PKT; |
| goto ExitFlashMode; |
| } |
| } |
| ++pagesFlashed; |
| } |
| } |
| dev_info(&glasshub->i2c_client->dev, |
| "%s: %d code page(s) flashed\n", |
| __FUNCTION__, pagesFlashed); |
| glasshub->flash_status = pagesFlashed ? FLASH_STATUS_OK : FLASH_ERROR_NO_PAGES_FLASHED; |
| } |
| ExitFlashMode: |
| if (bootflasher) { |
| uint8_t buffer[1]; |
| |
| /* put device back into bootloader mode */ |
| rc = reset_device_l(glasshub, 1); |
| if (rc) { |
| glasshub->flash_status = FLASH_ERROR_DEVICE_RESET_ERROR; |
| goto ExitFlashMode; |
| } |
| |
| /* get new bootloader version */ |
| buffer[0] = CMD_BOOTLOADER_VERSION; |
| rc = _i2c_read(glasshub, buffer, 1, buffer, 1); |
| if (rc) { |
| glasshub->flash_status = FLASH_ERROR_BOOTLOADER_VERSION; |
| goto ExitFlashMode; |
| } |
| glasshub->bootloader_version = buffer[0]; |
| } |
| clear_bit(FLAG_FLASH_MODE, &glasshub->flags); |
| } |
| } |
| |
| unlock: |
| if (!test_bit(FLAG_FLASH_MODE, &glasshub->flags) && glasshub->fw_image) { |
| exit_flash_mode_l(glasshub); |
| } |
| mutex_unlock(&glasshub->device_lock); |
| err_out: |
| return rc < 0 ? rc : count; |
| } |
| |
| /* show state of firmware update flag */ |
| static ssize_t update_fw_enable_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| int enable; |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| enable = test_bit(FLAG_FLASH_MODE, &glasshub->flags) ? 1 : 0; |
| return sprintf(buf, "%d\n", enable); |
| } |
| |
| /* show state of IRQ */ |
| static ssize_t irq_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "%d\n", gpio_get_value(glasshub->pdata->gpio_int_no)); |
| } |
| |
| /* show state of flash process */ |
| static ssize_t flash_status_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| int temp; |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| temp = glasshub->flash_status; |
| glasshub->flash_status = 0; |
| return sprintf(buf, "%d\n", temp); |
| } |
| |
| /* show count of received samples */ |
| static ssize_t sample_count_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "%lu\n", glasshub->sample_count); |
| } |
| |
| /* show debug value */ |
| static ssize_t debug_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "%d\n", glasshub->debug); |
| } |
| |
| /* write debug value */ |
| static ssize_t debug_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| unsigned long value = 0; |
| if (!kstrtoul(buf, 10, &value)) { |
| glasshub->debug = value ? 1 : 0; |
| } |
| return count; |
| } |
| |
| /* timestamp of last IRQ */ |
| static ssize_t irq_timestamp_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "%llu\n", glasshub->irq_timestamp); |
| } |
| |
| /* timestamp of last sample */ |
| static ssize_t last_timestamp_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "%llu\n", glasshub->last_timestamp); |
| } |
| |
| /* show current IRQ status without clearing IRQ */ |
| static ssize_t irq_status_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| unsigned value = 0xffff; |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| |
| mutex_lock(&glasshub->device_lock); |
| if (boot_device_l(glasshub) == 0) { |
| if (_i2c_read_reg(glasshub, REG_STATUS_READ_ONLY, &value)) { |
| set_bit(FLAG_DEVICE_MAY_BE_WEDGED, &glasshub->flags); |
| } |
| } |
| mutex_unlock(&glasshub->device_lock); |
| return sprintf(buf, "0x%02x\n", value); |
| } |
| |
| /* show last IRQ status */ |
| static ssize_t last_irq_status_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct glasshub_data *glasshub = dev_get_drvdata(dev); |
| return sprintf(buf, "0x%02x\n", glasshub->last_irq_status); |
| } |
| |
| static DEVICE_ATTR(update_fw_enable, DEV_MODE_RW, update_fw_enable_show, update_fw_enable_store); |
| static DEVICE_ATTR(update_fw_data, DEV_MODE_WO, NULL, update_fw_data_store); |
| static DEVICE_ATTR(version, DEV_MODE_RO, version_show, NULL); |
| static DEVICE_ATTR(bootloader_version, DEV_MODE_RO, bootloader_version_show, NULL); |
| static DEVICE_ATTR(driver_version, DEV_MODE_RO, driver_version_show, NULL); |
| static DEVICE_ATTR(driver_flags, DEV_MODE_RO, driver_flags_show, NULL); |
| static DEVICE_ATTR(passthru_enable, DEV_MODE_RW, passthru_enable_show, passthru_enable_store); |
| static DEVICE_ATTR(proxraw, DEV_MODE_RO, proxraw_show, NULL); |
| static DEVICE_ATTR(proxmin, DEV_MODE_RO, proxmin_show, NULL); |
| static DEVICE_ATTR(ambient_enable, DEV_MODE_RW, ambient_enable_show, ambient_enable_store); |
| static DEVICE_ATTR(visible, DEV_MODE_RO, visible_show, NULL); |
| static DEVICE_ATTR(ir, DEV_MODE_RO, ir_show, NULL); |
| static DEVICE_ATTR(don_doff_enable, DEV_MODE_RW, don_doff_enable_show, don_doff_enable_store); |
| static DEVICE_ATTR(don_doff, DEV_MODE_RO, don_doff_show, NULL); |
| static DEVICE_ATTR(don_doff_threshold, DEV_MODE_RW, don_doff_threshold_show, don_doff_threshold_store); |
| static DEVICE_ATTR(don_threshold, DEV_MODE_RW, don_threshold_show, don_threshold_store); |
| static DEVICE_ATTR(doff_threshold, DEV_MODE_RW, doff_threshold_show, doff_threshold_store); |
| static DEVICE_ATTR(don_doff_hysteresis, DEV_MODE_RW, don_doff_hysteresis_show, don_doff_hysteresis_store); |
| static DEVICE_ATTR(led_drive, DEV_MODE_RW, led_drive_show, led_drive_store); |
| static DEVICE_ATTR(calibrate, DEV_MODE_WO, NULL, calibrate_store); |
| static DEVICE_ATTR(calibration_values, DEV_MODE_RO, calibration_values_show, NULL); |
| static DEVICE_ATTR(prox_version, DEV_MODE_RO, prox_version_show, NULL); |
| static DEVICE_ATTR(wink, DEV_MODE_RO, wink_show, NULL); |
| static DEVICE_ATTR(wink_enable, DEV_MODE_RW, wink_enable_show, wink_enable_store); |
| static DEVICE_ATTR(pause, DEV_MODE_RW, pause_show, pause_store); |
| static DEVICE_ATTR(wink_flag_enable, DEV_MODE_RW, wink_flag_enable_show, wink_flag_enable_store); |
| static DEVICE_ATTR(wink_min, DEV_MODE_RW, wink_min_show, wink_min_store); |
| static DEVICE_ATTR(wink_max, DEV_MODE_RW, wink_max_show, wink_max_store); |
| static DEVICE_ATTR(detector_gain, DEV_MODE_RW, detector_gain_show, detector_gain_store); |
| static DEVICE_ATTR(detector_bias, DEV_MODE_RW, detector_bias_show, detector_bias_store); |
| static DEVICE_ATTR(mcu_debug, DEV_MODE_RW, mcu_debug_show, mcu_debug_store); |
| static DEVICE_ATTR(mcu_debug16, DEV_MODE_RW, mcu_debug16_show, mcu_debug16_store); |
| static DEVICE_ATTR(error_code, DEV_MODE_RO, error_code_show, NULL); |
| static DEVICE_ATTR(irq, DEV_MODE_RO, irq_show, NULL); |
| static DEVICE_ATTR(flash_status, DEV_MODE_RO, flash_status_show, NULL); |
| static DEVICE_ATTR(sample_count, DEV_MODE_RO, sample_count_show, NULL); |
| static DEVICE_ATTR(disable, DEV_MODE_RW, disable_show, disable_store); |
| static DEVICE_ATTR(debug, DEV_MODE_RW, debug_show, debug_store); |
| static DEVICE_ATTR(frame_count, DEV_MODE_RO, frame_count_show, NULL); |
| static DEVICE_ATTR(timer_count, DEV_MODE_RO, timer_count_show, NULL); |
| static DEVICE_ATTR(irq_timestamp, DEV_MODE_RO, irq_timestamp_show, NULL); |
| static DEVICE_ATTR(last_timestamp, DEV_MODE_RO, last_timestamp_show, NULL); |
| static DEVICE_ATTR(irq_status, DEV_MODE_RO, irq_status_show, NULL); |
| static DEVICE_ATTR(last_irq_status, DEV_MODE_RO, last_irq_status_show, NULL); |
| |
| static struct attribute *attrs[] = { |
| &dev_attr_passthru_enable.attr, |
| &dev_attr_proxraw.attr, |
| &dev_attr_proxmin.attr, |
| &dev_attr_ir.attr, |
| &dev_attr_visible.attr, |
| &dev_attr_ambient_enable.attr, |
| &dev_attr_don_doff_enable.attr, |
| &dev_attr_don_doff.attr, |
| &dev_attr_don_doff_threshold.attr, |
| &dev_attr_don_threshold.attr, |
| &dev_attr_doff_threshold.attr, |
| &dev_attr_don_doff_hysteresis.attr, |
| &dev_attr_led_drive.attr, |
| &dev_attr_calibrate.attr, |
| &dev_attr_calibration_values.attr, |
| &dev_attr_prox_version.attr, |
| &dev_attr_wink.attr, |
| &dev_attr_wink_enable.attr, |
| &dev_attr_pause.attr, |
| &dev_attr_wink_min.attr, |
| &dev_attr_wink_max.attr, |
| &dev_attr_wink_flag_enable.attr, |
| &dev_attr_detector_gain.attr, |
| &dev_attr_detector_bias.attr, |
| &dev_attr_mcu_debug.attr, |
| &dev_attr_mcu_debug16.attr, |
| &dev_attr_error_code.attr, |
| &dev_attr_sample_count.attr, |
| &dev_attr_frame_count.attr, |
| &dev_attr_timer_count.attr, |
| &dev_attr_irq_timestamp.attr, |
| &dev_attr_last_timestamp.attr, |
| &dev_attr_irq_status.attr, |
| &dev_attr_last_irq_status.attr, |
| NULL |
| }; |
| |
| static struct attribute_group attr_group = { |
| .attrs = attrs, |
| }; |
| |
| static struct attribute *bootmode_attrs[] = { |
| &dev_attr_bootloader_version.attr, |
| &dev_attr_version.attr, |
| &dev_attr_driver_version.attr, |
| &dev_attr_driver_flags.attr, |
| &dev_attr_update_fw_enable.attr, |
| &dev_attr_update_fw_data.attr, |
| &dev_attr_flash_status.attr, |
| &dev_attr_irq.attr, |
| &dev_attr_disable.attr, |
| &dev_attr_debug.attr, |
| NULL |
| }; |
| |
| static struct attribute_group bootmode_attr_group = { |
| .attrs = bootmode_attrs, |
| }; |
| |
| static int flush_prox_fifo(struct glasshub_data *glasshub) |
| { |
| if (mutex_lock_interruptible(&glasshub->device_lock)) { |
| dev_err(&glasshub->i2c_client->dev, |
| "%s: Unable to acquire device mutex\n", __func__); |
| return -EAGAIN; |
| } |
| kfifo_reset(&prox_fifo); |
| mutex_unlock(&glasshub->device_lock); |
| return 0; |
| } |
| |
| /* prox sensor open fops */ |
| static int glasshub_open(struct inode *inode, struct file *file) |
| { |
| if (atomic_xchg(&glasshub_opened, 1) != 0) { |
| return -EBUSY; |
| } |
| file->private_data = (void*)glasshub_private; |
| return flush_prox_fifo(glasshub_private); |
| } |
| |
| /* prox sensor release fops */ |
| static int glasshub_release(struct inode *inode, struct file *file) |
| { |
| struct glasshub_data *glasshub = (struct glasshub_data *)file->private_data; |
| if (atomic_xchg(&glasshub_opened, 0) == 0) { |
| dev_err(&glasshub->i2c_client->dev, "%s: Device has not been opened\n", __func__); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| /* prox sensor read function */ |
| static ssize_t glasshub_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) |
| { |
| struct glasshub_data *glasshub = (struct glasshub_data *)file->private_data; |
| unsigned int copied = 0; |
| int rc = 0; |
| |
| /* validate parameters */ |
| count = (count / sizeof(struct glasshub_data_user)) * sizeof(struct glasshub_data_user); |
| if (count < sizeof(struct glasshub_data_user)) { |
| dev_err(&glasshub->i2c_client->dev, |
| "%s: Invalid data size\n", __func__); |
| return -EINVAL; |
| } |
| |
| /* read from FIFO, blocking if no data */ |
| while (1) { |
| if (mutex_lock_interruptible(&glasshub->device_lock)) { |
| dev_err(&glasshub->i2c_client->dev, |
| "%s: Unable to acquire device mutex\n", __func__); |
| return -EAGAIN; |
| } |
| rc = kfifo_to_user(&prox_fifo, buf, count, &copied); |
| if ((rc < 0) || (copied > 0)) break; |
| mutex_unlock(&glasshub->device_lock); |
| rc = wait_event_interruptible(prox_read_wait, !kfifo_is_empty(&prox_fifo)); |
| } |
| |
| mutex_unlock(&glasshub->device_lock); |
| return copied ? copied : rc; |
| } |
| |
| static loff_t glasshub_llseek(struct file *file, loff_t offset, int whence) |
| { |
| if ((offset == 0) && (whence == SEEK_END)) { |
| struct glasshub_data *glasshub = (struct glasshub_data *)file->private_data; |
| return flush_prox_fifo(glasshub); |
| } |
| return -EINVAL; |
| } |
| |
| static unsigned int glasshub_poll(struct file *file, struct poll_table_struct *poll_table) |
| { |
| struct glasshub_data *glasshub = (struct glasshub_data *)file->private_data; |
| unsigned int ret = 0; |
| |
| if (mutex_lock_interruptible(&glasshub->device_lock)) { |
| dev_err(&glasshub->i2c_client->dev, |
| "%s: Unable to acquire device mutex\n", __func__); |
| ret |= POLLERR; |
| goto Exit; |
| } |
| |
| if (kfifo_len(&prox_fifo)) { |
| ret |= POLLIN|POLLRDNORM; |
| } else { |
| poll_wait(file, &prox_read_wait, poll_table); |
| } |
| mutex_unlock(&glasshub->device_lock); |
| |
| Exit: |
| return ret; |
| } |
| |
| static const struct file_operations glasshub_fops = { |
| .owner = THIS_MODULE, |
| .open = glasshub_open, |
| .release = glasshub_release, |
| .read = glasshub_read, |
| .llseek = glasshub_llseek, |
| .poll = glasshub_poll, |
| }; |
| |
| struct miscdevice glasshub_misc = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "glasshub", |
| .fops = &glasshub_fops |
| }; |
| |
| /* register device files */ |
| static int register_device_files(struct glasshub_data *glasshub) |
| { |
| int rc = 0; |
| unsigned app_version; |
| |
| /* if special boot flasher, don't register sysfs files */ |
| if (glasshub->app_version_major == BOOTLOADER_FLASHER) return rc; |
| |
| /* check app code version */ |
| app_version = ((unsigned) glasshub->app_version_major << 8) | glasshub->app_version_minor; |
| |
| /* make sure we have a supported firmware build on the MCU */ |
| if (app_version < MINIMUM_MCU_VERSION) { |
| dev_warn(&glasshub->i2c_client->dev, |
| "%s: WARNING: MCU application code is down-rev: %u.%u\n", |
| __FUNCTION__, |
| glasshub->app_version_major, |
| glasshub->app_version_minor); |
| dev_warn(&glasshub->i2c_client->dev, |
| "%s: All functions except firmware update are disabled\n", |
| __FUNCTION__); |
| |
| /* set device disable bit */ |
| set_bit(FLAG_DEVICE_DISABLED, &glasshub->flags); |
| } |
| |
| /* warn if experimental version */ |
| if (app_version >= EXPERIMENTAL_MCU_VERSION) { |
| dev_warn(&glasshub->i2c_client->dev, |
| "%s: WARNING: MCU application code is experimental version: %u.%u\n", |
| __FUNCTION__, |
| glasshub->app_version_major, |
| glasshub->app_version_minor); |
| } |
| |
| /* are sysfs files already created? */ |
| if (test_and_set_bit(FLAG_SYSFS_CREATED, &glasshub->flags)) goto Exit; |
| |
| /* create attributes */ |
| rc = sysfs_create_group(&glasshub->i2c_client->dev.kobj, &attr_group); |
| if (rc) { |
| dev_err(&glasshub->i2c_client->dev, "%s Unable to create sysfs class files\n", |
| __FUNCTION__); |
| goto Exit; |
| } |
| |
| /* register misc driver for prox data */ |
| rc = misc_register(&glasshub_misc); |
| if (rc) { |
| dev_err(&glasshub->i2c_client->dev, "%s Unable to create misc driver\n", |
| __FUNCTION__); |
| goto Exit; |
| } |
| |
| Exit: |
| return rc; |
| } |
| |
| static int glasshub_setup(struct glasshub_data *glasshub) { |
| int rc = 0; |
| uint8_t buffer; |
| |
| /* force glass hub reset */ |
| rc = reset_device_l(glasshub, 1); |
| |
| /* get bootloader version */ |
| buffer = CMD_BOOTLOADER_VERSION; |
| rc = _i2c_read(glasshub, &buffer, sizeof(buffer), &buffer, sizeof(buffer)); |
| if (rc) goto err_out; |
| glasshub->bootloader_version = buffer; |
| |
| /* get app version number */ |
| rc = get_app_version_l(glasshub); |
| if (rc) goto err_out; |
| |
| /* check bootloader version */ |
| /* TODO: Abort on anything less than V3 once we retire Joey devices */ |
| if (glasshub->bootloader_version < 3) { |
| dev_info(&glasshub->i2c_client->dev, |
| "%s: WARNING: MCU bootloader is down-rev: %u\n", |
| __FUNCTION__, glasshub->bootloader_version); |
| |
| /* this version is completely borked */ |
| if (glasshub->bootloader_version == 2) { |
| rc = -EIO; |
| goto err_out; |
| } |
| } |
| |
| /* request IRQ */ |
| rc = request_threaded_irq(glasshub->pdata->irq, glasshub_irq_handler, |
| glasshub_threaded_irq_handler, IRQF_TRIGGER_FALLING, |
| "glasshub_irq", glasshub); |
| if (rc) { |
| dev_err(&glasshub->i2c_client->dev, "%s request_threaded_irq failed\n", |
| __FUNCTION__); |
| } |
| |
| /* Allow this interrupt to wake the system */ |
| rc = irq_set_irq_wake(glasshub->pdata->irq, 1); |
| if (rc) { |
| dev_err(&glasshub->i2c_client->dev, "%s irq_set_irq_wake failed\n", __FUNCTION__); |
| } |
| |
| err_out: |
| return rc; |
| } |
| |
| static int __devinit glasshub_probe(struct i2c_client *i2c_client, |
| const struct i2c_device_id *id) |
| { |
| struct glasshub_data *glasshub = NULL; |
| int rc; |
| |
| dev_info(&i2c_client->dev, "%s: Probing glasshub...\n", __FUNCTION__); |
| glasshub = kzalloc(sizeof(struct glasshub_data), GFP_KERNEL); |
| if (!glasshub) |
| { |
| dev_err(&i2c_client->dev, "%s: Unable to allocate memory for driver structure\n", |
| __FUNCTION__); |
| return -ENOMEM; |
| } |
| |
| /* initialize data structure */ |
| glasshub->i2c_client = i2c_client; |
| glasshub->flags = 0; |
| glasshub->average_delta = PROX_INTERVAL; |
| mutex_init(&glasshub->device_lock); |
| |
| /* Set platform defaults */ |
| glasshub->pdata = (const struct glasshub_platform_data *)i2c_client->dev.platform_data; |
| if (!glasshub->pdata) { |
| dev_err(&i2c_client->dev, "%s: Platform data has not been set\n", __FUNCTION__); |
| goto err_out; |
| } |
| |
| /* setup the device */ |
| if (glasshub_setup(glasshub)) { |
| goto err_out; |
| } |
| |
| /* store driver data into device private structure */ |
| dev_set_drvdata(&i2c_client->dev, glasshub); |
| glasshub_private = glasshub; |
| |
| /* initialize prox data KFIFO */ |
| INIT_KFIFO(prox_fifo); |
| |
| /* create bootmode sysfs files */ |
| rc = sysfs_create_group(&glasshub->i2c_client->dev.kobj, &bootmode_attr_group); |
| if (rc) { |
| dev_err(&glasshub->i2c_client->dev, "%s Unable to create sysfs class files\n", |
| __FUNCTION__); |
| goto err_out; |
| } |
| |
| /* create sysfs files unless running the special bootflasher app */ |
| if (glasshub->app_version_major != BOOTLOADER_FLASHER) { |
| rc = register_device_files(glasshub); |
| if (rc) { |
| dev_err(&glasshub->i2c_client->dev, "%s Unable to create sysfs class files\n", |
| __FUNCTION__); |
| goto err_out; |
| } |
| } |
| dev_info(&i2c_client->dev, "%s: Probe successful\n", __FUNCTION__); |
| return 0; |
| |
| err_out: |
| dev_err(&i2c_client->dev, "%s: Probe failed\n", __FUNCTION__); |
| kfree(glasshub); |
| return -ENODEV; |
| } |
| |
| static const struct i2c_device_id glasshub_id[] = { |
| { DEVICE_NAME, 0 }, |
| }; |
| |
| static struct i2c_driver glasshub_driver = { |
| .probe = glasshub_probe, |
| .id_table = glasshub_id, |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = DEVICE_NAME, |
| }, |
| }; |
| |
| static int __init glasshub_init(void) |
| { |
| return i2c_add_driver(&glasshub_driver); |
| } |
| |
| static void __exit glasshub_exit(void) |
| { |
| i2c_del_driver(&glasshub_driver); |
| } |
| |
| module_init(glasshub_init); |
| module_exit(glasshub_exit); |
| |
| MODULE_AUTHOR("davidsparks@google.com"); |
| MODULE_DESCRIPTION("Glass Hub Driver"); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| MODULE_VERSION(DRIVER_VERSION); |