blob: fc6c7360233cb5d7a97035cc621565132dacb4e6 [file] [log] [blame]
/* 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);