mirror of
https://github.com/torvalds/linux.git
synced 2026-05-27 08:33:17 +02:00
ASPEED SoC driver updates for 6.17
The ASPEED LPC snoop driver was recently the cause of some concern. In addition to the initial fixes, the channel configuration paths are refactored to improve robustness against errors. -----BEGIN PGP SIGNATURE----- iJIEABYIADoWIQSoUT1x3bOSX/nAa8ajM9GZTrjhpgUCaGyE5RwcYW5kcmV3QGNv ZGVjb25zdHJ1Y3QuY29tLmF1AAoJEKMz0ZlOuOGmuHQA/2nA+lhSqWy3hjK9eada /l5d0ffV8jhbmMfWDUhcxrELAP40xkUSUdJD31LRrU/FDGjv7CIznZIldiSlD6tE JSNyDQ== =SNkf -----END PGP SIGNATURE----- gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmh+UmAACgkQmmx57+YA GNkWEQ/9G+zYrNpGXmRhvdXmiSEs0/d9V9qfCdr26dwFR0F6lsFPtm/TJ+Fqi2fJ dV2RkJx2+31XzapxKrlyT9L7IMZ4OtfhN+MhFCqv0cB1HDSnxVQKue9/0BsomRUd 8ZpV5GUTHrYPQvqJUN/eK8BO4INqEnp6XZ9ArTzRDUvbOiCSLzXP8nVaHwSbq3WB FqwbMKMah26sW+PgokEhG4Xopa27pN4wbD+0/d3NFFXFDE/KUNBGtxEKz1NOr7qx nnpMBZ7KWIg7UltiZ3h6by5yiVQYOW8ekS+j1GcrHLN++LPEJhYlk/Ab91jxCln3 /U+aWlbQuYiqVVZINeks/RyOhwB8eofV0OvqAJu3TmnkZq28w22cTJESvs5KO3d9 eKXTd8Xz4mimK9ov8xKof0G/CN4m38FdwSr6YsjIZhWYVh64z0AtA3DlyXbRBjbV EY1ePVZZy3S56LdPDk/ADxtHHDC0PL/nIupeCwQLr9GYZeJQrRkPNV9CS8sJeW+o YPWM/14V4jCxERauUkCimLDOnRgD+9TVEdS5+a5gz2Bdw3YQCOzyJgqHj8STXlYV zZt3NGbb3WC9NXy8EW2U8nyRRg1w1KDQU1d+T6VF7m1WVG8+mbXqVNTw7cUKEsCu 1MO5V8AfFqyA0fKSN8bGX/5j671u/v611gZGetpwRg135HP46j0= =CrwR -----END PGP SIGNATURE----- Merge tag 'aspeed-6.17-drivers-1' of https://git.kernel.org/pub/scm/linux/kernel/git/bmc/linux into soc/drivers ASPEED SoC driver updates for 6.17 The ASPEED LPC snoop driver was recently the cause of some concern. In addition to the initial fixes, the channel configuration paths are refactored to improve robustness against errors. * tag 'aspeed-6.17-drivers-1' of https://git.kernel.org/pub/scm/linux/kernel/git/bmc/linux: soc: aspeed: lpc-snoop: Lift channel config to const structs soc: aspeed: lpc-snoop: Consolidate channel initialisation soc: aspeed: lpc-snoop: Use dev_err_probe() where possible soc: aspeed: lpc-snoop: Switch to devm_clk_get_enabled() soc: aspeed: lpc-snoop: Rearrange channel paths soc: aspeed: lpc-snoop: Rename 'channel' to 'index' in channel paths soc: aspeed: lpc-snoop: Constrain parameters in channel paths soc: aspeed: lpc-snoop: Ensure model_data is valid soc: aspeed: lpc-snoop: Don't disable channels that aren't enabled soc: aspeed: lpc-snoop: Cleanup resources in stack-order Link: https://lore.kernel.org/r/9123f151280e52c63dcb645cb07d4eee3462c067.camel@codeconstruct.com.au Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
commit
708109c46f
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/kfifo.h>
|
||||
|
|
@ -25,7 +26,6 @@
|
|||
|
||||
#define DEVICE_NAME "aspeed-lpc-snoop"
|
||||
|
||||
#define NUM_SNOOP_CHANNELS 2
|
||||
#define SNOOP_FIFO_SIZE 2048
|
||||
|
||||
#define HICR5 0x80
|
||||
|
|
@ -57,7 +57,23 @@ struct aspeed_lpc_snoop_model_data {
|
|||
unsigned int has_hicrb_ensnp;
|
||||
};
|
||||
|
||||
enum aspeed_lpc_snoop_index {
|
||||
ASPEED_LPC_SNOOP_INDEX_0 = 0,
|
||||
ASPEED_LPC_SNOOP_INDEX_1 = 1,
|
||||
ASPEED_LPC_SNOOP_INDEX_MAX = ASPEED_LPC_SNOOP_INDEX_1,
|
||||
};
|
||||
|
||||
struct aspeed_lpc_snoop_channel_cfg {
|
||||
enum aspeed_lpc_snoop_index index;
|
||||
u32 hicr5_en;
|
||||
u32 snpwadr_mask;
|
||||
u32 snpwadr_shift;
|
||||
u32 hicrb_en;
|
||||
};
|
||||
|
||||
struct aspeed_lpc_snoop_channel {
|
||||
const struct aspeed_lpc_snoop_channel_cfg *cfg;
|
||||
bool enabled;
|
||||
struct kfifo fifo;
|
||||
wait_queue_head_t wq;
|
||||
struct miscdevice miscdev;
|
||||
|
|
@ -67,7 +83,24 @@ struct aspeed_lpc_snoop {
|
|||
struct regmap *regmap;
|
||||
int irq;
|
||||
struct clk *clk;
|
||||
struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS];
|
||||
struct aspeed_lpc_snoop_channel chan[ASPEED_LPC_SNOOP_INDEX_MAX + 1];
|
||||
};
|
||||
|
||||
static const struct aspeed_lpc_snoop_channel_cfg channel_cfgs[ASPEED_LPC_SNOOP_INDEX_MAX + 1] = {
|
||||
{
|
||||
.index = ASPEED_LPC_SNOOP_INDEX_0,
|
||||
.hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W,
|
||||
.snpwadr_mask = SNPWADR_CH0_MASK,
|
||||
.snpwadr_shift = SNPWADR_CH0_SHIFT,
|
||||
.hicrb_en = HICRB_ENSNP0D,
|
||||
},
|
||||
{
|
||||
.index = ASPEED_LPC_SNOOP_INDEX_1,
|
||||
.hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W,
|
||||
.snpwadr_mask = SNPWADR_CH1_MASK,
|
||||
.snpwadr_shift = SNPWADR_CH1_SHIFT,
|
||||
.hicrb_en = HICRB_ENSNP1D,
|
||||
},
|
||||
};
|
||||
|
||||
static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file)
|
||||
|
|
@ -181,98 +214,88 @@ static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
|
||||
struct device *dev,
|
||||
int channel, u16 lpc_port)
|
||||
__attribute__((nonnull))
|
||||
static int aspeed_lpc_enable_snoop(struct device *dev,
|
||||
struct aspeed_lpc_snoop *lpc_snoop,
|
||||
struct aspeed_lpc_snoop_channel *channel,
|
||||
const struct aspeed_lpc_snoop_channel_cfg *cfg,
|
||||
u16 lpc_port)
|
||||
{
|
||||
const struct aspeed_lpc_snoop_model_data *model_data;
|
||||
int rc = 0;
|
||||
u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en;
|
||||
const struct aspeed_lpc_snoop_model_data *model_data =
|
||||
of_device_get_match_data(dev);
|
||||
|
||||
init_waitqueue_head(&lpc_snoop->chan[channel].wq);
|
||||
/* Create FIFO datastructure */
|
||||
rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo,
|
||||
SNOOP_FIFO_SIZE, GFP_KERNEL);
|
||||
if (WARN_ON(channel->enabled))
|
||||
return -EBUSY;
|
||||
|
||||
init_waitqueue_head(&channel->wq);
|
||||
|
||||
channel->cfg = cfg;
|
||||
channel->miscdev.minor = MISC_DYNAMIC_MINOR;
|
||||
channel->miscdev.fops = &snoop_fops;
|
||||
channel->miscdev.parent = dev;
|
||||
|
||||
channel->miscdev.name =
|
||||
devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, cfg->index);
|
||||
if (!channel->miscdev.name)
|
||||
return -ENOMEM;
|
||||
|
||||
rc = kfifo_alloc(&channel->fifo, SNOOP_FIFO_SIZE, GFP_KERNEL);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR;
|
||||
lpc_snoop->chan[channel].miscdev.name =
|
||||
devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel);
|
||||
if (!lpc_snoop->chan[channel].miscdev.name) {
|
||||
rc = -ENOMEM;
|
||||
goto err_free_fifo;
|
||||
}
|
||||
lpc_snoop->chan[channel].miscdev.fops = &snoop_fops;
|
||||
lpc_snoop->chan[channel].miscdev.parent = dev;
|
||||
rc = misc_register(&lpc_snoop->chan[channel].miscdev);
|
||||
rc = misc_register(&channel->miscdev);
|
||||
if (rc)
|
||||
goto err_free_fifo;
|
||||
|
||||
/* Enable LPC snoop channel at requested port */
|
||||
switch (channel) {
|
||||
case 0:
|
||||
hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W;
|
||||
snpwadr_mask = SNPWADR_CH0_MASK;
|
||||
snpwadr_shift = SNPWADR_CH0_SHIFT;
|
||||
hicrb_en = HICRB_ENSNP0D;
|
||||
break;
|
||||
case 1:
|
||||
hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W;
|
||||
snpwadr_mask = SNPWADR_CH1_MASK;
|
||||
snpwadr_shift = SNPWADR_CH1_SHIFT;
|
||||
hicrb_en = HICRB_ENSNP1D;
|
||||
break;
|
||||
default:
|
||||
rc = -EINVAL;
|
||||
goto err_misc_deregister;
|
||||
}
|
||||
regmap_set_bits(lpc_snoop->regmap, HICR5, cfg->hicr5_en);
|
||||
regmap_update_bits(lpc_snoop->regmap, SNPWADR, cfg->snpwadr_mask,
|
||||
lpc_port << cfg->snpwadr_shift);
|
||||
|
||||
regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en);
|
||||
regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask,
|
||||
lpc_port << snpwadr_shift);
|
||||
if (model_data->has_hicrb_ensnp)
|
||||
regmap_update_bits(lpc_snoop->regmap, HICRB,
|
||||
hicrb_en, hicrb_en);
|
||||
model_data = of_device_get_match_data(dev);
|
||||
if (model_data && model_data->has_hicrb_ensnp)
|
||||
regmap_set_bits(lpc_snoop->regmap, HICRB, cfg->hicrb_en);
|
||||
|
||||
channel->enabled = true;
|
||||
|
||||
return 0;
|
||||
|
||||
err_misc_deregister:
|
||||
misc_deregister(&lpc_snoop->chan[channel].miscdev);
|
||||
err_free_fifo:
|
||||
kfifo_free(&lpc_snoop->chan[channel].fifo);
|
||||
kfifo_free(&channel->fifo);
|
||||
return rc;
|
||||
}
|
||||
|
||||
__attribute__((nonnull))
|
||||
static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
|
||||
int channel)
|
||||
struct aspeed_lpc_snoop_channel *channel)
|
||||
{
|
||||
switch (channel) {
|
||||
case 0:
|
||||
regmap_update_bits(lpc_snoop->regmap, HICR5,
|
||||
HICR5_EN_SNP0W | HICR5_ENINT_SNP0W,
|
||||
0);
|
||||
break;
|
||||
case 1:
|
||||
regmap_update_bits(lpc_snoop->regmap, HICR5,
|
||||
HICR5_EN_SNP1W | HICR5_ENINT_SNP1W,
|
||||
0);
|
||||
break;
|
||||
default:
|
||||
if (!channel->enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
kfifo_free(&lpc_snoop->chan[channel].fifo);
|
||||
misc_deregister(&lpc_snoop->chan[channel].miscdev);
|
||||
/* Disable interrupts along with the device */
|
||||
regmap_clear_bits(lpc_snoop->regmap, HICR5, channel->cfg->hicr5_en);
|
||||
|
||||
channel->enabled = false;
|
||||
/* Consider improving safety wrt concurrent reader(s) */
|
||||
misc_deregister(&channel->miscdev);
|
||||
kfifo_free(&channel->fifo);
|
||||
}
|
||||
|
||||
static void aspeed_lpc_snoop_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
/* Disable both snoop channels */
|
||||
aspeed_lpc_disable_snoop(lpc_snoop, &lpc_snoop->chan[0]);
|
||||
aspeed_lpc_disable_snoop(lpc_snoop, &lpc_snoop->chan[1]);
|
||||
}
|
||||
|
||||
static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct aspeed_lpc_snoop *lpc_snoop;
|
||||
struct device *dev;
|
||||
struct device_node *np;
|
||||
u32 port;
|
||||
struct device *dev;
|
||||
int idx;
|
||||
int rc;
|
||||
|
||||
dev = &pdev->dev;
|
||||
|
|
@ -290,69 +313,42 @@ static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
lpc_snoop->regmap = syscon_node_to_regmap(np);
|
||||
if (IS_ERR(lpc_snoop->regmap)) {
|
||||
dev_err(dev, "Couldn't get regmap\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (IS_ERR(lpc_snoop->regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(lpc_snoop->regmap), "Couldn't get regmap\n");
|
||||
|
||||
dev_set_drvdata(&pdev->dev, lpc_snoop);
|
||||
|
||||
rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port);
|
||||
if (rc) {
|
||||
dev_err(dev, "no snoop ports configured\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
lpc_snoop->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(lpc_snoop->clk)) {
|
||||
rc = PTR_ERR(lpc_snoop->clk);
|
||||
if (rc != -EPROBE_DEFER)
|
||||
dev_err(dev, "couldn't get clock\n");
|
||||
return rc;
|
||||
}
|
||||
rc = clk_prepare_enable(lpc_snoop->clk);
|
||||
if (rc) {
|
||||
dev_err(dev, "couldn't enable clock\n");
|
||||
return rc;
|
||||
}
|
||||
lpc_snoop->clk = devm_clk_get_enabled(dev, NULL);
|
||||
if (IS_ERR(lpc_snoop->clk))
|
||||
return dev_err_probe(dev, PTR_ERR(lpc_snoop->clk), "couldn't get clock");
|
||||
|
||||
rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev);
|
||||
if (rc)
|
||||
goto err;
|
||||
return rc;
|
||||
|
||||
rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port);
|
||||
if (rc)
|
||||
goto err;
|
||||
static_assert(ARRAY_SIZE(channel_cfgs) == ARRAY_SIZE(lpc_snoop->chan),
|
||||
"Broken implementation assumption regarding cfg count");
|
||||
for (idx = ASPEED_LPC_SNOOP_INDEX_0; idx <= ASPEED_LPC_SNOOP_INDEX_MAX; idx++) {
|
||||
u32 port;
|
||||
|
||||
/* Configuration of 2nd snoop channel port is optional */
|
||||
if (of_property_read_u32_index(dev->of_node, "snoop-ports",
|
||||
1, &port) == 0) {
|
||||
rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port);
|
||||
if (rc) {
|
||||
aspeed_lpc_disable_snoop(lpc_snoop, 0);
|
||||
goto err;
|
||||
}
|
||||
rc = of_property_read_u32_index(dev->of_node, "snoop-ports", idx, &port);
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
rc = aspeed_lpc_enable_snoop(dev, lpc_snoop, &lpc_snoop->chan[idx],
|
||||
&channel_cfgs[idx], port);
|
||||
if (rc)
|
||||
goto cleanup_channels;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return idx == ASPEED_LPC_SNOOP_INDEX_0 ? -ENODEV : 0;
|
||||
|
||||
err:
|
||||
clk_disable_unprepare(lpc_snoop->clk);
|
||||
cleanup_channels:
|
||||
aspeed_lpc_snoop_remove(pdev);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void aspeed_lpc_snoop_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
/* Disable both snoop channels */
|
||||
aspeed_lpc_disable_snoop(lpc_snoop, 0);
|
||||
aspeed_lpc_disable_snoop(lpc_snoop, 1);
|
||||
|
||||
clk_disable_unprepare(lpc_snoop->clk);
|
||||
}
|
||||
|
||||
static const struct aspeed_lpc_snoop_model_data ast2400_model_data = {
|
||||
.has_hicrb_ensnp = 0,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user