Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * API for creating and destroying USB onboard platform devices
  4 *
  5 * Copyright (c) 2022, Google LLC
  6 */
  7
  8#include <linux/device.h>
  9#include <linux/export.h>
 10#include <linux/kernel.h>
 11#include <linux/list.h>
 12#include <linux/of.h>
 13#include <linux/of_platform.h>
 14#include <linux/platform_device.h>
 15#include <linux/usb.h>
 16#include <linux/usb/hcd.h>
 17#include <linux/usb/of.h>
 18#include <linux/usb/onboard_dev.h>
 19
 20#include "onboard_usb_dev.h"
 21
 22struct pdev_list_entry {
 23	struct platform_device *pdev;
 24	struct list_head node;
 25};
 26
 27static bool of_is_onboard_usb_dev(struct device_node *np)
 28{
 29	return !!of_match_node(onboard_dev_match, np);
 30}
 31
 32/**
 33 * onboard_dev_create_pdevs -- create platform devices for onboard USB devices
 34 * @parent_hub	: parent hub to scan for connected onboard devices
 35 * @pdev_list	: list of onboard platform devices owned by the parent hub
 36 *
 37 * Creates a platform device for each supported onboard device that is connected
 38 * to the given parent hub. The platform device is in charge of initializing the
 39 * device (enable regulators, take the device out of reset, ...). For onboard
 40 * hubs, it can optionally control whether the device remains powered during
 41 * system suspend or not.
 42 *
 43 * To keep track of the platform devices they are added to a list that is owned
 44 * by the parent hub.
 45 *
 46 * Some background about the logic in this function, which can be a bit hard
 47 * to follow:
 48 *
 49 * Root hubs don't have dedicated device tree nodes, but use the node of their
 50 * HCD. The primary and secondary HCD are usually represented by a single DT
 51 * node. That means the root hubs of the primary and secondary HCD share the
 52 * same device tree node (the HCD node). As a result this function can be called
 53 * twice with the same DT node for root hubs. We only want to create a single
 54 * platform device for each physical onboard device, hence for root hubs the
 55 * loop is only executed for the root hub of the primary HCD. Since the function
 56 * scans through all child nodes it still creates pdevs for onboard devices
 57 * connected to the root hub of the secondary HCD if needed.
 58 *
 59 * Further there must be only one platform device for onboard hubs with a peer
 60 * hub (the hub is a single physical device). To achieve this two measures are
 61 * taken: pdevs for onboard hubs with a peer are only created when the function
 62 * is called on behalf of the parent hub that is connected to the primary HCD
 63 * (directly or through other hubs). For onboard hubs connected to root hubs
 64 * the function processes the nodes of both peers. A platform device is only
 65 * created if the peer hub doesn't have one already.
 66 */
 67void onboard_dev_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list)
 68{
 69	int i;
 70	struct usb_hcd *hcd = bus_to_hcd(parent_hub->bus);
 71	struct device_node *np, *npc;
 72	struct platform_device *pdev;
 73	struct pdev_list_entry *pdle;
 74
 75	if (!parent_hub->dev.of_node)
 76		return;
 77
 78	if (!parent_hub->parent && !usb_hcd_is_primary_hcd(hcd))
 79		return;
 80
 81	for (i = 1; i <= parent_hub->maxchild; i++) {
 82		np = usb_of_get_device_node(parent_hub, i);
 83		if (!np)
 84			continue;
 85
 86		if (!of_is_onboard_usb_dev(np))
 87			goto node_put;
 88
 89		npc = of_parse_phandle(np, "peer-hub", 0);
 90		if (npc) {
 91			if (!usb_hcd_is_primary_hcd(hcd)) {
 92				of_node_put(npc);
 93				goto node_put;
 94			}
 95
 96			pdev = of_find_device_by_node(npc);
 97			of_node_put(npc);
 98
 99			if (pdev) {
100				put_device(&pdev->dev);
101				goto node_put;
102			}
103		}
104
105		pdev = of_platform_device_create(np, NULL, &parent_hub->dev);
106		if (!pdev) {
107			dev_err(&parent_hub->dev,
108				"failed to create platform device for onboard dev '%pOF'\n", np);
109			goto node_put;
110		}
111
112		pdle = kzalloc(sizeof(*pdle), GFP_KERNEL);
113		if (!pdle) {
114			of_platform_device_destroy(&pdev->dev, NULL);
115			goto node_put;
116		}
117
118		pdle->pdev = pdev;
119		list_add(&pdle->node, pdev_list);
120
121node_put:
122		of_node_put(np);
123	}
124}
125EXPORT_SYMBOL_GPL(onboard_dev_create_pdevs);
126
127/**
128 * onboard_dev_destroy_pdevs -- free resources of onboard platform devices
129 * @pdev_list	: list of onboard platform devices
130 *
131 * Destroys the platform devices in the given list and frees the memory associated
132 * with the list entry.
133 */
134void onboard_dev_destroy_pdevs(struct list_head *pdev_list)
135{
136	struct pdev_list_entry *pdle, *tmp;
137
138	list_for_each_entry_safe(pdle, tmp, pdev_list, node) {
139		list_del(&pdle->node);
140		of_platform_device_destroy(&pdle->pdev->dev, NULL);
141		kfree(pdle);
142	}
143}
144EXPORT_SYMBOL_GPL(onboard_dev_destroy_pdevs);