| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "i2c.h" |
| |
| #include <lib/sync/completion.h> |
| #include <threads.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/metadata.h> |
| #include <ddk/metadata/i2c.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/mutex.h> |
| |
| #include "i2c-child.h" |
| #include "src/devices/i2c/drivers/i2c/i2c_bind.h" |
| |
| namespace i2c { |
| |
| void I2cDevice::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); } |
| |
| void I2cDevice::DdkRelease() { delete this; } |
| |
| zx_status_t I2cDevice::Create(void* ctx, zx_device_t* parent) { |
| i2c_impl_protocol_t i2c; |
| auto status = device_get_protocol(parent, ZX_PROTOCOL_I2C_IMPL, &i2c); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| fbl::AllocChecker ac; |
| std::unique_ptr<I2cDevice> device(new (&ac) I2cDevice(parent, &i2c)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| status = device->Init(&i2c); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = device->DdkAdd("i2c"); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| device->AddChildren(); |
| |
| __UNUSED auto* dummy = device.release(); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t I2cDevice::Init(ddk::I2cImplProtocolClient i2c) { |
| uint32_t bus_count = i2c.GetBusCount(); |
| if (!bus_count) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| fbl::AllocChecker ac; |
| i2c_buses_.reserve(bus_count, &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| for (uint32_t i = 0; i < bus_count; i++) { |
| auto i2c_bus = fbl::MakeRefCountedChecked<I2cBus>(&ac, this->zxdev_, i2c, i); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| auto status = i2c_bus->Start(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| i2c_buses_.push_back(std::move(i2c_bus)); |
| } |
| |
| return ZX_OK; |
| } |
| |
| void I2cDevice::AddChildren() { |
| size_t metadata_size; |
| auto status = device_get_metadata_size(zxdev(), DEVICE_METADATA_I2C_CHANNELS, &metadata_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: device_get_metadata_size failed %d", __func__, status); |
| return; |
| } |
| auto channel_count = metadata_size / sizeof(i2c_channel_t); |
| |
| fbl::AllocChecker ac; |
| std::unique_ptr<i2c_channel_t[]> channels(new (&ac) i2c_channel_t[channel_count]); |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: out of memory", __func__); |
| return; |
| } |
| |
| size_t actual; |
| status = device_get_metadata(zxdev(), DEVICE_METADATA_I2C_CHANNELS, channels.get(), metadata_size, |
| &actual); |
| if (status != ZX_OK || actual != metadata_size) { |
| zxlogf(ERROR, "%s: device_get_metadata failed %d", __func__, status); |
| return; |
| } |
| |
| for (uint32_t i = 0; i < channel_count; i++) { |
| const auto& channel = channels[i]; |
| const auto bus_id = channel.bus_id; |
| const auto address = channel.address; |
| const auto i2c_class = channel.i2c_class; |
| const auto vid = channel.vid; |
| const auto pid = channel.pid; |
| const auto did = channel.did; |
| |
| if (bus_id >= i2c_buses_.size()) { |
| zxlogf(ERROR, "%s: bus_id %u out of range", __func__, bus_id); |
| return; |
| } |
| |
| fbl::AllocChecker ac; |
| std::unique_ptr<I2cChild> dev(new (&ac) I2cChild(zxdev(), i2c_buses_[bus_id], channel.address)); |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: out of memory", __func__); |
| return; |
| } |
| |
| char name[20]; |
| snprintf(name, sizeof(name), "i2c-%u-%u", bus_id, address); |
| |
| if (vid || pid || did) { |
| zx_device_prop_t props[] = { |
| {BIND_I2C_BUS_ID, 0, bus_id}, {BIND_I2C_ADDRESS, 0, address}, |
| {BIND_PLATFORM_DEV_VID, 0, vid}, {BIND_PLATFORM_DEV_PID, 0, pid}, |
| {BIND_PLATFORM_DEV_DID, 0, did}, {BIND_I2C_CLASS, 0, i2c_class}, |
| }; |
| |
| status = dev->DdkAdd( |
| ddk::DeviceAddArgs(name).set_flags(DEVICE_ADD_ALLOW_MULTI_COMPOSITE).set_props(props)); |
| } else { |
| zx_device_prop_t props[] = { |
| {BIND_I2C_BUS_ID, 0, bus_id}, |
| {BIND_I2C_ADDRESS, 0, address}, |
| {BIND_I2C_CLASS, 0, i2c_class}, |
| }; |
| |
| status = dev->DdkAdd(ddk::DeviceAddArgs(name).set_props(props)); |
| } |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: DdkAdd failed %d", __func__, status); |
| return; |
| } |
| |
| // dev is now owned by devmgr. |
| __UNUSED auto ptr = dev.release(); |
| } |
| } |
| |
| static constexpr zx_driver_ops_t driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = I2cDevice::Create; |
| return ops; |
| }(); |
| |
| } // namespace i2c |
| |
| ZIRCON_DRIVER(i2c, i2c::driver_ops, "zircon", "0.1"); |