* [RESEND PATCH v1 03/11] drivers: soc: hisi: Add support for Hisilicon Djtag driver
From: Mark Rutland @ 2016-11-10 17:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1478151727-20250-4-git-send-email-anurup.m@huawei.com>
On Thu, Nov 03, 2016 at 01:41:59AM -0400, Anurup M wrote:
> From: Tan Xiaojun <tanxiaojun@huawei.com>
>
> The Hisilicon Djtag is an independent component which connects
> with some other components in the SoC by Debug Bus. This driver
> can be configured to access the registers of connecting components
> (like L3 cache) during real time debugging.
>
Just to check, is this likely to be used in multi-socket hardware, and
if so, are instances always-on?
> Signed-off-by: Tan Xiaojun <tanxiaojun@huawei.com>
> Signed-off-by: John Garry <john.garry@huawei.com>
> Signed-off-by: Anurup M <anurup.m@huawei.com>
> ---
> drivers/soc/Kconfig | 1 +
> drivers/soc/Makefile | 1 +
> drivers/soc/hisilicon/Kconfig | 12 +
> drivers/soc/hisilicon/Makefile | 1 +
> drivers/soc/hisilicon/djtag.c | 639 ++++++++++++++++++++++++++++++++++++
> include/linux/soc/hisilicon/djtag.h | 38 +++
> 6 files changed, 692 insertions(+)
> create mode 100644 drivers/soc/hisilicon/Kconfig
> create mode 100644 drivers/soc/hisilicon/Makefile
> create mode 100644 drivers/soc/hisilicon/djtag.c
> create mode 100644 include/linux/soc/hisilicon/djtag.h
Other than the PMU driver(s), what is going to use this?
If you don't have something in particular, please also place this under
drivers/perf/hisilicon, along with the PMU driver(s).
We can always move it later if necessary.
[...]
> +#define SC_DJTAG_TIMEOUT 100000 /* 100ms */
This would be better as:
#define SC_DJTAG_TIMEOUT_US (100 * USEC_PER_MSEC)
(you'll need to include <linux/time64.h>)
[...]
> +static void djtag_read32_relaxed(void __iomem *regs_base, u32 off, u32 *value)
> +{
> + void __iomem *reg_addr = regs_base + off;
> +
> + *value = readl_relaxed(reg_addr);
> +}
> +
> +static void djtag_write32(void __iomem *regs_base, u32 off, u32 val)
> +{
> + void __iomem *reg_addr = regs_base + off;
> +
> + writel(val, reg_addr);
> +}
I think these make the driver harder to read, especially given the read
function is void and takes an output pointer.
In either case you can call readl/writel directly with base + off for
the __iomem ptr. Please do that.
> +
> +/*
> + * djtag_readwrite_v1/v2: djtag read/write interface
> + * @reg_base: djtag register base address
> + * @offset: register's offset
> + * @mod_sel: module selection
> + * @mod_mask: mask to select specific modules for write
> + * @is_w: write -> true, read -> false
> + * @wval: value to register for write
> + * @chain_id: which sub module for read
> + * @rval: value in register for read
> + *
> + * Return non-zero if error, else return 0.
> + */
> +static int djtag_readwrite_v1(void __iomem *regs_base, u32 offset, u32 mod_sel,
> + u32 mod_mask, bool is_w, u32 wval, int chain_id, u32 *rval)
> +{
> + u32 rd;
> + int timeout = SC_DJTAG_TIMEOUT;
> +
> + if (!(mod_mask & CHAIN_UNIT_CFG_EN)) {
> + pr_warn("djtag: do nothing.\n");
> + return 0;
> + }
> +
> + /* djtag mster enable & accelerate R,W */
> + djtag_write32(regs_base, SC_DJTAG_MSTR_EN,
> + DJTAG_NOR_CFG | DJTAG_MSTR_EN);
> +
> + /* select module */
> + djtag_write32(regs_base, SC_DJTAG_DEBUG_MODULE_SEL, mod_sel);
> + djtag_write32(regs_base, SC_DJTAG_CHAIN_UNIT_CFG_EN,
> + mod_mask & CHAIN_UNIT_CFG_EN);
> +
> + if (is_w) {
> + djtag_write32(regs_base, SC_DJTAG_MSTR_WR, DJTAG_MSTR_W);
> + djtag_write32(regs_base, SC_DJTAG_MSTR_DATA, wval);
> + } else
> + djtag_write32(regs_base, SC_DJTAG_MSTR_WR, DJTAG_MSTR_R);
> +
> + /* address offset */
> + djtag_write32(regs_base, SC_DJTAG_MSTR_ADDR, offset);
> +
> + /* start to write to djtag register */
> + djtag_write32(regs_base, SC_DJTAG_MSTR_START_EN, DJTAG_MSTR_START_EN);
> +
> + /* ensure the djtag operation is done */
> + do {
> + djtag_read32_relaxed(regs_base, SC_DJTAG_MSTR_START_EN, &rd);
> + if (!(rd & DJTAG_MSTR_EN))
> + break;
> +
> + udelay(1);
> + } while (timeout--);
> +
> + if (timeout < 0) {
> + pr_err("djtag: %s timeout!\n", is_w ? "write" : "read");
> + return -EBUSY;
> + }
> +
> + if (!is_w)
> + djtag_read32_relaxed(regs_base,
> + SC_DJTAG_RD_DATA_BASE + chain_id * 0x4, rval);
> +
> + return 0;
> +}
Please factor out the common bits into helpers and have separate
read/write functions. It's incredibly difficult to follow the code with
read/write hidden behind a boolean parameter.
> +static const struct of_device_id djtag_of_match[] = {
> + /* for hip05(D02) cpu die */
> + { .compatible = "hisilicon,hip05-cpu-djtag-v1",
> + .data = (void *)djtag_readwrite_v1 },
> + /* for hip05(D02) io die */
> + { .compatible = "hisilicon,hip05-io-djtag-v1",
> + .data = (void *)djtag_readwrite_v1 },
> + /* for hip06(D03) cpu die */
> + { .compatible = "hisilicon,hip06-cpu-djtag-v1",
> + .data = (void *)djtag_readwrite_v1 },
> + /* for hip06(D03) io die */
> + { .compatible = "hisilicon,hip06-io-djtag-v2",
> + .data = (void *)djtag_readwrite_v2 },
> + /* for hip07(D05) cpu die */
> + { .compatible = "hisilicon,hip07-cpu-djtag-v2",
> + .data = (void *)djtag_readwrite_v2 },
> + /* for hip07(D05) io die */
> + { .compatible = "hisilicon,hip07-io-djtag-v2",
> + .data = (void *)djtag_readwrite_v2 },
> + {},
> +};
Binding documentation for all of these should be added *before* this
patch, per Documentation/devicetree/bindings/submitting-patches.txt.
Please move any relevant binding documentation earlier in the series.
> +MODULE_DEVICE_TABLE(of, djtag_of_match);
> +
> +static ssize_t
> +show_modalias(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct hisi_djtag_client *client = to_hisi_djtag_client(dev);
> +
> + return sprintf(buf, "%s%s\n", MODULE_PREFIX, client->name);
> +}
> +static DEVICE_ATTR(modalias, 0444, show_modalias, NULL);
> +
> +static struct attribute *hisi_djtag_dev_attrs[] = {
> + NULL,
> + /* modalias helps coldplug: modprobe $(cat .../modalias) */
> + &dev_attr_modalias.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(hisi_djtag_dev);
Why do we need to expose this under sysfs?
> +struct bus_type hisi_djtag_bus = {
> + .name = "hisi-djtag",
> + .match = hisi_djtag_device_match,
> + .probe = hisi_djtag_device_probe,
> + .remove = hisi_djtag_device_remove,
> +};
I take it the core bus code handles reference-counting users of the bus?
> +struct hisi_djtag_client *hisi_djtag_new_device(struct hisi_djtag_host *host,
> + struct device_node *node)
> +{
> + struct hisi_djtag_client *client;
> + int status;
> +
> + client = kzalloc(sizeof(*client), GFP_KERNEL);
> + if (!client)
> + return NULL;
> +
> + client->host = host;
> +
> + client->dev.parent = &client->host->dev;
> + client->dev.bus = &hisi_djtag_bus;
> + client->dev.type = &hisi_djtag_client_type;
> + client->dev.of_node = node;
I suspect that we should do:
client->dev.of_node = of_node_get(node);
... so that it's guaranteed we retain a reference.
> + snprintf(client->name, DJTAG_CLIENT_NAME_LEN, "%s%s",
> + DJTAG_PREFIX, node->name);
> + dev_set_name(&client->dev, "%s", client->name);
> +
> + status = device_register(&client->dev);
> + if (status < 0) {
> + pr_err("error adding new device, status=%d\n", status);
> + kfree(client);
> + return NULL;
> + }
> +
> + return client;
> +}
> +
> +static struct hisi_djtag_client *hisi_djtag_of_register_device(
> + struct hisi_djtag_host *host,
> + struct device_node *node)
> +{
> + struct hisi_djtag_client *client;
> +
> + client = hisi_djtag_new_device(host, node);
> + if (client == NULL) {
> + dev_err(&host->dev, "error registering device %s\n",
> + node->full_name);
> + of_node_put(node);
I don't think this should be here, given what djtag_register_devices()
does.
> + return ERR_PTR(-EINVAL);
> + }
> +
> + return client;
> +}
> +
> +static void djtag_register_devices(struct hisi_djtag_host *host)
> +{
> + struct device_node *node;
> + struct hisi_djtag_client *client;
> +
> + if (!host->of_node)
> + return;
> +
> + for_each_available_child_of_node(host->of_node, node) {
> + if (of_node_test_and_set_flag(node, OF_POPULATED))
> + continue;
> + client = hisi_djtag_of_register_device(host, node);
> + list_add(&client->next, &host->client_list);
> + }
> +}
Given hisi_djtag_of_register_device() can return ERR_PTR(-EINVAL), the
list_add is not safe.
> +static int djtag_host_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct hisi_djtag_host *host;
> + const struct of_device_id *of_id;
> + struct resource *res;
> + int rc;
> +
> + of_id = of_match_device(djtag_of_match, dev);
> + if (!of_id)
> + return -EINVAL;
> +
> + host = kzalloc(sizeof(*host), GFP_KERNEL);
> + if (!host)
> + return -ENOMEM;
> +
> + host->of_node = dev->of_node;
host->of_node = of_node_get(dev->of_node);
> + host->djtag_readwrite = of_id->data;
> + spin_lock_init(&host->lock);
> +
> + INIT_LIST_HEAD(&host->client_list);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res) {
> + dev_err(&pdev->dev, "No reg resorces!\n");
> + kfree(host);
> + return -EINVAL;
> + }
> +
> + if (!resource_size(res)) {
> + dev_err(&pdev->dev, "Zero reg entry!\n");
> + kfree(host);
> + return -EINVAL;
> + }
> +
> + host->sysctl_reg_map = devm_ioremap_resource(dev, res);
> + if (IS_ERR(host->sysctl_reg_map)) {
> + dev_warn(dev, "Unable to map sysctl registers.\n");
> + kfree(host);
> + return -EINVAL;
> + }
Please have a common error path rather than duplicating this free.
e.g. at the end of the function have:
err_free:
kfree(host);
return err;
... and in cases like this, have:
if (whatever()) {
dev_warn(dev, "this failed xxx\n");
err = -EINVAL;
goto err_free;
}
Thanks,
Mark.
^ permalink raw reply
* [PATCH] ARM: OMAP2+: Remove unused MUSB initialization code
From: Tony Lindgren @ 2016-11-10 17:54 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1478799800-27945-1-git-send-email-laurent.pinchart@ideasonboard.com>
* Laurent Pinchart <laurent.pinchart@ideasonboard.com> [161110 10:43]:
> With the removal of board-ldp.c and board-rx51.c, the last users of the
> MUSB initialization board code are gone. Remove it.
Thanks, I have the same patch already from 3 years ago
in omap-for-v3.14/omap3-board-removal branch that I'll
use. Still need to rebase and check few other patches
there before reposting them all. FYI, to avoid duplicate
work, the old barnch is at [0].
Regards,
Tony
https://git.kernel.org/cgit/linux/kernel/git/tmlind/linux-omap.git/log/?h=omap-for-v3.14/omap3-board-removal
^ permalink raw reply
* [PATCH] Replacement for Arm initrd memblock reserve and free inconsistency.
From: Russell King - ARM Linux @ 2016-11-10 17:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <592F4D3D062D79449F140027567F70E8FE080A79@exchmbx03>
On Wed, Nov 09, 2016 at 04:35:37PM +0000, william.helsby at stfc.ac.uk wrote:
> A boot time system crash was noticed with a segmentation fault just after the initrd image had been used to initialise the ramdisk.
> This occurred when the U-Boot loaded the ramdisk image from a FAT partition, but not when loaded by TFTPBOOT. This is not understood?
> However the problem was caused by free_initrd_mem freeing and "poisoning" memory that had been allocted to init/main.c to store the saved_command_line
> This patch reverses "ARM: 8167/1: extend the reserved memory for initrd to be page aligned" because it is safer to leave a partial head or tail page reserved (wasted) than to free a page which is partially still in use.
> If this is not acceptable (particularly if wanting large contiguous physical areas for DMA) then a better solution is required.
> This would extend the region reserved to page boundaries, if possible without overlapping other regions. My previous attempt to fix this coded this scheme, to grow the are reserved.
> However, this? again is not safe if in growing the area it then overlaps a region that is in use.
> Note this path is against the 4.6 kernel, but as far as I can tell applies equally to 4.8.
Please wrap commit messages at or before column 72, the exception is
for lines with a URL.
> diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c
> index 370581a..ff3e9c3 100644
> --- a/arch/arm/mm/init.c
> +++ b/arch/arm/mm/init.c
> @@ -770,12 +770,6 @@ static int keep_initrd;
> void free_initrd_mem(unsigned long start, unsigned long end)
> {
> ??????? if (!keep_initrd) {
> -?????????????? if (start == initrd_start)
> -?????????????????????? start = round_down(start, PAGE_SIZE);
> -?????????????? if (end == initrd_end)
> -?????????????????????? end = round_up(end, PAGE_SIZE);
> -
> -???? ??????????poison_init_mem((void *)start, PAGE_ALIGN(end) - start);
We're definitely not getting rid of the poisoning of the pages - the
poisoning there is to detect accesses to this memory which should not
be made.
The point of rounding up and down is to ensure that the partly-used
pages (which would have been previously reserved) are freed.
Probably a better fix is to round the start up/end down of the initrd
when reserving the memory region:
arch/arm/mm/init.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c
index 370581aeb871..ee8509e4329d 100644
--- a/arch/arm/mm/init.c
+++ b/arch/arm/mm/init.c
@@ -255,7 +255,11 @@ void __init arm_memblock_init(const struct machine_desc *mdesc)
phys_initrd_start = phys_initrd_size = 0;
}
if (phys_initrd_size) {
- memblock_reserve(phys_initrd_start, phys_initrd_size);
+ phys_addr_t start, size;
+
+ start = round_down(phys_initrd_start, PAGE_SIZE);
+ end = round_up(phys_initrd_start + phys_initrd_size, PAGE_SIZE);
+ memblock_reserve(start, end - start);
/* Now convert initrd to virtual addresses */
initrd_start = __phys_to_virt(phys_initrd_start);
and this should ensure that memblock_alloc() doesn't try to allocate
memory overlapping the pages containing the initrd.
Intentionally using pages overlapping the initrd is a recipe for
problems...
--
RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.
^ permalink raw reply related
* Summary of LPC guest MSI discussion in Santa Fe
From: Alex Williamson @ 2016-11-10 17:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <ddd8af9d-ad8f-78d8-3048-3d640b74470e@redhat.com>
On Thu, 10 Nov 2016 12:14:40 +0100
Auger Eric <eric.auger@redhat.com> wrote:
> Hi Will, Alex,
>
> On 10/11/2016 03:01, Will Deacon wrote:
> > On Wed, Nov 09, 2016 at 05:55:17PM -0700, Alex Williamson wrote:
> >> On Thu, 10 Nov 2016 01:14:42 +0100
> >> Auger Eric <eric.auger@redhat.com> wrote:
> >>> On 10/11/2016 00:59, Alex Williamson wrote:
> >>>> On Wed, 9 Nov 2016 23:38:50 +0000
> >>>> Will Deacon <will.deacon@arm.com> wrote:
> >>>>> On Wed, Nov 09, 2016 at 04:24:58PM -0700, Alex Williamson wrote:
> >>>>>> The VFIO_IOMMU_MAP_DMA ioctl is a contract, the user ask to map a range
> >>>>>> of IOVAs to a range of virtual addresses for a given device. If VFIO
> >>>>>> cannot reasonably fulfill that contract, it must fail. It's up to QEMU
> >>>>>> how to manage the hotplug and what memory regions it asks VFIO to map
> >>>>>> for a device, but VFIO must reject mappings that it (or the SMMU by
> >>>>>> virtue of using the IOMMU API) know to overlap reserved ranges. So I
> >>>>>> still disagree with the referenced statement. Thanks,
> >>>>>
> >>>>> I think that's a pity. Not only does it mean that both QEMU and the kernel
> >>>>> have more work to do (the former has to carve up its mapping requests,
> >>>>> whilst the latter has to check that it is indeed doing this), but it also
> >>>>> precludes the use of hugepage mappings on the IOMMU because of reserved
> >>>>> regions. For example, a 4k hole someplace may mean we can't put down 1GB
> >>>>> table entries for the guest memory in the SMMU.
> >>>>>
> >>>>> All this seems to do is add complexity and decrease performance. For what?
> >>>>> QEMU has to go read the reserved regions from someplace anyway. It's also
> >>>>> the way that VFIO works *today* on arm64 wrt reserved regions, it just has
> >>>>> no way to identify those holes at present.
> >>>>
> >>>> Sure, that sucks, but how is the alternative even an option? The user
> >>>> asked to map something, we can't, if we allow that to happen now it's a
> >>>> bug. Put the MSI doorbells somewhere that this won't be an issue. If
> >>>> the platform has it fixed somewhere that this is an issue, don't use
> >>>> that platform. The correctness of the interface is more important than
> >>>> catering to a poorly designed system layout IMO. Thanks,
> >>>
> >>> Besides above problematic, I started to prototype the sysfs API. A first
> >>> issue I face is the reserved regions become global to the iommu instead
> >>> of characterizing the iommu_domain, ie. the "reserved_regions" attribute
> >>> file sits below an iommu instance (~
> >>> /sys/class/iommu/dmar0/intel-iommu/reserved_regions ||
> >>> /sys/class/iommu/arm-smmu0/arm-smmu/reserved_regions).
> >>>
> >>> MSI reserved window can be considered global to the IOMMU. However PCIe
> >>> host bridge P2P regions rather are per iommu-domain.
> >
> > I don't think we can treat them as per-domain, given that we want to
> > enumerate this stuff before we've decided to do a hotplug (and therefore
> > don't have a domain).
> That's the issue indeed. We need to wait for the PCIe device to be
> connected to the iommu. Only on the VFIO SET_IOMMU we get the
> comprehensive list of P2P regions that can impact IOVA mapping for this
> iommu. This removes any advantage of sysfs API over previous VFIO
> capability chain API for P2P IOVA range enumeration at early stage.
For use through vfio we know that an iommu_domain is minimally composed
of an iommu_group and we can find all the p2p resources of that group
referencing /proc/iomem, at least for PCI-based groups. This is the
part that I don't think any sort of iommu sysfs attributes should be
duplicating.
> >>> Do you confirm the attribute file should contain both global reserved
> >>> regions and all per iommu_domain reserved regions?
> >>>
> >>> Thoughts?
> >>
> >> I don't think we have any business describing IOVA addresses consumed
> >> by peer devices in an IOMMU sysfs file. If it's a separate device it
> >> should be exposed by examining the rest of the topology. Regions
> >> consumed by PCI endpoints and interconnects are already exposed in
> >> sysfs. In fact, is this perhaps a more accurate model for these MSI
> >> controllers too? Perhaps they should be exposed in the bus topology
> >> somewhere as consuming the IOVA range.
> Currently on x86 the P2P regions are not checked when allowing
> passthrough. Aren't we more papist that the pope? As Don mentioned,
> shouldn't we simply consider that a platform that does not support
> proper ACS is not candidate for safe passthrough, like Juno?
There are two sides here, there's the kernel side vfio and there's how
QEMU makes use of vfio. On the kernel side, we create iommu groups as
the set of devices we consider isolated, that doesn't necessarily mean
that there isn't p2p within the group, in fact that potential often
determines the composition of the group. It's the user's problem how
to deal with that potential. When I talk about the contract with
userspace, I consider that to be at the iommu mapping, ie. for
transactions that actually make it to the iommu. In the case of x86,
we know that DMA mappings overlapping the MSI doorbells won't be
translated correctly, it's not a valid mapping for that range, and
therefore the iommu driver backing the IOMMU API should describe that
reserved range and reject mappings to it. For devices downstream of
the IOMMU, whether they be p2p or MSI controllers consuming fixed IOVA
space, I consider these to be problems beyond the scope of the IOMMU
API, and maybe that's where we've been going wrong all along.
Users like QEMU can currently discover potential p2p conflicts by
looking at the composition of an iommu group and taking into account
the host PCI resources of each device. We don't currently do this,
though we probably should. The reason we typically don't run into
problems with this is that (again) x86 has a fairly standard memory
layout. Potential p2p regions are typically in an MMIO hole in the
host that sufficiently matches an MMIO hole in the guest. So we don't
often have VM RAM, which could be a DMA target, matching those p2p
addresses. We also hope that any serious device assignment users have
singleton iommu groups, ie. the IO subsystem is designed to support
proper, fine grained isolation.
> At least we can state the feature also is missing on x86 and it would be
> nice to report the risk to the userspace and urge him to opt-in.
Sure, but the information is already there, it's "just" a matter of
QEMU taking it into account, which has some implications that VMs with
any potential of doing device assignment need to be instantiated with
address maps compatible with the host system, which is not an easy feat
for something often considered the ugly step-child of virtualization.
> To me taking into account those P2P still is controversial and induce
> the bulk of the complexity. Considering the migration use case discussed
> at LPC while only handling the MSI problem looks much easier.
> host can choose an MSI base that is QEMU mach-virt friendly, ie. non RAM
> region. Problem is to satisfy all potential uses though. When migrating,
> mach-virt still is being used so there should not be any collision. Am I
> missing some migration weird use cases here? Of course if we take into
> consideration new host PCIe P2P regions this becomes completely different.
Yep, x86 having a standard MSI range is a nice happenstance, so long as
we're running an x86 VM, we don't worry about that being a DMA target.
Running non-x86 VMs on x86 hosts hits this problem, but is several
orders of magnitude lower priority.
> We still have the good old v14 where the user space chose where MSI
> IOVA's are put without any risk of collision ;-)
>
> >> If DMA to an IOVA is consumed
> >> by an intermediate device before it hits the IOMMU vs not being
> >> translated as specified by the user at the IOMMU, I'm less inclined to
> >> call that something VFIO should reject.
> >
> > Oh, so perhaps we've been talking past each other. In all of these cases,
> > the SMMU can translate the access if it makes it that far. The issue is
> > that not all accesses do make it that far, because they may be "consumed"
> > by another device, such as an MSI doorbell or another endpoint. In other
> > words, I don't envisage a scenario where e.g. some address range just
> > bypasses the SMMU straight to memory. I realise now that that's not clear
> > from the slides I presented.
As above, so long as a transaction that does make it to the iommu is
translated as prescribed by the user, I have no basis for rejecting a
user requested translation. Downstream MSI controllers consuming IOVA
space is no different than the existing p2p problem that vfio considers
a userspace issue.
> >> However, instantiating a VM
> >> with holes to account for every potential peer device seems like it
> >> borders on insanity. Thanks,
> >
> > Ok, so rather than having a list of reserved regions under the iommu node,
> > you're proposing that each region is attributed to the device that "owns"
> > (consumes) it? I think that can work, but we need to make sure that:
> >
> > (a) The topology is walkable from userspace (where do you start?)
For PCI devices userspace can examine the topology of the iommu group
and exclude MMIO ranges of peer devices based on the BARs, which are
exposed in various places, pci-sysfs as well as /proc/iomem. For
non-PCI or MSI controllers... ???
> > (b) It also works for platform (non-PCI) devices, that lack much in the
> > way of bus hierarchy
No idea here, without a userspace visible topology the user is in the
dark as to what devices potentially sit between them and the iommu.
> > (c) It doesn't require Linux to have a driver bound to a device in order
> > for the ranges consumed by that device to be advertised (again,
> > more of an issue for non-PCI).
Right, PCI has this problem solved, be more like PCI ;)
> > How is this currently advertised for PCI? I'd really like to use the same
> > scheme irrespective of the bus type.
For all devices within an IOMMU group, /proc/iomem might be the
solution, but I don't know how the MSI controller works. If the MSI
controller belongs to the group, then maybe it's a matter of creating a
struct device for it that consumes resources and making it show up in
both the iommu group and /proc/iomem. An MSI controller shared between
groups, which already sounds like a bit of a violation of iommu groups,
would need to be discoverable some other way. Thanks,
Alex
^ permalink raw reply
* [PATCH] ARM: OMAP2+: Remove unused MUSB initialization code
From: Laurent Pinchart @ 2016-11-10 17:43 UTC (permalink / raw)
To: linux-arm-kernel
With the removal of board-ldp.c and board-rx51.c, the last users of the
MUSB initialization board code are gone. Remove it.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
arch/arm/mach-omap2/Makefile | 1 -
arch/arm/mach-omap2/usb-musb.c | 106 -----------------------------------------
arch/arm/mach-omap2/usb.h | 1 -
3 files changed, 108 deletions(-)
delete mode 100644 arch/arm/mach-omap2/usb-musb.c
diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile
index 5b37ec29996e..76e8ba70d952 100644
--- a/arch/arm/mach-omap2/Makefile
+++ b/arch/arm/mach-omap2/Makefile
@@ -242,7 +242,6 @@ obj-y += $(omap-flash-y) $(omap-flash-m)
omap-hsmmc-$(CONFIG_MMC_OMAP_HS) := hsmmc.o
obj-y += $(omap-hsmmc-m) $(omap-hsmmc-y)
-obj-y += usb-musb.o
obj-y += omap_phy_internal.o
obj-$(CONFIG_MACH_OMAP2_TUSB6010) += usb-tusb6010.o
diff --git a/arch/arm/mach-omap2/usb-musb.c b/arch/arm/mach-omap2/usb-musb.c
deleted file mode 100644
index e4562b2b973b..000000000000
--- a/arch/arm/mach-omap2/usb-musb.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * linux/arch/arm/mach-omap2/usb-musb.c
- *
- * This file will contain the board specific details for the
- * MENTOR USB OTG controller on OMAP3430
- *
- * Copyright (C) 2007-2008 Texas Instruments
- * Copyright (C) 2008 Nokia Corporation
- * Author: Vikram Pandita
- *
- * Generalization by:
- * Felipe Balbi <felipe.balbi@nokia.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
-
-#include <linux/types.h>
-#include <linux/errno.h>
-#include <linux/delay.h>
-#include <linux/platform_device.h>
-#include <linux/clk.h>
-#include <linux/dma-mapping.h>
-#include <linux/io.h>
-#include <linux/usb/musb.h>
-
-#include "omap_device.h"
-#include "soc.h"
-#include "mux.h"
-#include "usb.h"
-
-static struct musb_hdrc_config musb_config = {
- .multipoint = 1,
- .dyn_fifo = 1,
- .num_eps = 16,
- .ram_bits = 12,
-};
-
-static struct musb_hdrc_platform_data musb_plat = {
- .mode = MUSB_OTG,
-
- /* .clock is set dynamically */
- .config = &musb_config,
-
- /* REVISIT charge pump on TWL4030 can supply up to
- * 100 mA ... but this value is board-specific, like
- * "mode", and should be passed to usb_musb_init().
- */
- .power = 50, /* up to 100 mA */
-};
-
-static u64 musb_dmamask = DMA_BIT_MASK(32);
-
-static struct omap_musb_board_data musb_default_board_data = {
- .interface_type = MUSB_INTERFACE_ULPI,
- .mode = MUSB_OTG,
- .power = 100,
-};
-
-void __init usb_musb_init(struct omap_musb_board_data *musb_board_data)
-{
- struct omap_hwmod *oh;
- struct platform_device *pdev;
- struct device *dev;
- int bus_id = -1;
- const char *oh_name, *name;
- struct omap_musb_board_data *board_data;
-
- if (musb_board_data)
- board_data = musb_board_data;
- else
- board_data = &musb_default_board_data;
-
- /*
- * REVISIT: This line can be removed once all the platforms using
- * musb_core.c have been converted to use use clkdev.
- */
- musb_plat.clock = "ick";
- musb_plat.board_data = board_data;
- musb_plat.power = board_data->power >> 1;
- musb_plat.mode = board_data->mode;
- musb_plat.extvbus = board_data->extvbus;
-
- oh_name = "usb_otg_hs";
- name = "musb-omap2430";
-
- oh = omap_hwmod_lookup(oh_name);
- if (WARN(!oh, "%s: could not find omap_hwmod for %s\n",
- __func__, oh_name))
- return;
-
- pdev = omap_device_build(name, bus_id, oh, &musb_plat,
- sizeof(musb_plat));
- if (IS_ERR(pdev)) {
- pr_err("Could not build omap_device for %s %s\n",
- name, oh_name);
- return;
- }
-
- dev = &pdev->dev;
- get_device(dev);
- dev->dma_mask = &musb_dmamask;
- dev->coherent_dma_mask = musb_dmamask;
- put_device(dev);
-}
diff --git a/arch/arm/mach-omap2/usb.h b/arch/arm/mach-omap2/usb.h
index 3395365ef1db..1951535646d2 100644
--- a/arch/arm/mach-omap2/usb.h
+++ b/arch/arm/mach-omap2/usb.h
@@ -60,7 +60,6 @@ struct usbhs_phy_data {
bool vcc_polarity; /* 1 active high, 0 active low */
};
-extern void usb_musb_init(struct omap_musb_board_data *board_data);
extern void usbhs_init(struct usbhs_omap_platform_data *pdata);
extern int usbhs_init_phys(struct usbhs_phy_data *phy, int num_phys);
--
Regards,
Laurent Pinchart
^ permalink raw reply related
* [PATCHv2] PCI: QDF2432 32 bit config space accessors
From: Bjorn Helgaas @ 2016-11-10 17:42 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAKv+Gu9_sdcoN7rjQp-R=2vKY3rECt0BC3eWGpse32o2Q4LHXg@mail.gmail.com>
On Thu, Nov 10, 2016 at 06:25:16PM +0800, Ard Biesheuvel wrote:
> On 10 November 2016 at 06:49, Bjorn Helgaas <helgaas@kernel.org> wrote:
> > On Wed, Nov 09, 2016 at 08:29:23PM +0000, Ard Biesheuvel wrote:
> >> Hi Bjorn,
> >>
> >> On 9 November 2016 at 20:06, Bjorn Helgaas <helgaas@kernel.org> wrote:
> >> > On Wed, Nov 09, 2016 at 02:25:56PM -0500, Christopher Covington wrote:
> >> >> Hi Bjorn,
> >> >>
> >> [...]
> >> >>
> >> >> We're working to add the PNP0C02 resource to future firmware, but it's
> >> >> not in the current firmware. Are dmesg and /proc/iomem from the
> >> >> current firmware interesting or should we wait for the update to file?
> >> >
> >> > Note that the ECAM space is not the only thing that should be
> >> > described via these PNP0C02 devices. *All* non-enumerable resources
> >> > should be described by the _CRS method of some ACPI device. Here's a
> >> > sample from my laptop:
> >> >
> >> > PCI: MMCONFIG for domain 0000 [bus 00-3f] at [mem 0xf8000000-0xfbffffff] (base 0xf8000000)
> >> > system 00:01: [io 0x1800-0x189f] could not be reserved
> >> > system 00:01: [io 0x0800-0x087f] has been reserved
> >> > system 00:01: [io 0x0880-0x08ff] has been reserved
> >> > system 00:01: [io 0x0900-0x097f] has been reserved
> >> > system 00:01: [io 0x0980-0x09ff] has been reserved
> >> > system 00:01: [io 0x0a00-0x0a7f] has been reserved
> >> > system 00:01: [io 0x0a80-0x0aff] has been reserved
> >> > system 00:01: [io 0x0b00-0x0b7f] has been reserved
> >> > system 00:01: [io 0x0b80-0x0bff] has been reserved
> >> > system 00:01: [io 0x15e0-0x15ef] has been reserved
> >> > system 00:01: [io 0x1600-0x167f] has been reserved
> >> > system 00:01: [io 0x1640-0x165f] has been reserved
> >> > system 00:01: [mem 0xf8000000-0xfbffffff] could not be reserved
> >> > system 00:01: [mem 0xfed10000-0xfed13fff] has been reserved
> >> > system 00:01: [mem 0xfed18000-0xfed18fff] has been reserved
> >> > system 00:01: [mem 0xfed19000-0xfed19fff] has been reserved
> >> > system 00:01: [mem 0xfeb00000-0xfebfffff] has been reserved
> >> > system 00:01: [mem 0xfed20000-0xfed3ffff] has been reserved
> >> > system 00:01: [mem 0xfed90000-0xfed93fff] has been reserved
> >> > system 00:01: [mem 0xf7fe0000-0xf7ffffff] has been reserved
> >> > system 00:01: Plug and Play ACPI device, IDs PNP0c02 (active)
> >> >
> >> > Do you have firmware in the field that may not get updated? If so,
> >> > I'd like to see the whole solution for that firmware, including the
> >> > MCFG quirk (which tells the PCI core where the ECAM region is) and
> >> > whatever PNP0C02 quirk you figure out to actually reserve the region.
> >> >
> >> > I proposed a PNP0C02 quirk to Duc along these lines of the below. I
> >> > don't actually know if it's feasible, but it didn't look as bad as I
> >> > expected, so I'd kind of like somebody to try it out. I think you
> >> > would have to call this via a DMI hook (do you have DMI on arm64?),
> >> > maybe from pnp_init() or similar.
> >>
> >> We do have SMBIOS/DMI on arm64, but we have been successful so far not
> >> to rely on it for quirks, and we'd very much like to keep it that way.
> >>
> >> Since this ACPI _CRS method has nothing to do with SMBIOS/DMI, surely
> >> there is a better way to wire up the reservation code to the
> >> information exposed by ACPI?
> >
> > I'm open to other ways, feel free to propose one :)
> >
> > If you do a quirk, you need some way to identify the machine/firmware
> > combination, because you don't want to apply the quirk on every
> > machine. You're trying to work around a firmware issue, so you
> > probably want something tied to the firmware version. On x86, that's
> > typically done with DMI.
> >
>
> I think I misunderstood the purpose of the example: that should only
> be necessary if the _CRS methods are missing from the firmware, right?
> If we update the firmware to cover all non-enumerable resources by
> such a method, we shouldn't need any such quirks at all IIUC
Right: if the firmware provides a PNP0C02 device with _CRS that
includes the ECAM area, we don't need any PNP/ACPI quirks. We will
still need the MCFG quirks since the hardware doesn't fully support
ECAM.
For the PNP/ACPI quirks, there are two interesting cases:
1) Firmware provides a PNP0C02 device, but its _CRS doesn't include
the ECAM space, and
2) Firmware doesn't provide a PNP0C02 device at all.
For case 1, we could consider adding the ECAM space to the existing
device. This is essentially what quirk_amd_mmconfig_area() does.
For case 2, we would have to fabricate the PNP0C02 device itself, then
add the ECAM space to it. I don't think there's any existing code
that does this, so this is what the example I proposed in this thread
does.
One could argue that it might be cleaner to use case 2 instead of the
case 1 approach because it avoids mucking with an ACPI device from
firmware. For devices that support _SRS, case 1 might break things
because _CRS and _SRS are supposed to use the same resource descriptor
buffer, and if we add resources the firmware doesn't know about, I
don't think we'll encode the _SRS buffer correctly. But this is only
a theoretical risk because we basically never use _SRS today.
In either case, there has to be a mechanism to do the quirk only on
the machine/firmware that needs it, of course.
Bjorn
^ permalink raw reply
* [PATCH v2 2/6] mfd: dt: Add bindings for the Aspeed SoC Display Controller (GFX)
From: Rob Herring @ 2016-11-10 17:40 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CACPK8XeG2WnpNuHUf9VD+mZenWboq-4wei=LOkN4qVR72QbGXQ@mail.gmail.com>
On Wed, Nov 9, 2016 at 9:19 PM, Joel Stanley <joel@jms.id.au> wrote:
> On Thu, Nov 10, 2016 at 4:56 AM, Rob Herring <robh@kernel.org> wrote:
>> On Thu, Nov 03, 2016 at 01:07:57AM +1030, Andrew Jeffery wrote:
>>> The Aspeed SoC Display Controller is presented as a syscon device to
>>> arbitrate access by display and pinmux drivers. Video pinmux
>>> configuration on fifth generation SoCs depends on bits in both the
>>> System Control Unit and the Display Controller.
>>>
>>> Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
>>> ---
>>> Documentation/devicetree/bindings/mfd/aspeed-gfx.txt | 17 +++++++++++++++++
>>
>> The register space can't be split to 2 nodes?
>
> Do you mean splitting the GFX IP and enable register into two nodes?
>
> We can't. Pinmux needs to check bit 6 and 7 in GFX064, which is in the
> middle the IP block:
>
> GFX060: CRT Control Register I
> GFX064: CRT Control Register II
> GFX068: CRT Status Register
> GFX06C: CRT Misc Setting Register
Okay.
>>> +The Aspeed SoC Display Controller primarily does as its name suggests, but also
>>> +participates in pinmux requests on the g5 SoCs. It is therefore considered a
>>> +syscon device.
>>> +
>>> +Required properties:
>>> +- compatible: "aspeed,ast2500-gfx", "syscon"
>>
>> I think perhaps we should drop the syscon here and the driver should
>> just register as a syscon.
>
> We want the regmap to be present whenever the GFX driver or pinmux is
> loaded. If we register it in pinmux but chose to not build in that
> driver, we lack the regmap. Same for the case where a user builds in
> the GFX driver and not pinmux. I think this means we want the syscon
> compatible string, unless my understanding is wrong?
Right.
Acked-by: Rob Herring <robh@kernel.org>
Rob
^ permalink raw reply
* [PATCH v6 2/9] drm/hisilicon/hibmc: Add video memory management
From: Sean Paul @ 2016-11-10 17:35 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1477639682-22520-3-git-send-email-zourongrong@gmail.com>
On Fri, Oct 28, 2016 at 3:27 AM, Rongrong Zou <zourongrong@gmail.com> wrote:
> Hibmc have 32m video memory which can be accessed through PCIe by host,
> we use ttm to manage these memory.
>
> Signed-off-by: Rongrong Zou <zourongrong@gmail.com>
> ---
> drivers/gpu/drm/hisilicon/hibmc/Kconfig | 1 +
> drivers/gpu/drm/hisilicon/hibmc/Makefile | 2 +-
> drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 12 +
> drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 46 +++
> drivers/gpu/drm/hisilicon/hibmc/hibmc_ttm.c | 490 ++++++++++++++++++++++++
> 5 files changed, 550 insertions(+), 1 deletion(-)
> create mode 100644 drivers/gpu/drm/hisilicon/hibmc/hibmc_ttm.c
>
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/Kconfig b/drivers/gpu/drm/hisilicon/hibmc/Kconfig
> index a9af90d..bcb8c18 100644
> --- a/drivers/gpu/drm/hisilicon/hibmc/Kconfig
> +++ b/drivers/gpu/drm/hisilicon/hibmc/Kconfig
> @@ -1,6 +1,7 @@
> config DRM_HISI_HIBMC
> tristate "DRM Support for Hisilicon Hibmc"
> depends on DRM && PCI
> + select DRM_TTM
>
> help
> Choose this option if you have a Hisilicon Hibmc soc chipset.
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/Makefile b/drivers/gpu/drm/hisilicon/hibmc/Makefile
> index 97cf4a0..d5c40b8 100644
> --- a/drivers/gpu/drm/hisilicon/hibmc/Makefile
> +++ b/drivers/gpu/drm/hisilicon/hibmc/Makefile
> @@ -1,5 +1,5 @@
> ccflags-y := -Iinclude/drm
> -hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_power.o
> +hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_power.o hibmc_ttm.o
>
> obj-$(CONFIG_DRM_HISI_HIBMC) +=hibmc-drm.o
> #obj-y += hibmc-drm.o
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c
> index 4669d42..81f4301 100644
> --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c
> +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c
> @@ -31,6 +31,7 @@
> #ifdef CONFIG_COMPAT
> .compat_ioctl = drm_compat_ioctl,
> #endif
> + .mmap = hibmc_mmap,
> .poll = drm_poll,
> .read = drm_read,
> .llseek = no_llseek,
> @@ -46,6 +47,8 @@ static void hibmc_disable_vblank(struct drm_device *dev, unsigned int pipe)
> }
>
> static struct drm_driver hibmc_driver = {
> + .driver_features = DRIVER_GEM,
> +
nit: extra space
> .fops = &hibmc_fops,
> .name = "hibmc",
> .date = "20160828",
> @@ -55,6 +58,10 @@ static void hibmc_disable_vblank(struct drm_device *dev, unsigned int pipe)
> .get_vblank_counter = drm_vblank_no_hw_counter,
> .enable_vblank = hibmc_enable_vblank,
> .disable_vblank = hibmc_disable_vblank,
> + .gem_free_object_unlocked = hibmc_gem_free_object,
> + .dumb_create = hibmc_dumb_create,
> + .dumb_map_offset = hibmc_dumb_mmap_offset,
> + .dumb_destroy = drm_gem_dumb_destroy,
> };
>
> static int hibmc_pm_suspend(struct device *dev)
> @@ -163,6 +170,7 @@ static int hibmc_unload(struct drm_device *dev)
> {
> struct hibmc_drm_device *hidev = dev->dev_private;
>
> + hibmc_mm_fini(hidev);
> hibmc_hw_fini(hidev);
> dev->dev_private = NULL;
> return 0;
> @@ -183,6 +191,10 @@ static int hibmc_load(struct drm_device *dev, unsigned long flags)
> if (ret)
> goto err;
>
> + ret = hibmc_mm_init(hidev);
> + if (ret)
> + goto err;
> +
> return 0;
>
> err:
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h
> index 0037341..db8d80e 100644
> --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h
> +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h
> @@ -20,6 +20,8 @@
> #define HIBMC_DRM_DRV_H
>
> #include <drm/drmP.h>
> +#include <drm/ttm/ttm_bo_driver.h>
> +#include <drm/drm_gem.h>
nit: alphabetize
>
> struct hibmc_drm_device {
> /* hw */
> @@ -30,6 +32,50 @@ struct hibmc_drm_device {
>
> /* drm */
> struct drm_device *dev;
> +
> + /* ttm */
> + struct {
> + struct drm_global_reference mem_global_ref;
> + struct ttm_bo_global_ref bo_global_ref;
> + struct ttm_bo_device bdev;
> + bool initialized;
> + } ttm;
I don't think you gain anything other than keystrokes from the substruct
> +
> + bool mm_inited;
> };
>
> +struct hibmc_bo {
> + struct ttm_buffer_object bo;
> + struct ttm_placement placement;
> + struct ttm_bo_kmap_obj kmap;
> + struct drm_gem_object gem;
> + struct ttm_place placements[3];
> + int pin_count;
> +};
> +
> +static inline struct hibmc_bo *hibmc_bo(struct ttm_buffer_object *bo)
> +{
> + return container_of(bo, struct hibmc_bo, bo);
> +}
> +
> +static inline struct hibmc_bo *gem_to_hibmc_bo(struct drm_gem_object *gem)
> +{
> + return container_of(gem, struct hibmc_bo, gem);
> +}
> +
> +#define DRM_FILE_PAGE_OFFSET (0x100000000ULL >> PAGE_SHIFT)
Hide this in ttm.c
> +
> +int hibmc_gem_create(struct drm_device *dev, u32 size, bool iskernel,
> + struct drm_gem_object **obj);
> +
> +int hibmc_mm_init(struct hibmc_drm_device *hibmc);
> +void hibmc_mm_fini(struct hibmc_drm_device *hibmc);
> +int hibmc_bo_pin(struct hibmc_bo *bo, u32 pl_flag, u64 *gpu_addr);
> +void hibmc_gem_free_object(struct drm_gem_object *obj);
> +int hibmc_dumb_create(struct drm_file *file, struct drm_device *dev,
> + struct drm_mode_create_dumb *args);
> +int hibmc_dumb_mmap_offset(struct drm_file *file, struct drm_device *dev,
> + u32 handle, u64 *offset);
> +int hibmc_mmap(struct file *filp, struct vm_area_struct *vma);
> +
> #endif
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_ttm.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_ttm.c
> new file mode 100644
> index 0000000..0802ebd
> --- /dev/null
> +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_ttm.c
> @@ -0,0 +1,490 @@
> +/* Hisilicon Hibmc SoC drm driver
> + *
> + * Based on the bochs drm driver.
> + *
> + * Copyright (c) 2016 Huawei Limited.
> + *
> + * Author:
> + * Rongrong Zou <zourongrong@huawei.com>
> + * Rongrong Zou <zourongrong@gmail.com>
> + * Jianhua Li <lijianhua@huawei.com>
> + *
> + * 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.
> + *
> + */
> +
> +#include "hibmc_drm_drv.h"
> +#include <ttm/ttm_page_alloc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_atomic_helper.h>
> +
> +static inline struct hibmc_drm_device *
> +hibmc_bdev(struct ttm_bo_device *bd)
> +{
> + return container_of(bd, struct hibmc_drm_device, ttm.bdev);
> +}
> +
> +static int
> +hibmc_ttm_mem_global_init(struct drm_global_reference *ref)
> +{
> + return ttm_mem_global_init(ref->object);
> +}
> +
> +static void
> +hibmc_ttm_mem_global_release(struct drm_global_reference *ref)
> +{
> + ttm_mem_global_release(ref->object);
> +}
> +
> +static int hibmc_ttm_global_init(struct hibmc_drm_device *hibmc)
> +{
> + struct drm_global_reference *global_ref;
> + int r;
nit: try not to use one character variable names unless it's for the
purpose of a loop (ie: i,j). You also use ret elsewhere in the driver,
so it'd be nice to remain consistent
> +
> + global_ref = &hibmc->ttm.mem_global_ref;
I think using the global_ref local obfuscates what you're doing here.
It saves you 6 characters while typing, but adds a layer of
indirection for all future readers.
> + global_ref->global_type = DRM_GLOBAL_TTM_MEM;
> + global_ref->size = sizeof(struct ttm_mem_global);
> + global_ref->init = &hibmc_ttm_mem_global_init;
> + global_ref->release = &hibmc_ttm_mem_global_release;
> + r = drm_global_item_ref(global_ref);
> + if (r != 0) {
nit: if (r)
> + DRM_ERROR("Failed setting up TTM memory accounting subsystem.\n"
> + );
Breaking up the line for one character is probably not worthwhile, and
you should really print the error. How about:
DRM_ERROR("Could not get ref on ttm global ret=%d.\n", ret);
> + return r;
> + }
> +
> + hibmc->ttm.bo_global_ref.mem_glob =
> + hibmc->ttm.mem_global_ref.object;
> + global_ref = &hibmc->ttm.bo_global_ref.ref;
> + global_ref->global_type = DRM_GLOBAL_TTM_BO;
> + global_ref->size = sizeof(struct ttm_bo_global);
> + global_ref->init = &ttm_bo_global_init;
> + global_ref->release = &ttm_bo_global_release;
> + r = drm_global_item_ref(global_ref);
> + if (r != 0) {
> + DRM_ERROR("Failed setting up TTM BO subsystem.\n");
> + drm_global_item_unref(&hibmc->ttm.mem_global_ref);
> + return r;
> + }
> + return 0;
> +}
> +
> +static void
> +hibmc_ttm_global_release(struct hibmc_drm_device *hibmc)
> +{
> + if (!hibmc->ttm.mem_global_ref.release)
Are you actually hitting this condition? This seems like it's papering
over something else.
> + return;
> +
> + drm_global_item_unref(&hibmc->ttm.bo_global_ref.ref);
> + drm_global_item_unref(&hibmc->ttm.mem_global_ref);
> + hibmc->ttm.mem_global_ref.release = NULL;
> +}
> +
> +static void hibmc_bo_ttm_destroy(struct ttm_buffer_object *tbo)
> +{
> + struct hibmc_bo *bo;
> +
> + bo = container_of(tbo, struct hibmc_bo, bo);
nit: No need to split this into a separate line.
> +
> + drm_gem_object_release(&bo->gem);
> + kfree(bo);
> +}
> +
> +static bool hibmc_ttm_bo_is_hibmc_bo(struct ttm_buffer_object *bo)
> +{
> + if (bo->destroy == &hibmc_bo_ttm_destroy)
> + return true;
> + return false;
return bo->destroy == &hibmc_bo_ttm_destroy;
> +}
> +
> +static int
> +hibmc_bo_init_mem_type(struct ttm_bo_device *bdev, u32 type,
> + struct ttm_mem_type_manager *man)
> +{
> + switch (type) {
> + case TTM_PL_SYSTEM:
> + man->flags = TTM_MEMTYPE_FLAG_MAPPABLE;
> + man->available_caching = TTM_PL_MASK_CACHING;
> + man->default_caching = TTM_PL_FLAG_CACHED;
> + break;
> + case TTM_PL_VRAM:
> + man->func = &ttm_bo_manager_func;
> + man->flags = TTM_MEMTYPE_FLAG_FIXED |
> + TTM_MEMTYPE_FLAG_MAPPABLE;
> + man->available_caching = TTM_PL_FLAG_UNCACHED |
> + TTM_PL_FLAG_WC;
> + man->default_caching = TTM_PL_FLAG_WC;
> + break;
> + default:
> + DRM_ERROR("Unsupported memory type %u\n", type);
> + return -EINVAL;
> + }
> + return 0;
> +}
> +
> +void hibmc_ttm_placement(struct hibmc_bo *bo, int domain)
> +{
> + u32 c = 0;
Can you please use a more descriptive name than 'c'?
> + u32 i;
> +
> + bo->placement.placement = bo->placements;
> + bo->placement.busy_placement = bo->placements;
> + if (domain & TTM_PL_FLAG_VRAM)
> + bo->placements[c++].flags = TTM_PL_FLAG_WC |
> + TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_VRAM;
nit: you're alignment is off here and below
> + if (domain & TTM_PL_FLAG_SYSTEM)
> + bo->placements[c++].flags = TTM_PL_MASK_CACHING |
> + TTM_PL_FLAG_SYSTEM;
> + if (!c)
> + bo->placements[c++].flags = TTM_PL_MASK_CACHING |
> + TTM_PL_FLAG_SYSTEM;
> +
> + bo->placement.num_placement = c;
> + bo->placement.num_busy_placement = c;
> + for (i = 0; i < c; ++i) {
nit: we tend towards post-increment in kernel
> + bo->placements[i].fpfn = 0;
> + bo->placements[i].lpfn = 0;
> + }
> +}
> +
> +static void
> +hibmc_bo_evict_flags(struct ttm_buffer_object *bo, struct ttm_placement *pl)
> +{
> + struct hibmc_bo *hibmcbo = hibmc_bo(bo);
> +
> + if (!hibmc_ttm_bo_is_hibmc_bo(bo))
> + return;
> +
> + hibmc_ttm_placement(hibmcbo, TTM_PL_FLAG_SYSTEM);
> + *pl = hibmcbo->placement;
> +}
> +
> +static int hibmc_bo_verify_access(struct ttm_buffer_object *bo,
> + struct file *filp)
> +{
> + struct hibmc_bo *hibmcbo = hibmc_bo(bo);
> +
> + return drm_vma_node_verify_access(&hibmcbo->gem.vma_node,
> + filp->private_data);
> +}
> +
> +static int hibmc_ttm_io_mem_reserve(struct ttm_bo_device *bdev,
> + struct ttm_mem_reg *mem)
> +{
> + struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type];
> + struct hibmc_drm_device *hibmc = hibmc_bdev(bdev);
> +
> + mem->bus.addr = NULL;
> + mem->bus.offset = 0;
> + mem->bus.size = mem->num_pages << PAGE_SHIFT;
> + mem->bus.base = 0;
> + mem->bus.is_iomem = false;
> + if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE))
> + return -EINVAL;
> + switch (mem->mem_type) {
> + case TTM_PL_SYSTEM:
> + /* system memory */
> + return 0;
> + case TTM_PL_VRAM:
> + mem->bus.offset = mem->start << PAGE_SHIFT;
> + mem->bus.base = pci_resource_start(hibmc->dev->pdev, 0);
> + mem->bus.is_iomem = true;
> + break;
> + default:
> + return -EINVAL;
> + }
> + return 0;
> +}
> +
> +static void hibmc_ttm_io_mem_free(struct ttm_bo_device *bdev,
> + struct ttm_mem_reg *mem)
> +{
> +}
No need to stub this, the caller does a NULL-check before invoking
> +
> +static void hibmc_ttm_backend_destroy(struct ttm_tt *tt)
> +{
> + ttm_tt_fini(tt);
> + kfree(tt);
> +}
> +
> +static struct ttm_backend_func hibmc_tt_backend_func = {
> + .destroy = &hibmc_ttm_backend_destroy,
> +};
> +
> +static struct ttm_tt *hibmc_ttm_tt_create(struct ttm_bo_device *bdev,
> + unsigned long size,
> + u32 page_flags,
> + struct page *dummy_read_page)
> +{
> + struct ttm_tt *tt;
> +
> + tt = kzalloc(sizeof(*tt), GFP_KERNEL);
> + if (!tt)
Print error
> + return NULL;
> + tt->func = &hibmc_tt_backend_func;
> + if (ttm_tt_init(tt, bdev, size, page_flags, dummy_read_page)) {
Here too?
> + kfree(tt);
> + return NULL;
> + }
> + return tt;
> +}
> +
> +static int hibmc_ttm_tt_populate(struct ttm_tt *ttm)
> +{
> + return ttm_pool_populate(ttm);
> +}
> +
> +static void hibmc_ttm_tt_unpopulate(struct ttm_tt *ttm)
> +{
> + ttm_pool_unpopulate(ttm);
> +}
> +
> +struct ttm_bo_driver hibmc_bo_driver = {
> + .ttm_tt_create = hibmc_ttm_tt_create,
> + .ttm_tt_populate = hibmc_ttm_tt_populate,
> + .ttm_tt_unpopulate = hibmc_ttm_tt_unpopulate,
> + .init_mem_type = hibmc_bo_init_mem_type,
> + .evict_flags = hibmc_bo_evict_flags,
> + .move = NULL,
> + .verify_access = hibmc_bo_verify_access,
> + .io_mem_reserve = &hibmc_ttm_io_mem_reserve,
> + .io_mem_free = &hibmc_ttm_io_mem_free,
> + .lru_tail = &ttm_bo_default_lru_tail,
> + .swap_lru_tail = &ttm_bo_default_swap_lru_tail,
> +};
> +
> +int hibmc_mm_init(struct hibmc_drm_device *hibmc)
> +{
> + int ret;
> + struct drm_device *dev = hibmc->dev;
> + struct ttm_bo_device *bdev = &hibmc->ttm.bdev;
> +
> + ret = hibmc_ttm_global_init(hibmc);
> + if (ret)
> + return ret;
> +
> + ret = ttm_bo_device_init(&hibmc->ttm.bdev,
> + hibmc->ttm.bo_global_ref.ref.object,
> + &hibmc_bo_driver,
> + dev->anon_inode->i_mapping,
> + DRM_FILE_PAGE_OFFSET,
> + true);
> + if (ret) {
Call hibmc_ttm_global_release here?
> + DRM_ERROR("Error initialising bo driver; %d\n", ret);
> + return ret;
> + }
> +
> + ret = ttm_bo_init_mm(bdev, TTM_PL_VRAM,
> + hibmc->fb_size >> PAGE_SHIFT);
> + if (ret) {
Clean up here as well?
> + DRM_ERROR("Failed ttm VRAM init: %d\n", ret);
> + return ret;
> + }
> +
> + hibmc->mm_inited = true;
> + return 0;
> +}
> +
> +void hibmc_mm_fini(struct hibmc_drm_device *hibmc)
> +{
> + if (!hibmc->mm_inited)
> + return;
> +
> + ttm_bo_device_release(&hibmc->ttm.bdev);
> + hibmc_ttm_global_release(hibmc);
> + hibmc->mm_inited = false;
> +}
> +
> +int hibmc_bo_create(struct drm_device *dev, int size, int align,
> + u32 flags, struct hibmc_bo **phibmcbo)
> +{
> + struct hibmc_drm_device *hibmc = dev->dev_private;
> + struct hibmc_bo *hibmcbo;
> + size_t acc_size;
> + int ret;
> +
> + hibmcbo = kzalloc(sizeof(*hibmcbo), GFP_KERNEL);
> + if (!hibmcbo)
> + return -ENOMEM;
> +
> + ret = drm_gem_object_init(dev, &hibmcbo->gem, size);
> + if (ret) {
> + kfree(hibmcbo);
> + return ret;
> + }
> +
> + hibmcbo->bo.bdev = &hibmc->ttm.bdev;
> +
> + hibmc_ttm_placement(hibmcbo, TTM_PL_FLAG_VRAM | TTM_PL_FLAG_SYSTEM);
> +
> + acc_size = ttm_bo_dma_acc_size(&hibmc->ttm.bdev, size,
> + sizeof(struct hibmc_bo));
> +
> + ret = ttm_bo_init(&hibmc->ttm.bdev, &hibmcbo->bo, size,
> + ttm_bo_type_device, &hibmcbo->placement,
> + align >> PAGE_SHIFT, false, NULL, acc_size,
> + NULL, NULL, hibmc_bo_ttm_destroy);
> + if (ret)
Missing hibmcbo clean up here
> + return ret;
> +
> + *phibmcbo = hibmcbo;
> + return 0;
> +}
> +
> +static inline u64 hibmc_bo_gpu_offset(struct hibmc_bo *bo)
> +{
> + return bo->bo.offset;
> +}
I don't think this function provides any value
> +
> +int hibmc_bo_pin(struct hibmc_bo *bo, u32 pl_flag, u64 *gpu_addr)
> +{
> + int i, ret;
> +
> + if (bo->pin_count) {
> + bo->pin_count++;
> + if (gpu_addr)
> + *gpu_addr = hibmc_bo_gpu_offset(bo);
Are you missing a return here?
> + }
> +
> + hibmc_ttm_placement(bo, pl_flag);
> + for (i = 0; i < bo->placement.num_placement; i++)
> + bo->placements[i].flags |= TTM_PL_FLAG_NO_EVICT;
> + ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false);
> + if (ret)
> + return ret;
> +
> + bo->pin_count = 1;
> + if (gpu_addr)
> + *gpu_addr = hibmc_bo_gpu_offset(bo);
> + return 0;
> +}
> +
> +int hibmc_bo_push_sysram(struct hibmc_bo *bo)
> +{
> + int i, ret;
> +
> + if (!bo->pin_count) {
> + DRM_ERROR("unpin bad %p\n", bo);
> + return 0;
> + }
> + bo->pin_count--;
> + if (bo->pin_count)
> + return 0;
> +
> + if (bo->kmap.virtual)
ttm_bo_kunmap already does this check so you don't have to
> + ttm_bo_kunmap(&bo->kmap);
> +
> + hibmc_ttm_placement(bo, TTM_PL_FLAG_SYSTEM);
> + for (i = 0; i < bo->placement.num_placement ; i++)
> + bo->placements[i].flags |= TTM_PL_FLAG_NO_EVICT;
> +
> + ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false);
> + if (ret) {
> + DRM_ERROR("pushing to VRAM failed\n");
Print ret
> + return ret;
> + }
> + return 0;
> +}
> +
> +int hibmc_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> + struct drm_file *file_priv;
> + struct hibmc_drm_device *hibmc;
> +
> + if (unlikely(vma->vm_pgoff < DRM_FILE_PAGE_OFFSET))
> + return -EINVAL;
> +
> + file_priv = filp->private_data;
> + hibmc = file_priv->minor->dev->dev_private;
> + return ttm_bo_mmap(filp, vma, &hibmc->ttm.bdev);
> +}
> +
> +int hibmc_gem_create(struct drm_device *dev, u32 size, bool iskernel,
> + struct drm_gem_object **obj)
> +{
> + struct hibmc_bo *hibmcbo;
> + int ret;
> +
> + *obj = NULL;
> +
> + size = PAGE_ALIGN(size);
> + if (size == 0)
Print error
> + return -EINVAL;
> +
> + ret = hibmc_bo_create(dev, size, 0, 0, &hibmcbo);
> + if (ret) {
> + if (ret != -ERESTARTSYS)
> + DRM_ERROR("failed to allocate GEM object\n");
Print ret
> + return ret;
> + }
> + *obj = &hibmcbo->gem;
> + return 0;
> +}
> +
> +int hibmc_dumb_create(struct drm_file *file, struct drm_device *dev,
> + struct drm_mode_create_dumb *args)
> +{
> + struct drm_gem_object *gobj;
> + u32 handle;
> + int ret;
> +
> + args->pitch = ALIGN(args->width * ((args->bpp + 7) / 8), 16);
What's up with the bpp + 7 here? Perhaps you're looking for DIV_ROUND_UP?
> + args->size = args->pitch * args->height;
> +
> + ret = hibmc_gem_create(dev, args->size, false,
> + &gobj);
> + if (ret)
> + return ret;
> +
> + ret = drm_gem_handle_create(file, gobj, &handle);
> + drm_gem_object_unreference_unlocked(gobj);
> + if (ret)
Print error here
> + return ret;
> +
> + args->handle = handle;
> + return 0;
> +}
> +
> +static void hibmc_bo_unref(struct hibmc_bo **bo)
> +{
> + struct ttm_buffer_object *tbo;
> +
> + if ((*bo) == NULL)
> + return;
> +
> + tbo = &((*bo)->bo);
> + ttm_bo_unref(&tbo);
> + *bo = NULL;
> +}
> +
> +void hibmc_gem_free_object(struct drm_gem_object *obj)
> +{
> + struct hibmc_bo *hibmcbo = gem_to_hibmc_bo(obj);
> +
> + hibmc_bo_unref(&hibmcbo);
> +}
> +
> +static u64 hibmc_bo_mmap_offset(struct hibmc_bo *bo)
> +{
> + return drm_vma_node_offset_addr(&bo->bo.vma_node);
> +}
> +
> +int hibmc_dumb_mmap_offset(struct drm_file *file, struct drm_device *dev,
> + u32 handle, u64 *offset)
> +{
> + struct drm_gem_object *obj;
> + struct hibmc_bo *bo;
> +
> + obj = drm_gem_object_lookup(file, handle);
> + if (!obj)
> + return -ENOENT;
> +
> + bo = gem_to_hibmc_bo(obj);
> + *offset = hibmc_bo_mmap_offset(bo);
> +
> + drm_gem_object_unreference_unlocked(obj);
> + return 0;
> +}
> --
> 1.9.1
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [PATCH v6 1/9] drm/hisilicon/hibmc: Add hisilicon hibmc drm master driver
From: Sean Paul @ 2016-11-10 17:35 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1477639682-22520-2-git-send-email-zourongrong@gmail.com>
On Fri, Oct 28, 2016 at 3:27 AM, Rongrong Zou <zourongrong@gmail.com> wrote:
> Add DRM master driver for Hisilicon Hibmc SoC which used for
> Out-of-band management. Blow is the general hardware connection,
> both the Hibmc and the host CPU are on the same mother board.
>
> +----------+ +----------+
> | | PCIe | Hibmc |
> |host CPU( |<----->| display |
> |arm64,x86)| |subsystem |
> +----------+ +----------+
>
> Signed-off-by: Rongrong Zou <zourongrong@gmail.com>
> ---
> drivers/gpu/drm/hisilicon/Kconfig | 1 +
> drivers/gpu/drm/hisilicon/Makefile | 1 +
> drivers/gpu/drm/hisilicon/hibmc/Kconfig | 7 +
> drivers/gpu/drm/hisilicon/hibmc/Makefile | 5 +
> drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 269 ++++++++++++++++++++++
> drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 35 +++
> drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_power.c | 85 +++++++
> drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_power.h | 28 +++
> drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_regs.h | 212 +++++++++++++++++
> 9 files changed, 643 insertions(+)
> create mode 100644 drivers/gpu/drm/hisilicon/hibmc/Kconfig
> create mode 100644 drivers/gpu/drm/hisilicon/hibmc/Makefile
> create mode 100644 drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c
> create mode 100644 drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h
> create mode 100644 drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_power.c
> create mode 100644 drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_power.h
> create mode 100644 drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_regs.h
>
> diff --git a/drivers/gpu/drm/hisilicon/Kconfig b/drivers/gpu/drm/hisilicon/Kconfig
> index 558c61b..2fd2724 100644
> --- a/drivers/gpu/drm/hisilicon/Kconfig
> +++ b/drivers/gpu/drm/hisilicon/Kconfig
> @@ -2,4 +2,5 @@
> # hisilicon drm device configuration.
> # Please keep this list sorted alphabetically
>
> +source "drivers/gpu/drm/hisilicon/hibmc/Kconfig"
> source "drivers/gpu/drm/hisilicon/kirin/Kconfig"
> diff --git a/drivers/gpu/drm/hisilicon/Makefile b/drivers/gpu/drm/hisilicon/Makefile
> index e3f6d49..c8155bf 100644
> --- a/drivers/gpu/drm/hisilicon/Makefile
> +++ b/drivers/gpu/drm/hisilicon/Makefile
> @@ -2,4 +2,5 @@
> # Makefile for hisilicon drm drivers.
> # Please keep this list sorted alphabetically
>
> +obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc/
> obj-$(CONFIG_DRM_HISI_KIRIN) += kirin/
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/Kconfig b/drivers/gpu/drm/hisilicon/hibmc/Kconfig
> new file mode 100644
> index 0000000..a9af90d
> --- /dev/null
> +++ b/drivers/gpu/drm/hisilicon/hibmc/Kconfig
> @@ -0,0 +1,7 @@
> +config DRM_HISI_HIBMC
> + tristate "DRM Support for Hisilicon Hibmc"
> + depends on DRM && PCI
> +
> + help
> + Choose this option if you have a Hisilicon Hibmc soc chipset.
> + If M is selected the module will be called hibmc-drm.
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/Makefile b/drivers/gpu/drm/hisilicon/hibmc/Makefile
> new file mode 100644
> index 0000000..97cf4a0
> --- /dev/null
> +++ b/drivers/gpu/drm/hisilicon/hibmc/Makefile
> @@ -0,0 +1,5 @@
> +ccflags-y := -Iinclude/drm
> +hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_power.o
> +
> +obj-$(CONFIG_DRM_HISI_HIBMC) +=hibmc-drm.o
nit: Improper spacing here
> +#obj-y += hibmc-drm.o
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c
> new file mode 100644
> index 0000000..4669d42
> --- /dev/null
> +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c
> @@ -0,0 +1,269 @@
> +/* Hisilicon Hibmc SoC drm driver
> + *
> + * Based on the bochs drm driver.
> + *
> + * Copyright (c) 2016 Huawei Limited.
> + *
> + * Author:
> + * Rongrong Zou <zourongrong@huawei.com>
> + * Rongrong Zou <zourongrong@gmail.com>
> + * Jianhua Li <lijianhua@huawei.com>
> + *
> + * 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.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/console.h>
> +
> +#include "hibmc_drm_drv.h"
> +#include "hibmc_drm_regs.h"
> +#include "hibmc_drm_power.h"
nit: Alphabetize headers
> +
> +static const struct file_operations hibmc_fops = {
> + .owner = THIS_MODULE,
> + .open = drm_open,
> + .release = drm_release,
> + .unlocked_ioctl = drm_ioctl,
> +#ifdef CONFIG_COMPAT
drm_compat_ioctl is now initialized to NULL, so you can remove the #ifdef
> + .compat_ioctl = drm_compat_ioctl,
> +#endif
> + .poll = drm_poll,
> + .read = drm_read,
> + .llseek = no_llseek,
> +};
> +
> +static int hibmc_enable_vblank(struct drm_device *dev, unsigned int pipe)
> +{
> + return 0;
> +}
> +
> +static void hibmc_disable_vblank(struct drm_device *dev, unsigned int pipe)
> +{
> +}
> +
> +static struct drm_driver hibmc_driver = {
> + .fops = &hibmc_fops,
> + .name = "hibmc",
> + .date = "20160828",
> + .desc = "hibmc drm driver",
> + .major = 1,
> + .minor = 0,
> + .get_vblank_counter = drm_vblank_no_hw_counter,
> + .enable_vblank = hibmc_enable_vblank,
> + .disable_vblank = hibmc_disable_vblank,
> +};
> +
> +static int hibmc_pm_suspend(struct device *dev)
> +{
> + return 0;
> +}
> +
> +static int hibmc_pm_resume(struct device *dev)
> +{
> + return 0;
> +}
> +
> +static const struct dev_pm_ops hibmc_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(hibmc_pm_suspend,
> + hibmc_pm_resume)
> +};
> +
> +static int hibmc_hw_config(struct hibmc_drm_device *hidev)
> +{
> + unsigned int reg;
> +
> + /* On hardware reset, power mode 0 is default. */
> + hibmc_set_power_mode(hidev, HIBMC_PW_MODE_CTL_MODE_MODE0);
> +
> + /* Enable display power gate & LOCALMEM power gate*/
> + reg = readl(hidev->mmio + HIBMC_CURRENT_GATE);
> + reg &= ~HIBMC_CURR_GATE_DISPLAY_MASK;
> + reg &= ~HIBMC_CURR_GATE_LOCALMEM_MASK;
> + reg |= HIBMC_CURR_GATE_DISPLAY(ON);
> + reg |= HIBMC_CURR_GATE_LOCALMEM(ON);
> +
> + hibmc_set_current_gate(hidev, reg);
> +
> + /* Reset the memory controller. If the memory controller
> + * is not reset in chip,the system might hang when sw accesses
> + * the memory.The memory should be resetted after
> + * changing the MXCLK.
> + */
> + reg = readl(hidev->mmio + HIBMC_MISC_CTRL);
> + reg &= ~HIBMC_MSCCTL_LOCALMEM_RESET_MASK;
> + reg |= HIBMC_MSCCTL_LOCALMEM_RESET(RESET);
> + writel(reg, hidev->mmio + HIBMC_MISC_CTRL);
> +
> + reg &= ~HIBMC_MSCCTL_LOCALMEM_RESET_MASK;
> + reg |= HIBMC_MSCCTL_LOCALMEM_RESET(NORMAL);
> +
> + writel(reg, hidev->mmio + HIBMC_MISC_CTRL);
> +
> + /* We can add more initialization as needed. */
> +
> + return 0;
Consider using void return type here to simplify error checking in the
caller, especially since it looks like you aren't checking the return
code, anyways :)
> +}
> +
> +static int hibmc_hw_map(struct hibmc_drm_device *hidev)
> +{
> + struct drm_device *dev = hidev->dev;
> + struct pci_dev *pdev = dev->pdev;
> + resource_size_t addr, size, ioaddr, iosize;
> +
> + ioaddr = pci_resource_start(pdev, 1);
> + iosize = MB(2);
> +
> + hidev->mmio = ioremap_nocache(ioaddr, iosize);
Use devm_ioremap_nocache to avoid managing the resource directly
> +
nit: extra space
> + if (!hidev->mmio) {
> + DRM_ERROR("Cannot map mmio region\n");
> + return -ENOMEM;
> + }
> +
> + addr = pci_resource_start(pdev, 0);
> + size = MB(32);
Pull size and iosize out into #defines with descriptive names
> +
> + hidev->fb_map = ioremap(addr, size);
Use devm_ioremap to avoid managing the resource directly.
> + if (!hidev->fb_map) {
> + DRM_ERROR("Cannot map framebuffer\n");
> + return -ENOMEM;
> + }
> + hidev->fb_base = addr;
> + hidev->fb_size = size;
> +
> + return 0;
> +}
> +
> +static void hibmc_hw_fini(struct hibmc_drm_device *hidev)
> +{
> + if (hidev->mmio)
> + iounmap(hidev->mmio);
> + if (hidev->fb_map)
> + iounmap(hidev->fb_map);
> +}
You don't need this function if you use the devm variants above
> +
> +static int hibmc_hw_init(struct hibmc_drm_device *hidev)
> +{
> + int ret;
> +
> + ret = hibmc_hw_map(hidev);
> + if (ret)
> + return ret;
> +
> + hibmc_hw_config(hidev);
> +
> + return 0;
> +}
> +
> +static int hibmc_unload(struct drm_device *dev)
> +{
> + struct hibmc_drm_device *hidev = dev->dev_private;
> +
> + hibmc_hw_fini(hidev);
> + dev->dev_private = NULL;
> + return 0;
> +}
> +
> +static int hibmc_load(struct drm_device *dev, unsigned long flags)
flags isn't used anywhere?
> +{
> + struct hibmc_drm_device *hidev;
> + int ret;
> +
> + hidev = devm_kzalloc(dev->dev, sizeof(*hidev), GFP_KERNEL);
> + if (!hidev)
Print error here?
> + return -ENOMEM;
> + dev->dev_private = hidev;
> + hidev->dev = dev;
> +
> + ret = hibmc_hw_init(hidev);
> + if (ret)
> + goto err;
> +
> + return 0;
> +
> +err:
> + hibmc_unload(dev);
> + DRM_ERROR("failed to initialize drm driver.\n");
Print the return value
> + return ret;
> +}
> +
> +static int hibmc_pci_probe(struct pci_dev *pdev,
> + const struct pci_device_id *ent)
> +{
> + struct drm_device *dev;
> + int ret;
> +
> + dev = drm_dev_alloc(&hibmc_driver, &pdev->dev);
> + if (!dev)
Print error here
> + return -ENOMEM;
> +
> + dev->pdev = pdev;
> + pci_set_drvdata(pdev, dev);
> +
> + ret = pci_enable_device(pdev);
> + if (ret)
and here, and in other failure paths
> + goto err_free;
> +
> + ret = hibmc_load(dev, 0);
> + if (ret)
> + goto err_disable;
> +
> + ret = drm_dev_register(dev, 0);
> + if (ret)
> + goto err_unload;
> +
> + return 0;
> +
> +err_unload:
> + hibmc_unload(dev);
> +err_disable:
> + pci_disable_device(pdev);
> +err_free:
> + drm_dev_unref(dev);
> +
> + return ret;
> +}
> +
> +static void hibmc_pci_remove(struct pci_dev *pdev)
> +{
> + struct drm_device *dev = pci_get_drvdata(pdev);
> +
> + drm_dev_unregister(dev);
> + hibmc_unload(dev);
> + drm_dev_unref(dev);
> +}
> +
> +static struct pci_device_id hibmc_pci_table[] = {
> + {0x19e5, 0x1711, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
> + {0,}
> +};
> +
> +static struct pci_driver hibmc_pci_driver = {
> + .name = "hibmc-drm",
> + .id_table = hibmc_pci_table,
> + .probe = hibmc_pci_probe,
> + .remove = hibmc_pci_remove,
> + .driver.pm = &hibmc_pm_ops,
> +};
> +
> +static int __init hibmc_init(void)
> +{
> + return pci_register_driver(&hibmc_pci_driver);
> +}
> +
> +static void __exit hibmc_exit(void)
> +{
> + return pci_unregister_driver(&hibmc_pci_driver);
> +}
> +
> +module_init(hibmc_init);
> +module_exit(hibmc_exit);
> +
> +MODULE_DEVICE_TABLE(pci, hibmc_pci_table);
> +MODULE_AUTHOR("RongrongZou <zourongrong@huawei.com>");
> +MODULE_DESCRIPTION("DRM Driver for Hisilicon Hibmc");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h
> new file mode 100644
> index 0000000..0037341
> --- /dev/null
> +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h
> @@ -0,0 +1,35 @@
> +/* Hisilicon Hibmc SoC drm driver
> + *
> + * Based on the bochs drm driver.
> + *
> + * Copyright (c) 2016 Huawei Limited.
> + *
> + * Author:
> + * Rongrong Zou <zourongrong@huawei.com>
> + * Rongrong Zou <zourongrong@gmail.com>
> + * Jianhua Li <lijianhua@huawei.com>
> + *
> + * 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.
> + *
> + */
> +
> +#ifndef HIBMC_DRM_DRV_H
> +#define HIBMC_DRM_DRV_H
> +
> +#include <drm/drmP.h>
> +
> +struct hibmc_drm_device {
nit: Calling this hibmc_drm_priv would probably make things easier to
read. When I read hibmc_drm_device, it makes me think that it's an
extension of drm_device, which this isn't.
> + /* hw */
> + void __iomem *mmio;
> + void __iomem *fb_map;
> + unsigned long fb_base;
> + unsigned long fb_size;
> +
> + /* drm */
> + struct drm_device *dev;
> +};
> +
> +#endif
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_power.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_power.c
> new file mode 100644
> index 0000000..1036542
> --- /dev/null
> +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_power.c
I don't think you need a new file for these functions. Just stash them
in hibmc_drm_drv.c
> @@ -0,0 +1,85 @@
> +/* Hisilicon Hibmc SoC drm driver
> + *
> + * Based on the bochs drm driver.
> + *
> + * Copyright (c) 2016 Huawei Limited.
> + *
> + * Author:
> + * Rongrong Zou <zourongrong@huawei.com>
> + * Rongrong Zou <zourongrong@gmail.com>
> + * Jianhua Li <lijianhua@huawei.com>
> + *
> + * 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.
> + *
> + */
> +
> +#include "hibmc_drm_drv.h"
> +#include "hibmc_drm_regs.h"
> +
> +/*
> + * It can operate in one of three modes: 0, 1 or Sleep.
> + */
> +void hibmc_set_power_mode(struct hibmc_drm_device *hidev,
> + unsigned int power_mode)
> +{
> + unsigned int control_value = 0;
> + void __iomem *mmio = hidev->mmio;
> +
> + if (power_mode > HIBMC_PW_MODE_CTL_MODE_SLEEP)
> + return;
> +
> + control_value = readl(mmio + HIBMC_POWER_MODE_CTRL);
> + control_value &= ~HIBMC_PW_MODE_CTL_MODE_MASK;
> +
> + control_value |= HIBMC_PW_MODE_CTL_MODE(power_mode) &
> + HIBMC_PW_MODE_CTL_MODE_MASK;
> +
> + /* Set up other fields in Power Control Register */
> + if (power_mode == HIBMC_PW_MODE_CTL_MODE_SLEEP) {
> + control_value &= ~HIBMC_PW_MODE_CTL_OSC_INPUT_MASK;
You do this in both branches of the conditional
> + control_value |= HIBMC_PW_MODE_CTL_OSC_INPUT(0) &
> + HIBMC_PW_MODE_CTL_OSC_INPUT_MASK;
> + } else {
> + control_value &= ~HIBMC_PW_MODE_CTL_OSC_INPUT_MASK;
> + control_value |= HIBMC_PW_MODE_CTL_OSC_INPUT(1) &
> + HIBMC_PW_MODE_CTL_OSC_INPUT_MASK;
> + }
I think you could simplify this by adding a new local.
unsigned int input = 1;
if (power_mode == HIBMC_PW_MODE_CTL_MODE_SLEEP)
input = 0;
control_value = readl(mmio + HIBMC_POWER_MODE_CTRL);
control_value &= ~(HIBMC_PW_MODE_CTL_MODE_MASK |
HIBMC_PW_MODE_CTL_OSC_INPUT_MASK);
control_value |= HIBMC_PW_MODE_CTL_MODE(power_mode) &
HIBMC_PW_MODE_CTL_MODE_MASK;
control_value |= HIBMC_PW_MODE_CTL_OSC_INPUT(input) &
HIBMC_PW_MODE_CTL_OSC_INPUT_MASK;
> + /* Program new power mode. */
> + writel(control_value, mmio + HIBMC_POWER_MODE_CTRL);
> +}
> +
> +static unsigned int hibmc_get_power_mode(struct hibmc_drm_device *hidev)
> +{
> + void __iomem *mmio = hidev->mmio;
> +
> + return (readl(mmio + HIBMC_POWER_MODE_CTRL) &
> + HIBMC_PW_MODE_CTL_MODE_MASK) >> HIBMC_PW_MODE_CTL_MODE_SHIFT;
nit: You're doing a lot of work in the return statement here.
> +}
> +
> +void hibmc_set_current_gate(struct hibmc_drm_device *hidev, unsigned int gate)
> +{
> + unsigned int gate_reg;
> + unsigned int mode;
> + void __iomem *mmio = hidev->mmio;
> +
> + /* Get current power mode. */
nit: try to avoid comments that don't add value
> + mode = hibmc_get_power_mode(hidev);
> +
> + switch (mode) {
> + case HIBMC_PW_MODE_CTL_MODE_MODE0:
> + gate_reg = HIBMC_MODE0_GATE;
> + break;
> +
> + case HIBMC_PW_MODE_CTL_MODE_MODE1:
> + gate_reg = HIBMC_MODE1_GATE;
> + break;
> +
> + default:
> + gate_reg = HIBMC_MODE0_GATE;
> + break;
> + }
> + writel(gate, mmio + gate_reg);
> +}
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_power.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_power.h
> new file mode 100644
> index 0000000..e20e1aa
> --- /dev/null
> +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_power.h
> @@ -0,0 +1,28 @@
> +/* Hisilicon Hibmc SoC drm driver
> + *
> + * Based on the bochs drm driver.
> + *
> + * Copyright (c) 2016 Huawei Limited.
> + *
> + * Author:
> + * Rongrong Zou <zourongrong@huawei.com>
> + * Rongrong Zou <zourongrong@gmail.com>
> + * Jianhua Li <lijianhua@huawei.com>
> + *
> + * 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.
> + *
> + */
> +
> +#ifndef HIBMC_DRM_POWER_H
> +#define HIBMC_DRM_POWER_H
> +
> +#include "hibmc_drm_drv.h"
> +
> +void hibmc_set_power_mode(struct hibmc_drm_device *hidev,
> + unsigned int power_mode);
> +void hibmc_set_current_gate(struct hibmc_drm_device *hidev,
> + unsigned int gate);
> +#endif
> diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_regs.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_regs.h
> new file mode 100644
> index 0000000..9c804ca
> --- /dev/null
> +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_regs.h
> @@ -0,0 +1,212 @@
> +/* Hisilicon Hibmc SoC drm driver
> + *
> + * Based on the bochs drm driver.
> + *
> + * Copyright (c) 2016 Huawei Limited.
> + *
> + * Author:
> + * Rongrong Zou <zourongrong@huawei.com>
> + * Rongrong Zou <zourongrong@gmail.com>
> + * Jianhua Li <lijianhua@huawei.com>
> + *
> + * 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.
> + *
> + */
> +
> +#ifndef HIBMC_DRM_HW_H
> +#define HIBMC_DRM_HW_H
> +
> +#define OFF 0
> +#define ON 1
> +#define DISABLE 0
> +#define ENABLE 1
These are not good names. I think you can probably hardcode the 0's
and 1's in the code instead of these. However, if you want to use
them, at least add a HIBMC_ prefix
> +
> +/* register definition */
> +#define HIBMC_MISC_CTRL 0x4
> +
> +#define HIBMC_MSCCTL_LOCALMEM_RESET(x) ((x) << 6)
> +#define HIBMC_MSCCTL_LOCALMEM_RESET_MASK 0x40
> +
> +#define RESET 0
> +#define NORMAL 1
Same naming nit here. Add a prefix
> +
> +#define HIBMC_CURRENT_GATE 0x000040
> +#define HIBMC_CURR_GATE_DISPLAY(x) ((x) << 2)
> +#define HIBMC_CURR_GATE_DISPLAY_MASK 0x4
> +
> +#define HIBMC_CURR_GATE_LOCALMEM(x) ((x) << 1)
> +#define HIBMC_CURR_GATE_LOCALMEM_MASK 0x2
> +
> +#define HIBMC_MODE0_GATE 0x000044
> +#define HIBMC_MODE1_GATE 0x000048
> +#define HIBMC_POWER_MODE_CTRL 0x00004C
> +
> +#define HIBMC_PW_MODE_CTL_OSC_INPUT(x) ((x) << 3)
> +#define HIBMC_PW_MODE_CTL_OSC_INPUT_MASK 0x8
> +
> +#define HIBMC_PW_MODE_CTL_MODE(x) ((x) << 0)
> +#define HIBMC_PW_MODE_CTL_MODE_MASK 0x03
> +#define HIBMC_PW_MODE_CTL_MODE_SHIFT 0
> +
> +#define HIBMC_PW_MODE_CTL_MODE_MODE0 0
> +#define HIBMC_PW_MODE_CTL_MODE_MODE1 1
> +#define HIBMC_PW_MODE_CTL_MODE_SLEEP 2
> +
> +#define HIBMC_PANEL_PLL_CTRL 0x00005C
> +#define HIBMC_CRT_PLL_CTRL 0x000060
> +
> +#define HIBMC_PLL_CTRL_BYPASS(x) ((x) << 18)
> +#define HIBMC_PLL_CTRL_BYPASS_MASK 0x40000
> +
> +#define HIBMC_PLL_CTRL_POWER(x) ((x) << 17)
> +#define HIBMC_PLL_CTRL_POWER_MASK 0x20000
> +
> +#define HIBMC_PLL_CTRL_INPUT(x) ((x) << 16)
> +#define HIBMC_PLL_CTRL_INPUT_MASK 0x10000
> +
> +#define OSC 0
Naming
> +#define TESTCLK 1
This doesn't seem to be used?
> +
> +#define HIBMC_PLL_CTRL_POD(x) ((x) << 14)
> +#define HIBMC_PLL_CTRL_POD_MASK 0xC000
> +
> +#define HIBMC_PLL_CTRL_OD(x) ((x) << 12)
> +#define HIBMC_PLL_CTRL_OD_MASK 0x3000
> +
> +#define HIBMC_PLL_CTRL_N(x) ((x) << 8)
> +#define HIBMC_PLL_CTRL_N_MASK 0xF00
> +
> +#define HIBMC_PLL_CTRL_M(x) ((x) << 0)
> +#define HIBMC_PLL_CTRL_M_MASK 0xFF
> +
> +#define HIBMC_CRT_DISP_CTL 0x80200
> +
> +#define HIBMC_CRT_DISP_CTL_CRTSELECT(x) ((x) << 25)
> +#define HIBMC_CRT_DISP_CTL_CRTSELECT_MASK 0x2000000
> +
> +#define CRTSELECT_VGA 0
> +#define CRTSELECT_CRT 1
> +
> +#define HIBMC_CRT_DISP_CTL_CLOCK_PHASE(x) ((x) << 14)
> +#define HIBMC_CRT_DISP_CTL_CLOCK_PHASE_MASK 0x4000
> +
> +#define PHASE_ACTIVE_HIGH 0
> +#define PHASE_ACTIVE_LOW 1
> +
> +#define HIBMC_CRT_DISP_CTL_VSYNC_PHASE(x) ((x) << 13)
> +#define HIBMC_CRT_DISP_CTL_VSYNC_PHASE_MASK 0x2000
> +
> +#define HIBMC_CRT_DISP_CTL_HSYNC_PHASE(x) ((x) << 12)
> +#define HIBMC_CRT_DISP_CTL_HSYNC_PHASE_MASK 0x1000
> +
> +#define HIBMC_CRT_DISP_CTL_TIMING(x) ((x) << 8)
> +#define HIBMC_CRT_DISP_CTL_TIMING_MASK 0x100
> +
> +#define HIBMC_CRT_DISP_CTL_PLANE(x) ((x) << 2)
> +#define HIBMC_CRT_DISP_CTL_PLANE_MASK 4
> +
> +#define HIBMC_CRT_DISP_CTL_FORMAT(x) ((x) << 0)
> +#define HIBMC_CRT_DISP_CTL_FORMAT_MASK 0x03
> +
> +#define HIBMC_CRT_FB_ADDRESS 0x080204
> +
> +#define HIBMC_CRT_FB_WIDTH 0x080208
> +#define HIBMC_CRT_FB_WIDTH_WIDTH(x) ((x) << 16)
> +#define HIBMC_CRT_FB_WIDTH_WIDTH_MASK 0x3FFF0000
> +#define HIBMC_CRT_FB_WIDTH_OFFS(x) ((x) << 0)
> +#define HIBMC_CRT_FB_WIDTH_OFFS_MASK 0x3FFF
> +
> +#define HIBMC_CRT_HORZ_TOTAL 0x08020C
> +#define HIBMC_CRT_HORZ_TOTAL_TOTAL(x) ((x) << 16)
> +#define HIBMC_CRT_HORZ_TOTAL_TOTAL_MASK 0xFFF0000
> +
> +#define HIBMC_CRT_HORZ_TOTAL_DISPLAY_END(x) ((x) << 0)
> +#define HIBMC_CRT_HORZ_TOTAL_DISPLAY_END_MASK 0xFFF
> +
> +#define HIBMC_CRT_HORZ_SYNC 0x080210
> +#define HIBMC_CRT_HORZ_SYNC_WIDTH(x) ((x) << 16)
> +#define HIBMC_CRT_HORZ_SYNC_WIDTH_MASK 0xFF0000
> +
> +#define HIBMC_CRT_HORZ_SYNC_START(x) ((x) << 0)
> +#define HIBMC_CRT_HORZ_SYNC_START_MASK 0xFFF
> +
> +#define HIBMC_CRT_VERT_TOTAL 0x080214
> +#define HIBMC_CRT_VERT_TOTAL_TOTAL(x) ((x) << 16)
> +#define HIBMC_CRT_VERT_TOTAL_TOTAL_MASK 0x7FFF0000
> +
> +#define HIBMC_CRT_VERT_TOTAL_DISPLAY_END(x) ((x) << 0)
> +#define HIBMC_CRT_VERT_TOTAL_DISPLAY_END_MASK 0x7FF
> +
> +#define HIBMC_CRT_VERT_SYNC 0x080218
> +#define HIBMC_CRT_VERT_SYNC_HEIGHT(x) ((x) << 16)
> +#define HIBMC_CRT_VERT_SYNC_HEIGHT_MASK 0x3F0000
> +
> +#define HIBMC_CRT_VERT_SYNC_START(x) ((x) << 0)
> +#define HIBMC_CRT_VERT_SYNC_START_MASK 0x7FF
> +
> +/* Auto Centering */
> +#define HIBMC_CRT_AUTO_CENTERING_TL 0x080280
> +#define HIBMC_CRT_AUTO_CENTERING_TL_TOP(x) ((x) << 16)
> +#define HIBMC_CRT_AUTO_CENTERING_TL_TOP_MSK 0x7FF0000
> +
> +#define HIBMC_CRT_AUTO_CENTERING_TL_LEFT(x) ((x) << 0)
> +#define HIBMC_CRT_AUTO_CENTERING_TL_LEFT_MSK 0x7FF
> +
> +#define HIBMC_CRT_AUTO_CENTERING_BR 0x080284
> +#define HIBMC_CRT_AUTO_CENTERING_BR_BOTTOM(x) ((x) << 16)
> +#define HIBMC_CRT_AUTO_CENTERING_BR_BOTTOM_MASK 0x7FF0000
> +
> +#define HIBMC_CRT_AUTO_CENTERING_BR_RIGHT(x) ((x) << 0)
> +#define HIBMC_CRT_AUTO_CENTERING_BR_RIGHT_MASK 0x7FF
> +
> +/* register to control panel output */
> +#define DISPLAY_CONTROL_HISILE 0x80288
> +
> +#define HIBMC_RAW_INTERRUPT 0x80290
> +#define HIBMC_RAW_INTERRUPT_VBLANK(x) ((x) << 2)
> +#define HIBMC_RAW_INTERRUPT_VBLANK_MASK 0x4
> +
> +#define HIBMC_RAW_INTERRUPT_EN 0x80298
> +#define HIBMC_RAW_INTERRUPT_EN_VBLANK(x) ((x) << 2)
> +#define HIBMC_RAW_INTERRUPT_EN_VBLANK_MASK 0x4
> +
> +/* register and values for PLL control */
> +#define CRT_PLL1_HS 0x802a8
> +#define CRT_PLL1_HS_25MHZ 0x23d40f02
> +#define CRT_PLL1_HS_40MHZ 0x23940801
> +#define CRT_PLL1_HS_65MHZ 0x23940d01
> +#define CRT_PLL1_HS_78MHZ 0x23540F82
> +#define CRT_PLL1_HS_74MHZ 0x23941dc2
> +#define CRT_PLL1_HS_80MHZ 0x23941001
> +#define CRT_PLL1_HS_80MHZ_1152 0x23540fc2
> +#define CRT_PLL1_HS_108MHZ 0x23b41b01
> +#define CRT_PLL1_HS_162MHZ 0x23480681
> +#define CRT_PLL1_HS_148MHZ 0x23541dc2
> +#define CRT_PLL1_HS_193MHZ 0x234807c1
> +
> +#define CRT_PLL2_HS 0x802ac
> +#define CRT_PLL2_HS_25MHZ 0x206B851E
> +#define CRT_PLL2_HS_40MHZ 0x30000000
> +#define CRT_PLL2_HS_65MHZ 0x40000000
> +#define CRT_PLL2_HS_78MHZ 0x50E147AE
> +#define CRT_PLL2_HS_74MHZ 0x602B6AE7
> +#define CRT_PLL2_HS_80MHZ 0x70000000
> +#define CRT_PLL2_HS_108MHZ 0x80000000
> +#define CRT_PLL2_HS_162MHZ 0xA0000000
> +#define CRT_PLL2_HS_148MHZ 0xB0CCCCCD
> +#define CRT_PLL2_HS_193MHZ 0xC0872B02
> +
> +/* Global macros */
> +#define RGB(r, g, b) \
Not used anywhere?
> +( \
> + (unsigned long)(((r) << 16) | ((g) << 8) | (b)) \
> +)
> +
> +#define PADDING(align, data) (((data) + (align) - 1) & (~((align) - 1)))
> +
This is only used in hibmc_drm_de.c, move it in there. It might also
be nice to make it a type-checked function while you're at it.
> +#define MB(x) ((x) << 20)
> +
This is only used in 2 places, I think you can just hardcode the value
in their respective #defines
> +#endif
> --
> 1.9.1
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Applied "ASoC: sun4i-codec: fix semicolon.cocci warnings" to the asoc tree
From: Mark Brown @ 2016-11-10 17:29 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161109163518.GA42508@lkp-nex06.lkp.intel.com>
The patch
ASoC: sun4i-codec: fix semicolon.cocci warnings
has been applied to the asoc tree at
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying
to this mail.
Thanks,
Mark
>From 35db57622c31af687d5cb14104e91897d778a8fc Mon Sep 17 00:00:00 2001
From: kbuild test robot <fengguang.wu@intel.com>
Date: Thu, 10 Nov 2016 00:35:18 +0800
Subject: [PATCH] ASoC: sun4i-codec: fix semicolon.cocci warnings
sound/soc/sunxi/sun4i-codec.c:1339:2-3: Unneeded semicolon
Remove unneeded semicolon.
Generated by: scripts/coccinelle/misc/semicolon.cocci
Signed-off-by: Fengguang Wu <fengguang.wu@intel.com>
Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
sound/soc/sunxi/sun4i-codec.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index 6379efd21f00..092fdcf6de95 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -1336,7 +1336,7 @@ static int sun4i_codec_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "Failed to get reset control\n");
return PTR_ERR(scodec->rst);
}
- };
+ }
scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa",
GPIOD_OUT_LOW);
--
2.10.2
^ permalink raw reply related
* [PATCH v3 00/10] Add DT support for ohci-da8xx
From: Axel Haslam @ 2016-11-10 17:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161110120253.GA5385@kroah.com>
On Thu, Nov 10, 2016 at 1:02 PM, Greg KH <gregkh@linuxfoundation.org> wrote:
> On Tue, Nov 08, 2016 at 05:37:41PM +0100, Axel Haslam wrote:
>> Hi,
>>
>> On Mon, Nov 7, 2016 at 9:39 PM, Axel Haslam <ahaslam@baylibre.com> wrote:
>> > The purpose of this patch series is to add DT support for the davinci
>> > ohci driver.
>> >
>>
>> To make it easier to review. I will split the arch/arm and driver
>> patches into separate series.
>
> I don't think it's easier, as now I have no idea what order, or what
> tree it should go through :(
Hi Greg,
i will resend the series one by one and wait for each to be merged
before sending the next, so that there is no confusion on the ordering
or on what tree they should apply.
Regards
Axel.
>
> I'm guessing not mine, so all are now deleted from my patch queue...
>
> greg k-h
^ permalink raw reply
* [PATCH 1/5] iommu: Allow taking a reference on a group directly
From: Joerg Roedel @ 2016-11-10 17:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161109180059.GJ17771@arm.com>
On Wed, Nov 09, 2016 at 06:00:59PM +0000, Will Deacon wrote:
> I'd still rather the new function was renamed. We already have the group,
> so calling a weird underscore version of iommu_group_get is really
> counter-intuitive.
>
> Joerg -- do you have a preference?
iommu_group_ref_get sound best to me. Unfortunatly C has no function
overloading ;)
Joerg
^ permalink raw reply
* [PATCH v27 1/9] memblock: add memblock_cap_memory_range()
From: Will Deacon @ 2016-11-10 17:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161102045153.12008-1-takahiro.akashi@linaro.org>
On Wed, Nov 02, 2016 at 01:51:53PM +0900, AKASHI Takahiro wrote:
> Add memblock_cap_memory_range() which will remove all the memblock regions
> except the range specified in the arguments.
>
> This function, like memblock_mem_limit_remove_map(), will not remove
> memblocks with MEMMAP_NOMAP attribute as they may be mapped and accessed
> later as "device memory."
> See the commit a571d4eb55d8 ("mm/memblock.c: add new infrastructure to
> address the mem limit issue").
>
> This function is used, in a succeeding patch in the series of arm64 kdump
> suuport, to limit the range of usable memory, System RAM, on crash dump
> kernel.
> (Please note that "mem=" parameter is of little use for this purpose.)
>
> Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
> Cc: linux-mm at kvack.org
> Cc: Andrew Morton <akpm@linux-foundation.org>
> ---
> include/linux/memblock.h | 1 +
> mm/memblock.c | 28 ++++++++++++++++++++++++++++
> 2 files changed, 29 insertions(+)
>
> diff --git a/include/linux/memblock.h b/include/linux/memblock.h
> index 5b759c9..0e770af 100644
> --- a/include/linux/memblock.h
> +++ b/include/linux/memblock.h
> @@ -334,6 +334,7 @@ phys_addr_t memblock_start_of_DRAM(void);
> phys_addr_t memblock_end_of_DRAM(void);
> void memblock_enforce_memory_limit(phys_addr_t memory_limit);
> void memblock_mem_limit_remove_map(phys_addr_t limit);
> +void memblock_cap_memory_range(phys_addr_t base, phys_addr_t size);
> bool memblock_is_memory(phys_addr_t addr);
> int memblock_is_map_memory(phys_addr_t addr);
> int memblock_is_region_memory(phys_addr_t base, phys_addr_t size);
> diff --git a/mm/memblock.c b/mm/memblock.c
> index 7608bc3..eb53876 100644
> --- a/mm/memblock.c
> +++ b/mm/memblock.c
> @@ -1544,6 +1544,34 @@ void __init memblock_mem_limit_remove_map(phys_addr_t limit)
> (phys_addr_t)ULLONG_MAX);
> }
>
> +void __init memblock_cap_memory_range(phys_addr_t base, phys_addr_t size)
> +{
> + int start_rgn, end_rgn;
> + int i, ret;
> +
> + if (!size)
> + return;
> +
> + ret = memblock_isolate_range(&memblock.memory, base, size,
> + &start_rgn, &end_rgn);
> + if (ret)
> + return;
> +
> + /* remove all the MAP regions */
> + for (i = memblock.memory.cnt - 1; i >= end_rgn; i--)
> + if (!memblock_is_nomap(&memblock.memory.regions[i]))
> + memblock_remove_region(&memblock.memory, i);
> +
> + for (i = start_rgn - 1; i >= 0; i--)
> + if (!memblock_is_nomap(&memblock.memory.regions[i]))
> + memblock_remove_region(&memblock.memory, i);
> +
> + /* truncate the reserved regions */
> + memblock_remove_range(&memblock.reserved, 0, base);
> + memblock_remove_range(&memblock.reserved,
> + base + size, (phys_addr_t)ULLONG_MAX);
> +}
This duplicates a bunch of the logic in memblock_mem_limit_remove_map. Can
you not implement that in terms of your new, more general, function? e.g.
by passing base == 0, and size == limit?
Will
^ permalink raw reply
* [RESEND PATCH v1 02/11] dt-bindings: hisi: Add Hisilicon HiP05/06/07 Sysctrl and Djtag dts bindings
From: Mark Rutland @ 2016-11-10 17:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1478151727-20250-3-git-send-email-anurup.m@huawei.com>
Hi,
On Thu, Nov 03, 2016 at 01:41:58AM -0400, Anurup M wrote:
> From: Tan Xiaojun <tanxiaojun@huawei.com>
>
> 1) Add Hisilicon HiP05/06/07 CPU and ALGSUB system controller dts
> bindings.
> 2) Add Hisilicon Djtag dts binding.
>
> Signed-off-by: Tan Xiaojun <tanxiaojun@huawei.com>
> Signed-off-by: Anurup M <anurup.m@huawei.com>
> ---
> .../bindings/arm/hisilicon/hisilicon.txt | 82 ++++++++++++++++++++++
> 1 file changed, 82 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt
> index 7df79a7..341cbb9 100644
> --- a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt
> +++ b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt
> @@ -270,3 +270,85 @@ Required Properties:
> [1]: bootwrapper size
> [2]: relocation physical address
> [3]: relocation size
> +-----------------------------------------------------------------------
> +The Hisilicon Djtag in CPU die is an independent component which connects with
> +some other components in the SoC by Debug Bus. This driver can be configured
> +to access the registers of connecting components (like L3 cache, l3 cache PMU
> + etc.) during real time debugging by sysctrl. These components appear as child
> +nodes of djtag.
Please put the djtag binding in a new file.
It's clearly unrelated to many other things in this file, which should
also be split out.
> +The Hip05/06/07 CPU system controller(sysctrl) support to manage some important
> +components (such as clock, reset, soft reset, secure debugger, etc.).
> +The CPU sysctrl registers in hip05/06/07 doesnot use syscon but will be mapped
> +by djtag driver for use by connecting components.
The djtag driver is irrelvant here.
If there's a realtionship between the two, please define that in the
binding rather than implicitly assuming it in the driver.
> +
> +Required properties:
> + - compatible : "hisilicon,hip05-cpu-djtag-v1"
> + - reg : Register address and size
> +
> +Hisilicon HiP06 djtag for CPU sysctrl
> +Required properties:
> +- compatible : "hisilicon,hip06-sysctrl", "syscon", "simple-mfd";
This looks messy. Why is this syscon and a simple-mfd?
We should kill off / deprecate the syscon binding. It's completely
meaningless.
> +- reg : Register address and size
> +- djtag :
> + - compatible : "hisilicon,hip06-cpu-djtag-v1"
> + - reg : Register address and size
> +
> +Hisilicon HiP07 djtag for CPU sysctrl
> +Required properties:
> + - compatible : "hisilicon,hip07-cpu-djtag-v2"
> + - reg : Register address and size
> +
> +Example:
> + /* for Hisilicon HiP05 djtag for CPU sysctrl */
> + djtag0: djtag at 80010000 {
> + compatible = "hisilicon,hip05-cpu-djtag-v1";
> + reg = <0x0 0x80010000 0x0 0x10000>;
> +
> + /* For L3 cache PMU */
> + pmul3c0 {
> + compatible = "hisilicon,hisi-pmu-l3c-v1";
> + scl-id = <0x02>;
> + num-events = <0x16>;
> + num-counters = <0x08>;
> + module-id = <0x04>;
> + num-banks = <0x04>;
> + cfgen-map = <0x02 0x04 0x01 0x08>;
> + counter-reg = <0x170>;
> + evctrl-reg = <0x04>;
> + event-en = <0x1000000>;
> + evtype-reg = <0x140>;
> + };
This sub-node needs a binding document.
These properties are completely opaque
> + };
> +
> +-----------------------------------------------------------------------
> +The Hisilicon HiP05/06/07 ALGSUB system controller(sysctrl) is in IO die
> +of SoC. It has a similar function as the Hisilicon HiP05/06/07 CPU system
> +controller in CPU die and it manage different components, like RSA, etc.
> +The Hisilicon Djtag in IO die has a similar function as in CPU die and maps
> +the sysctrl registers for use by connecting components.
> +All connecting components shall appear as child nodes of djtag.
I don't follow the above. This describes an ALGSUB system controllerm
but the documentation below is all about djtag.
Thanks,
Mark.
> +Hisilicon HiP05 djtag for ALGSUB sysctrl
> +Required properties:
> + - compatible : "hisilicon,hip05-io-djtag-v1"
> + - reg : Register address and size
> +
> +Hisilicon HiP06 djtag for ALGSUB sysctrl
> +Required properties:
> + - compatible : "hisilicon,hip06-io-djtag-v2"
> + - reg : Register address and size
> +
> +Hisilicon HiP07 djtag for ALGSUB sysctrl
> +Required properties:
> + - compatible : "hisilicon,hip07-io-djtag-v2"
> + - reg : Register address and size
> +
> +Example:
> + /* for Hisilicon HiP05 djtag for alg sysctrl */
> + djtag0: djtag at d0000000 {
> + compatible = "hisilicon,hip05-io-djtag-v1";
> + reg = <0x0 0xd0000000 0x0 0x10000>;
> + };
> --
> 2.1.4
>
^ permalink raw reply
* [PATCH 1/2] arm64: perf: Move ARMv8 PMU perf event definitions to asm/perf_event.h
From: Will Deacon @ 2016-11-10 17:17 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <733f067d-7f3b-df28-eab7-a15abba947fc@arm.com>
On Thu, Nov 10, 2016 at 03:32:12PM +0000, Marc Zyngier wrote:
> On 10/11/16 15:12, Wei Huang wrote:
> >
> >
> > On 11/10/2016 03:10 AM, Marc Zyngier wrote:
> >> Hi Wei,
> >>
> >> On 09/11/16 19:57, Wei Huang wrote:
> >>> This patch moves ARMv8-related perf event definitions from perf_event.c
> >>> to asm/perf_event.h; so KVM code can use them directly. This also help
> >>> remove a duplicated definition of SW_INCR in perf_event.h.
> >>>
> >>> Signed-off-by: Wei Huang <wei@redhat.com>
> >>> ---
> >>> arch/arm64/include/asm/perf_event.h | 161 +++++++++++++++++++++++++++++++++++-
> >>> arch/arm64/kernel/perf_event.c | 161 ------------------------------------
> >>> 2 files changed, 160 insertions(+), 162 deletions(-)
> >>>
> >>> diff --git a/arch/arm64/include/asm/perf_event.h b/arch/arm64/include/asm/perf_event.h
> >>> index 2065f46..6c7b18b 100644
> >>> --- a/arch/arm64/include/asm/perf_event.h
> >>> +++ b/arch/arm64/include/asm/perf_event.h
> >>> @@ -46,7 +46,166 @@
> >>> #define ARMV8_PMU_EVTYPE_MASK 0xc800ffff /* Mask for writable bits */
> >>> #define ARMV8_PMU_EVTYPE_EVENT 0xffff /* Mask for EVENT bits */
> >>>
> >>> -#define ARMV8_PMU_EVTYPE_EVENT_SW_INCR 0 /* Software increment event */
> >>> +/*
> >>> + * ARMv8 PMUv3 Performance Events handling code.
> >>> + * Common event types.
> >>> + */
> >>> +
> >>> +/* Required events. */
> >>> +#define ARMV8_PMUV3_PERFCTR_SW_INCR 0x00
> >>> +#define ARMV8_PMUV3_PERFCTR_L1D_CACHE_REFILL 0x03
> >>> +#define ARMV8_PMUV3_PERFCTR_L1D_CACHE 0x04
> >>> +#define ARMV8_PMUV3_PERFCTR_BR_MIS_PRED 0x10
> >>> +#define ARMV8_PMUV3_PERFCTR_CPU_CYCLES 0x11
> >>> +#define ARMV8_PMUV3_PERFCTR_BR_PRED 0x12
> >>
> >> In my initial review, I asked for the "required" events to be moved to a
> >> shared location. What's the rational for moving absolutely everything?
> >
> > I did notice the phrase "required" in the original email. However I
> > think it is weird to have two places for a same set of PMU definitions.
> > Other developers might think these two are missing if they don't search
> > kernel files carefully.
> >
> > If Will Deacon and you insist, I can move only two defs to perf_event.h,
> > consolidated with the 2nd patch into a single one.
>
> My personal feeling is that only architected events should be in a
> public header. The CPU-specific ones are probably better kept private,
> as it is doubtful that other users would appear).
>
> I'll leave it up to Will to decide, as all I want to avoid is the
> duplication of constants between the PMU and KVM code bases.
Yeah, just take the sets that you need (i.e. the architected events).
Also, check that it builds.
Will
^ permalink raw reply
* Summary of LPC guest MSI discussion in Santa Fe
From: Alex Williamson @ 2016-11-10 17:07 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161110144007.GC2078@8bytes.org>
On Thu, 10 Nov 2016 15:40:07 +0100
Joerg Roedel <joro@8bytes.org> wrote:
> On Wed, Nov 09, 2016 at 01:01:14PM -0700, Alex Williamson wrote:
> > Well, it's not like QEMU or libvirt stumbling through sysfs to figure
> > out where holes could be in order to instantiate a VM with matching
> > holes, just in case someone might decide to hot-add a device into the
> > VM, at some point, and hopefully they don't migrate the VM to another
> > host with a different layout first, is all that much less disgusting or
> > foolproof. It's just that in order to dynamically remove a page as a
> > possible DMA target we require a paravirt channel, such as a balloon
> > driver that's able to pluck a specific page. In some ways it's
> > actually less disgusting, but it puts some prerequisites on
> > enlightening the guest OS. Thanks,
>
> I think it is much simpler if libvirt/qemu just go through all
> potentially assignable devices on a system and pre-exclude any addresses
> from guest RAM beforehand, rather than doing something like this with
> paravirt/ballooning when a device is hot-added. There is no guarantee
> that you can take a page away from a linux-guest.
Right, I'm not advocating a paravirt ballooning approach, just pointing
out that they're both terrible, just in different ways. Thanks,
Alex
^ permalink raw reply
* [PATCH v4 1/5] arm64: perf: Basic uncore counter support for Cavium ThunderX SOC
From: Mark Rutland @ 2016-11-10 16:54 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <73173d6ad2430eead5e9da40564a90a60961b6d9.1477741719.git.jglauber@cavium.com>
Hi Jan,
Apologies for the delay in getting to this.
On Sat, Oct 29, 2016 at 01:55:29PM +0200, Jan Glauber wrote:
> diff --git a/drivers/perf/uncore/uncore_cavium.c b/drivers/perf/uncore/uncore_cavium.c
> new file mode 100644
> index 0000000..a7b4277
> --- /dev/null
> +++ b/drivers/perf/uncore/uncore_cavium.c
> @@ -0,0 +1,351 @@
> +/*
> + * Cavium Thunder uncore PMU support.
> + *
> + * Copyright (C) 2015,2016 Cavium Inc.
> + * Author: Jan Glauber <jan.glauber@cavium.com>
> + */
> +
> +#include <linux/cpufeature.h>
> +#include <linux/numa.h>
> +#include <linux/slab.h>
I believe the following includes are necessary for APIs and/or data
explicitly referenced by the driver code:
#include <linux/atomic.h>
#include <linux/cpuhotplug.h>
#include <linux/cpumask.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <linux/perf_event.h>
#include <linux/printk.h>
#include <linux/smp.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <asm/local64.h>
... please add those here.
[...]
> +/*
> + * Some notes about the various counters supported by this "uncore" PMU
> + * and the design:
> + *
> + * All counters are 64 bit long.
> + * There are no overflow interrupts.
> + * Counters are summarized per node/socket.
> + * Most devices appear as separate PCI devices per socket with the exception
> + * of OCX TLK which appears as one PCI device per socket and contains several
> + * units with counters that are merged.
As a general note, as I commented on the QC L2 PMU driver [1,2], we need
to figure out if we should be aggregating physical PMUs or not.
Judging by subsequent patches, each unit has individual counters and
controls, and thus we cannot atomically read/write counters or controls
across them. As such, I do not think we should aggregate them, and
should expose them separately to userspace.
That will simplify a number of things (e.g. the CPU migration code no
longer has to iterate over a list of units).
> + * Some counters are selected via a control register (L2C TAD) and read by
> + * a number of counter registers, others (L2C CBC, LMC & OCX TLK) have
> + * one dedicated counter per event.
> + * Some counters are not stoppable (L2C CBC & LMC).
> + * Some counters are read-only (LMC).
> + * All counters belong to PCI devices, the devices may have additional
> + * drivers but we assume we are the only user of the counter registers.
> + * We map the whole PCI BAR so we must be careful to forbid access to
> + * addresses that contain neither counters nor counter control registers.
> + */
> +
> +void thunder_uncore_read(struct perf_event *event)
> +{
> + struct thunder_uncore *uncore = to_uncore(event->pmu);
> + struct hw_perf_event *hwc = &event->hw;
> + struct thunder_uncore_node *node;
> + struct thunder_uncore_unit *unit;
> + u64 prev, delta, new = 0;
> +
> + node = get_node(hwc->config, uncore);
> +
> + /* read counter values from all units on the node */
> + list_for_each_entry(unit, &node->unit_list, entry)
> + new += readq(hwc->event_base + unit->map);
> +
> + prev = local64_read(&hwc->prev_count);
> + local64_set(&hwc->prev_count, new);
> + delta = new - prev;
> + local64_add(delta, &event->count);
> +}
> +
> +int thunder_uncore_add(struct perf_event *event, int flags, u64 config_base,
> + u64 event_base)
> +{
> + struct thunder_uncore *uncore = to_uncore(event->pmu);
> + struct hw_perf_event *hwc = &event->hw;
> + struct thunder_uncore_node *node;
> + int id;
> +
> + node = get_node(hwc->config, uncore);
> + id = get_id(hwc->config);
> +
> + if (!cmpxchg(&node->events[id], NULL, event))
> + hwc->idx = id;
Judging by thunder_uncore_event_init() and get_id(), the specific
counter to use is chosen by the user, rather than allocated as
necessary. Yet the block comment before thunder_uncore_read() said only
some events have a dedicated counter.
This does not seem right. Why are we not choosing a relevant counter
dynamically?
As Will commented, we shouldn't need a full-barrier cmpxchg() here; the
pmu::{add,del} are serialised by the core perf code as ctx->lock has to
be held (and we have no interrupt to worry about). If we want to use
cmpxchg() for convenience, it can be a cmpxchg_relaxed().
> + if (hwc->idx == -1)
> + return -EBUSY;
> +
> + hwc->config_base = config_base;
> + hwc->event_base = event_base;
> + hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED;
> +
> + if (flags & PERF_EF_START)
> + uncore->pmu.start(event, PERF_EF_RELOAD);
> +
> + return 0;
> +}
> +
> +void thunder_uncore_del(struct perf_event *event, int flags)
> +{
> + struct thunder_uncore *uncore = to_uncore(event->pmu);
> + struct hw_perf_event *hwc = &event->hw;
> + struct thunder_uncore_node *node;
> + int i;
> +
> + event->pmu->stop(event, PERF_EF_UPDATE);
> +
> + /*
> + * For programmable counters we need to check where we installed it.
> + * To keep this function generic always test the more complicated
> + * case (free running counters won't need the loop).
> + */
> + node = get_node(hwc->config, uncore);
> + for (i = 0; i < node->num_counters; i++) {
> + if (cmpxchg(&node->events[i], event, NULL) == event)
> + break;
Likewise, this can be cmpxchg_relaxed().
[...]
> +int thunder_uncore_event_init(struct perf_event *event)
> +{
> + uncore = to_uncore(event->pmu);
> + if (!uncore)
> + return -ENODEV;
Given to_uncore() uses container_of(), we can lose the check here; the
result cannot be NULL.
> + if (!uncore->event_valid(event->attr.config & UNCORE_EVENT_ID_MASK))
> + return -EINVAL;
Judging by the header, it looks like the node gets dropped in the high
bits. I'm not sure it makes sense to encode that in the user ABI given
the aggregation comments above.
In x86 uncore PMU drivers, one cpu per node is exposed in the cpumask,
and that's how they target nodes. We should either do that, or have
completely separate instances.
Either way, that will also remove the need for exposing the varying
NODE_SHIFT under sysfs.
> +
> + /* check NUMA node */
> + node = get_node(event->attr.config, uncore);
> + if (!node) {
> + pr_debug("Invalid NUMA node selected\n");
> + return -EINVAL;
> + }
... and this to, since the node will either be implicit in the cpu
performing the monitoring, or in the PMU instance the event was
requested from.
> +
> + hwc->config = event->attr.config;
> + hwc->idx = -1;
> + return 0;
> +}
I believe that we should also check that the leader (and siblings) are compatible.
Something like l2x0_pmu_group_is_valid in arch/arm/mm/cache-l2x0-pmu.c.
We also need to ensure that the events in a group are all on the same
CPU (the one exposed via the cpumask). The l2x0 PMU also does this in
its event_init path.
[...]
> + cpuhp_state_add_instance_nocalls(CPUHP_AP_UNCORE_CAVIUM_ONLINE,
> + &uncore->node);
> + ret = perf_pmu_register(&uncore->pmu, uncore->pmu.name, -1);
> + if (ret)
> + goto fail;
> +fail:
> + node_id = 0;
> + while (uncore->nodes[node_id]) {
> + node = uncore->nodes[node_id];
> +
> + list_for_each_entry_safe(unit, tmp, &node->unit_list, entry) {
> + if (unit->pdev) {
> + if (unit->map)
> + iounmap(unit->map);
> + pci_dev_put(unit->pdev);
> + }
> + kfree(unit);
> + }
> + kfree(uncore->nodes[node_id]);
> + node_id++;
> + }
> + return ret;
> +}
Shouldn't we remove the instance from the cpuhp state machine in the
failure path?
[...]
> diff --git a/drivers/perf/uncore/uncore_cavium.h b/drivers/perf/uncore/uncore_cavium.h
> new file mode 100644
> index 0000000..b5d64b5
> --- /dev/null
> +++ b/drivers/perf/uncore/uncore_cavium.h
> @@ -0,0 +1,71 @@
> +#include <linux/io.h>
> +#include <linux/list.h>
> +#include <linux/pci.h>
> +#include <linux/perf_event.h>
I believe this header also needs:
#include <linux/cpumask.h>
#include <linux/kernel.h>
#include <linux/stringify.h>
#include <linux/sysfs.h>
#include <linux/types.h>
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "thunderx_uncore: " fmt
IIRC this needs to be set before including <linux/printk.h>. Does this
work reliably, given that printk.h is likely included first?
Thanks,
Mark.
[1] http://lists.infradead.org/pipermail/linux-arm-kernel/2016-November/466764.html
[2] http://lists.infradead.org/pipermail/linux-arm-kernel/2016-November/466768.html
^ permalink raw reply
* [PATCH v2] dt-bindings: video: exynos7-decon: Remove obsolete samsung, power-domain property
From: Krzysztof Kozlowski @ 2016-11-10 16:50 UTC (permalink / raw)
To: linux-arm-kernel
The samsung,power-domain property is obsolete since commit 0da658704136
("ARM: dts: convert to generic power domain bindings for exynos DT").
Replace it with generic one.
Signed-off-by: Krzysztof Kozlowski <krzk@kernel.org>
Reviewed-by: Sylwester Nawrocki <s.nawrocki@samsung.com>
Reviewed-by: Javier Martinez Canillas <javier@osg.samsung.com>
Acked-by: Rob Herring <robh@kernel.org>
---
Changes since v1:
1. Just add the acks/reviews.
I though it will be applied by Exynos DRM maintainer but there was no
response.
Dear Rob, could you pick it up?
---
Documentation/devicetree/bindings/display/exynos/exynos7-decon.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/display/exynos/exynos7-decon.txt b/Documentation/devicetree/bindings/display/exynos/exynos7-decon.txt
index 3938caacf11c..8346fb18a358 100644
--- a/Documentation/devicetree/bindings/display/exynos/exynos7-decon.txt
+++ b/Documentation/devicetree/bindings/display/exynos/exynos7-decon.txt
@@ -33,7 +33,7 @@ Required properties:
- i80-if-timings: timing configuration for lcd i80 interface support.
Optional Properties:
-- samsung,power-domain: a phandle to DECON power domain node.
+- power-domains: a phandle to DECON power domain node.
- display-timings: timing settings for DECON, as described in document [1].
Can be used in case timings cannot be provided otherwise
or to override timings provided by the panel.
--
2.7.4
^ permalink raw reply related
* [PATCH v3 1/2] ARM: EXYNOS: Remove static mapping of SCU SFR
From: Krzysztof Kozlowski @ 2016-11-10 16:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <a43af947-6002-bc65-f73b-02d57a78d6cb@samsung.com>
On Thu, Nov 10, 2016 at 06:07:54PM +0530, pankaj.dubey wrote:
> So if CONFIG_SMP is disable then there is no sense of exynos_scu_enable
> as well. So wow about using below patch?
>
> --------------------------------------------------------
>
> Subject: [PATCH] ARM: exynos: fix build fail due to exynos_scu_enable
>
> Build failed if we disable CONFIG_SMP as shown below:
This is fine with me.
(...)
> Of-course your idea to move it in core SCU file is also good that we
> lots of duplication in different architecture can be avoided.
>
> In that case I can think of following patch:
>
> [PATCH] ARM: scu: use SCU device node to enable SCU
>
> Many platforms are duplicating code for enabling SCU, lets add
> common code to enable SCU using SCU device node so the duplication in
> each platform can be avoided.
>
> Suggested-by: Arnd Bergmann <arnd@arndb.de>
> Signed-off-by: Pankaj Dubey <pankaj.dubey@samsung.com>
> ---
> arch/arm/include/asm/smp_scu.h | 2 ++
> arch/arm/kernel/smp_scu.c | 17 +++++++++++++++++
> 2 files changed, 19 insertions(+)
>
> diff --git a/arch/arm/include/asm/smp_scu.h b/arch/arm/include/asm/smp_scu.h
> index bfe163c..e5e2492 100644
> --- a/arch/arm/include/asm/smp_scu.h
> +++ b/arch/arm/include/asm/smp_scu.h
> @@ -38,8 +38,10 @@ static inline int scu_power_mode(void __iomem
> *scu_base, unsigned int mode)
> #endif
>
> #if defined(CONFIG_SMP) && defined(CONFIG_HAVE_ARM_SCU)
> +int of_scu_enable(void);
> void scu_enable(void __iomem *scu_base);
> #else
> +static inline int of_scu_enable(void) {return 0;}
> static inline void scu_enable(void __iomem *scu_base) {}
> #endif
>
> diff --git a/arch/arm/kernel/smp_scu.c b/arch/arm/kernel/smp_scu.c
> index 72f9241..7c16d16 100644
> --- a/arch/arm/kernel/smp_scu.c
> +++ b/arch/arm/kernel/smp_scu.c
> @@ -34,6 +34,23 @@ unsigned int __init scu_get_core_count(void __iomem
> *scu_base)
> return (ncores & 0x03) + 1;
> }
>
> +int of_scu_enable(void)
> +{
> + struct device_node *np;
> + void __iomem *scu_base;
> +
> + np = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-scu");
> + scu_base = of_iomap(np, 0);
> + of_node_put(np);
> + if (!scu_base) {
> + pr_err("%s failed to map scu_base\n", __func__);
> + return -ENOMEM;
> + }
> + scu_enable(scu_base);
> + iounmap(scu_base);
> + return 0;
> +}
> +
> /*
> * Enable the SCU
> */
> --
>
>
> Followed by cleanup in various architecture where this piece of code is
> duplicated and all of them can call directly of_scu_enable()
This looks better to me.
>
>
> Please let me know which one you will prefer for fixing build issue.
>
> @Krzysztof, please let me know if I need to resubmit SCU series again
> with fix or you will accept build fix patch on top of already taken patch.
The code is already in my next/soc branch and I prefer to avoid
rebasing/dropping commits so how about:
1. Creating a generic wrapper which arm-soc will apply,
2. Provide me a tag with it (by arm-soc folks),
3. Fix the Exynos !SMP build on top of the tag (by using generic
approach).
Arnd,
Are you fine with this?
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH] PCI: mvebu: Take control of mbus windows setup by the firmware
From: Jason Gunthorpe @ 2016-11-10 16:45 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161110093037.376ae79d@free-electrons.com>
On Thu, Nov 10, 2016 at 09:30:37AM +0100, Thomas Petazzoni wrote:
> Hello,
>
> On Wed, 26 Oct 2016 11:44:40 -0600, Jason Gunthorpe wrote:
> > The firmware may setup the mbus to access PCI-E and indicate this
> > has happened with a ranges mapping for the PCI-E ID. If this happens
> > then the mbus setup and the pci dynamic setup conflict, creating
> > problems.
> >
> > Have PCI-E assume control of the firmware specified default mapping by
> > setting the value of the bridge window to match the firmware mapping.
> >
> > Signed-off-by: Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
>
> Sorry for the late feedback. I am not sure to fully understand what you
> are trying to do here.
> However, one thing that confuses me specifically is how can the kernel
> get any MBus mapping set up by the firmware? Indeed, when the
> mvebu-mbus driver initializes, it destroys all existing MBus windows
> that might have been left by the firmware/bootloader:
Sort of, yes, it wipes out the hardware, but then it parses the DT
and puts back the ranges via mbus_dt_setup.
The issue is what happens if mbus_dt_setup adds a mapping for PCI
aperature from the firmware's DT - in this case the PCI driver does not
know that the mbus driver created the mapping and becomes unable to
manipulate the mbus windows due to the conflict detection logic.
The solution is to have the PCI driver read the current state of the
mbus window - out of the mbus registers that were programmed from the
DT ranges by mbus_dt_setup. Then it knows to delete the DT described
window before setting a new window and the conflicts are avoided.
> Why does Linux needs to rely on what the firmware has setup in terms of
> MBus windows? Why can't Linux just find out the right BAR base/size
> like it is doing for all other devices?
Unfortunately that is sort of how DT is expected to work, in my case I
have DT sub nodes off the PCI device in DT and I need DT address
translation to work for those nodes.
In DT land the PCI stuff expects the firmware to provide a complete
address map, and my firmware does, but in doing so I hit this bug :)
Jason
^ permalink raw reply
* PM regression with LED changes in next-20161109
From: Hans de Goede @ 2016-11-10 16:44 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161110162925.GA28832@amd>
Hi,
On 10-11-16 17:29, Pavel Machek wrote:
> Hi!
>
>>>>> Looks like commit 883d32ce3385 ("leds: core: Add support for poll()ing
>>>>> the sysfs brightness attr for changes.") breaks runtime PM for me.
>>>>>
>>>>> On my omap dm3730 based test system, idle power consumption is over 70
>>>>> times higher now with this patch! It goes from about 6mW for the core
>>>>> system to over 440mW during idle meaning there's some busy timer now
>>>>> active.
>>>>>
>>>>> Reverting this patch fixes the issue. Any ideas?
>
> Are you using any LED that toggles with high frequency? Like perhaps
> LED that is lit when CPU is active?
>
>>> So a user can do "echo 128 > brightness && cat brightness" and
>>> get out 0, or 128, depending purely on timing.
> ...
>>> Reading from this file while a trigger is active returns
>>> the
>>> top brightness trigger is going to use.
>
> Yes, that sounds sane.
>
>> It seems that we should get back to your initial approach. i.e. only
>> brightness changes caused by hardware should be reported.
>
> I don't think enabling poll() here is good idea. Some hardware won't
> be able to tell you that it changed the state. Returning maximum
> brightness trigger is going to use seems easier/better.
The idea here is to allow userspace to poll() on the brightness
sysfs atrribute to detect changes autonomously done by the hardware,
such as e.g. happens on both Dell and Thinkpad laptops when pressing
the keyboard backlight cycle hotkey. Note that these keys do not
generate key-press events, the cycling through the brightness levels
(including off) is done entirely in firmware.
But we do get other ACPI events for this which we can use to let
userspace know this happens, which is something which user-
interfaces which allow control over the kbd backlight want to know.
I understand that we will not always be able to do this, here is the
Documentation/ABI/testing/sysfs-class-led text I have in mind:
The file supports poll() to detect changes, changes are only
signalled when this file is written or when the hardware /
firmware changes the brightness itself and the driver can detect
this. Changes done by kernel triggers / software blinking are
not signalled.
Note the "and the driver can detect this" language, that has been there
since v1 of the poll() notification patch since I already expected not
all hardware to be able to signal this.
Regards,
Hans
^ permalink raw reply
* [v17 2/2] drm/bridge: Add I2C based driver for ps8640 bridge
From: Enric Balletbo Serra @ 2016-11-10 16:39 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1472280263-18177-2-git-send-email-jitao.shi@mediatek.com>
Hi Jitao,
2016-08-27 8:44 GMT+02:00 Jitao Shi <jitao.shi@mediatek.com>:
> This patch adds drm_bridge driver for parade DSI to eDP bridge chip.
>
> Signed-off-by: Jitao Shi <jitao.shi@mediatek.com>
> Reviewed-by: Daniel Kurtz <djkurtz@chromium.org>
> ---
> Changes since v16:
> - Disable ps8640 DSI MCS Function.
> - Rename gpios name more clearly.
> - Tune the ps8640 power on sequence.
>
> Changes since v15:
> - Drop drm_connector_(un)register calls from parade ps8640.
> The main DRM driver mtk_drm_drv now calls
> drm_connector_register_all() after drm_dev_register() in the
> mtk_drm_bind() function. That function should iterate over all
> connectors and call drm_connector_register() for each of them.
> So, remove drm_connector_(un)register calls from parade ps8640.
>
> Changes since v14:
> - update copyright info.
> - change bridge_to_ps8640 and connector_to_ps8640 to inline function.
> - fix some coding style.
> - use sizeof as array counter.
> - use drm_get_edid when read edid.
> - add mutex when firmware updating.
>
> Changes since v13:
> - add const on data, ps8640_write_bytes(struct i2c_client *client, const u8 *data, u16 data_len)
> - fix PAGE2_SW_REST tyro.
> - move the buf[3] init to entrance of the function.
>
> Changes since v12:
> - fix hw_chip_id build warning
>
> Changes since v11:
> - Remove depends on I2C, add DRM depends
> - Reuse ps8640_write_bytes() in ps8640_write_byte()
> - Use timer check for polling like the routines in <linux/iopoll.h>
> - Fix no drm_connector_unregister/drm_connector_cleanup when ps8640_bridge_attach fail
> - Check the ps8640 hardware id in ps8640_validate_firmware
> - Remove fw_version check
> - Move ps8640_validate_firmware before ps8640_enter_bl
> - Add ddc_i2c unregister when probe fail and ps8640_remove
> ---
> drivers/gpu/drm/bridge/Kconfig | 12 +
> drivers/gpu/drm/bridge/Makefile | 1 +
> drivers/gpu/drm/bridge/parade-ps8640.c | 1077 ++++++++++++++++++++++++++++++++
> 3 files changed, 1090 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/parade-ps8640.c
>
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index b590e67..c59d043 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -50,6 +50,18 @@ config DRM_PARADE_PS8622
> ---help---
> Parade eDP-LVDS bridge chip driver.
>
> +config DRM_PARADE_PS8640
> + tristate "Parade PS8640 MIPI DSI to eDP Converter"
> + depends on DRM
> + depends on OF
> + select DRM_KMS_HELPER
> + select DRM_MIPI_DSI
> + select DRM_PANEL
> + ---help---
> + Choose this option if you have PS8640 for display
> + The PS8640 is a high-performance and low-power
> + MIPI DSI to eDP converter
> +
> config DRM_SII902X
> tristate "Silicon Image sii902x RGB/HDMI bridge"
> depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index efdb07e..3360537 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -5,6 +5,7 @@ obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
> obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
> obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
> +obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o
> obj-$(CONFIG_DRM_SII902X) += sii902x.o
> obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
> obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
> diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c
> new file mode 100644
> index 0000000..7d67431
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/parade-ps8640.c
> @@ -0,0 +1,1077 @@
> +/*
> + * Copyright (c) 2016 MediaTek Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * 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.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/firmware.h>
> +#include <linux/gpio.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_graph.h>
> +#include <linux/regulator/consumer.h>
> +#include <asm/unaligned.h>
> +#include <drm/drm_panel.h>
> +
> +#include <drmP.h>
> +#include <drm_atomic_helper.h>
> +#include <drm_crtc_helper.h>
> +#include <drm_crtc.h>
> +#include <drm_edid.h>
> +#include <drm_mipi_dsi.h>
> +
> +#define PAGE1_VSTART 0x6b
> +#define PAGE2_SPI_CFG3 0x82
> +#define I2C_TO_SPI_RESET 0x20
> +#define PAGE2_ROMADD_BYTE1 0x8e
> +#define PAGE2_ROMADD_BYTE2 0x8f
> +#define PAGE2_SWSPI_WDATA 0x90
> +#define PAGE2_SWSPI_RDATA 0x91
> +#define PAGE2_SWSPI_LEN 0x92
> +#define PAGE2_SWSPI_CTL 0x93
> +#define TRIGGER_NO_READBACK 0x05
> +#define TRIGGER_READBACK 0x01
> +#define PAGE2_SPI_STATUS 0x9e
> +#define SPI_READY 0x0c
> +#define PAGE2_GPIO_L 0xa6
> +#define PAGE2_GPIO_H 0xa7
> +#define PS_GPIO9 BIT(1)
> +#define PAGE2_IROM_CTRL 0xb0
> +#define IROM_ENABLE 0xc0
> +#define IROM_DISABLE 0x80
> +#define PAGE2_SW_RESET 0xbc
> +#define SPI_SW_RESET BIT(7)
> +#define MPU_SW_RESET BIT(6)
> +#define PAGE2_ENCTLSPI_WR 0xda
> +#define PAGE2_I2C_BYPASS 0xea
> +#define I2C_BYPASS_EN 0xd0
> +#define PAGE2_MCS_EN 0xf3
> +#define MCS_EN BIT(0)
> +#define PAGE3_SET_ADD 0xfe
> +#define PAGE3_SET_VAL 0xff
> +#define VDO_CTL_ADD 0x13
> +#define VDO_DIS 0x18
> +#define VDO_EN 0x1c
> +#define PAGE4_REV_L 0xf0
> +#define PAGE4_REV_H 0xf1
> +#define PAGE4_CHIP_L 0xf2
> +#define PAGE4_CHIP_H 0xf3
> +
> +/* Firmware */
> +#define PS_FW_NAME "ps864x_fw.bin"
> +
About the firmware discussion I think that if you want to maintain the
upgrade firmware thing you should also include this patch in the
series.
https://chromium-review.googlesource.com/#/c/317221/
Otherwise, if this is not really needed I think that remove this from
the driver is the best. Just an opinion, this is something the
maintainer should decide.
> +#define FW_CHIP_ID_OFFSET 0
> +#define FW_VERSION_OFFSET 2
> +#define EDID_I2C_ADDR 0x50
> +
> +#define WRITE_STATUS_REG_CMD 0x01
> +#define READ_STATUS_REG_CMD 0x05
> +#define BUSY BIT(0)
> +#define CLEAR_ALL_PROTECT 0x00
> +#define BLK_PROTECT_BITS 0x0c
> +#define STATUS_REG_PROTECT BIT(7)
> +#define WRITE_ENABLE_CMD 0x06
> +#define CHIP_ERASE_CMD 0xc7
> +#define MAX_DEVS 0x8
> +
> +struct ps8640_info {
> + u8 family_id;
> + u8 variant_id;
> + u16 version;
> +};
> +
> +struct ps8640 {
> + struct drm_connector connector;
> + struct drm_bridge bridge;
> + struct edid *edid;
> + struct mipi_dsi_device dsi;
> + struct i2c_client *page[MAX_DEVS];
> + struct i2c_client *ddc_i2c;
> + struct regulator_bulk_data supplies[2];
> + struct drm_panel *panel;
> + struct gpio_desc *gpio_reset;
> + struct gpio_desc *gpio_power_down;
> + struct gpio_desc *gpio_mode_sel;
> + bool enabled;
> +
> + /* firmware file info */
> + struct ps8640_info info;
> + bool in_fw_update;
> + /* for firmware update protect */
> + struct mutex fw_mutex;
> +};
> +
> +static const u8 enc_ctrl_code[6] = { 0xaa, 0x55, 0x50, 0x41, 0x52, 0x44 };
> +static const u8 hw_chip_id[4] = { 0x00, 0x0a, 0x00, 0x30 };
> +
> +static inline struct ps8640 *bridge_to_ps8640(struct drm_bridge *e)
> +{
> + return container_of(e, struct ps8640, bridge);
> +}
> +
> +static inline struct ps8640 *connector_to_ps8640(struct drm_connector *e)
> +{
> + return container_of(e, struct ps8640, connector);
> +}
> +
> +static int ps8640_read(struct i2c_client *client, u8 reg, u8 *data,
> + u16 data_len)
> +{
> + int ret;
> + struct i2c_msg msgs[] = {
> + {
> + .addr = client->addr,
> + .flags = 0,
> + .len = 1,
> + .buf = ®,
> + },
> + {
> + .addr = client->addr,
> + .flags = I2C_M_RD,
> + .len = data_len,
> + .buf = data,
> + }
> + };
> +
> + ret = i2c_transfer(client->adapter, msgs, 2);
> +
> + if (ret == 2)
> + return 0;
> + if (ret < 0)
> + return ret;
> + else
> + return -EIO;
> +}
> +
> +static int ps8640_write_bytes(struct i2c_client *client, const u8 *data,
> + u16 data_len)
> +{
> + int ret;
> + struct i2c_msg msg;
> +
> + msg.addr = client->addr;
> + msg.flags = 0;
> + msg.len = data_len;
> + msg.buf = (u8 *)data;
> +
> + ret = i2c_transfer(client->adapter, &msg, 1);
> + if (ret == 1)
> + return 0;
> + if (ret < 0)
> + return ret;
> + else
> + return -EIO;
> +}
> +
> +static int ps8640_write_byte(struct i2c_client *client, u8 reg, u8 data)
> +{
> + u8 buf[] = { reg, data };
> +
> + return ps8640_write_bytes(client, buf, sizeof(buf));
> +}
> +
> +static void ps8640_get_mcu_fw_version(struct ps8640 *ps_bridge)
> +{
> + struct i2c_client *client = ps_bridge->page[5];
> + u8 fw_ver[2];
> +
> + ps8640_read(client, 0x4, fw_ver, sizeof(fw_ver));
> + ps_bridge->info.version = (fw_ver[0] << 8) | fw_ver[1];
> +
> + DRM_INFO_ONCE("ps8640 rom fw version %d.%d\n", fw_ver[0], fw_ver[1]);
> +}
> +
> +static int ps8640_bridge_unmute(struct ps8640 *ps_bridge)
> +{
> + struct i2c_client *client = ps_bridge->page[3];
> + u8 vdo_ctrl_buf[3] = { PAGE3_SET_ADD, VDO_CTL_ADD, VDO_EN };
> +
> + return ps8640_write_bytes(client, vdo_ctrl_buf, sizeof(vdo_ctrl_buf));
> +}
> +
> +static int ps8640_bridge_mute(struct ps8640 *ps_bridge)
> +{
> + struct i2c_client *client = ps_bridge->page[3];
> + u8 vdo_ctrl_buf[3] = { PAGE3_SET_ADD, VDO_CTL_ADD, VDO_DIS };
> +
> + return ps8640_write_bytes(client, vdo_ctrl_buf, sizeof(vdo_ctrl_buf));
> +}
> +
> +static void ps8640_pre_enable(struct drm_bridge *bridge)
> +{
> + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
> + struct i2c_client *client = ps_bridge->page[2];
> + struct i2c_client *page1 = ps_bridge->page[1];
> + int err;
> + u8 set_vdo_done, mcs_en, vstart;
> + ktime_t timeout;
> +
> + if (ps_bridge->in_fw_update)
> + return;
> +
> + if (ps_bridge->enabled)
> + return;
> +
> + err = drm_panel_prepare(ps_bridge->panel);
> + if (err < 0) {
> + DRM_ERROR("failed to prepare panel: %d\n", err);
> + return;
> + }
> +
> + err = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies),
> + ps_bridge->supplies);
> + if (err < 0) {
> + DRM_ERROR("cannot enable regulators %d\n", err);
> + goto err_panel_unprepare;
> + }
> +
> + gpiod_set_value(ps_bridge->gpio_power_down, 1);
> + gpiod_set_value(ps_bridge->gpio_reset, 0);
> + usleep_range(2000, 2500);
> + gpiod_set_value(ps_bridge->gpio_reset, 1);
> +
> + /*
> + * Wait for the ps8640 embed mcu ready
> + * First wait 200ms and then check the mcu ready flag every 20ms
> + */
> + msleep(200);
> +
> + timeout = ktime_add_ms(ktime_get(), 200);
> + for (;;) {
> + err = ps8640_read(client, PAGE2_GPIO_H, &set_vdo_done, 1);
> + if (err < 0) {
> + DRM_ERROR("failed read PAGE2_GPIO_H: %d\n", err);
> + goto err_regulators_disable;
> + }
> + if ((set_vdo_done & PS_GPIO9) == PS_GPIO9)
> + break;
> + if (ktime_compare(ktime_get(), timeout) > 0)
> + break;
> + msleep(20);
> + }
> +
> + msleep(50);
> +
> + ps8640_read(page1, PAGE1_VSTART, &vstart, 1);
> + DRM_INFO("PS8640 PAGE1.0x6B = 0x%x\n", vstart);
> +
> + /**
> + * The Manufacturer Command Set (MCS) is a device dependent interface
> + * intended for factory programming of the display module default
> + * parameters. Once the display module is configured, the MCS shall be
> + * disabled by the manufacturer. Once disabled, all MCS commands are
> + * ignored by the display interface.
> + */
> + ps8640_read(client, PAGE2_MCS_EN, &mcs_en, 1);
> + ps8640_write_byte(client, PAGE2_MCS_EN, mcs_en & ~MCS_EN);
> +
> + if (ps_bridge->info.version == 0)
> + ps8640_get_mcu_fw_version(ps_bridge);
> +
> + err = ps8640_bridge_unmute(ps_bridge);
> + if (err)
> + DRM_ERROR("failed to enable unmutevideo: %d\n", err);
> + /* Switch access edp panel's edid through i2c */
> + ps8640_write_byte(client, PAGE2_I2C_BYPASS, I2C_BYPASS_EN);
> + ps_bridge->enabled = true;
> +
> + return;
> +
> +err_regulators_disable:
> + regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
> + ps_bridge->supplies);
> +err_panel_unprepare:
> + drm_panel_unprepare(ps_bridge->panel);
> +}
> +
> +static void ps8640_enable(struct drm_bridge *bridge)
> +{
> + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
> + int err;
> +
> + err = drm_panel_enable(ps_bridge->panel);
> + if (err < 0)
> + DRM_ERROR("failed to enable panel: %d\n", err);
> +}
> +
> +static void ps8640_disable(struct drm_bridge *bridge)
> +{
> + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
> + int err;
> +
> + err = drm_panel_disable(ps_bridge->panel);
> + if (err < 0)
> + DRM_ERROR("failed to disable panel: %d\n", err);
> +}
> +
> +static void ps8640_post_disable(struct drm_bridge *bridge)
> +{
> + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
> + int err;
> +
> + if (ps_bridge->in_fw_update)
> + return;
> +
> + if (!ps_bridge->enabled)
> + return;
> +
> + ps_bridge->enabled = false;
> +
> + err = ps8640_bridge_mute(ps_bridge);
> + if (err < 0)
> + DRM_ERROR("failed to unmutevideo: %d\n", err);
> +
> + gpiod_set_value(ps_bridge->gpio_reset, 0);
> + gpiod_set_value(ps_bridge->gpio_power_down, 0);
> + err = regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
> + ps_bridge->supplies);
> + if (err < 0)
> + DRM_ERROR("cannot disable regulators %d\n", err);
> +
> + err = drm_panel_unprepare(ps_bridge->panel);
> + if (err)
> + DRM_ERROR("failed to unprepare panel: %d\n", err);
> +}
> +
> +static int ps8640_get_modes(struct drm_connector *connector)
> +{
> + struct ps8640 *ps_bridge = connector_to_ps8640(connector);
> + struct edid *edid;
> + int num_modes = 0;
> + bool power_off;
> +
> + if (ps_bridge->edid)
> + return drm_add_edid_modes(connector, ps_bridge->edid);
> +
> + power_off = !ps_bridge->enabled;
> + ps8640_pre_enable(&ps_bridge->bridge);
> +
> + edid = drm_get_edid(connector, ps_bridge->ddc_i2c->adapter);
> + if (!edid)
> + goto out;
> +
> + ps_bridge->edid = edid;
> + drm_mode_connector_update_edid_property(connector, ps_bridge->edid);
> + num_modes = drm_add_edid_modes(connector, ps_bridge->edid);
> +
> +out:
> + if (power_off)
> + ps8640_post_disable(&ps_bridge->bridge);
> +
> + return num_modes;
> +}
> +
> +static struct drm_encoder *ps8640_best_encoder(struct drm_connector *connector)
> +{
> + struct ps8640 *ps_bridge = connector_to_ps8640(connector);
> +
> + return ps_bridge->bridge.encoder;
> +}
> +
> +static const struct drm_connector_helper_funcs ps8640_connector_helper_funcs = {
> + .get_modes = ps8640_get_modes,
> + .best_encoder = ps8640_best_encoder,
> +};
> +
> +static enum drm_connector_status ps8640_detect(struct drm_connector *connector,
> + bool force)
> +{
> + return connector_status_connected;
> +}
> +
> +static const struct drm_connector_funcs ps8640_connector_funcs = {
> + .dpms = drm_atomic_helper_connector_dpms,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .detect = ps8640_detect,
> + .reset = drm_atomic_helper_connector_reset,
> + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +int ps8640_bridge_attach(struct drm_bridge *bridge)
> +{
> + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
> + struct device *dev = &ps_bridge->page[0]->dev;
> + struct device_node *port, *in_ep;
> + struct device_node *dsi_node = NULL;
> + struct mipi_dsi_host *host = NULL;
> + int ret;
> +
> + ret = drm_connector_init(bridge->dev, &ps_bridge->connector,
> + &ps8640_connector_funcs,
> + DRM_MODE_CONNECTOR_eDP);
> +
> + if (ret) {
> + DRM_ERROR("Failed to initialize connector with drm: %d\n", ret);
> + return ret;
> + }
> +
> + drm_connector_helper_add(&ps_bridge->connector,
> + &ps8640_connector_helper_funcs);
> +
> + ps_bridge->connector.dpms = DRM_MODE_DPMS_ON;
> + drm_mode_connector_attach_encoder(&ps_bridge->connector,
> + bridge->encoder);
> +
> + if (ps_bridge->panel)
> + drm_panel_attach(ps_bridge->panel, &ps_bridge->connector);
> +
> + /* port at 0 is ps8640 dsi input port */
> + port = of_graph_get_port_by_id(dev->of_node, 0);
> + if (port) {
> + in_ep = of_get_child_by_name(port, "endpoint");
> + of_node_put(port);
> + if (in_ep) {
> + dsi_node = of_graph_get_remote_port_parent(in_ep);
> + of_node_put(in_ep);
> + }
> + }
> + if (dsi_node) {
> + host = of_find_mipi_dsi_host_by_node(dsi_node);
> + of_node_put(dsi_node);
> + if (!host) {
> + ret = -ENODEV;
> + goto err;
> + }
> + }
> +
> + ps_bridge->dsi.host = host;
> + ps_bridge->dsi.mode_flags = MIPI_DSI_MODE_VIDEO |
> + MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
> + ps_bridge->dsi.format = MIPI_DSI_FMT_RGB888;
> + ps_bridge->dsi.lanes = 4;
> + ret = mipi_dsi_attach(&ps_bridge->dsi);
> + if (ret)
> + goto err;
> +
> + return 0;
> +err:
> + if (ps_bridge->panel)
> + drm_panel_detach(ps_bridge->panel);
> + drm_connector_cleanup(&ps_bridge->connector);
> + return ret;
> +}
> +
> +static const struct drm_bridge_funcs ps8640_bridge_funcs = {
> + .attach = ps8640_bridge_attach,
> + .disable = ps8640_disable,
> + .post_disable = ps8640_post_disable,
> + .pre_enable = ps8640_pre_enable,
> + .enable = ps8640_enable,
> +};
> +
> +/* Firmware Version is returned as Major.Minor */
> +static ssize_t ps8640_fw_version_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct ps8640 *ps_bridge = dev_get_drvdata(dev);
> + struct ps8640_info *info = &ps_bridge->info;
> +
> + return scnprintf(buf, PAGE_SIZE, "%u.%u\n", info->version >> 8,
> + info->version & 0xff);
> +}
> +
> +/* Hardware Version is returned as FamilyID.VariantID */
> +static ssize_t ps8640_hw_version_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct ps8640 *ps_bridge = dev_get_drvdata(dev);
> + struct ps8640_info *info = &ps_bridge->info;
> +
> + return scnprintf(buf, PAGE_SIZE, "ps%u.%u\n", info->family_id,
> + info->variant_id);
> +}
> +
> +static int ps8640_spi_send_cmd(struct ps8640 *ps_bridge, u8 *cmd, u8 cmd_len)
> +{
> + struct i2c_client *client = ps_bridge->page[2];
> + u8 i, buf[3] = { PAGE2_SWSPI_LEN, cmd_len - 1, TRIGGER_NO_READBACK };
> + int ret;
> +
> + ret = ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_ENABLE);
> + if (ret)
> + goto err;
> +
> + /* write command in write port */
> + for (i = 0; i < cmd_len; i++) {
> + ret = ps8640_write_byte(client, PAGE2_SWSPI_WDATA, cmd[i]);
> + if (ret)
> + goto err_irom_disable;
> + }
> +
> + ret = ps8640_write_bytes(client, buf, sizeof(buf));
> + if (ret)
> + goto err_irom_disable;
> +
> + ret = ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_DISABLE);
> + if (ret)
> + goto err;
> +
> + return 0;
> +err_irom_disable:
> + ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_DISABLE);
> +err:
> + dev_err(&client->dev, "send command err: %d\n", ret);
> + return ret;
> +}
> +
> +static int ps8640_wait_spi_ready(struct ps8640 *ps_bridge)
> +{
> + struct i2c_client *client = ps_bridge->page[2];
> + u8 spi_rdy_st;
> + ktime_t timeout;
> +
> + timeout = ktime_add_ms(ktime_get(), 200);
> + for (;;) {
> + ps8640_read(client, PAGE2_SPI_STATUS, &spi_rdy_st, 1);
> + if ((spi_rdy_st & SPI_READY) != SPI_READY)
> + break;
> +
> + if (ktime_compare(ktime_get(), timeout) > 0) {
> + dev_err(&client->dev, "wait spi ready timeout\n");
> + return -EBUSY;
> + }
> +
> + msleep(20);
> + }
> +
> + return 0;
> +}
> +
> +static int ps8640_wait_spi_nobusy(struct ps8640 *ps_bridge)
> +{
> + struct i2c_client *client = ps_bridge->page[2];
> + u8 spi_status, buf[3] = { PAGE2_SWSPI_LEN, 0, TRIGGER_READBACK };
> + int ret;
> + ktime_t timeout;
> +
> + timeout = ktime_add_ms(ktime_get(), 500);
> + for (;;) {
> + /* 0x05 RDSR; Read-Status-Register */
> + ret = ps8640_write_byte(client, PAGE2_SWSPI_WDATA,
> + READ_STATUS_REG_CMD);
> + if (ret)
> + goto err_send_cmd_exit;
> +
> + ret = ps8640_write_bytes(client, buf, 3);
> + if (ret)
> + goto err_send_cmd_exit;
> +
> + /* delay for cmd send */
> + usleep_range(300, 500);
> + /* wait for SPI ROM until not busy */
> + ret = ps8640_read(client, PAGE2_SWSPI_RDATA, &spi_status, 1);
> + if (ret)
> + goto err_send_cmd_exit;
> +
> + if (!(spi_status & BUSY))
> + break;
> +
> + if (ktime_compare(ktime_get(), timeout) > 0) {
> + dev_err(&client->dev, "wait spi no busy timeout: %d\n",
> + ret);
> + return -EBUSY;
> + }
> + }
> +
> + return 0;
> +
> +err_send_cmd_exit:
> + dev_err(&client->dev, "send command err: %d\n", ret);
> + return ret;
> +}
> +
> +static int ps8640_wait_rom_idle(struct ps8640 *ps_bridge)
> +{
> + struct i2c_client *client = ps_bridge->page[0];
> + int ret;
> +
> + ret = ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_ENABLE);
> + if (ret)
> + goto exit;
> +
> + ret = ps8640_wait_spi_ready(ps_bridge);
> + if (ret)
> + goto err_spi;
> +
> + ret = ps8640_wait_spi_nobusy(ps_bridge);
> + if (ret)
> + goto err_spi;
> +
> + ret = ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_DISABLE);
> + if (ret)
> + goto exit;
> +
> + return 0;
> +
> +err_spi:
> + ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_DISABLE);
> +exit:
> + dev_err(&client->dev, "wait ps8640 rom idle fail: %d\n", ret);
> +
> + return ret;
> +}
> +
> +static int ps8640_spi_dl_mode(struct ps8640 *ps_bridge)
> +{
> + struct i2c_client *client = ps_bridge->page[2];
> + int ret;
> +
> + /* switch ps8640 mode to spi dl mode */
> + if (ps_bridge->gpio_mode_sel)
> + gpiod_set_value(ps_bridge->gpio_mode_sel, 0);
> +
> + /* reset spi interface */
> + ret = ps8640_write_byte(client, PAGE2_SW_RESET,
> + SPI_SW_RESET | MPU_SW_RESET);
> + if (ret)
> + goto exit;
> +
> + ret = ps8640_write_byte(client, PAGE2_SW_RESET, MPU_SW_RESET);
> + if (ret)
> + goto exit;
> +
> + return 0;
> +
> +exit:
> + dev_err(&client->dev, "fail reset spi interface: %d\n", ret);
> +
> + return ret;
> +}
> +
> +static int ps8640_rom_prepare(struct ps8640 *ps_bridge)
> +{
> + struct i2c_client *client = ps_bridge->page[2];
> + struct device *dev = &client->dev;
> + u8 i, cmd[2];
> + int ret;
> +
> + cmd[0] = WRITE_ENABLE_CMD;
> + ret = ps8640_spi_send_cmd(ps_bridge, cmd, 1);
> + if (ret) {
> + dev_err(dev, "failed enable-write-status-register: %d\n", ret);
> + return ret;
> + }
> +
> + cmd[0] = WRITE_STATUS_REG_CMD;
> + cmd[1] = CLEAR_ALL_PROTECT;
> + ret = ps8640_spi_send_cmd(ps_bridge, cmd, 2);
> + if (ret) {
> + dev_err(dev, "fail disable all protection: %d\n", ret);
> + return ret;
> + }
> +
> + /* wait for SPI module ready */
> + ret = ps8640_wait_rom_idle(ps_bridge);
> + if (ret) {
> + dev_err(dev, "fail wait rom idle: %d\n", ret);
> + return ret;
> + }
> +
> + ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_ENABLE);
> + for (i = 0; i < ARRAY_SIZE(enc_ctrl_code); i++)
> + ps8640_write_byte(client, PAGE2_ENCTLSPI_WR, enc_ctrl_code[i]);
> + ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_DISABLE);
> +
> + /* Enable-Write-Status-Register */
> + cmd[0] = WRITE_ENABLE_CMD;
> + ret = ps8640_spi_send_cmd(ps_bridge, cmd, 1);
> + if (ret) {
> + dev_err(dev, "fail enable-write-status-register: %d\n", ret);
> + return ret;
> + }
> +
> + /* chip erase command */
> + cmd[0] = CHIP_ERASE_CMD;
> + ret = ps8640_spi_send_cmd(ps_bridge, cmd, 1);
> + if (ret) {
> + dev_err(dev, "fail disable all protection: %d\n", ret);
> + return ret;
> + }
> +
> + ret = ps8640_wait_rom_idle(ps_bridge);
> + if (ret) {
> + dev_err(dev, "fail wait rom idle: %d\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int ps8640_check_chip_id(struct ps8640 *ps_bridge)
> +{
> + struct i2c_client *client = ps_bridge->page[4];
> + u8 buf[4];
> +
> + ps8640_read(client, PAGE4_REV_L, buf, 4);
> + return memcmp(buf, hw_chip_id, sizeof(buf));
> +}
> +
> +static int ps8640_validate_firmware(struct ps8640 *ps_bridge,
> + const struct firmware *fw)
> +{
> + struct i2c_client *client = ps_bridge->page[0];
> + u16 fw_chip_id;
> +
> + /*
> + * Get the chip_id from the firmware. Make sure that it is the
> + * right controller to do the firmware and config update.
> + */
> + fw_chip_id = get_unaligned_le16(fw->data + FW_CHIP_ID_OFFSET);
> +
> + if (fw_chip_id != 0x8640 && ps8640_check_chip_id(ps_bridge) == 0) {
> + dev_err(&client->dev,
> + "chip id mismatch: fw 0x%x vs. chip 0x8640\n",
> + fw_chip_id);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int ps8640_write_rom(struct ps8640 *ps_bridge, const struct firmware *fw)
> +{
> + struct i2c_client *client = ps_bridge->page[0];
> + struct device *dev = &client->dev;
> + struct i2c_client *client2 = ps_bridge->page[2];
> + struct i2c_client *client7 = ps_bridge->page[7];
> + size_t pos, cpy_len;
> + u8 buf[257];
> + int ret;
> +
> + ps8640_write_byte(client2, PAGE2_SPI_CFG3, I2C_TO_SPI_RESET);
> + msleep(100);
> + ps8640_write_byte(client2, PAGE2_SPI_CFG3, 0x00);
> +
> + for (pos = 0; pos < fw->size; pos += cpy_len) {
> + buf[0] = PAGE2_ROMADD_BYTE1;
> + buf[1] = pos >> 8;
> + buf[2] = pos >> 16;
> + ret = ps8640_write_bytes(client2, buf, 3);
> + if (ret)
> + goto error;
> + cpy_len = fw->size >= 256 + pos ? 256 : fw->size - pos;
> + buf[0] = 0;
> + memcpy(buf + 1, fw->data + pos, cpy_len);
> + ret = ps8640_write_bytes(client7, buf, cpy_len + 1);
> + if (ret)
> + goto error;
> +
> + dev_dbg(dev, "fw update completed %zu / %zu bytes\n", pos,
> + fw->size);
> + }
> + return 0;
> +
> +error:
> + dev_err(dev, "failed write external flash, %d\n", ret);
> + return ret;
> +}
> +
> +static int ps8640_spi_normal_mode(struct ps8640 *ps_bridge)
> +{
> + u8 cmd[2];
> + struct i2c_client *client = ps_bridge->page[2];
> +
> + /* Enable-Write-Status-Register */
> + cmd[0] = WRITE_ENABLE_CMD;
> + ps8640_spi_send_cmd(ps_bridge, cmd, 1);
> +
> + /* protect BPL/BP0/BP1 */
> + cmd[0] = WRITE_STATUS_REG_CMD;
> + cmd[1] = BLK_PROTECT_BITS | STATUS_REG_PROTECT;
> + ps8640_spi_send_cmd(ps_bridge, cmd, 2);
> +
> + /* wait for SPI rom ready */
> + ps8640_wait_rom_idle(ps_bridge);
> +
> + /* disable PS8640 mapping function */
> + ps8640_write_byte(client, PAGE2_ENCTLSPI_WR, 0x00);
> +
> + if (ps_bridge->gpio_mode_sel)
> + gpiod_set_value(ps_bridge->gpio_mode_sel, 1);
> + return 0;
> +}
> +
> +static int ps8640_enter_bl(struct ps8640 *ps_bridge)
> +{
> + ps_bridge->in_fw_update = true;
> + return ps8640_spi_dl_mode(ps_bridge);
> +}
> +
> +static void ps8640_exit_bl(struct ps8640 *ps_bridge, const struct firmware *fw)
> +{
> + ps8640_spi_normal_mode(ps_bridge);
> + ps_bridge->in_fw_update = false;
> +}
> +
> +static int ps8640_load_fw(struct ps8640 *ps_bridge, const struct firmware *fw)
> +{
> + struct i2c_client *client = ps_bridge->page[0];
> + struct device *dev = &client->dev;
> + int ret;
> + bool ps8640_status_backup = ps_bridge->enabled;
> +
> + ret = ps8640_validate_firmware(ps_bridge, fw);
> + if (ret)
> + return ret;
> +
> + mutex_lock(&ps_bridge->fw_mutex);
> + if (!ps_bridge->in_fw_update) {
> + if (!ps8640_status_backup)
> + ps8640_pre_enable(&ps_bridge->bridge);
> +
> + ret = ps8640_enter_bl(ps_bridge);
> + if (ret)
> + goto exit;
> + }
> +
> + ret = ps8640_rom_prepare(ps_bridge);
> + if (ret)
> + goto exit;
> +
> + ret = ps8640_write_rom(ps_bridge, fw);
> +
> +exit:
> + if (ret)
> + dev_err(dev, "Failed to load firmware, %d\n", ret);
> +
> + ps8640_exit_bl(ps_bridge, fw);
> + if (!ps8640_status_backup)
> + ps8640_post_disable(&ps_bridge->bridge);
> + mutex_unlock(&ps_bridge->fw_mutex);
> + return ret;
> +}
> +
> +static ssize_t ps8640_update_fw_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct ps8640 *ps_bridge = i2c_get_clientdata(client);
> + const struct firmware *fw;
> + int error;
> +
> + error = request_firmware(&fw, PS_FW_NAME, dev);
> + if (error) {
> + dev_err(dev, "Unable to open firmware %s: %d\n",
> + PS_FW_NAME, error);
> + return error;
> + }
> +
> + error = ps8640_load_fw(ps_bridge, fw);
> + if (error)
> + dev_err(dev, "The firmware update failed(%d)\n", error);
> + else
> + dev_info(dev, "The firmware update succeeded\n");
> +
> + release_firmware(fw);
> + return error ? error : count;
> +}
> +
> +static DEVICE_ATTR(fw_version, S_IRUGO, ps8640_fw_version_show, NULL);
> +static DEVICE_ATTR(hw_version, S_IRUGO, ps8640_hw_version_show, NULL);
> +static DEVICE_ATTR(update_fw, S_IWUSR, NULL, ps8640_update_fw_store);
> +
> +static struct attribute *ps8640_attrs[] = {
> + &dev_attr_fw_version.attr,
> + &dev_attr_hw_version.attr,
> + &dev_attr_update_fw.attr,
> + NULL
> +};
> +
> +static const struct attribute_group ps8640_attr_group = {
> + .attrs = ps8640_attrs,
> +};
> +
> +static void ps8640_remove_sysfs_group(void *data)
> +{
> + struct ps8640 *ps_bridge = data;
> +
> + sysfs_remove_group(&ps_bridge->page[0]->dev.kobj, &ps8640_attr_group);
> +}
> +
> +static int ps8640_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct device *dev = &client->dev;
> + struct ps8640 *ps_bridge;
> + struct device_node *np = dev->of_node;
> + struct device_node *port, *out_ep;
> + struct device_node *panel_node = NULL;
> + int ret;
> + u32 i;
> +
> + ps_bridge = devm_kzalloc(dev, sizeof(*ps_bridge), GFP_KERNEL);
> + if (!ps_bridge)
> + return -ENOMEM;
> +
> + /* port at 1 is ps8640 output port */
> + port = of_graph_get_port_by_id(np, 1);
> + if (port) {
> + out_ep = of_get_child_by_name(port, "endpoint");
> + of_node_put(port);
> + if (out_ep) {
> + panel_node = of_graph_get_remote_port_parent(out_ep);
> + of_node_put(out_ep);
> + }
> + }
> + if (panel_node) {
> + ps_bridge->panel = of_drm_find_panel(panel_node);
> + of_node_put(panel_node);
> + if (!ps_bridge->panel)
> + return -EPROBE_DEFER;
> + }
> +
> + mutex_init(&ps_bridge->fw_mutex);
> + ps_bridge->supplies[0].supply = "vdd33";
> + ps_bridge->supplies[1].supply = "vdd12";
> + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ps_bridge->supplies),
> + ps_bridge->supplies);
> + if (ret) {
> + dev_info(dev, "failed to get regulators: %d\n", ret);
> + return ret;
> + }
> +
> + ps_bridge->gpio_mode_sel = devm_gpiod_get_optional(&client->dev,
> + "mode-sel",
> + GPIOD_OUT_HIGH);
> + if (IS_ERR(ps_bridge->gpio_mode_sel)) {
> + ret = PTR_ERR(ps_bridge->gpio_mode_sel);
> + dev_err(dev, "cannot get mode-sel %d\n", ret);
> + return ret;
> + }
> +
> + ps_bridge->gpio_power_down = devm_gpiod_get(&client->dev, "sleep",
> + GPIOD_OUT_LOW);
> + if (IS_ERR(ps_bridge->gpio_power_down)) {
> + ret = PTR_ERR(ps_bridge->gpio_power_down);
> + dev_err(dev, "cannot get sleep: %d\n", ret);
> + return ret;
> + }
> +
> + /*
> + * Request the reset pin low to avoid the bridge being
> + * initialized prematurely
> + */
> + ps_bridge->gpio_reset = devm_gpiod_get(&client->dev, "reset",
> + GPIOD_OUT_LOW);
> + if (IS_ERR(ps_bridge->gpio_reset)) {
> + ret = PTR_ERR(ps_bridge->gpio_reset);
> + dev_err(dev, "cannot get reset: %d\n", ret);
> + return ret;
> + }
> +
> + ps_bridge->bridge.funcs = &ps8640_bridge_funcs;
> + ps_bridge->bridge.of_node = dev->of_node;
> +
> + ps_bridge->page[0] = client;
> + ps_bridge->ddc_i2c = i2c_new_dummy(client->adapter, EDID_I2C_ADDR);
> + if (!ps_bridge->ddc_i2c) {
> + dev_err(dev, "failed ddc_i2c dummy device, address%02x\n",
> + EDID_I2C_ADDR);
> + return -EBUSY;
> + }
> + /*
> + * ps8640 uses multiple addresses, use dummy devices for them
> + * page[0]: for DP control
> + * page[1]: for VIDEO Bridge
> + * page[2]: for control top
> + * page[3]: for DSI Link Control1
> + * page[4]: for MIPI Phy
> + * page[5]: for VPLL
> + * page[6]: for DSI Link Control2
> + * page[7]: for spi rom mapping
> + */
> + for (i = 1; i < MAX_DEVS; i++) {
> + ps_bridge->page[i] = i2c_new_dummy(client->adapter,
> + client->addr + i);
> + if (!ps_bridge->page[i]) {
> + dev_err(dev, "failed i2c dummy device, address%02x\n",
> + client->addr + i);
> + ret = -EBUSY;
> + goto exit_dummy;
> + }
> + }
> + i2c_set_clientdata(client, ps_bridge);
> +
> + ret = sysfs_create_group(&client->dev.kobj, &ps8640_attr_group);
> + if (ret) {
> + dev_err(dev, "failed to create sysfs entries: %d\n", ret);
> + goto exit_dummy;
> + }
> +
> + ret = devm_add_action(dev, ps8640_remove_sysfs_group, ps_bridge);
> + if (ret) {
> + dev_err(dev, "failed to add sysfs cleanup action: %d\n", ret);
> + goto exit_remove_sysfs;
> + }
> +
> + ret = drm_bridge_add(&ps_bridge->bridge);
> + if (ret) {
> + dev_err(dev, "Failed to add bridge: %d\n", ret);
> + goto exit_remove_sysfs;
> + }
> + return 0;
> +
> +exit_remove_sysfs:
> + sysfs_remove_group(&ps_bridge->page[0]->dev.kobj, &ps8640_attr_group);
> +exit_dummy:
> + while (--i)
> + i2c_unregister_device(ps_bridge->page[i]);
> + i2c_unregister_device(ps_bridge->ddc_i2c);
> + return ret;
> +}
> +
> +static int ps8640_remove(struct i2c_client *client)
> +{
> + struct ps8640 *ps_bridge = i2c_get_clientdata(client);
> + int i = MAX_DEVS;
> +
> + drm_bridge_remove(&ps_bridge->bridge);
> + sysfs_remove_group(&ps_bridge->page[0]->dev.kobj, &ps8640_attr_group);
> + while (--i)
> + i2c_unregister_device(ps_bridge->page[i]);
> +
> + i2c_unregister_device(ps_bridge->ddc_i2c);
> + return 0;
> +}
> +
> +static const struct i2c_device_id ps8640_i2c_table[] = {
> + { "parade,ps8640", 0 },
I think that you should remove the manufacturer prefix here, note that
the I2C core removes the manufacturer prefix from the compatible field
so it reports to user-space the uevent: i2c:ps8640, but this doesn't
match with the device id which is parade,ps8640. If you build the
driver as a module will not autoload the driver, to fix this just
remove the manufacturer prefix.
> + {},
nit: add { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(i2c, ps8640_i2c_table);
> +
> +static const struct of_device_id ps8640_match[] = {
> + { .compatible = "parade,ps8640" },
> + {},
nit: add { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, ps8640_match);
> +
> +static struct i2c_driver ps8640_driver = {
> + .id_table = ps8640_i2c_table,
> + .probe = ps8640_probe,
> + .remove = ps8640_remove,
> + .driver = {
> + .name = "parade,ps8640",
Also remove the manufacturer from the name, guess it's more common.
> + .of_match_table = ps8640_match,
> + },
> +};
> +module_i2c_driver(ps8640_driver);
> +
> +MODULE_AUTHOR("Jitao Shi <jitao.shi@mediatek.com>");
> +MODULE_AUTHOR("CK Hu <ck.hu@mediatek.com>");
> +MODULE_DESCRIPTION("PARADE ps8640 DSI-eDP converter driver");
> +MODULE_LICENSE("GPL v2");
> --
> 1.7.9.5
>
A part of these minor things.
Reviewed-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
>
> _______________________________________________
> Linux-mediatek mailing list
> Linux-mediatek at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-mediatek
^ permalink raw reply
* PM regression with LED changes in next-20161109
From: Pavel Machek @ 2016-11-10 16:36 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <7cc1ba55-1bd0-ae3f-5c9b-f16efb8dcc80@samsung.com>
Hi!
> >>>The current docs say not about (sw) blinking, but that should be treated
> >>>just
> >>>like a trigger IMHO.
> >>
> >>You'r right, we should describe the semantics on reading, but it would
> >>have to be as follows:
> >>
> >>Reading from this file returns LED brightness at given moment, i.e.
> >>even though LED class device brightness setting is greater than 0, the
> >>momentary brightness can be 0 if the readout occurred during low phase
> >>of blink cycle.
> >
> >Why would it need to read like this, because this is the current behavior ?
>
> We have led_update_brightness() which was introduced long time ago and
> is used in brightness_show(). Note that if LED controller changed
> actual LED brightness e.g. due to battery voltage dropping below
> certain threshold, we wouldn't be able to find it out otherwise
> (except if we added separate sysfs file for that).
And we should have a separate sysfs file for that. Note that on some
hardware leds, you are able to let hardware control them, but if you
do, you can't really tell the current state. Examples are n900 and
thinkpad-acpi. So it is better we don't pretend we can get that value
for userspace.
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161110/1b646938/attachment.sig>
^ permalink raw reply
* [PATCH fpga 9/9] fpga: Remove support for non-sg drivers
From: Jason Gunthorpe @ 2016-11-10 16:33 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <bad801c5-fe3a-665b-de67-bf69c42fb2b4@gmail.com>
> > struct fpga_manager_ops {
> > enum fpga_mgr_states (*state)(struct fpga_manager *mgr);
> > - int (*write_init)(struct fpga_manager *mgr, u32 flags,
> > - const char *buf, size_t count);
> > - int (*write)(struct fpga_manager *mgr, const char *buf, size_t count);
> > int (*write_init_sg)(struct fpga_manager *mgr, u32 flags,
> > struct sg_table *sgt);
> > int (*write_sg)(struct fpga_manager *mgr, struct sg_table *sgt);
> > @@ -118,6 +113,8 @@ struct fpga_manager {
> >
> > int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags,
> > const char *buf, size_t count);
> > +int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, u32 flags,
> > + struct sg_table *sgt);
> >
> > int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags,
> > const char *image_name);
> I don't have any feeling either way about switching to scatter-gather.
> (Not zynq or socfpga user)
> But I do object to renaming the API.
> write_init() and write() do not imply a particular implementation, nor even that
> the buffer is coherent.
Neither the sg or old linear interface imply any particular
underlying driver implementation.
All that is being changed is how the list of physical pages gets
passed to the driver. The linear interface requires them to be
contiguously mapped (eg in a vmap) while the SG interface
directly passes a list of physical page addresses.
Any alogrithm that works with the old interface can run on the new
interface, and the new interface can support much better options for
DMA drivers, while not requiring the higher layers to perform a high
order allocation (vmap or otherwise) to create the contiguous memory.
The reason the old interface is being deleted here is so the fpga mgr
API can be expanded to accept a sg list directly. Since we cannot
convert a general sg list to linear memory the liner option must be
totally removed.
> I am working to merge an fpga manager which uses SPI to load the bitstream
> (see https://www.spinics.net/lists/arm-kernel/msg539328.html)
> Any dma in use there would come from the spi driver. write_init_sg, and write_sg
> don't make any sense in my case.
No, it still make lots of sense.
SPI has been slowly transforming to use the same sort of SG scheme
universally, including facing the client. (see
6ad45a27cbe343ec8d7888e5edf6335499a4b555)
Some day your driver can just pass the SGs directly to spi and
everything will be great.
In the mean time it can do sg_miter_next to get mapped buffers.
> Would it not make sense to keep the top level API the same?
Fundamentally no.
Jason
^ permalink raw reply
* PM regression with LED changes in next-20161109
From: Pavel Machek @ 2016-11-10 16:29 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <5bd5333e-0dbb-6333-0a48-ca4d3a990f9c@samsung.com>
Hi!
> >>>Looks like commit 883d32ce3385 ("leds: core: Add support for poll()ing
> >>>the sysfs brightness attr for changes.") breaks runtime PM for me.
> >>>
> >>>On my omap dm3730 based test system, idle power consumption is over 70
> >>>times higher now with this patch! It goes from about 6mW for the core
> >>>system to over 440mW during idle meaning there's some busy timer now
> >>>active.
> >>>
> >>>Reverting this patch fixes the issue. Any ideas?
Are you using any LED that toggles with high frequency? Like perhaps
LED that is lit when CPU is active?
> >So a user can do "echo 128 > brightness && cat brightness" and
> >get out 0, or 128, depending purely on timing.
...
> > Reading from this file while a trigger is active returns
> >the
> > top brightness trigger is going to use.
Yes, that sounds sane.
> It seems that we should get back to your initial approach. i.e. only
> brightness changes caused by hardware should be reported.
I don't think enabling poll() here is good idea. Some hardware won't
be able to tell you that it changed the state. Returning maximum
brightness trigger is going to use seems easier/better.
Best regards,
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161110/4ee8fe9c/attachment.sig>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox