NetBSD/sys/dev/i2c/i2c.c

842 lines
19 KiB
C
Raw Permalink Normal View History

/* $NetBSD: i2c.c,v 1.90 2022/10/24 10:17:40 riastradh Exp $ */
/*
* Copyright (c) 2003 Wasabi Systems, Inc.
* All rights reserved.
*
* Written by Jason R. Thorpe for Wasabi Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed for the NetBSD Project by
* Wasabi Systems, Inc.
* 4. The name of Wasabi Systems, Inc. may not be used to endorse
* or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef _KERNEL_OPT
#include "opt_i2c.h"
#include "opt_fdt.h"
#ifdef FDT
#define I2C_USE_FDT
#endif /* FDT */
#if defined(__aarch64__) || defined(__amd64__)
#include "acpica.h"
#if NACPICA > 0
#define I2C_USE_ACPI
#endif /* NACPICA > 0 */
#endif /* __aarch64__ || __amd64__ */
#endif /* _KERNEL_OPT */
2007-12-11 15:09:21 +03:00
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: i2c.c,v 1.90 2022/10/24 10:17:40 riastradh Exp $");
2007-12-11 15:09:21 +03:00
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/event.h>
#include <sys/conf.h>
#include <sys/malloc.h>
#include <sys/kmem.h>
#include <sys/kthread.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/fcntl.h>
2011-10-02 15:38:48 +04:00
#include <sys/module.h>
#include <sys/once.h>
#include <sys/mutex.h>
#ifdef I2C_USE_ACPI
#include <dev/acpi/acpivar.h>
#endif /* I2C_USE_ACPI */
#ifdef I2C_USE_FDT
#include <dev/fdt/fdtvar.h>
#endif /* I2C_USE_FDT */
#include <dev/i2c/i2cvar.h>
#include "ioconf.h"
#include "locators.h"
#ifndef I2C_MAX_ADDR
#define I2C_MAX_ADDR 0x3ff /* 10-bit address, max */
#endif
struct iic_softc {
2018-06-07 16:30:49 +03:00
device_t sc_dev;
i2c_tag_t sc_tag;
device_t sc_devices[I2C_MAX_ADDR + 1];
};
static dev_type_open(iic_open);
static dev_type_close(iic_close);
static dev_type_ioctl(iic_ioctl);
int iic_init(void);
kmutex_t iic_mtx;
int iic_refcnt;
ONCE_DECL(iic_once);
const struct cdevsw iic_cdevsw = {
.d_open = iic_open,
.d_close = iic_close,
.d_read = noread,
.d_write = nowrite,
.d_ioctl = iic_ioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = nommap,
.d_kqfilter = nokqfilter,
.d_discard = nodiscard,
.d_flag = D_OTHER
};
static void iic_fill_compat(struct i2c_attach_args*, const char*,
size_t, char **);
static int
iic_print_direct(void *aux, const char *pnp)
{
struct i2c_attach_args *ia = aux;
if (pnp != NULL)
aprint_normal("%s%s%s%s at %s addr 0x%02x",
ia->ia_name ? ia->ia_name : "(unknown)",
ia->ia_ncompat ? " (" : "",
ia->ia_ncompat ? ia->ia_compat[0] : "",
ia->ia_ncompat ? ")" : "",
pnp, ia->ia_addr);
else
aprint_normal(" addr 0x%02x", ia->ia_addr);
return UNCONF;
}
static int
iic_print(void *aux, const char *pnp)
{
struct i2c_attach_args *ia = aux;
if (ia->ia_addr != (i2c_addr_t)IICCF_ADDR_DEFAULT)
aprint_normal(" addr 0x%x", ia->ia_addr);
2011-10-02 16:25:40 +04:00
return UNCONF;
}
2018-06-07 16:30:49 +03:00
static bool
iic_is_special_address(i2c_addr_t addr)
{
/*
* See: https://www.i2c-bus.org/addressing/
*/
/* General Call (read) / Start Byte (write) */
if (addr == 0x00)
return (true);
/* CBUS Addresses */
if (addr == 0x01)
return (true);
/* Reserved for Different Bus Formats */
if (addr == 0x02)
return (true);
/* Reserved for future purposes */
if (addr == 0x03)
return (true);
/* High Speed Master Code */
if ((addr & 0x7c) == 0x04)
return (true);
/* 10-bit Slave Addressing prefix */
if ((addr & 0x7c) == 0x78)
return (true);
/* Reserved for future purposes */
if ((addr & 0x7c) == 0x7c)
return (true);
return (false);
}
static int
iic_probe_none(struct iic_softc *sc,
const struct i2c_attach_args *ia, int flags)
{
return (0);
}
static int
iic_probe_smbus_quick_write(struct iic_softc *sc,
const struct i2c_attach_args *ia, int flags)
{
int error;
if ((error = iic_acquire_bus(ia->ia_tag, flags)) == 0) {
error = iic_smbus_quick_write(ia->ia_tag, ia->ia_addr, flags);
}
(void) iic_release_bus(ia->ia_tag, flags);
return (error);
}
static int
iic_probe_smbus_receive_byte(struct iic_softc *sc,
const struct i2c_attach_args *ia, int flags)
{
int error;
if ((error = iic_acquire_bus(ia->ia_tag, flags)) == 0) {
uint8_t dummy;
error = iic_smbus_receive_byte(ia->ia_tag, ia->ia_addr,
&dummy, flags);
}
(void) iic_release_bus(ia->ia_tag, flags);
return (error);
}
static bool
2020-07-07 19:14:23 +03:00
iic_indirect_driver_is_permitted(struct iic_softc *sc, cfdata_t cf)
2018-06-07 16:30:49 +03:00
{
prop_object_iterator_t iter;
2020-07-07 19:14:23 +03:00
prop_array_t permitlist;
2018-06-07 16:30:49 +03:00
prop_string_t pstr;
prop_type_t ptype;
bool rv = false;
2020-07-07 19:14:23 +03:00
permitlist = prop_dictionary_get(device_properties(sc->sc_dev),
I2C_PROP_INDIRECT_DEVICE_PERMITLIST);
if (permitlist == NULL) {
/* No permitlist -> everything allowed */
2018-06-07 16:30:49 +03:00
return (true);
}
2020-07-07 19:14:23 +03:00
if ((ptype = prop_object_type(permitlist)) != PROP_TYPE_ARRAY) {
2018-06-07 16:30:49 +03:00
aprint_error_dev(sc->sc_dev,
"invalid property type (%d) for '%s'; must be array (%d)\n",
2020-07-07 19:14:23 +03:00
ptype, I2C_PROP_INDIRECT_DEVICE_PERMITLIST,
PROP_TYPE_ARRAY);
2018-06-07 16:30:49 +03:00
return (false);
}
2020-07-07 19:14:23 +03:00
iter = prop_array_iterator(permitlist);
2018-06-07 16:30:49 +03:00
while ((pstr = prop_object_iterator_next(iter)) != NULL) {
2020-06-11 05:32:06 +03:00
if (prop_string_equals_string(pstr, cf->cf_name)) {
2018-06-07 16:30:49 +03:00
rv = true;
break;
}
}
prop_object_iterator_release(iter);
return (rv);
}
static int
iic_search(device_t parent, cfdata_t cf, const int *ldesc, void *aux)
{
struct iic_softc *sc = device_private(parent);
struct i2c_attach_args ia;
2018-06-07 16:30:49 +03:00
int (*probe_func)(struct iic_softc *,
const struct i2c_attach_args *, int);
prop_string_t pstr;
i2c_addr_t first_addr, last_addr;
/*
2018-06-07 16:30:49 +03:00
* Before we do any more work, consult the allowed-driver
2020-07-07 19:14:23 +03:00
* permit-list for this bus (if any).
*/
2020-07-07 19:14:23 +03:00
if (iic_indirect_driver_is_permitted(sc, cf) == false)
2018-06-07 16:30:49 +03:00
return (0);
/* default to "quick write". */
probe_func = iic_probe_smbus_quick_write;
pstr = prop_dictionary_get(device_properties(sc->sc_dev),
I2C_PROP_INDIRECT_PROBE_STRATEGY);
if (pstr == NULL) {
/* Use the default. */
2020-06-11 05:32:06 +03:00
} else if (prop_string_equals_string(pstr,
2018-06-07 16:30:49 +03:00
I2C_PROBE_STRATEGY_QUICK_WRITE)) {
probe_func = iic_probe_smbus_quick_write;
2020-06-11 05:32:06 +03:00
} else if (prop_string_equals_string(pstr,
2018-06-07 16:30:49 +03:00
I2C_PROBE_STRATEGY_RECEIVE_BYTE)) {
probe_func = iic_probe_smbus_receive_byte;
2020-06-11 05:32:06 +03:00
} else if (prop_string_equals_string(pstr,
2018-06-07 16:30:49 +03:00
I2C_PROBE_STRATEGY_NONE)) {
probe_func = iic_probe_none;
} else {
aprint_error_dev(sc->sc_dev,
"unknown probe strategy '%s'; defaulting to '%s'\n",
2020-06-11 05:32:06 +03:00
prop_string_value(pstr),
2018-06-07 16:30:49 +03:00
I2C_PROBE_STRATEGY_QUICK_WRITE);
/* Use the default. */
}
ia.ia_tag = sc->sc_tag;
ia.ia_name = NULL;
ia.ia_ncompat = 0;
ia.ia_compat = NULL;
ia.ia_prop = NULL;
2018-06-07 16:30:49 +03:00
if (cf->cf_loc[IICCF_ADDR] == IICCF_ADDR_DEFAULT) {
/*
* This particular config directive has
* wildcarded the address, so we will
* scan the entire bus for it.
*/
first_addr = 0;
last_addr = I2C_MAX_ADDR;
} else {
/*
* This config directive hard-wires the i2c
* bus address for the device, so there is
* no need to go poking around at any other
* addresses.
*/
if (cf->cf_loc[IICCF_ADDR] < 0 ||
cf->cf_loc[IICCF_ADDR] > I2C_MAX_ADDR) {
/* Invalid config directive! */
return (0);
}
first_addr = last_addr = cf->cf_loc[IICCF_ADDR];
}
for (ia.ia_addr = first_addr; ia.ia_addr <= last_addr; ia.ia_addr++) {
int error, match_result;
/*
* Skip I2C addresses that are reserved for
* special purposes.
*/
if (iic_is_special_address(ia.ia_addr))
continue;
/*
* Skip addresses where a device is already attached.
*/
if (sc->sc_devices[ia.ia_addr] != NULL)
continue;
2018-06-07 16:30:49 +03:00
/*
* Call the "match" routine for the device. If that
* returns success, then call the probe strategy
* function.
*
* We do it in this order because i2c devices tend
* to be found at a small number of possible addresses
* (e.g. read-time clocks that are only ever found at
* 0x68). This gives the driver a chance to skip any
* address that are not valid for the device, saving
* us from having to poke at the bus to see if anything
* is there.
*/
Merge thorpej-cfargs branch: Simplify and make extensible the config_search() / config_found() / config_attach() interfaces: rather than having different variants for which arguments you want pass along, just have a single call that takes a variadic list of tag-value arguments. Adjust all call sites: - Simplify wherever possible; don't pass along arguments that aren't actually needed. - Don't be explicit about what interface attribute is attaching if the device only has one. (More simplification.) - Add a config_probe() function to be used in indirect configuiration situations, making is visibly easier to see when indirect config is in play, and allowing for future change in semantics. (As of now, this is just a wrapper around config_match(), but that is an implementation detail.) Remove unnecessary or redundant interface attributes where they're not needed. There are currently 5 "cfargs" defined: - CFARG_SUBMATCH (submatch function for direct config) - CFARG_SEARCH (search function for indirect config) - CFARG_IATTR (interface attribte) - CFARG_LOCATORS (locators array) - CFARG_DEVHANDLE (devhandle_t - wraps OFW, ACPI, etc. handles) ...and a sentinel value CFARG_EOL. Add some extra sanity checking to ensure that interface attributes aren't ambiguous. Use CFARG_DEVHANDLE in MI FDT, OFW, and ACPI code, and macppc and shark ports to associate those device handles with device_t instance. This will trickle trough to more places over time (need back-end for pre-OFW Sun OBP; any others?).
2021-04-25 02:36:23 +03:00
match_result = config_probe(parent, cf, &ia);/*XXX*/
2018-06-07 16:30:49 +03:00
if (match_result <= 0)
continue;
/*
* If the quality of the match by the driver was low
* (i.e. matched on being a valid address only, didn't
* perform any hardware probe), invoke our probe routine
* to see if it looks like something is really there.
*/
if (match_result == I2C_MATCH_ADDRESS_ONLY &&
2019-12-23 21:20:02 +03:00
(error = (*probe_func)(sc, &ia, 0)) != 0)
continue;
2018-06-07 16:30:49 +03:00
sc->sc_devices[ia.ia_addr] =
2021-08-07 19:18:40 +03:00
config_attach(parent, cf, &ia, iic_print, CFARGS_NONE);
}
2011-10-02 16:25:40 +04:00
return 0;
}
static void
iic_child_detach(device_t parent, device_t child)
{
struct iic_softc *sc = device_private(parent);
int i;
for (i = 0; i <= I2C_MAX_ADDR; i++)
if (sc->sc_devices[i] == child) {
sc->sc_devices[i] = NULL;
break;
}
}
2011-07-31 19:58:25 +04:00
static int
iic_rescan(device_t self, const char *ifattr, const int *locators)
{
Merge thorpej-cfargs branch: Simplify and make extensible the config_search() / config_found() / config_attach() interfaces: rather than having different variants for which arguments you want pass along, just have a single call that takes a variadic list of tag-value arguments. Adjust all call sites: - Simplify wherever possible; don't pass along arguments that aren't actually needed. - Don't be explicit about what interface attribute is attaching if the device only has one. (More simplification.) - Add a config_probe() function to be used in indirect configuiration situations, making is visibly easier to see when indirect config is in play, and allowing for future change in semantics. (As of now, this is just a wrapper around config_match(), but that is an implementation detail.) Remove unnecessary or redundant interface attributes where they're not needed. There are currently 5 "cfargs" defined: - CFARG_SUBMATCH (submatch function for direct config) - CFARG_SEARCH (search function for indirect config) - CFARG_IATTR (interface attribte) - CFARG_LOCATORS (locators array) - CFARG_DEVHANDLE (devhandle_t - wraps OFW, ACPI, etc. handles) ...and a sentinel value CFARG_EOL. Add some extra sanity checking to ensure that interface attributes aren't ambiguous. Use CFARG_DEVHANDLE in MI FDT, OFW, and ACPI code, and macppc and shark ports to associate those device handles with device_t instance. This will trickle trough to more places over time (need back-end for pre-OFW Sun OBP; any others?).
2021-04-25 02:36:23 +03:00
config_search(self, NULL,
2021-08-07 19:18:40 +03:00
CFARGS(.search = iic_search,
.locators = locators));
2011-07-31 19:58:25 +04:00
return 0;
}
static int
iic_match(device_t parent, cfdata_t cf, void *aux)
{
2011-10-02 16:25:40 +04:00
return 1;
}
static void
iic_attach(device_t parent, device_t self, void *aux)
{
2006-03-29 10:41:24 +04:00
struct iic_softc *sc = device_private(self);
struct i2cbus_attach_args *iba = aux;
prop_array_t child_devices;
prop_dictionary_t props;
char *buf;
i2c_tag_t ic;
bool no_indirect_config = false;
2011-10-02 22:58:45 +04:00
aprint_naive("\n");
aprint_normal(": I2C bus\n");
2018-06-07 16:30:49 +03:00
sc->sc_dev = self;
sc->sc_tag = iba->iba_tag;
ic = sc->sc_tag;
2007-12-09 23:27:42 +03:00
if (!pmf_device_register(self, NULL, NULL))
aprint_error_dev(self, "couldn't establish power handler\n");
if (iba->iba_child_devices) {
child_devices = iba->iba_child_devices;
no_indirect_config = true;
} else {
props = device_properties(parent);
prop_dictionary_get_bool(props, "i2c-no-indirect-config",
&no_indirect_config);
child_devices = prop_dictionary_get(props, "i2c-child-devices");
}
if (child_devices) {
unsigned int i, count;
prop_dictionary_t dev;
prop_data_t cdata;
uint32_t addr;
uint64_t cookie;
uint32_t cookietype;
const char *name;
struct i2c_attach_args ia;
devhandle_t devhandle;
int loc[IICCF_NLOCS];
memset(loc, 0, sizeof loc);
count = prop_array_count(child_devices);
for (i = 0; i < count; i++) {
dev = prop_array_get(child_devices, i);
if (!dev) continue;
2021-06-21 06:04:27 +03:00
if (!prop_dictionary_get_string(
dev, "name", &name)) {
/* "name" property is optional. */
name = NULL;
}
if (!prop_dictionary_get_uint32(dev, "addr", &addr))
continue;
if (!prop_dictionary_get_uint64(dev, "cookie", &cookie))
cookie = 0;
if (!prop_dictionary_get_uint32(dev, "cookietype",
&cookietype))
cookietype = I2C_COOKIE_NONE;
loc[IICCF_ADDR] = addr;
memset(&ia, 0, sizeof ia);
ia.ia_addr = addr;
ia.ia_tag = ic;
ia.ia_name = name;
ia.ia_cookie = cookie;
ia.ia_cookietype = cookietype;
ia.ia_prop = dev;
devhandle = devhandle_invalid();
#ifdef I2C_USE_FDT
if (cookietype == I2C_COOKIE_OF) {
devhandle = devhandle_from_of(devhandle,
(int)cookie);
}
#endif /* I2C_USE_FDT */
#ifdef I2C_USE_ACPI
if (cookietype == I2C_COOKIE_ACPI) {
devhandle =
devhandle_from_acpi(devhandle,
(ACPI_HANDLE)cookie);
}
#endif /* I2C_USE_ACPI */
buf = NULL;
cdata = prop_dictionary_get(dev, "compatible");
if (cdata)
iic_fill_compat(&ia,
2020-06-11 05:32:06 +03:00
prop_data_value(cdata),
prop_data_size(cdata), &buf);
if (name == NULL && cdata == NULL) {
2011-10-02 22:58:45 +04:00
aprint_error_dev(self,
"WARNING: ignoring bad child device entry "
"for address 0x%02x\n", addr);
} else {
if (addr > I2C_MAX_ADDR) {
aprint_error_dev(self,
"WARNING: ignoring bad device "
"address @ 0x%02x\n", addr);
} else if (sc->sc_devices[addr] == NULL) {
sc->sc_devices[addr] =
Merge thorpej-cfargs branch: Simplify and make extensible the config_search() / config_found() / config_attach() interfaces: rather than having different variants for which arguments you want pass along, just have a single call that takes a variadic list of tag-value arguments. Adjust all call sites: - Simplify wherever possible; don't pass along arguments that aren't actually needed. - Don't be explicit about what interface attribute is attaching if the device only has one. (More simplification.) - Add a config_probe() function to be used in indirect configuiration situations, making is visibly easier to see when indirect config is in play, and allowing for future change in semantics. (As of now, this is just a wrapper around config_match(), but that is an implementation detail.) Remove unnecessary or redundant interface attributes where they're not needed. There are currently 5 "cfargs" defined: - CFARG_SUBMATCH (submatch function for direct config) - CFARG_SEARCH (search function for indirect config) - CFARG_IATTR (interface attribte) - CFARG_LOCATORS (locators array) - CFARG_DEVHANDLE (devhandle_t - wraps OFW, ACPI, etc. handles) ...and a sentinel value CFARG_EOL. Add some extra sanity checking to ensure that interface attributes aren't ambiguous. Use CFARG_DEVHANDLE in MI FDT, OFW, and ACPI code, and macppc and shark ports to associate those device handles with device_t instance. This will trickle trough to more places over time (need back-end for pre-OFW Sun OBP; any others?).
2021-04-25 02:36:23 +03:00
config_found(self, &ia,
iic_print_direct,
CFARGS(.locators = loc,
.devhandle = devhandle));
}
2011-10-02 22:58:45 +04:00
}
if (ia.ia_compat)
free(ia.ia_compat, M_TEMP);
if (buf)
free(buf, M_TEMP);
}
} else if (!no_indirect_config) {
/*
* Attach all i2c devices described in the kernel
* configuration file.
*/
2011-07-31 19:58:25 +04:00
iic_rescan(self, "iic", NULL);
}
}
2011-10-02 22:58:45 +04:00
static int
iic_detach(device_t self, int flags)
{
int error;
2011-10-02 22:58:45 +04:00
error = config_detach_children(self, flags);
if (error)
return error;
2011-10-02 22:58:45 +04:00
pmf_device_deregister(self);
return 0;
}
static void
iic_fill_compat(struct i2c_attach_args *ia, const char *compat, size_t len,
char **buffer)
{
int count, i;
const char *c, *start, **ptr;
*buffer = NULL;
for (i = count = 0, c = compat; i < len; i++, c++)
if (*c == 0)
count++;
count += 2;
ptr = malloc(sizeof(char*)*count, M_TEMP, M_WAITOK);
if (!ptr) return;
for (i = count = 0, start = c = compat; i < len; i++, c++) {
if (*c == 0) {
ptr[count++] = start;
start = c+1;
}
}
if (start < compat+len) {
/* last string not 0 terminated */
size_t l = c-start;
*buffer = malloc(l+1, M_TEMP, M_WAITOK);
memcpy(*buffer, start, l);
(*buffer)[l] = 0;
ptr[count++] = *buffer;
}
ptr[count] = NULL;
ia->ia_compat = ptr;
ia->ia_ncompat = count;
}
/*
* iic_compatible_match --
* Match a device's "compatible" property against the list
* of compatible strings provided by the driver.
*/
int
iic_compatible_match(const struct i2c_attach_args *ia,
const struct device_compatible_entry *compats)
{
int match_result;
match_result = device_compatible_match(ia->ia_compat, ia->ia_ncompat,
compats);
if (match_result) {
match_result =
MIN(I2C_MATCH_DIRECT_COMPATIBLE + match_result - 1,
I2C_MATCH_DIRECT_COMPATIBLE_MAX);
}
return match_result;
}
/*
* iic_compatible_lookup --
* Look the compatible entry that matches one of the driver's
* "compatible" strings. The first match is returned.
*/
const struct device_compatible_entry *
iic_compatible_lookup(const struct i2c_attach_args *ia,
const struct device_compatible_entry *compats)
{
return device_compatible_lookup(ia->ia_compat, ia->ia_ncompat,
compats);
}
/*
* iic_use_direct_match --
* Helper for direct-config of i2c. Returns true if this is
* a direct-config situation, along with match result.
* Returns false if the driver should use indirect-config
* matching logic.
*/
bool
iic_use_direct_match(const struct i2c_attach_args *ia, const cfdata_t cf,
const struct device_compatible_entry *compats,
int *match_resultp)
{
KASSERT(match_resultp != NULL);
if (ia->ia_name != NULL &&
strcmp(ia->ia_name, cf->cf_name) == 0) {
*match_resultp = I2C_MATCH_DIRECT_SPECIFIC;
return true;
}
if (ia->ia_ncompat > 0 && ia->ia_compat != NULL) {
*match_resultp = iic_compatible_match(ia, compats);
return true;
}
return false;
}
static int
iic_open(dev_t dev, int flag, int fmt, lwp_t *l)
{
struct iic_softc *sc = device_lookup_private(&iic_cd, minor(dev));
mutex_enter(&iic_mtx);
if (sc == NULL) {
mutex_exit(&iic_mtx);
return ENXIO;
}
iic_refcnt++;
mutex_exit(&iic_mtx);
return 0;
}
static int
iic_close(dev_t dev, int flag, int fmt, lwp_t *l)
{
mutex_enter(&iic_mtx);
iic_refcnt--;
mutex_exit(&iic_mtx);
return 0;
}
static int
iic_ioctl_exec(struct iic_softc *sc, i2c_ioctl_exec_t *iie, int flag)
{
i2c_tag_t ic = sc->sc_tag;
uint8_t *buf = NULL;
void *cmd = NULL;
int error;
/* Validate parameters */
if (iie->iie_addr > I2C_MAX_ADDR)
return EINVAL;
if (iie->iie_cmdlen > I2C_EXEC_MAX_CMDLEN ||
iie->iie_buflen > I2C_EXEC_MAX_BUFLEN)
return EINVAL;
if (iie->iie_cmd != NULL && iie->iie_cmdlen == 0)
return EINVAL;
if (iie->iie_buf != NULL && iie->iie_buflen == 0)
return EINVAL;
if (I2C_OP_WRITE_P(iie->iie_op) && (flag & FWRITE) == 0)
return EBADF;
#if 0
/* Disallow userspace access to devices that have drivers attached. */
if (sc->sc_devices[iie->iie_addr] != NULL)
return EBUSY;
#endif
if (iie->iie_cmd != NULL) {
cmd = kmem_alloc(iie->iie_cmdlen, KM_SLEEP);
error = copyin(iie->iie_cmd, cmd, iie->iie_cmdlen);
2015-04-04 18:16:54 +03:00
if (error)
goto out;
}
if (iie->iie_buf != NULL) {
buf = kmem_alloc(iie->iie_buflen, KM_SLEEP);
if (I2C_OP_WRITE_P(iie->iie_op)) {
error = copyin(iie->iie_buf, buf, iie->iie_buflen);
if (error)
goto out;
}
}
iic_acquire_bus(ic, 0);
error = iic_exec(ic, iie->iie_op, iie->iie_addr, cmd, iie->iie_cmdlen,
buf, iie->iie_buflen, 0);
iic_release_bus(ic, 0);
/*
* Some drivers return error codes on failure, and others return -1.
*/
if (error < 0)
error = EIO;
2015-04-04 18:16:54 +03:00
out:
if (iie->iie_buf != NULL && I2C_OP_READ_P(iie->iie_op))
error = copyout(buf, iie->iie_buf, iie->iie_buflen);
if (buf)
kmem_free(buf, iie->iie_buflen);
if (cmd)
kmem_free(cmd, iie->iie_cmdlen);
if (error)
return error;
return error;
}
static int
iic_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l)
{
struct iic_softc *sc = device_lookup_private(&iic_cd, minor(dev));
if (sc == NULL)
return ENXIO;
switch (cmd) {
case I2C_IOCTL_EXEC:
return iic_ioctl_exec(sc, (i2c_ioctl_exec_t *)data, flag);
default:
return ENODEV;
}
}
CFATTACH_DECL3_NEW(iic, sizeof(struct iic_softc),
iic_match, iic_attach, iic_detach, NULL, iic_rescan, iic_child_detach,
DVF_DETACH_SHUTDOWN);
2011-10-02 15:38:48 +04:00
MODULE(MODULE_CLASS_DRIVER, iic, "i2cexec,i2c_bitbang,i2c_subr");
2011-10-02 15:38:48 +04:00
#ifdef _MODULE
#include "ioconf.c"
#endif
int
iic_init(void)
{
mutex_init(&iic_mtx, MUTEX_DEFAULT, IPL_NONE);
iic_refcnt = 0;
return 0;
}
2011-10-02 15:38:48 +04:00
static int
iic_modcmd(modcmd_t cmd, void *opaque)
{
#ifdef _MODULE
int bmajor, cmajor;
#endif
2011-10-02 15:38:48 +04:00
int error;
error = 0;
switch (cmd) {
case MODULE_CMD_INIT:
RUN_ONCE(&iic_once, iic_init);
2011-10-02 15:38:48 +04:00
#ifdef _MODULE
mutex_enter(&iic_mtx);
bmajor = cmajor = -1;
error = devsw_attach("iic", NULL, &bmajor,
&iic_cdevsw, &cmajor);
if (error != 0) {
mutex_exit(&iic_mtx);
break;
}
2011-10-02 15:38:48 +04:00
error = config_init_component(cfdriver_ioconf_iic,
cfattach_ioconf_iic, cfdata_ioconf_iic);
if (error) {
2011-10-02 15:38:48 +04:00
aprint_error("%s: unable to init component\n",
iic_cd.cd_name);
devsw_detach(NULL, &iic_cdevsw);
}
mutex_exit(&iic_mtx);
2011-10-02 15:38:48 +04:00
#endif
break;
case MODULE_CMD_FINI:
mutex_enter(&iic_mtx);
if (iic_refcnt != 0) {
mutex_exit(&iic_mtx);
return EBUSY;
}
2011-10-02 15:38:48 +04:00
#ifdef _MODULE
error = config_fini_component(cfdriver_ioconf_iic,
2011-10-02 15:38:48 +04:00
cfattach_ioconf_iic, cfdata_ioconf_iic);
if (error != 0) {
mutex_exit(&iic_mtx);
break;
}
devsw_detach(NULL, &iic_cdevsw);
2011-10-02 15:38:48 +04:00
#endif
mutex_exit(&iic_mtx);
2011-10-02 15:38:48 +04:00
break;
default:
error = ENOTTY;
}
return error;
}