/**************************************************************************** * boards/arm/cxd56xx/drivers/sensors/apds9930_scu.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_APDS9930_SCU) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define APDS9930_ADDR 0x39 /* I2C Slave Address */ #define APDS9930_DEVICEID 0x39 /* Device ID */ #define APDS9930_ALS_BYTESPERSAMPLE 4 #define APDS9930_PS_BYTESPERSAMPLE 2 #define APDS9930_ELEMENTSIZE 0 /* APDS9930 Registers */ #define APDS9930_CMD_TYPE_REPEAT 0x80 #define APDS9930_CMD_TYPE_AUTOINC 0xA0 #define APDS9930_CMD_TYPE_PSINTCLR 0xE5 #define APDS9930_ENABLE 0x00 #define APDS9930_ATIME 0x01 #define APDS9930_PTIME 0x02 #define APDS9930_WTIME 0x03 #define APDS9930_AILTL 0x04 #define APDS9930_AILTH 0x05 #define APDS9930_AIHTL 0x06 #define APDS9930_AIHTH 0x07 #define APDS9930_PILTL 0x08 #define APDS9930_PILTH 0x09 #define APDS9930_PIHTL 0x0A #define APDS9930_PIHTH 0x0B #define APDS9930_PERS 0x0C #define APDS9930_CONFIG 0x0D #define APDS9930_PPULSE 0x0E #define APDS9930_CONTROL 0x0F #define APDS9930_ID 0x12 #define APDS9930_STATUS 0x13 #define APDS9930_CH0DATAL 0x14 #define APDS9930_CH0DATAH 0x15 #define APDS9930_CH1DATAL 0x16 #define APDS9930_CH1DATAH 0x17 #define APDS9930_PDATAL 0x18 #define APDS9930_PDATAH 0x19 #define APDS9930_POFFSET 0x1E /* Register ENABLE */ #define APDS9930_ENABLE_SAI (1 << 6) #define APDS9930_ENABLE_PIEN (1 << 5) #define APDS9930_ENABLE_AIEN (1 << 4) #define APDS9930_ENABLE_WEN (1 << 3) #define APDS9930_ENABLE_PEN (1 << 2) #define APDS9930_ENABLE_AEN (1 << 1) #define APDS9930_ENABLE_PON (1 << 0) #define APDS9930_ENABLE_STANDBY 0x00 /* Register ATIME */ #define APDS9930_ATIME_VAL 0xff /* Register PTIME */ #define APDS9930_PTIME_VAL 0xff /* Register WTIME */ #define APDS9930_WTIME_VAL 0xff /* Register PPULSE */ #define APDS9930_PPULSE_VAL 0x01 /* Register CONTROL */ #define APDS9930_CONTROL_PDRIVE_100MA (0 << 6) #define APDS9930_CONTROL_PDIODE_CH1 (2 << 4) #define APDS9930_CONTROL_PGAIN_X1 (0 << 2) #define APDS9930_CONTROL_AGAIN_X1 (0 << 0) /* PS threshold default value */ #define APDS9930_PS_HT_DEFAULT 0x0300 #define APDS9930_PS_LT_DEFAULT 0x0300 /* Register PERS */ #define APDS9930_PERS_PS_DEFAULT 2 #define SETENABLE_TYPE_PS 0 #define SETENABLE_TYPE_ALS 1 #ifndef itemsof # define itemsof(array) (sizeof(array)/sizeof(array[0])) #endif /**************************************************************************** * Private Type Definitions ****************************************************************************/ /* Structure for apds9930 device */ struct apds9930_dev_s { struct i2c_master_s *i2c; /* I2C interface */ uint8_t addr; /* I2C address */ int port; /* I2C port */ struct seq_s *seq; /* Sequencer instance */ int minor; /* Minor device number */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Character driver methods */ static int apds9930_open_als(struct file *filep); static int apds9930_open_ps(struct file *filep); static int apds9930_close_als(struct file *filep); static int apds9930_close_ps(struct file *filep); static ssize_t apds9930_read_als(struct file *filep, char *buffer, size_t buflen); static ssize_t apds9930_read_ps(struct file *filep, char *buffer, size_t buflen); static ssize_t apds9930_write(struct file *filep, const char *buffer, size_t buflen); static int apds9930_ioctl_als(struct file *filep, int cmd, unsigned long arg); static int apds9930_ioctl_ps(struct file *filep, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ /* Ambient light sensor */ static const struct file_operations g_apds9930alsfops = { apds9930_open_als, /* open */ apds9930_close_als, /* close */ apds9930_read_als, /* read */ apds9930_write, /* write */ NULL, /* seek */ apds9930_ioctl_als, /* ioctl */ }; /* Proximity sensor */ static const struct file_operations g_apds9930psfops = { apds9930_open_ps, /* open */ apds9930_close_ps, /* close */ apds9930_read_ps, /* read */ apds9930_write, /* write */ NULL, /* seek */ apds9930_ioctl_ps, /* ioctl */ }; /* SCU instructions for pick ambient light sensing data. */ static const uint16_t g_apds9930alsinst[] = { SCU_INST_SEND(APDS9930_CMD_TYPE_AUTOINC | APDS9930_CH0DATAL), SCU_INST_RECV(APDS9930_ALS_BYTESPERSAMPLE) | SCU_INST_LAST, }; #ifndef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT /* SCU instructions for pick proximity sensing data. */ static const uint16_t g_apds9930psinst[] = { SCU_INST_SEND(APDS9930_CMD_TYPE_AUTOINC | APDS9930_PDATAL), SCU_INST_RECV(APDS9930_PS_BYTESPERSAMPLE) | SCU_INST_LAST, }; #endif /* Reference count */ static int g_als_refcnt = 0; #ifndef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT static int g_ps_refcnt = 0; #endif /* Sequencer instance */ static struct seq_s *g_als_seq = NULL; #ifndef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT static struct seq_s *g_ps_seq = NULL; #endif #ifdef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT /* Proximity interrupt config */ static uint16_t g_ps_lthreshold = APDS9930_PS_LT_DEFAULT; static uint16_t g_ps_hthreshold = APDS9930_PS_HT_DEFAULT; static uint8_t g_ps_persistence = APDS9930_PERS_PS_DEFAULT << 4; #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: apds9930_getreg8 * * Description: * Read from an 8-bit APDS9930 register * ****************************************************************************/ static uint8_t apds9930_getreg8(struct apds9930_dev_s *priv, uint8_t regaddr) { uint8_t regval = 0; uint16_t inst[2]; /* Send register to read and get the next byte */ inst[0] = SCU_INST_SEND(APDS9930_CMD_TYPE_REPEAT | regaddr); inst[1] = SCU_INST_RECV(1) | SCU_INST_LAST; scu_i2ctransfer(priv->port, priv->addr, inst, 2, ®val, 1); return regval; } /**************************************************************************** * Name: apds9930_putreg8 * * Description: * Write to an 8-bit APDS9930 register * ****************************************************************************/ static void apds9930_putreg8(struct apds9930_dev_s *priv, uint8_t regaddr, uint8_t regval) { uint16_t inst[2]; /* Send register address and set the value */ inst[0] = SCU_INST_SEND(APDS9930_CMD_TYPE_REPEAT | regaddr); inst[1] = SCU_INST_SEND(regval) | SCU_INST_LAST; scu_i2ctransfer(priv->port, priv->addr, inst, 2, NULL, 0); } #ifdef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT /**************************************************************************** * Name: apds9930_getreg16 * * Description: * Read from an 16-bit APDS9930 register * ****************************************************************************/ static uint16_t apds9930_getreg16(struct apds9930_dev_s *priv, uint8_t regaddr) { uint16_t regval = 0; uint16_t inst[2]; /* Send register to read and get the next byte */ inst[0] = SCU_INST_SEND(APDS9930_CMD_TYPE_AUTOINC | regaddr); inst[1] = SCU_INST_RECV(2) | SCU_INST_LAST; scu_i2ctransfer(priv->port, priv->addr, inst, 2, (uint8_t *)®val, 2); return regval; } /**************************************************************************** * Name: apds9930_putreg16 * * Description: * Write to an 16-bit APDS9930 register * ****************************************************************************/ static void apds9930_putreg16(struct apds9930_dev_s *priv, uint8_t regaddr, uint16_t regval) { uint16_t inst[3]; /* Send register address and set the value */ inst[0] = SCU_INST_SEND(APDS9930_CMD_TYPE_AUTOINC | regaddr); inst[1] = SCU_INST_SEND((uint8_t)(regval & 0xff)); inst[2] = SCU_INST_SEND((uint8_t)(regval >> 8)) | SCU_INST_LAST; scu_i2ctransfer(priv->port, priv->addr, inst, 3, NULL, 0); } /**************************************************************************** * Name: apds9930_intclr * * Description: * APDS9930 Interrupt clear * ****************************************************************************/ static void apds9930_intclr(struct apds9930_dev_s *priv) { uint16_t inst = SCU_INST_SEND(APDS9930_CMD_TYPE_PSINTCLR) | SCU_INST_LAST; /* Send command */ scu_i2ctransfer(priv->port, priv->addr, &inst, 1, NULL, 0); } #endif /**************************************************************************** * Name: apds9930_checkid * * Description: * Read and verify the APDS9930 chip ID * ****************************************************************************/ static int apds9930_checkid(struct apds9930_dev_s *priv) { uint8_t id; /* Read Device ID */ id = apds9930_getreg8(priv, APDS9930_ID); if (id != APDS9930_DEVICEID) { /* Device ID is not Correct */ snerr("Wrong Device ID! %02x (Exp:%02x)\n", id, APDS9930_DEVICEID); return -ENODEV; } return OK; } /**************************************************************************** * Name: apds9930_setenable * * Description: * Set MODE_CONTROL register * ****************************************************************************/ static void apds9930_setenable(struct apds9930_dev_s *priv, uint8_t type, bool enable) { uint8_t val; uint8_t checkbit; uint8_t setbit; irqstate_t flags; if (type == SETENABLE_TYPE_PS) { checkbit = APDS9930_ENABLE_AEN; setbit = APDS9930_ENABLE_PEN; } else { checkbit = APDS9930_ENABLE_PEN; setbit = APDS9930_ENABLE_AEN; } flags = enter_critical_section(); val = apds9930_getreg8(priv, APDS9930_ENABLE); if (val & checkbit) { if (enable) { val |= setbit; #ifdef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT if (type == SETENABLE_TYPE_PS) { val |= APDS9930_ENABLE_PIEN; } #endif } else { val &= ~setbit; #ifdef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT if (type == SETENABLE_TYPE_PS) { val &= ~APDS9930_ENABLE_PIEN; } #endif } } else { if (enable) { val = APDS9930_ENABLE_PON | APDS9930_ENABLE_WEN | setbit; #ifdef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT if (type == SETENABLE_TYPE_PS) { val |= APDS9930_ENABLE_PIEN; } #endif } else { val = APDS9930_ENABLE_STANDBY; } } apds9930_putreg8(priv, APDS9930_ENABLE, val); leave_critical_section(flags); } /**************************************************************************** * Name: apds9930als_seqinit * * Description: * Initialize SCU sequencer. * ****************************************************************************/ static int apds9930als_seqinit(struct apds9930_dev_s *priv) { DEBUGASSERT(g_als_seq == NULL); /* Open sequencer */ g_als_seq = seq_open(SEQ_TYPE_NORMAL, SCU_BUS_I2C0); if (!g_als_seq) { return -ENOENT; } priv->seq = g_als_seq; seq_setaddress(priv->seq, priv->addr); /* Set instruction and sample data information to sequencer */ seq_setinstruction(priv->seq, g_apds9930alsinst, itemsof(g_apds9930alsinst)); seq_setsample(priv->seq, APDS9930_ALS_BYTESPERSAMPLE, 0, APDS9930_ELEMENTSIZE, false); return OK; } #ifndef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT /**************************************************************************** * Name: apds9930ps_seqinit * * Description: * Initialize SCU sequencer. * ****************************************************************************/ static int apds9930ps_seqinit(struct apds9930_dev_s *priv) { DEBUGASSERT(g_ps_seq == NULL); /* Open sequencer */ g_ps_seq = seq_open(SEQ_TYPE_NORMAL, SCU_BUS_I2C0); if (!g_ps_seq) { return -ENOENT; } priv->seq = g_ps_seq; seq_setaddress(priv->seq, priv->addr); /* Set instruction and sample data information to sequencer */ seq_setinstruction(priv->seq, g_apds9930psinst, itemsof(g_apds9930psinst)); seq_setsample(priv->seq, APDS9930_PS_BYTESPERSAMPLE, 0, APDS9930_ELEMENTSIZE, false); return OK; } #endif /**************************************************************************** * Name: apds9930_open_als * * Description: * This function is called whenever the APDS9930 device is opened. * ****************************************************************************/ static int apds9930_open_als(struct file *filep) { struct inode *inode = filep->f_inode; struct apds9930_dev_s *priv = inode->i_private; if (g_als_refcnt == 0) { int ret; ret = apds9930als_seqinit(priv); if (ret < 0) { return ret; } apds9930_setenable(priv, SETENABLE_TYPE_ALS, true); } else { /* Set existing sequencer */ priv->seq = g_als_seq; } g_als_refcnt++; return OK; } /**************************************************************************** * Name: apds9930_open_ps * * Description: * This function is called whenever the APDS9930 device is opened. * ****************************************************************************/ static int apds9930_open_ps(struct file *filep) { #ifndef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT struct inode *inode = filep->f_inode; struct apds9930_dev_s *priv = inode->i_private; if (g_ps_refcnt == 0) { int ret; ret = apds9930ps_seqinit(priv); if (ret < 0) { return ret; } apds9930_setenable(priv, SETENABLE_TYPE_PS, true); } else { /* Set existing sequencer */ priv->seq = g_ps_seq; } g_ps_refcnt++; #endif return OK; } /**************************************************************************** * Name: apds9930_close_als * * Description: * This routine is called when the APDS9930 device is closed. * ****************************************************************************/ static int apds9930_close_als(struct file *filep) { struct inode *inode = filep->f_inode; struct apds9930_dev_s *priv = inode->i_private; g_als_refcnt--; seq_ioctl(priv->seq, priv->minor, SCUIOC_STOP, 0); if (g_als_refcnt == 0) { apds9930_setenable(priv, SETENABLE_TYPE_ALS, false); seq_close(g_als_seq); g_als_seq = NULL; } else { seq_ioctl(priv->seq, priv->minor, SCUIOC_FREEFIFO, 0); } return OK; } /**************************************************************************** * Name: apds9930_close_ps * * Description: * This routine is called when the APDS9930 device is closed. * ****************************************************************************/ static int apds9930_close_ps(struct file *filep) { #ifndef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT struct inode *inode = filep->f_inode; struct apds9930_dev_s *priv = inode->i_private; g_ps_refcnt--; seq_ioctl(priv->seq, priv->minor, SCUIOC_STOP, 0); if (g_ps_refcnt == 0) { apds9930_setenable(priv, SETENABLE_TYPE_PS, false); if (g_ps_seq) { seq_close(g_ps_seq); g_ps_seq = NULL; } } else { seq_ioctl(priv->seq, priv->minor, SCUIOC_FREEFIFO, 0); } #endif return OK; } /**************************************************************************** * Name: apds9930_read_als ****************************************************************************/ static ssize_t apds9930_read_als(struct file *filep, char *buffer, size_t len) { struct inode *inode = filep->f_inode; struct apds9930_dev_s *priv = inode->i_private; len = len / APDS9930_ALS_BYTESPERSAMPLE * APDS9930_ALS_BYTESPERSAMPLE; len = seq_read(priv->seq, priv->minor, buffer, len); return len; } /**************************************************************************** * Name: apds9930_read_ps ****************************************************************************/ static ssize_t apds9930_read_ps(struct file *filep, char *buffer, size_t len) { struct inode *inode = filep->f_inode; struct apds9930_dev_s *priv = inode->i_private; len = len / APDS9930_PS_BYTESPERSAMPLE * APDS9930_PS_BYTESPERSAMPLE; #ifdef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT if (len) { len = APDS9930_PS_BYTESPERSAMPLE; *(uint16_t *)buffer = apds9930_getreg16(priv, APDS9930_PDATAL); } #else len = seq_read(priv->seq, priv->minor, buffer, len); #endif return len; } /**************************************************************************** * Name: apds9930_write ****************************************************************************/ static ssize_t apds9930_write(struct file *filep, const char *buffer, size_t buflen) { return -ENOSYS; } /**************************************************************************** * Name: apds9930_ioctl_als ****************************************************************************/ static int apds9930_ioctl_als(struct file *filep, int cmd, unsigned long arg) { struct inode *inode = filep->f_inode; struct apds9930_dev_s *priv = inode->i_private; int ret = OK; switch (cmd) { default: { if (_SCUIOCVALID(cmd)) { /* Redirect SCU commands */ ret = seq_ioctl(priv->seq, priv->minor, cmd, arg); } else { snerr("Unrecognized cmd: %d\n", cmd); ret = - ENOTTY; } } break; } return ret; } /**************************************************************************** * Name: apds9930_ioctl_ps ****************************************************************************/ static int apds9930_ioctl_ps(struct file *filep, int cmd, unsigned long arg) { struct inode *inode = filep->f_inode; struct apds9930_dev_s *priv = inode->i_private; int ret = OK; switch (cmd) { #ifdef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT case SNIOC_SETPSLTHRESHOLD: { g_ps_lthreshold = (uint16_t)arg; } break; case SNIOC_SETPSHTHRESHOLD: { g_ps_hthreshold = (uint16_t)arg; } break; case SNIOC_SETPSPERSISTENCE: { g_ps_persistence = ((uint8_t)arg & 0xf) << 4; } break; case SNIOC_STARTPSMEASUREMENT: { apds9930_putreg16(priv, APDS9930_PILTL, g_ps_lthreshold); apds9930_putreg16(priv, APDS9930_PIHTL, g_ps_hthreshold); apds9930_putreg8(priv, APDS9930_PERS, g_ps_persistence); apds9930_setenable(priv, SETENABLE_TYPE_PS, true); } break; case SNIOC_STOPPSMEASUREMENT: { apds9930_setenable(priv, SETENABLE_TYPE_PS, false); } break; case SNIOC_GETINTSTATUS: { uint8_t intstatus = apds9930_getreg8(priv, APDS9930_STATUS); *(uint8_t *)(uintptr_t)arg = intstatus; sninfo("Get proximity IntStatus 0x%02x\n", intstatus); } break; case SNIOC_CLEARPSINT: { apds9930_intclr(priv); } break; #endif default: { if (_SCUIOCVALID(cmd)) { #ifndef CONFIG_SENSORS_APDS9930_PROXIMITY_INTERRUPT /* Redirect SCU commands */ ret = seq_ioctl(priv->seq, priv->minor, cmd, arg); #else snerr("Unregistered SCU sequencer cmd: %d\n", cmd); ret = - ENOTTY; #endif } else { snerr("Unrecognized cmd: %d\n", cmd); ret = - ENOTTY; } } break; } return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: apds9930_init * * Description: * Initialize the APDS9930 device * * Input Parameters: * i2c - An instance of the I2C interface to use to communicate with * APDS9930 * port - I2C port (0 or 1) * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ int apds9930_init(struct i2c_master_s *i2c, int port) { struct apds9930_dev_s tmp; struct apds9930_dev_s *priv = &tmp; int ret; uint8_t val; /* Setup temporary device structure for initialization */ priv->i2c = i2c; priv->addr = APDS9930_ADDR; priv->port = port; /* Check Device ID */ ret = apds9930_checkid(priv); if (ret < 0) { snerr("Failed to register driver: %d\n", ret); return ret; } /* Disable and power down */ val = APDS9930_ENABLE_STANDBY; apds9930_putreg8(priv, APDS9930_ENABLE, val); /* ALS ADC time */ val = APDS9930_ATIME_VAL; apds9930_putreg8(priv, APDS9930_ATIME, val); /* PS ADC time */ val = APDS9930_PTIME_VAL; apds9930_putreg8(priv, APDS9930_PTIME, val); /* Wait time */ val = APDS9930_WTIME_VAL; apds9930_putreg8(priv, APDS9930_WTIME, val); /* PS Pulse count */ val = APDS9930_PPULSE_VAL; apds9930_putreg8(priv, APDS9930_PPULSE, val); /* Control */ val = APDS9930_CONTROL_PDRIVE_100MA | APDS9930_CONTROL_PDIODE_CH1 | APDS9930_CONTROL_PGAIN_X1 | APDS9930_CONTROL_AGAIN_X1; apds9930_putreg8(priv, APDS9930_CONTROL, val); return OK; } /**************************************************************************** * Name: apds9930als_register * * Description: * Register the APDS9930 ambient light sensor character device as 'devpath' * * Input Parameters: * devpath - The full path to the driver to register. E.g., "/dev/light0" * minor - minor device number * i2c - An instance of the I2C interface to use to communicate with * APDS9930 * port - I2C port (0 or 1) * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ int apds9930als_register(const char *devpath, int minor, struct i2c_master_s *i2c, int port) { struct apds9930_dev_s *priv; char path[16]; int ret; /* Initialize the APDS9930 device structure */ priv = (struct apds9930_dev_s *) kmm_malloc(sizeof(struct apds9930_dev_s)); if (!priv) { snerr("Failed to allocate instance\n"); return -ENOMEM; } priv->i2c = i2c; priv->addr = APDS9930_ADDR; priv->port = port; priv->seq = NULL; priv->minor = minor; /* Register the character driver */ snprintf(path, sizeof(path), "%s%d", devpath, minor); ret = register_driver(path, &g_apds9930alsfops, 0666, priv); if (ret < 0) { snerr("Failed to register driver: %d\n", ret); kmm_free(priv); } return ret; } /**************************************************************************** * Name: apds9930ps_register * * Description: * Register the APDS9930 proximity sensor character device as 'devpath' * * Input Parameters: * devpath - The full path to the driver to register. E.g., "/dev/proxim0" * minor - minor device number * i2c - An instance of the I2C interface to use to communicate with * APDS9930 * port - I2C port (0 or 1) * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ int apds9930ps_register(const char *devpath, int minor, struct i2c_master_s *i2c, int port) { struct apds9930_dev_s *priv; char path[16]; int ret; /* Initialize the APDS9930 device structure */ priv = (struct apds9930_dev_s *) kmm_malloc(sizeof(struct apds9930_dev_s)); if (!priv) { snerr("Failed to allocate instance\n"); return -ENOMEM; } priv->i2c = i2c; priv->addr = APDS9930_ADDR; priv->port = port; priv->seq = NULL; priv->minor = minor; /* Register the character driver */ snprintf(path, sizeof(path), "%s%d", devpath, minor); ret = register_driver(path, &g_apds9930psfops, 0666, priv); if (ret < 0) { snerr("Failed to register driver: %d\n", ret); kmm_free(priv); } sninfo("APDS9930 proximity sensor driver loaded successfully!\n"); return ret; } #endif /* CONFIG_I2C && CONFIG_SENSORS_APDS9930_SCU */