mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 01:10:04 +00:00
![Serge Semin](/assets/img/avatar_default.png)
In order to simplify the clock-related code there is a way to convert the current fixed clocks array into using the common bulk clocks kernel API with dynamic set of the clock handlers and device-managed clock-resource tracking. It's a bit tricky due to the complication coming from the requirement to support the platforms (da850, spear13xx) with the non-OF-based clock source, but still doable. Before this modification there are two methods have been used to get the clocks connected to an AHCI device: clk_get() - to get the very first clock in the list and of_clk_get() - to get the rest of them. Basically the platforms with non-OF-based clocks definition could specify only a single reference clock source. The platforms with OF-hw clocks have been luckier and could setup up to AHCI_MAX_CLKS clocks. Such semantic can be retained with using devm_clk_bulk_get_all() to retrieve the clocks defined via the DT firmware and devm_clk_get_optional() otherwise. In both cases using the device-managed version of the methods will cause the automatic resources deallocation on the AHCI device removal event. The only complicated part in the suggested approach is the explicit allocation and initialization of the clk_bulk_data structure instance for the non-OF reference clocks. It's required in order to use the Bulk Clocks API for the both denoted cases of the clocks definition. Note aside with the clock-related code reduction and natural simplification, there are several bonuses the suggested modification provides. First of all the limitation of having no greater than AHCI_MAX_CLKS clocks is now removed, since the devm_clk_bulk_get_all() method will allocate as many reference clocks data descriptors as there are clocks specified for the device. Secondly the clock names are auto-detected. So the LLDD (glue) drivers can make sure that the required clocks are specified just by checking the clock IDs in the clk_bulk_data array. Thirdly using the handy Bulk Clocks kernel API improves the clocks-handling code readability. And the last but not least this modification implements a true optional clocks support to the ahci_platform_get_resources() method. Indeed the previous clocks getting procedure just stopped getting the clocks on any errors (aside from non-critical -EPROBE_DEFER) in a way so the callee wasn't even informed about abnormal loop termination. The new implementation lacks of such problem. The ahci_platform_get_resources() will return an error code if the corresponding clocks getting method ends execution abnormally. Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru> Reviewed-by: Hannes Reinecke <hare@suse.de> Signed-off-by: Damien Le Moal <damien.lemoal@opensource.wdc.com>
196 lines
5 KiB
C
196 lines
5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* DaVinci DM816 AHCI SATA platform driver
|
|
*
|
|
* Copyright (C) 2017 BayLibre SAS
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/libata.h>
|
|
#include <linux/ahci_platform.h>
|
|
|
|
#include "ahci.h"
|
|
|
|
#define AHCI_DM816_DRV_NAME "ahci-dm816"
|
|
|
|
#define AHCI_DM816_PHY_ENPLL(x) ((x) << 0)
|
|
#define AHCI_DM816_PHY_MPY(x) ((x) << 1)
|
|
#define AHCI_DM816_PHY_LOS(x) ((x) << 12)
|
|
#define AHCI_DM816_PHY_RXCDR(x) ((x) << 13)
|
|
#define AHCI_DM816_PHY_RXEQ(x) ((x) << 16)
|
|
#define AHCI_DM816_PHY_TXSWING(x) ((x) << 23)
|
|
|
|
#define AHCI_DM816_P0PHYCR_REG 0x178
|
|
#define AHCI_DM816_P1PHYCR_REG 0x1f8
|
|
|
|
#define AHCI_DM816_PLL_OUT 1500000000LU
|
|
|
|
static const unsigned long pll_mpy_table[] = {
|
|
400, 500, 600, 800, 825, 1000, 1200,
|
|
1250, 1500, 1600, 1650, 2000, 2200, 2500
|
|
};
|
|
|
|
static int ahci_dm816_get_mpy_bits(unsigned long refclk_rate)
|
|
{
|
|
unsigned long pll_multiplier;
|
|
int i;
|
|
|
|
/*
|
|
* We need to determine the value of the multiplier (MPY) bits.
|
|
* In order to include the 8.25 multiplier we need to first divide
|
|
* the refclk rate by 100.
|
|
*/
|
|
pll_multiplier = AHCI_DM816_PLL_OUT / (refclk_rate / 100);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pll_mpy_table); i++) {
|
|
if (pll_mpy_table[i] == pll_multiplier)
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* We should have divided evenly - if not, return an invalid
|
|
* value.
|
|
*/
|
|
return -1;
|
|
}
|
|
|
|
static int ahci_dm816_phy_init(struct ahci_host_priv *hpriv, struct device *dev)
|
|
{
|
|
unsigned long refclk_rate;
|
|
int mpy;
|
|
u32 val;
|
|
|
|
/*
|
|
* We should have been supplied two clocks: the functional and
|
|
* keep-alive clock and the external reference clock. We need the
|
|
* rate of the latter to calculate the correct value of MPY bits.
|
|
*/
|
|
if (hpriv->n_clks < 2) {
|
|
dev_err(dev, "reference clock not supplied\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
refclk_rate = clk_get_rate(hpriv->clks[1].clk);
|
|
if ((refclk_rate % 100) != 0) {
|
|
dev_err(dev, "reference clock rate must be divisible by 100\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mpy = ahci_dm816_get_mpy_bits(refclk_rate);
|
|
if (mpy < 0) {
|
|
dev_err(dev, "can't calculate the MPY bits value\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Enable the PHY and configure the first HBA port. */
|
|
val = AHCI_DM816_PHY_MPY(mpy) | AHCI_DM816_PHY_LOS(1) |
|
|
AHCI_DM816_PHY_RXCDR(4) | AHCI_DM816_PHY_RXEQ(1) |
|
|
AHCI_DM816_PHY_TXSWING(3) | AHCI_DM816_PHY_ENPLL(1);
|
|
writel(val, hpriv->mmio + AHCI_DM816_P0PHYCR_REG);
|
|
|
|
/* Configure the second HBA port. */
|
|
val = AHCI_DM816_PHY_LOS(1) | AHCI_DM816_PHY_RXCDR(4) |
|
|
AHCI_DM816_PHY_RXEQ(1) | AHCI_DM816_PHY_TXSWING(3);
|
|
writel(val, hpriv->mmio + AHCI_DM816_P1PHYCR_REG);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ahci_dm816_softreset(struct ata_link *link,
|
|
unsigned int *class, unsigned long deadline)
|
|
{
|
|
int pmp, ret;
|
|
|
|
pmp = sata_srst_pmp(link);
|
|
|
|
/*
|
|
* There's an issue with the SATA controller on DM816 SoC: if we
|
|
* enable Port Multiplier support, but the drive is connected directly
|
|
* to the board, it can't be detected. As a workaround: if PMP is
|
|
* enabled, we first call ahci_do_softreset() and pass it the result of
|
|
* sata_srst_pmp(). If this call fails, we retry with pmp = 0.
|
|
*/
|
|
ret = ahci_do_softreset(link, class, pmp, deadline, ahci_check_ready);
|
|
if (pmp && ret == -EBUSY)
|
|
return ahci_do_softreset(link, class, 0,
|
|
deadline, ahci_check_ready);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct ata_port_operations ahci_dm816_port_ops = {
|
|
.inherits = &ahci_platform_ops,
|
|
.softreset = ahci_dm816_softreset,
|
|
};
|
|
|
|
static const struct ata_port_info ahci_dm816_port_info = {
|
|
.flags = AHCI_FLAG_COMMON,
|
|
.pio_mask = ATA_PIO4,
|
|
.udma_mask = ATA_UDMA6,
|
|
.port_ops = &ahci_dm816_port_ops,
|
|
};
|
|
|
|
static struct scsi_host_template ahci_dm816_platform_sht = {
|
|
AHCI_SHT(AHCI_DM816_DRV_NAME),
|
|
};
|
|
|
|
static int ahci_dm816_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct ahci_host_priv *hpriv;
|
|
int rc;
|
|
|
|
hpriv = ahci_platform_get_resources(pdev, 0);
|
|
if (IS_ERR(hpriv))
|
|
return PTR_ERR(hpriv);
|
|
|
|
rc = ahci_platform_enable_resources(hpriv);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ahci_dm816_phy_init(hpriv, dev);
|
|
if (rc)
|
|
goto disable_resources;
|
|
|
|
rc = ahci_platform_init_host(pdev, hpriv,
|
|
&ahci_dm816_port_info,
|
|
&ahci_dm816_platform_sht);
|
|
if (rc)
|
|
goto disable_resources;
|
|
|
|
return 0;
|
|
|
|
disable_resources:
|
|
ahci_platform_disable_resources(hpriv);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(ahci_dm816_pm_ops,
|
|
ahci_platform_suspend,
|
|
ahci_platform_resume);
|
|
|
|
static const struct of_device_id ahci_dm816_of_match[] = {
|
|
{ .compatible = "ti,dm816-ahci", },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ahci_dm816_of_match);
|
|
|
|
static struct platform_driver ahci_dm816_driver = {
|
|
.probe = ahci_dm816_probe,
|
|
.remove = ata_platform_remove_one,
|
|
.driver = {
|
|
.name = AHCI_DM816_DRV_NAME,
|
|
.of_match_table = ahci_dm816_of_match,
|
|
.pm = &ahci_dm816_pm_ops,
|
|
},
|
|
};
|
|
module_platform_driver(ahci_dm816_driver);
|
|
|
|
MODULE_DESCRIPTION("DaVinci DM816 AHCI SATA platform driver");
|
|
MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>");
|
|
MODULE_LICENSE("GPL");
|